mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-05 19:04:43 +03:00
chore: remove web mode (#1625)
This commit is contained in:
parent
cf49a9ee7b
commit
e241c1bef8
@ -47,6 +47,7 @@
|
||||
"extract-zip": "^1.6.6",
|
||||
"https-proxy-agent": "^3.0.0",
|
||||
"jpeg-js": "^0.3.6",
|
||||
"mime": "^2.4.4",
|
||||
"pngjs": "^3.4.0",
|
||||
"progress": "^2.0.3",
|
||||
"proxy-from-env": "^1.1.0",
|
||||
@ -56,6 +57,7 @@
|
||||
"devDependencies": {
|
||||
"@types/debug": "0.0.31",
|
||||
"@types/extract-zip": "^1.6.2",
|
||||
"@types/mime": "^2.0.1",
|
||||
"@types/node": "^8.10.34",
|
||||
"@types/pngjs": "^3.4.0",
|
||||
"@types/proxy-from-env": "^1.0.0",
|
||||
|
@ -15,10 +15,10 @@
|
||||
*/
|
||||
|
||||
import { BrowserContext, BrowserContextOptions } from './browserContext';
|
||||
import * as platform from './platform';
|
||||
import { Page } from './page';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export interface Browser extends platform.EventEmitterType {
|
||||
export interface Browser extends EventEmitter {
|
||||
newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
|
||||
contexts(): BrowserContext[];
|
||||
newPage(options?: BrowserContextOptions): Promise<Page>;
|
||||
|
@ -21,7 +21,6 @@ import { Events as CommonEvents } from '../events';
|
||||
import { assert, debugError, helper } from '../helper';
|
||||
import * as network from '../network';
|
||||
import { Page, PageBinding, Worker } from '../page';
|
||||
import * as platform from '../platform';
|
||||
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
||||
import * as types from '../types';
|
||||
import { ConnectionEvents, CRConnection, CRSession } from './crConnection';
|
||||
@ -30,8 +29,9 @@ import { readProtocolStream } from './crProtocolHelper';
|
||||
import { Events } from './events';
|
||||
import { Protocol } from './protocol';
|
||||
import { CRExecutionContext } from './crExecutionContext';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export class CRBrowser extends platform.EventEmitter implements Browser {
|
||||
export class CRBrowser extends EventEmitter implements Browser {
|
||||
readonly _connection: CRConnection;
|
||||
_session: CRSession;
|
||||
private _clientRootSessionPromise: Promise<CRSession> | null = null;
|
||||
@ -221,7 +221,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
||||
});
|
||||
}
|
||||
|
||||
async stopTracing(): Promise<platform.BufferType> {
|
||||
async stopTracing(): Promise<Buffer> {
|
||||
assert(this._tracingClient, 'Tracing was not started.');
|
||||
const [event] = await Promise.all([
|
||||
new Promise(f => this._tracingClient!.once('Tracing.tracingComplete', f)),
|
||||
@ -242,7 +242,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
||||
return this._clientRootSessionPromise;
|
||||
}
|
||||
|
||||
_setDebugFunction(debugFunction: platform.DebuggerType) {
|
||||
_setDebugFunction(debugFunction: debug.IDebugger) {
|
||||
this._connection._debugProtocol = debugFunction;
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,10 @@
|
||||
*/
|
||||
|
||||
import { assert } from '../helper';
|
||||
import * as platform from '../platform';
|
||||
import * as debug from 'debug';
|
||||
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
||||
import { Protocol } from './protocol';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export const ConnectionEvents = {
|
||||
Disconnected: Symbol('ConnectionEvents.Disconnected')
|
||||
@ -28,13 +29,13 @@ export const ConnectionEvents = {
|
||||
// should ignore.
|
||||
export const kBrowserCloseMessageId = -9999;
|
||||
|
||||
export class CRConnection extends platform.EventEmitter {
|
||||
export class CRConnection extends EventEmitter {
|
||||
private _lastId = 0;
|
||||
private readonly _transport: ConnectionTransport;
|
||||
private readonly _sessions = new Map<string, CRSession>();
|
||||
readonly rootSession: CRSession;
|
||||
_closed = false;
|
||||
_debugProtocol: platform.DebuggerType;
|
||||
_debugProtocol: debug.IDebugger;
|
||||
|
||||
constructor(transport: ConnectionTransport) {
|
||||
super();
|
||||
@ -43,7 +44,7 @@ export class CRConnection extends platform.EventEmitter {
|
||||
this._transport.onclose = this._onClose.bind(this);
|
||||
this.rootSession = new CRSession(this, '', 'browser', '');
|
||||
this._sessions.set('', this.rootSession);
|
||||
this._debugProtocol = platform.debug('pw:protocol');
|
||||
this._debugProtocol = debug('pw:protocol');
|
||||
(this._debugProtocol as any).color = '34';
|
||||
}
|
||||
|
||||
@ -118,7 +119,7 @@ export const CRSessionEvents = {
|
||||
Disconnected: Symbol('Events.CDPSession.Disconnected')
|
||||
};
|
||||
|
||||
export class CRSession extends platform.EventEmitter {
|
||||
export class CRSession extends EventEmitter {
|
||||
_connection: CRConnection | null;
|
||||
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
|
||||
private readonly _targetType: string;
|
||||
|
@ -21,7 +21,6 @@ import { assert, debugError, helper, RegisteredListener } from '../helper';
|
||||
import { Protocol } from './protocol';
|
||||
import * as network from '../network';
|
||||
import * as frames from '../frames';
|
||||
import * as platform from '../platform';
|
||||
import { Credentials } from '../types';
|
||||
import { CRPage } from './crPage';
|
||||
|
||||
@ -191,7 +190,7 @@ export class CRNetworkManager {
|
||||
_createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response {
|
||||
const getResponseBody = async () => {
|
||||
const response = await this._client.send('Network.getResponseBody', { requestId: request._requestId });
|
||||
return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||
};
|
||||
return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), getResponseBody);
|
||||
}
|
||||
@ -281,7 +280,7 @@ class InterceptableRequest implements network.RouteDelegate {
|
||||
}
|
||||
|
||||
async fulfill(response: network.FulfillResponse) {
|
||||
const responseBody = response.body && helper.isString(response.body) ? platform.Buffer.from(response.body) : (response.body || null);
|
||||
const responseBody = response.body && helper.isString(response.body) ? Buffer.from(response.body) : (response.body || null);
|
||||
|
||||
const responseHeaders: { [s: string]: string; } = {};
|
||||
if (response.headers) {
|
||||
@ -291,7 +290,7 @@ class InterceptableRequest implements network.RouteDelegate {
|
||||
if (response.contentType)
|
||||
responseHeaders['content-type'] = response.contentType;
|
||||
if (responseBody && !('content-length' in responseHeaders))
|
||||
responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody));
|
||||
responseHeaders['content-length'] = String(Buffer.byteLength(responseBody));
|
||||
|
||||
await this._client.send('Fetch.fulfillRequest', {
|
||||
requestId: this._interceptionId!,
|
||||
|
@ -36,7 +36,6 @@ import { CRPDF } from './crPdf';
|
||||
import { CRBrowserContext } from './crBrowser';
|
||||
import * as types from '../types';
|
||||
import { ConsoleMessage } from '../console';
|
||||
import * as platform from '../platform';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
|
||||
@ -455,7 +454,7 @@ export class CRPage implements PageDelegate {
|
||||
await this._client.send('Emulation.setDefaultBackgroundColorOverride', { color });
|
||||
}
|
||||
|
||||
async takeScreenshot(format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<platform.BufferType> {
|
||||
async takeScreenshot(format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer> {
|
||||
const { visualViewport } = await this._client.send('Page.getLayoutMetrics');
|
||||
if (!documentRect) {
|
||||
documentRect = {
|
||||
@ -472,7 +471,7 @@ export class CRPage implements PageDelegate {
|
||||
// ignore current page scale.
|
||||
const clip = { ...documentRect, scale: viewportRect ? visualViewport.scale : 1 };
|
||||
const result = await this._client.send('Page.captureScreenshot', { format, quality, clip });
|
||||
return platform.Buffer.from(result.data, 'base64');
|
||||
return Buffer.from(result.data, 'base64');
|
||||
}
|
||||
|
||||
async resetViewport(): Promise<void> {
|
||||
@ -587,7 +586,7 @@ export class CRPage implements PageDelegate {
|
||||
await this._client.send('Page.enable').catch(e => {});
|
||||
}
|
||||
|
||||
async pdf(options?: types.PDFOptions): Promise<platform.BufferType> {
|
||||
async pdf(options?: types.PDFOptions): Promise<Buffer> {
|
||||
return this._pdf.generate(options);
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
import { assert, helper } from '../helper';
|
||||
import * as platform from '../platform';
|
||||
import * as types from '../types';
|
||||
import { CRSession } from './crConnection';
|
||||
import { readProtocolStream } from './crProtocolHelper';
|
||||
@ -77,7 +76,7 @@ export class CRPDF {
|
||||
this._client = client;
|
||||
}
|
||||
|
||||
async generate(options: types.PDFOptions = {}): Promise<platform.BufferType> {
|
||||
async generate(options: types.PDFOptions = {}): Promise<Buffer> {
|
||||
const {
|
||||
scale = 1,
|
||||
displayHeaderFooter = false,
|
||||
|
@ -18,7 +18,8 @@
|
||||
import { assert } from '../helper';
|
||||
import { CRSession } from './crConnection';
|
||||
import { Protocol } from './protocol';
|
||||
import * as platform from '../platform';
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
|
||||
export function getExceptionMessage(exceptionDetails: Protocol.Runtime.ExceptionDetails): string {
|
||||
if (exceptionDetails.exception)
|
||||
@ -61,24 +62,24 @@ export async function releaseObject(client: CRSession, remoteObject: Protocol.Ru
|
||||
await client.send('Runtime.releaseObject', {objectId: remoteObject.objectId}).catch(error => {});
|
||||
}
|
||||
|
||||
export async function readProtocolStream(client: CRSession, handle: string, path: string | null): Promise<platform.BufferType> {
|
||||
export async function readProtocolStream(client: CRSession, handle: string, path: string | null): Promise<Buffer> {
|
||||
let eof = false;
|
||||
let fd: number | undefined;
|
||||
if (path)
|
||||
fd = await platform.openFdAsync(path, 'w');
|
||||
fd = await util.promisify(fs.open)(path, 'w');
|
||||
const bufs = [];
|
||||
while (!eof) {
|
||||
const response = await client.send('IO.read', {handle});
|
||||
eof = response.eof;
|
||||
const buf = platform.Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
|
||||
const buf = Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
|
||||
bufs.push(buf);
|
||||
if (path)
|
||||
await platform.writeFdAsync(fd!, buf);
|
||||
await util.promisify(fs.write)(fd!, buf);
|
||||
}
|
||||
if (path)
|
||||
await platform.closeFdAsync(fd!);
|
||||
await util.promisify(fs.close)(fd!);
|
||||
await client.send('IO.close', {handle});
|
||||
return platform.Buffer.concat(bufs);
|
||||
return Buffer.concat(bufs);
|
||||
}
|
||||
|
||||
export function toConsoleMessageLocation(stackTrace: Protocol.Runtime.StackTrace | undefined) {
|
||||
|
19
src/dom.ts
19
src/dom.ts
@ -14,15 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as mime from 'mime';
|
||||
import * as path from 'path';
|
||||
import * as util from 'util';
|
||||
import * as frames from './frames';
|
||||
import { assert, debugError, helper } from './helper';
|
||||
import Injected from './injected/injected';
|
||||
import * as input from './input';
|
||||
import * as js from './javascript';
|
||||
import * as types from './types';
|
||||
import { assert, helper, debugError } from './helper';
|
||||
import Injected from './injected/injected';
|
||||
import { Page } from './page';
|
||||
import * as platform from './platform';
|
||||
import { selectors } from './selectors';
|
||||
import * as types from './types';
|
||||
|
||||
export type PointerActionOptions = {
|
||||
modifiers?: input.Modifier[];
|
||||
@ -257,9 +260,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
for (const item of ff) {
|
||||
if (typeof item === 'string') {
|
||||
const file: types.FilePayload = {
|
||||
name: platform.basename(item),
|
||||
type: platform.getMimeType(item),
|
||||
data: await platform.readFileAsync(item, 'base64')
|
||||
name: path.basename(item),
|
||||
type: mime.getType(item) || 'application/octet-stream',
|
||||
data: await util.promisify(fs.readFile)(item, 'base64')
|
||||
};
|
||||
filePayloads.push(file);
|
||||
} else {
|
||||
@ -316,7 +319,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
return this._page._delegate.getBoundingBox(this);
|
||||
}
|
||||
|
||||
async screenshot(options?: types.ElementScreenshotOptions): Promise<platform.BufferType> {
|
||||
async screenshot(options?: types.ElementScreenshotOptions): Promise<Buffer> {
|
||||
return this._page._screenshotter.screenshotElement(this, options);
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from './platform';
|
||||
import { EventEmitter } from 'events';
|
||||
import { helper } from './helper';
|
||||
|
||||
export class ExtendedEventEmitter extends EventEmitter {
|
||||
|
@ -21,15 +21,15 @@ import { Events } from '../events';
|
||||
import { assert, helper, RegisteredListener } from '../helper';
|
||||
import * as network from '../network';
|
||||
import { Page, PageBinding } from '../page';
|
||||
import * as platform from '../platform';
|
||||
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
||||
import * as types from '../types';
|
||||
import { ConnectionEvents, FFConnection } from './ffConnection';
|
||||
import { headersArray } from './ffNetworkManager';
|
||||
import { FFPage } from './ffPage';
|
||||
import { Protocol } from './protocol';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export class FFBrowser extends platform.EventEmitter implements Browser {
|
||||
export class FFBrowser extends EventEmitter implements Browser {
|
||||
_connection: FFConnection;
|
||||
readonly _ffPages: Map<string, FFPage>;
|
||||
readonly _defaultContext: FFBrowserContext;
|
||||
@ -147,7 +147,7 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
|
||||
await disconnected;
|
||||
}
|
||||
|
||||
_setDebugFunction(debugFunction: platform.DebuggerType) {
|
||||
_setDebugFunction(debugFunction: debug.IDebugger) {
|
||||
this._connection._debugProtocol = debugFunction;
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {assert} from '../helper';
|
||||
import * as platform from '../platform';
|
||||
import * as debug from 'debug';
|
||||
import { EventEmitter } from 'events';
|
||||
import { assert } from '../helper';
|
||||
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
||||
import { Protocol } from './protocol';
|
||||
|
||||
@ -28,12 +29,12 @@ export const ConnectionEvents = {
|
||||
// should ignore.
|
||||
export const kBrowserCloseMessageId = -9999;
|
||||
|
||||
export class FFConnection extends platform.EventEmitter {
|
||||
export class FFConnection extends EventEmitter {
|
||||
private _lastId: number;
|
||||
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
|
||||
private _transport: ConnectionTransport;
|
||||
readonly _sessions: Map<string, FFSession>;
|
||||
_debugProtocol: platform.DebuggerType = platform.debug('pw:protocol');
|
||||
_debugProtocol = debug('pw:protocol');
|
||||
_closed: boolean;
|
||||
|
||||
on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||
@ -135,7 +136,7 @@ export const FFSessionEvents = {
|
||||
Disconnected: Symbol('Disconnected')
|
||||
};
|
||||
|
||||
export class FFSession extends platform.EventEmitter {
|
||||
export class FFSession extends EventEmitter {
|
||||
_connection: FFConnection;
|
||||
_disposed = false;
|
||||
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
|
||||
|
@ -20,7 +20,6 @@ import { FFSession } from './ffConnection';
|
||||
import { Page } from '../page';
|
||||
import * as network from '../network';
|
||||
import * as frames from '../frames';
|
||||
import * as platform from '../platform';
|
||||
import { Protocol } from './protocol';
|
||||
|
||||
export class FFNetworkManager {
|
||||
@ -73,7 +72,7 @@ export class FFNetworkManager {
|
||||
});
|
||||
if (response.evicted)
|
||||
throw new Error(`Response body for ${request.request.method()} ${request.request.url()} was evicted!`);
|
||||
return platform.Buffer.from(response.base64body, 'base64');
|
||||
return Buffer.from(response.base64body, 'base64');
|
||||
};
|
||||
const headers: network.Headers = {};
|
||||
for (const {name, value} of event.headers)
|
||||
@ -171,7 +170,7 @@ class InterceptableRequest implements network.RouteDelegate {
|
||||
}
|
||||
|
||||
async fulfill(response: network.FulfillResponse) {
|
||||
const responseBody = response.body && helper.isString(response.body) ? platform.Buffer.from(response.body) : (response.body || null);
|
||||
const responseBody = response.body && helper.isString(response.body) ? Buffer.from(response.body) : (response.body || null);
|
||||
|
||||
const responseHeaders: { [s: string]: string; } = {};
|
||||
if (response.headers) {
|
||||
@ -181,7 +180,7 @@ class InterceptableRequest implements network.RouteDelegate {
|
||||
if (response.contentType)
|
||||
responseHeaders['content-type'] = response.contentType;
|
||||
if (responseBody && !('content-length' in responseHeaders))
|
||||
responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody));
|
||||
responseHeaders['content-length'] = String(Buffer.byteLength(responseBody));
|
||||
|
||||
await this._session.send('Network.fulfillInterceptedRequest', {
|
||||
requestId: this._id,
|
||||
|
@ -21,7 +21,6 @@ import { Events } from '../events';
|
||||
import * as frames from '../frames';
|
||||
import { assert, debugError, helper, RegisteredListener } from '../helper';
|
||||
import { Page, PageBinding, PageDelegate, Worker } from '../page';
|
||||
import * as platform from '../platform';
|
||||
import { kScreenshotDuringNavigationError } from '../screenshotter';
|
||||
import * as types from '../types';
|
||||
import { getAccessibilityTree } from './ffAccessibility';
|
||||
@ -340,7 +339,7 @@ export class FFPage implements PageDelegate {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
async takeScreenshot(format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<platform.BufferType> {
|
||||
async takeScreenshot(format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer> {
|
||||
if (!documentRect) {
|
||||
const context = await this._page.mainFrame()._utilityContext();
|
||||
const scrollOffset = await context.evaluateInternal(() => ({ x: window.scrollX, y: window.scrollY }));
|
||||
@ -361,7 +360,7 @@ export class FFPage implements PageDelegate {
|
||||
e.message = kScreenshotDuringNavigationError;
|
||||
throw e;
|
||||
});
|
||||
return platform.Buffer.from(data, 'base64');
|
||||
return Buffer.from(data, 'base64');
|
||||
}
|
||||
|
||||
async resetViewport(): Promise<void> {
|
||||
|
@ -15,17 +15,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as types from './types';
|
||||
import * as js from './javascript';
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import { ConsoleMessage } from './console';
|
||||
import * as dom from './dom';
|
||||
import * as network from './network';
|
||||
import { helper, assert, RegisteredListener } from './helper';
|
||||
import { TimeoutError } from './errors';
|
||||
import { Events } from './events';
|
||||
import { assert, helper, RegisteredListener } from './helper';
|
||||
import * as js from './javascript';
|
||||
import * as network from './network';
|
||||
import { Page } from './page';
|
||||
import { ConsoleMessage } from './console';
|
||||
import * as platform from './platform';
|
||||
import { selectors } from './selectors';
|
||||
import * as types from './types';
|
||||
|
||||
type ContextType = 'main' | 'utility';
|
||||
type ContextData = {
|
||||
@ -109,7 +110,7 @@ export class FrameManager {
|
||||
await this._page._delegate.inputActionEpilogue();
|
||||
await barrier.waitFor();
|
||||
// Resolve in the next task, after all waitForNavigations.
|
||||
await new Promise(platform.makeWaitForNextTask());
|
||||
await new Promise(helper.makeWaitForNextTask());
|
||||
return result;
|
||||
} finally {
|
||||
this._pendingNavigationBarriers.delete(barrier);
|
||||
@ -549,7 +550,7 @@ export class Frame {
|
||||
if (url !== null)
|
||||
return (await context.evaluateHandleInternal(addScriptUrl, { url, type })).asElement()!;
|
||||
if (path !== null) {
|
||||
let contents = await platform.readFileAsync(path, 'utf8');
|
||||
let contents = await util.promisify(fs.readFile)(path, 'utf8');
|
||||
contents += '//# sourceURL=' + path.replace(/\n/g, '');
|
||||
return (await context.evaluateHandleInternal(addScriptContent, { content: contents, type })).asElement()!;
|
||||
}
|
||||
@ -598,7 +599,7 @@ export class Frame {
|
||||
return (await context.evaluateHandleInternal(addStyleUrl, url)).asElement()!;
|
||||
|
||||
if (path !== null) {
|
||||
let contents = await platform.readFileAsync(path, 'utf8');
|
||||
let contents = await util.promisify(fs.readFile)(path, 'utf8');
|
||||
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
|
||||
return (await context.evaluateHandleInternal(addStyleContent, contents)).asElement()!;
|
||||
}
|
||||
|
@ -15,18 +15,24 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as crypto from 'crypto';
|
||||
import * as debug from 'debug';
|
||||
import { EventEmitter } from 'events';
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import { TimeoutError } from './errors';
|
||||
import * as platform from './platform';
|
||||
import * as types from './types';
|
||||
|
||||
export const debugError = platform.debug(`pw:error`);
|
||||
export const debugError = debug(`pw:error`);
|
||||
|
||||
export type RegisteredListener = {
|
||||
emitter: platform.EventEmitterType;
|
||||
emitter: EventEmitter;
|
||||
eventName: (string | symbol);
|
||||
handler: (...args: any[]) => void;
|
||||
};
|
||||
|
||||
export type Listener = (...args: any[]) => void;
|
||||
|
||||
class Helper {
|
||||
static evaluationString(fun: Function | string, ...args: any[]): string {
|
||||
if (Helper.isString(fun)) {
|
||||
@ -47,7 +53,7 @@ class Helper {
|
||||
if (fun.content !== undefined) {
|
||||
fun = fun.content;
|
||||
} else if (fun.path !== undefined) {
|
||||
let contents = await platform.readFileAsync(fun.path, 'utf8');
|
||||
let contents = await util.promisify(fs.readFile)(fun.path, 'utf8');
|
||||
if (addSourceUrl)
|
||||
contents += '//# sourceURL=' + fun.path.replace(/\n/g, '');
|
||||
fun = contents;
|
||||
@ -59,7 +65,7 @@ class Helper {
|
||||
}
|
||||
|
||||
static installApiHooks(className: string, classType: any) {
|
||||
const log = platform.debug('pw:api');
|
||||
const log = debug('pw:api');
|
||||
for (const methodName of Reflect.ownKeys(classType.prototype)) {
|
||||
const method = Reflect.get(classType.prototype, methodName);
|
||||
if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function')
|
||||
@ -102,7 +108,7 @@ class Helper {
|
||||
}
|
||||
|
||||
static addEventListener(
|
||||
emitter: platform.EventEmitterType,
|
||||
emitter: EventEmitter,
|
||||
eventName: (string | symbol),
|
||||
handler: (...args: any[]) => void): RegisteredListener {
|
||||
emitter.on(eventName, handler);
|
||||
@ -110,7 +116,7 @@ class Helper {
|
||||
}
|
||||
|
||||
static removeEventListeners(listeners: Array<{
|
||||
emitter: platform.EventEmitterType;
|
||||
emitter: EventEmitter;
|
||||
eventName: (string | symbol);
|
||||
handler: (...args: any[]) => void;
|
||||
}>) {
|
||||
@ -140,7 +146,7 @@ class Helper {
|
||||
}
|
||||
|
||||
static async waitForEvent(
|
||||
emitter: platform.EventEmitterType,
|
||||
emitter: EventEmitter,
|
||||
eventName: (string | symbol),
|
||||
predicate: Function,
|
||||
timeout: number,
|
||||
@ -296,6 +302,45 @@ class Helper {
|
||||
assert(typeof match === 'function', 'url parameter should be string, RegExp or function');
|
||||
return match(url);
|
||||
}
|
||||
|
||||
// See https://joel.tools/microtasks/
|
||||
static makeWaitForNextTask() {
|
||||
if (parseInt(process.versions.node, 10) >= 11)
|
||||
return setImmediate;
|
||||
|
||||
// Unlike Node 11, Node 10 and less have a bug with Task and MicroTask execution order:
|
||||
// - https://github.com/nodejs/node/issues/22257
|
||||
//
|
||||
// So we can't simply run setImmediate to dispatch code in a following task.
|
||||
// However, we can run setImmediate from-inside setImmediate to make sure we're getting
|
||||
// in the following task.
|
||||
|
||||
let spinning = false;
|
||||
const callbacks: (() => void)[] = [];
|
||||
const loop = () => {
|
||||
const callback = callbacks.shift();
|
||||
if (!callback) {
|
||||
spinning = false;
|
||||
return;
|
||||
}
|
||||
setImmediate(loop);
|
||||
// Make sure to call callback() as the last thing since it's
|
||||
// untrusted code that might throw.
|
||||
callback();
|
||||
};
|
||||
|
||||
return (callback: () => void) => {
|
||||
callbacks.push(callback);
|
||||
if (!spinning) {
|
||||
spinning = true;
|
||||
setImmediate(loop);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static guid(): string {
|
||||
return crypto.randomBytes(16).toString('hex');
|
||||
}
|
||||
}
|
||||
|
||||
export function assert(value: any, message?: string): asserts value {
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import * as types from './types';
|
||||
import * as dom from './dom';
|
||||
import * as platform from './platform';
|
||||
import { helper } from './helper';
|
||||
|
||||
export interface ExecutionContextDelegate {
|
||||
evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: string | Function, ...args: any[]): Promise<any>;
|
||||
@ -144,7 +144,7 @@ export async function prepareFunctionCall<T>(
|
||||
const handles: (Promise<JSHandle | T>)[] = [];
|
||||
const toDispose: Promise<JSHandle>[] = [];
|
||||
const pushHandle = (handle: Promise<JSHandle | T>): string => {
|
||||
const guid = platform.guid();
|
||||
const guid = helper.guid();
|
||||
guids.push(guid);
|
||||
handles.push(handle);
|
||||
return guid;
|
||||
|
@ -14,9 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as mime from 'mime';
|
||||
import * as util from 'util';
|
||||
import * as frames from './frames';
|
||||
import { assert, helper } from './helper';
|
||||
import * as platform from './platform';
|
||||
|
||||
export type NetworkCookie = {
|
||||
name: string,
|
||||
@ -227,8 +229,8 @@ export class Route {
|
||||
response = {
|
||||
status: response.status,
|
||||
headers: response.headers,
|
||||
contentType: platform.getMimeType(response.path),
|
||||
body: await platform.readFileBuffer(response.path)
|
||||
contentType: mime.getType(response.path) || 'application/octet-stream',
|
||||
body: await util.promisify(fs.readFile)(response.path)
|
||||
};
|
||||
}
|
||||
await this._delegate.fulfill(response);
|
||||
@ -242,11 +244,11 @@ export class Route {
|
||||
|
||||
export type RouteHandler = (route: Route, request: Request) => void;
|
||||
|
||||
type GetResponseBodyCallback = () => Promise<platform.BufferType>;
|
||||
type GetResponseBodyCallback = () => Promise<Buffer>;
|
||||
|
||||
export class Response {
|
||||
private _request: Request;
|
||||
private _contentPromise: Promise<platform.BufferType> | null = null;
|
||||
private _contentPromise: Promise<Buffer> | null = null;
|
||||
_finishedPromise: Promise<Error | null>;
|
||||
private _finishedPromiseCallback: any;
|
||||
private _status: number;
|
||||
@ -296,7 +298,7 @@ export class Response {
|
||||
return this._finishedPromise;
|
||||
}
|
||||
|
||||
body(): Promise<platform.BufferType> {
|
||||
body(): Promise<Buffer> {
|
||||
if (!this._contentPromise) {
|
||||
this._contentPromise = this._finishedPromise.then(async error => {
|
||||
if (error)
|
||||
@ -330,7 +332,7 @@ export type FulfillResponse = {
|
||||
status?: number,
|
||||
headers?: Headers,
|
||||
contentType?: string,
|
||||
body?: string | platform.BufferType,
|
||||
body?: string | Buffer,
|
||||
};
|
||||
|
||||
export interface RouteDelegate {
|
||||
|
18
src/page.ts
18
src/page.ts
@ -17,7 +17,7 @@
|
||||
|
||||
import * as dom from './dom';
|
||||
import * as frames from './frames';
|
||||
import { assert, debugError, helper } from './helper';
|
||||
import { assert, debugError, helper, Listener } from './helper';
|
||||
import * as input from './input';
|
||||
import * as js from './javascript';
|
||||
import * as network from './network';
|
||||
@ -28,8 +28,8 @@ import { Events } from './events';
|
||||
import { BrowserContext, BrowserContextBase } from './browserContext';
|
||||
import { ConsoleMessage, ConsoleMessageLocation } from './console';
|
||||
import * as accessibility from './accessibility';
|
||||
import * as platform from './platform';
|
||||
import { ExtendedEventEmitter } from './extendedEventEmitter';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export interface PageDelegate {
|
||||
readonly rawMouse: input.RawMouse;
|
||||
@ -55,7 +55,7 @@ export interface PageDelegate {
|
||||
canScreenshotOutsideViewport(): boolean;
|
||||
resetViewport(): Promise<void>; // Only called if canScreenshotOutsideViewport() returns false.
|
||||
setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void>;
|
||||
takeScreenshot(format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<platform.BufferType>;
|
||||
takeScreenshot(format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer>;
|
||||
|
||||
isElementHandle(remoteObject: any): boolean;
|
||||
adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>>;
|
||||
@ -69,7 +69,7 @@ export interface PageDelegate {
|
||||
scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<void>;
|
||||
|
||||
getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}>;
|
||||
pdf?: (options?: types.PDFOptions) => Promise<platform.BufferType>;
|
||||
pdf?: (options?: types.PDFOptions) => Promise<Buffer>;
|
||||
coverage?: () => any;
|
||||
|
||||
// Work around Chrome's non-associated input and protocol.
|
||||
@ -106,7 +106,7 @@ export class Page extends ExtendedEventEmitter {
|
||||
readonly _frameManager: frames.FrameManager;
|
||||
readonly accessibility: accessibility.Accessibility;
|
||||
private _workers = new Map<string, Worker>();
|
||||
readonly pdf: ((options?: types.PDFOptions) => Promise<platform.BufferType>) | undefined;
|
||||
readonly pdf: ((options?: types.PDFOptions) => Promise<Buffer>) | undefined;
|
||||
readonly coverage: any;
|
||||
readonly _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = [];
|
||||
_ownedContext: BrowserContext | undefined;
|
||||
@ -408,7 +408,7 @@ export class Page extends ExtendedEventEmitter {
|
||||
route.continue();
|
||||
}
|
||||
|
||||
async screenshot(options?: types.ScreenshotOptions): Promise<platform.BufferType> {
|
||||
async screenshot(options?: types.ScreenshotOptions): Promise<Buffer> {
|
||||
return this._screenshotter.screenshotPage(options);
|
||||
}
|
||||
|
||||
@ -506,7 +506,7 @@ export class Page extends ExtendedEventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
on(event: string | symbol, listener: platform.Listener): this {
|
||||
on(event: string | symbol, listener: Listener): this {
|
||||
if (event === Events.Page.FileChooser) {
|
||||
if (!this.listenerCount(event))
|
||||
this._delegate.setFileChooserIntercepted(true);
|
||||
@ -515,7 +515,7 @@ export class Page extends ExtendedEventEmitter {
|
||||
return this;
|
||||
}
|
||||
|
||||
removeListener(event: string | symbol, listener: platform.Listener): this {
|
||||
removeListener(event: string | symbol, listener: Listener): this {
|
||||
super.removeListener(event, listener);
|
||||
if (event === Events.Page.FileChooser && !this.listenerCount(event))
|
||||
this._delegate.setFileChooserIntercepted(false);
|
||||
@ -523,7 +523,7 @@ export class Page extends ExtendedEventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
export class Worker extends platform.EventEmitter {
|
||||
export class Worker extends EventEmitter {
|
||||
private _url: string;
|
||||
private _executionContextPromise: Promise<js.ExecutionContext>;
|
||||
private _executionContextCallback: (value?: js.ExecutionContext) => void;
|
||||
|
562
src/platform.ts
562
src/platform.ts
@ -1,562 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Note: this is the only file outside of src/server which can import external dependencies.
|
||||
// All dependencies must be listed in web.webpack.config.js to avoid bundling them.
|
||||
import * as nodeEvents from 'events';
|
||||
import * as nodeFS from 'fs';
|
||||
import * as nodePath from 'path';
|
||||
import * as nodeDebug from 'debug';
|
||||
import * as nodeBuffer from 'buffer';
|
||||
import * as jpeg from 'jpeg-js';
|
||||
import * as png from 'pngjs';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as NodeWebSocket from 'ws';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
import { assert, helper } from './helper';
|
||||
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from './transport';
|
||||
|
||||
export const isNode = typeof process === 'object' && !!process && typeof process.versions === 'object' && !!process.versions && !!process.versions.node;
|
||||
|
||||
export function promisify(nodeFunction: Function): Function {
|
||||
assert(isNode);
|
||||
function promisified(...args: any[]) {
|
||||
return new Promise((resolve, reject) => {
|
||||
function callback(err: any, ...result: any[]) {
|
||||
if (err)
|
||||
return reject(err);
|
||||
if (result.length === 1)
|
||||
return resolve(result[0]);
|
||||
return resolve(result);
|
||||
}
|
||||
nodeFunction.call(null, ...args, callback);
|
||||
});
|
||||
}
|
||||
return promisified;
|
||||
}
|
||||
|
||||
export type Listener = (...args: any[]) => void;
|
||||
export const EventEmitter: typeof nodeEvents.EventEmitter = isNode ? nodeEvents.EventEmitter : (
|
||||
class EventEmitterImpl {
|
||||
private _deliveryQueue?: {listener: Listener, args: any[]}[];
|
||||
private _listeners = new Map<string | symbol, Set<Listener>>();
|
||||
|
||||
on(event: string | symbol, listener: Listener): this {
|
||||
let set = this._listeners.get(event);
|
||||
if (!set) {
|
||||
set = new Set();
|
||||
this._listeners.set(event, set);
|
||||
}
|
||||
set.add(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
addListener(event: string | symbol, listener: Listener): this {
|
||||
return this.on(event, listener);
|
||||
}
|
||||
|
||||
once(event: string | symbol, listener: Listener): this {
|
||||
const wrapped = (...args: any[]) => {
|
||||
this.removeListener(event, wrapped);
|
||||
listener(...args);
|
||||
};
|
||||
return this.addListener(event, wrapped);
|
||||
}
|
||||
|
||||
removeListener(event: string | symbol, listener: Listener): this {
|
||||
const set = this._listeners.get(event);
|
||||
if (set)
|
||||
set.delete(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
emit(event: string | symbol, ...args: any[]): boolean {
|
||||
const set = this._listeners.get(event);
|
||||
if (!set || !set.size)
|
||||
return true;
|
||||
const dispatch = !this._deliveryQueue;
|
||||
if (!this._deliveryQueue)
|
||||
this._deliveryQueue = [];
|
||||
for (const listener of set)
|
||||
this._deliveryQueue.push({ listener, args });
|
||||
if (!dispatch)
|
||||
return true;
|
||||
for (let index = 0; index < this._deliveryQueue.length; index++) {
|
||||
const { listener, args } = this._deliveryQueue[index];
|
||||
listener(...args);
|
||||
}
|
||||
this._deliveryQueue = undefined;
|
||||
return true;
|
||||
}
|
||||
|
||||
listenerCount(event: string | symbol): number {
|
||||
const set = this._listeners.get(event);
|
||||
return set ? set.size : 0;
|
||||
}
|
||||
}
|
||||
) as any as typeof nodeEvents.EventEmitter;
|
||||
export type EventEmitterType = nodeEvents.EventEmitter;
|
||||
|
||||
export type DebuggerType = nodeDebug.IDebugger;
|
||||
export type DebugType = nodeDebug.IDebug;
|
||||
export const debug: DebugType = isNode ? nodeDebug : (
|
||||
function debug(namespace: string) {
|
||||
return () => {};
|
||||
}
|
||||
) as any as DebugType;
|
||||
|
||||
export const Buffer: typeof nodeBuffer.Buffer = isNode ? nodeBuffer.Buffer : (
|
||||
class BufferImpl {
|
||||
readonly data: ArrayBuffer;
|
||||
|
||||
static from(data: string | ArrayBuffer, encoding: string = 'utf8'): BufferImpl {
|
||||
return new BufferImpl(data, encoding);
|
||||
}
|
||||
|
||||
static byteLength(buffer: BufferImpl | string, encoding: string = 'utf8'): number {
|
||||
if (helper.isString(buffer))
|
||||
buffer = new BufferImpl(buffer, encoding);
|
||||
return buffer.data.byteLength;
|
||||
}
|
||||
|
||||
static concat(buffers: BufferImpl[]): BufferImpl {
|
||||
if (!buffers.length)
|
||||
return new BufferImpl(new ArrayBuffer(0));
|
||||
if (buffers.length === 1)
|
||||
return buffers[0];
|
||||
const view = new Uint8Array(buffers.reduce((a, b) => a + b.data.byteLength, 0));
|
||||
let offset = 0;
|
||||
for (const buffer of buffers) {
|
||||
view.set(new Uint8Array(buffer.data), offset);
|
||||
offset += buffer.data.byteLength;
|
||||
}
|
||||
return new BufferImpl(view.buffer);
|
||||
}
|
||||
|
||||
constructor(data: string | ArrayBuffer, encoding: string = 'utf8') {
|
||||
if (data instanceof ArrayBuffer) {
|
||||
this.data = data;
|
||||
} else {
|
||||
if (encoding === 'base64') {
|
||||
const binary = atob(data);
|
||||
this.data = new ArrayBuffer(binary.length * 2);
|
||||
const view = new Uint16Array(this.data);
|
||||
for (let i = 0; i < binary.length; i++)
|
||||
view[i] = binary.charCodeAt(i);
|
||||
} else if (encoding === 'utf8') {
|
||||
const encoder = new TextEncoder();
|
||||
this.data = encoder.encode(data).buffer;
|
||||
} else {
|
||||
throw new Error('Unsupported encoding "' + encoding + '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toString(encoding: string = 'utf8'): string {
|
||||
if (encoding === 'base64') {
|
||||
const binary = String.fromCharCode(...new Uint16Array(this.data));
|
||||
return btoa(binary);
|
||||
}
|
||||
const decoder = new TextDecoder(encoding, { fatal: true });
|
||||
return decoder.decode(this.data);
|
||||
}
|
||||
}
|
||||
) as any as typeof nodeBuffer.Buffer;
|
||||
export type BufferType = Buffer;
|
||||
|
||||
function assertFileAccess() {
|
||||
assert(isNode, 'Working with filesystem using "path" is only supported in Node.js');
|
||||
}
|
||||
|
||||
export async function readFileAsync(file: string, encoding: string): Promise<string> {
|
||||
assertFileAccess();
|
||||
return await promisify(nodeFS.readFile)(file, encoding);
|
||||
}
|
||||
|
||||
export async function readFileBuffer(file: string): Promise<BufferType> {
|
||||
assertFileAccess();
|
||||
return await promisify(nodeFS.readFile)(file);
|
||||
}
|
||||
|
||||
export async function writeFileAsync(file: string, data: any) {
|
||||
assertFileAccess();
|
||||
return await promisify(nodeFS.writeFile)(file, data);
|
||||
}
|
||||
|
||||
export function basename(file: string): string {
|
||||
assertFileAccess();
|
||||
return nodePath.basename(file);
|
||||
}
|
||||
|
||||
export async function openFdAsync(file: string, flags: string): Promise<number> {
|
||||
assertFileAccess();
|
||||
return await promisify(nodeFS.open)(file, flags);
|
||||
}
|
||||
|
||||
export async function writeFdAsync(fd: number, buffer: Buffer): Promise<void> {
|
||||
assertFileAccess();
|
||||
return await promisify(nodeFS.write)(fd, buffer);
|
||||
}
|
||||
|
||||
export async function closeFdAsync(fd: number): Promise<void> {
|
||||
assertFileAccess();
|
||||
return await promisify(nodeFS.close)(fd);
|
||||
}
|
||||
|
||||
export function getMimeType(file: string): string {
|
||||
const extension = file.substring(file.lastIndexOf('.') + 1);
|
||||
return extensionToMime[extension] || 'application/octet-stream';
|
||||
}
|
||||
|
||||
export function pngToJpeg(buffer: Buffer, quality?: number): Buffer {
|
||||
assert(isNode, 'Converting from png to jpeg is only supported in Node.js');
|
||||
return jpeg.encode(png.PNG.sync.read(buffer), quality).data;
|
||||
}
|
||||
|
||||
function nodeFetch(url: string): Promise<string> {
|
||||
let resolve: (url: string) => void;
|
||||
let reject: (e: Error) => void = () => {};
|
||||
const promise = new Promise<string>((res, rej) => { resolve = res; reject = rej; });
|
||||
|
||||
const endpointURL = new URL(url);
|
||||
const protocol = endpointURL.protocol === 'https:' ? https : http;
|
||||
const request = protocol.request(endpointURL, res => {
|
||||
let data = '';
|
||||
if (res.statusCode !== 200) {
|
||||
// Consume response data to free up memory.
|
||||
res.resume();
|
||||
reject(new Error('HTTP ' + res.statusCode));
|
||||
return;
|
||||
}
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => resolve(data));
|
||||
});
|
||||
|
||||
request.on('error', reject);
|
||||
request.end();
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
export function fetchUrl(url: string): Promise<string> {
|
||||
if (isNode)
|
||||
return nodeFetch(url);
|
||||
return fetch(url).then(response => {
|
||||
if (!response.ok)
|
||||
throw new Error('HTTP ' + response.status + ' ' + response.statusText);
|
||||
return response.text();
|
||||
});
|
||||
}
|
||||
|
||||
// See https://joel.tools/microtasks/
|
||||
export function makeWaitForNextTask() {
|
||||
if (!isNode)
|
||||
return (func: () => void) => setTimeout(func, 0);
|
||||
|
||||
if (parseInt(process.versions.node, 10) >= 11)
|
||||
return setImmediate;
|
||||
|
||||
// Unlike Node 11, Node 10 and less have a bug with Task and MicroTask execution order:
|
||||
// - https://github.com/nodejs/node/issues/22257
|
||||
//
|
||||
// So we can't simply run setImmediate to dispatch code in a following task.
|
||||
// However, we can run setImmediate from-inside setImmediate to make sure we're getting
|
||||
// in the following task.
|
||||
|
||||
let spinning = false;
|
||||
const callbacks: (() => void)[] = [];
|
||||
const loop = () => {
|
||||
const callback = callbacks.shift();
|
||||
if (!callback) {
|
||||
spinning = false;
|
||||
return;
|
||||
}
|
||||
setImmediate(loop);
|
||||
// Make sure to call callback() as the last thing since it's
|
||||
// untrusted code that might throw.
|
||||
callback();
|
||||
};
|
||||
|
||||
return (callback: () => void) => {
|
||||
callbacks.push(callback);
|
||||
if (!spinning) {
|
||||
spinning = true;
|
||||
setImmediate(loop);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function guid(): string {
|
||||
if (isNode)
|
||||
return crypto.randomBytes(16).toString('hex');
|
||||
const a = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(a);
|
||||
return Array.from(a).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// 'onmessage' handler must be installed synchronously when 'onopen' callback is invoked to
|
||||
// avoid missing incoming messages.
|
||||
export async function connectToWebsocket<T>(url: string, onopen: (transport: ConnectionTransport) => Promise<T> | T): Promise<T> {
|
||||
const transport = new WebSocketTransport(url);
|
||||
return new Promise<T>((fulfill, reject) => {
|
||||
transport._ws.addEventListener('open', async () => fulfill(await onopen(transport)));
|
||||
transport._ws.addEventListener('error', event => reject(new Error('WebSocket error: ' + (event as ErrorEvent).message)));
|
||||
});
|
||||
}
|
||||
|
||||
class WebSocketTransport implements ConnectionTransport {
|
||||
_ws: WebSocket;
|
||||
|
||||
onmessage?: (message: ProtocolResponse) => void;
|
||||
onclose?: () => void;
|
||||
|
||||
constructor(url: string) {
|
||||
this._ws = (isNode ? new NodeWebSocket(url, [], {
|
||||
perMessageDeflate: false,
|
||||
maxPayload: 256 * 1024 * 1024, // 256Mb
|
||||
}) : new WebSocket(url)) as WebSocket;
|
||||
// The 'ws' module in node sometimes sends us multiple messages in a single task.
|
||||
// In Web, all IO callbacks (e.g. WebSocket callbacks)
|
||||
// are dispatched into separate tasks, so there's no need
|
||||
// to do anything extra.
|
||||
const messageWrap: (cb: () => void) => void = isNode ? makeWaitForNextTask() : cb => cb();
|
||||
|
||||
this._ws.addEventListener('message', event => {
|
||||
messageWrap(() => {
|
||||
if (this.onmessage)
|
||||
this.onmessage.call(null, JSON.parse(event.data));
|
||||
});
|
||||
});
|
||||
|
||||
this._ws.addEventListener('close', event => {
|
||||
if (this.onclose)
|
||||
this.onclose.call(null);
|
||||
});
|
||||
// Silently ignore all errors - we don't know what to do with them.
|
||||
this._ws.addEventListener('error', () => {});
|
||||
}
|
||||
|
||||
send(message: ProtocolRequest) {
|
||||
this._ws.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
close() {
|
||||
this._ws.close();
|
||||
}
|
||||
}
|
||||
|
||||
const extensionToMime: { [key: string]: string } = {
|
||||
'ai': 'application/postscript',
|
||||
'apng': 'image/apng',
|
||||
'appcache': 'text/cache-manifest',
|
||||
'au': 'audio/basic',
|
||||
'bmp': 'image/bmp',
|
||||
'cer': 'application/pkix-cert',
|
||||
'cgm': 'image/cgm',
|
||||
'coffee': 'text/coffeescript',
|
||||
'conf': 'text/plain',
|
||||
'crl': 'application/pkix-crl',
|
||||
'css': 'text/css',
|
||||
'csv': 'text/csv',
|
||||
'def': 'text/plain',
|
||||
'doc': 'application/msword',
|
||||
'dot': 'application/msword',
|
||||
'drle': 'image/dicom-rle',
|
||||
'dtd': 'application/xml-dtd',
|
||||
'ear': 'application/java-archive',
|
||||
'emf': 'image/emf',
|
||||
'eps': 'application/postscript',
|
||||
'exr': 'image/aces',
|
||||
'fits': 'image/fits',
|
||||
'g3': 'image/g3fax',
|
||||
'gbr': 'application/rpki-ghostbusters',
|
||||
'gif': 'image/gif',
|
||||
'glb': 'model/gltf-binary',
|
||||
'gltf': 'model/gltf+json',
|
||||
'gz': 'application/gzip',
|
||||
'h261': 'video/h261',
|
||||
'h263': 'video/h263',
|
||||
'h264': 'video/h264',
|
||||
'heic': 'image/heic',
|
||||
'heics': 'image/heic-sequence',
|
||||
'heif': 'image/heif',
|
||||
'heifs': 'image/heif-sequence',
|
||||
'htm': 'text/html',
|
||||
'html': 'text/html',
|
||||
'ics': 'text/calendar',
|
||||
'ief': 'image/ief',
|
||||
'ifb': 'text/calendar',
|
||||
'iges': 'model/iges',
|
||||
'igs': 'model/iges',
|
||||
'in': 'text/plain',
|
||||
'ini': 'text/plain',
|
||||
'jade': 'text/jade',
|
||||
'jar': 'application/java-archive',
|
||||
'jls': 'image/jls',
|
||||
'jp2': 'image/jp2',
|
||||
'jpe': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'jpf': 'image/jpx',
|
||||
'jpg': 'image/jpeg',
|
||||
'jpg2': 'image/jp2',
|
||||
'jpgm': 'video/jpm',
|
||||
'jpgv': 'video/jpeg',
|
||||
'jpm': 'image/jpm',
|
||||
'jpx': 'image/jpx',
|
||||
'js': 'application/javascript',
|
||||
'json': 'application/json',
|
||||
'json5': 'application/json5',
|
||||
'jsx': 'text/jsx',
|
||||
'jxr': 'image/jxr',
|
||||
'kar': 'audio/midi',
|
||||
'ktx': 'image/ktx',
|
||||
'less': 'text/less',
|
||||
'list': 'text/plain',
|
||||
'litcoffee': 'text/coffeescript',
|
||||
'log': 'text/plain',
|
||||
'm1v': 'video/mpeg',
|
||||
'm21': 'application/mp21',
|
||||
'm2a': 'audio/mpeg',
|
||||
'm2v': 'video/mpeg',
|
||||
'm3a': 'audio/mpeg',
|
||||
'm4a': 'audio/mp4',
|
||||
'm4p': 'application/mp4',
|
||||
'man': 'text/troff',
|
||||
'manifest': 'text/cache-manifest',
|
||||
'markdown': 'text/markdown',
|
||||
'mathml': 'application/mathml+xml',
|
||||
'md': 'text/markdown',
|
||||
'mdx': 'text/mdx',
|
||||
'me': 'text/troff',
|
||||
'mesh': 'model/mesh',
|
||||
'mft': 'application/rpki-manifest',
|
||||
'mid': 'audio/midi',
|
||||
'midi': 'audio/midi',
|
||||
'mj2': 'video/mj2',
|
||||
'mjp2': 'video/mj2',
|
||||
'mjs': 'application/javascript',
|
||||
'mml': 'text/mathml',
|
||||
'mov': 'video/quicktime',
|
||||
'mp2': 'audio/mpeg',
|
||||
'mp21': 'application/mp21',
|
||||
'mp2a': 'audio/mpeg',
|
||||
'mp3': 'audio/mpeg',
|
||||
'mp4': 'video/mp4',
|
||||
'mp4a': 'audio/mp4',
|
||||
'mp4s': 'application/mp4',
|
||||
'mp4v': 'video/mp4',
|
||||
'mpe': 'video/mpeg',
|
||||
'mpeg': 'video/mpeg',
|
||||
'mpg': 'video/mpeg',
|
||||
'mpg4': 'video/mp4',
|
||||
'mpga': 'audio/mpeg',
|
||||
'mrc': 'application/marc',
|
||||
'ms': 'text/troff',
|
||||
'msh': 'model/mesh',
|
||||
'n3': 'text/n3',
|
||||
'oga': 'audio/ogg',
|
||||
'ogg': 'audio/ogg',
|
||||
'ogv': 'video/ogg',
|
||||
'ogx': 'application/ogg',
|
||||
'otf': 'font/otf',
|
||||
'p10': 'application/pkcs10',
|
||||
'p7c': 'application/pkcs7-mime',
|
||||
'p7m': 'application/pkcs7-mime',
|
||||
'p7s': 'application/pkcs7-signature',
|
||||
'p8': 'application/pkcs8',
|
||||
'pdf': 'application/pdf',
|
||||
'pki': 'application/pkixcmp',
|
||||
'pkipath': 'application/pkix-pkipath',
|
||||
'png': 'image/png',
|
||||
'ps': 'application/postscript',
|
||||
'pskcxml': 'application/pskc+xml',
|
||||
'qt': 'video/quicktime',
|
||||
'rmi': 'audio/midi',
|
||||
'rng': 'application/xml',
|
||||
'roa': 'application/rpki-roa',
|
||||
'roff': 'text/troff',
|
||||
'rsd': 'application/rsd+xml',
|
||||
'rss': 'application/rss+xml',
|
||||
'rtf': 'application/rtf',
|
||||
'rtx': 'text/richtext',
|
||||
's3m': 'audio/s3m',
|
||||
'sgi': 'image/sgi',
|
||||
'sgm': 'text/sgml',
|
||||
'sgml': 'text/sgml',
|
||||
'shex': 'text/shex',
|
||||
'shtml': 'text/html',
|
||||
'sil': 'audio/silk',
|
||||
'silo': 'model/mesh',
|
||||
'slim': 'text/slim',
|
||||
'slm': 'text/slim',
|
||||
'snd': 'audio/basic',
|
||||
'spx': 'audio/ogg',
|
||||
'stl': 'model/stl',
|
||||
'styl': 'text/stylus',
|
||||
'stylus': 'text/stylus',
|
||||
'svg': 'image/svg+xml',
|
||||
'svgz': 'image/svg+xml',
|
||||
't': 'text/troff',
|
||||
't38': 'image/t38',
|
||||
'text': 'text/plain',
|
||||
'tfx': 'image/tiff-fx',
|
||||
'tif': 'image/tiff',
|
||||
'tiff': 'image/tiff',
|
||||
'tr': 'text/troff',
|
||||
'ts': 'video/mp2t',
|
||||
'tsv': 'text/tab-separated-values',
|
||||
'ttc': 'font/collection',
|
||||
'ttf': 'font/ttf',
|
||||
'ttl': 'text/turtle',
|
||||
'txt': 'text/plain',
|
||||
'uri': 'text/uri-list',
|
||||
'uris': 'text/uri-list',
|
||||
'urls': 'text/uri-list',
|
||||
'vcard': 'text/vcard',
|
||||
'vrml': 'model/vrml',
|
||||
'vtt': 'text/vtt',
|
||||
'war': 'application/java-archive',
|
||||
'wasm': 'application/wasm',
|
||||
'wav': 'audio/wav',
|
||||
'weba': 'audio/webm',
|
||||
'webm': 'video/webm',
|
||||
'webmanifest': 'application/manifest+json',
|
||||
'webp': 'image/webp',
|
||||
'wmf': 'image/wmf',
|
||||
'woff': 'font/woff',
|
||||
'woff2': 'font/woff2',
|
||||
'wrl': 'model/vrml',
|
||||
'x3d': 'model/x3d+xml',
|
||||
'x3db': 'model/x3d+fastinfoset',
|
||||
'x3dbz': 'model/x3d+binary',
|
||||
'x3dv': 'model/x3d-vrml',
|
||||
'x3dvz': 'model/x3d+vrml',
|
||||
'x3dz': 'model/x3d+xml',
|
||||
'xaml': 'application/xaml+xml',
|
||||
'xht': 'application/xhtml+xml',
|
||||
'xhtml': 'application/xhtml+xml',
|
||||
'xm': 'audio/xm',
|
||||
'xml': 'text/xml',
|
||||
'xsd': 'application/xml',
|
||||
'xsl': 'application/xml',
|
||||
'xslt': 'application/xslt+xml',
|
||||
'yaml': 'text/yaml',
|
||||
'yml': 'text/yaml',
|
||||
'zip': 'application/zip'
|
||||
};
|
@ -15,11 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as mime from 'mime';
|
||||
import * as util from 'util';
|
||||
import * as dom from './dom';
|
||||
import { assert, helper } from './helper';
|
||||
import * as types from './types';
|
||||
import { Page } from './page';
|
||||
import * as platform from './platform';
|
||||
import * as types from './types';
|
||||
|
||||
export class Screenshotter {
|
||||
private _queue = new TaskQueue();
|
||||
@ -78,7 +80,7 @@ export class Screenshotter {
|
||||
return fullPageSize;
|
||||
}
|
||||
|
||||
async screenshotPage(options: types.ScreenshotOptions = {}): Promise<platform.BufferType> {
|
||||
async screenshotPage(options: types.ScreenshotOptions = {}): Promise<Buffer> {
|
||||
const format = validateScreenshotOptions(options);
|
||||
return this._queue.postTask(async () => {
|
||||
const { viewportSize, originalViewportSize } = await this._originalViewportSize();
|
||||
@ -102,7 +104,7 @@ export class Screenshotter {
|
||||
}).catch(rewriteError);
|
||||
}
|
||||
|
||||
async screenshotElement(handle: dom.ElementHandle, options: types.ElementScreenshotOptions = {}): Promise<platform.BufferType> {
|
||||
async screenshotElement(handle: dom.ElementHandle, options: types.ElementScreenshotOptions = {}): Promise<Buffer> {
|
||||
const format = validateScreenshotOptions(options);
|
||||
return this._queue.postTask(async () => {
|
||||
const { viewportSize, originalViewportSize } = await this._originalViewportSize();
|
||||
@ -138,7 +140,7 @@ export class Screenshotter {
|
||||
}).catch(rewriteError);
|
||||
}
|
||||
|
||||
private async _screenshot(format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, options: types.ElementScreenshotOptions, overridenViewportSize: types.Size | null, originalViewportSize: types.Size | null): Promise<platform.BufferType> {
|
||||
private async _screenshot(format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, options: types.ElementScreenshotOptions, overridenViewportSize: types.Size | null, originalViewportSize: types.Size | null): Promise<Buffer> {
|
||||
const shouldSetDefaultBackground = options.omitBackground && format === 'png';
|
||||
if (shouldSetDefaultBackground)
|
||||
await this._page._delegate.setBackgroundColor({ r: 0, g: 0, b: 0, a: 0});
|
||||
@ -153,7 +155,7 @@ export class Screenshotter {
|
||||
await this._page._delegate.resetViewport();
|
||||
}
|
||||
if (options.path)
|
||||
await platform.writeFileAsync(options.path, buffer);
|
||||
await util.promisify(fs.writeFile)(options.path, buffer);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@ -196,7 +198,7 @@ function validateScreenshotOptions(options: types.ScreenshotOptions): 'png' | 'j
|
||||
assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type);
|
||||
format = options.type;
|
||||
} else if (options.path) {
|
||||
const mimeType = platform.getMimeType(options.path);
|
||||
const mimeType = mime.getType(options.path);
|
||||
if (mimeType === 'image/png')
|
||||
format = 'png';
|
||||
else if (mimeType === 'image/jpeg')
|
||||
|
@ -25,10 +25,9 @@ import * as path from 'path';
|
||||
import { getProxyForUrl } from 'proxy-from-env';
|
||||
import * as URL from 'url';
|
||||
import { assert } from '../helper';
|
||||
import * as platform from '../platform';
|
||||
|
||||
const unlinkAsync = platform.promisify(fs.unlink.bind(fs));
|
||||
const chmodAsync = platform.promisify(fs.chmod.bind(fs));
|
||||
const unlinkAsync = util.promisify(fs.unlink.bind(fs));
|
||||
const chmodAsync = util.promisify(fs.chmod.bind(fs));
|
||||
const existsAsync = (path: string): Promise<boolean> => new Promise(resolve => fs.stat(path, err => resolve(!err)));
|
||||
|
||||
const DEFAULT_DOWNLOAD_HOSTS = {
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { ChildProcess, execSync } from 'child_process';
|
||||
import * as platform from '../platform';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export class WebSocketWrapper {
|
||||
readonly wsEndpoint: string;
|
||||
@ -46,7 +46,7 @@ export class WebSocketWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
export class BrowserServer extends platform.EventEmitter {
|
||||
export class BrowserServer extends EventEmitter {
|
||||
private _process: ChildProcess;
|
||||
private _gracefullyClose: () => Promise<void>;
|
||||
private _webSocketWrapper: WebSocketWrapper | null;
|
||||
|
@ -18,9 +18,9 @@
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as util from 'util';
|
||||
import { debugError, helper, assert } from '../helper';
|
||||
import { CRBrowser } from '../chromium/crBrowser';
|
||||
import * as platform from '../platform';
|
||||
import * as ws from 'ws';
|
||||
import { launchProcess } from '../server/processLauncher';
|
||||
import { kBrowserCloseMessageId } from '../chromium/crConnection';
|
||||
@ -29,7 +29,7 @@ import { LaunchOptions, BrowserArgOptions, BrowserType, ConnectOptions, LaunchSe
|
||||
import { LaunchType } from '../browser';
|
||||
import { BrowserServer, WebSocketWrapper } from './browserServer';
|
||||
import { Events } from '../events';
|
||||
import { ConnectionTransport, ProtocolRequest } from '../transport';
|
||||
import { ConnectionTransport, ProtocolRequest, WebSocketTransport } from '../transport';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
|
||||
export class Chromium implements BrowserType<CRBrowser> {
|
||||
@ -85,14 +85,14 @@ export class Chromium implements BrowserType<CRBrowser> {
|
||||
let temporaryUserDataDir: string | null = null;
|
||||
if (!userDataDir) {
|
||||
userDataDir = await mkdtempAsync(CHROMIUM_PROFILE_PATH);
|
||||
temporaryUserDataDir = userDataDir!;
|
||||
temporaryUserDataDir = userDataDir;
|
||||
}
|
||||
|
||||
const chromeArguments = [];
|
||||
if (!ignoreDefaultArgs)
|
||||
chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir!));
|
||||
chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir));
|
||||
else if (Array.isArray(ignoreDefaultArgs))
|
||||
chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir!).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1));
|
||||
chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1));
|
||||
else
|
||||
chromeArguments.push(...args);
|
||||
|
||||
@ -133,7 +133,7 @@ export class Chromium implements BrowserType<CRBrowser> {
|
||||
}
|
||||
|
||||
async connect(options: ConnectOptions): Promise<CRBrowser> {
|
||||
return await platform.connectToWebsocket(options.wsEndpoint, transport => {
|
||||
return await WebSocketTransport.connect(options.wsEndpoint, transport => {
|
||||
return CRBrowser.connect(transport, false, options.slowMo);
|
||||
});
|
||||
}
|
||||
@ -178,7 +178,7 @@ export class Chromium implements BrowserType<CRBrowser> {
|
||||
|
||||
function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number): WebSocketWrapper {
|
||||
const server = new ws.Server({ port });
|
||||
const guid = platform.guid();
|
||||
const guid = helper.guid();
|
||||
|
||||
const awaitingBrowserTarget = new Map<number, ws>();
|
||||
const sessionToSocket = new Map<string, ws>();
|
||||
@ -296,7 +296,7 @@ function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number
|
||||
}
|
||||
|
||||
|
||||
const mkdtempAsync = platform.promisify(fs.mkdtemp);
|
||||
const mkdtempAsync = util.promisify(fs.mkdtemp);
|
||||
|
||||
const CHROMIUM_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_dev_profile-');
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as util from 'util';
|
||||
import * as ws from 'ws';
|
||||
import { LaunchType } from '../browser';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
@ -26,13 +27,12 @@ import { Events } from '../events';
|
||||
import { FFBrowser } from '../firefox/ffBrowser';
|
||||
import { kBrowserCloseMessageId } from '../firefox/ffConnection';
|
||||
import { debugError, helper, assert } from '../helper';
|
||||
import * as platform from '../platform';
|
||||
import { BrowserServer, WebSocketWrapper } from './browserServer';
|
||||
import { BrowserArgOptions, BrowserType, LaunchOptions, LaunchServerOptions, ConnectOptions } from './browserType';
|
||||
import { launchProcess, waitForLine } from './processLauncher';
|
||||
import { ConnectionTransport, SequenceNumberMixer } from '../transport';
|
||||
import { ConnectionTransport, SequenceNumberMixer, WebSocketTransport } from '../transport';
|
||||
|
||||
const mkdtempAsync = platform.promisify(fs.mkdtemp);
|
||||
const mkdtempAsync = util.promisify(fs.mkdtemp);
|
||||
|
||||
export class Firefox implements BrowserType<FFBrowser> {
|
||||
private _executablePath: (string|undefined);
|
||||
@ -50,7 +50,7 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||
async launch(options: LaunchOptions = {}): Promise<FFBrowser> {
|
||||
assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
|
||||
const browserServer = await this._launchServer(options, 'local');
|
||||
const browser = await platform.connectToWebsocket(browserServer.wsEndpoint()!, transport => {
|
||||
const browser = await WebSocketTransport.connect(browserServer.wsEndpoint()!, transport => {
|
||||
return FFBrowser.connect(transport, false, options.slowMo);
|
||||
});
|
||||
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
|
||||
@ -69,7 +69,7 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||
slowMo = 0,
|
||||
} = options;
|
||||
const browserServer = await this._launchServer(options, 'persistent', userDataDir);
|
||||
const browser = await platform.connectToWebsocket(browserServer.wsEndpoint()!, transport => {
|
||||
const browser = await WebSocketTransport.connect(browserServer.wsEndpoint()!, transport => {
|
||||
return FFBrowser.connect(transport, true, slowMo);
|
||||
});
|
||||
await helper.waitWithTimeout(browser._firstPagePromise, 'first page', timeout);
|
||||
@ -103,9 +103,9 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||
}
|
||||
|
||||
if (!ignoreDefaultArgs)
|
||||
firefoxArguments.push(...this._defaultArgs(options, launchType, userDataDir!, 0));
|
||||
firefoxArguments.push(...this._defaultArgs(options, launchType, userDataDir, 0));
|
||||
else if (Array.isArray(ignoreDefaultArgs))
|
||||
firefoxArguments.push(...this._defaultArgs(options, launchType, userDataDir!, 0).filter(arg => !ignoreDefaultArgs.includes(arg)));
|
||||
firefoxArguments.push(...this._defaultArgs(options, launchType, userDataDir, 0).filter(arg => !ignoreDefaultArgs.includes(arg)));
|
||||
else
|
||||
firefoxArguments.push(...args);
|
||||
|
||||
@ -133,7 +133,7 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||
// We try to gracefully close to prevent crash reporting and core dumps.
|
||||
// Note that it's fine to reuse the pipe transport, since
|
||||
// our connection ignores kBrowserCloseMessageId.
|
||||
const transport = await platform.connectToWebsocket(browserWSEndpoint!, async transport => transport);
|
||||
const transport = await WebSocketTransport.connect(browserWSEndpoint!, async transport => transport);
|
||||
const message = { method: 'Browser.close', params: {}, id: kBrowserCloseMessageId };
|
||||
await transport.send(message);
|
||||
},
|
||||
@ -149,14 +149,14 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||
|
||||
let browserServer: BrowserServer | undefined = undefined;
|
||||
let browserWSEndpoint: string | undefined = undefined;
|
||||
const webSocketWrapper = launchType === 'server' ? (await platform.connectToWebsocket(innerEndpoint, t => wrapTransportWithWebSocket(t, port))) : new WebSocketWrapper(innerEndpoint, []);
|
||||
const webSocketWrapper = launchType === 'server' ? (await WebSocketTransport.connect(innerEndpoint, t => wrapTransportWithWebSocket(t, port))) : new WebSocketWrapper(innerEndpoint, []);
|
||||
browserWSEndpoint = webSocketWrapper.wsEndpoint;
|
||||
browserServer = new BrowserServer(launchedProcess, gracefullyClose, webSocketWrapper);
|
||||
return browserServer;
|
||||
}
|
||||
|
||||
async connect(options: ConnectOptions): Promise<FFBrowser> {
|
||||
return await platform.connectToWebsocket(options.wsEndpoint, transport => {
|
||||
return await WebSocketTransport.connect(options.wsEndpoint, transport => {
|
||||
return FFBrowser.connect(transport, false, options.slowMo);
|
||||
});
|
||||
}
|
||||
@ -197,7 +197,7 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||
|
||||
function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number): WebSocketWrapper {
|
||||
const server = new ws.Server({ port });
|
||||
const guid = platform.guid();
|
||||
const guid = helper.guid();
|
||||
const idMixer = new SequenceNumberMixer<{id: number, socket: ws}>();
|
||||
const pendingBrowserContextCreations = new Set<number>();
|
||||
const pendingBrowserContextDeletions = new Map<number, string>();
|
||||
|
@ -17,13 +17,12 @@
|
||||
|
||||
import { debugError, helper, RegisteredListener } from '../helper';
|
||||
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
||||
import { makeWaitForNextTask } from '../platform';
|
||||
|
||||
export class PipeTransport implements ConnectionTransport {
|
||||
private _pipeWrite: NodeJS.WritableStream | null;
|
||||
private _pendingMessage = '';
|
||||
private _eventListeners: RegisteredListener[];
|
||||
private _waitForNextTask = makeWaitForNextTask();
|
||||
private _waitForNextTask = helper.makeWaitForNextTask();
|
||||
private readonly _closeCallback: () => void;
|
||||
|
||||
onmessage?: (message: ProtocolResponse) => void;
|
||||
|
@ -16,14 +16,15 @@
|
||||
*/
|
||||
|
||||
import * as childProcess from 'child_process';
|
||||
import * as stream from 'stream';
|
||||
import * as removeFolder from 'rimraf';
|
||||
import { helper } from '../helper';
|
||||
import * as debug from 'debug';
|
||||
import * as readline from 'readline';
|
||||
import * as removeFolder from 'rimraf';
|
||||
import * as stream from 'stream';
|
||||
import * as util from 'util';
|
||||
import { TimeoutError } from '../errors';
|
||||
import * as platform from '../platform';
|
||||
import { helper } from '../helper';
|
||||
|
||||
const removeFolderAsync = platform.promisify(removeFolder);
|
||||
const removeFolderAsync = util.promisify(removeFolder);
|
||||
|
||||
export type LaunchProcessOptions = {
|
||||
executablePath: string,
|
||||
@ -48,9 +49,9 @@ let lastLaunchedId = 0;
|
||||
|
||||
export async function launchProcess(options: LaunchProcessOptions): Promise<LaunchResult> {
|
||||
const id = ++lastLaunchedId;
|
||||
const debugBrowser = platform.debug(`pw:browser:proc:[${id}]`);
|
||||
const debugBrowserOut = platform.debug(`pw:browser:out:[${id}]`);
|
||||
const debugBrowserErr = platform.debug(`pw:browser:err:[${id}]`);
|
||||
const debugBrowser = debug(`pw:browser:proc:[${id}]`);
|
||||
const debugBrowserOut = debug(`pw:browser:out:[${id}]`);
|
||||
const debugBrowserErr = debug(`pw:browser:err:[${id}]`);
|
||||
(debugBrowser as any).color = '33';
|
||||
(debugBrowserOut as any).color = '178';
|
||||
(debugBrowserErr as any).color = '160';
|
||||
|
@ -20,12 +20,12 @@ import { PipeTransport } from './pipeTransport';
|
||||
import { launchProcess } from './processLauncher';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as platform from '../platform';
|
||||
import * as os from 'os';
|
||||
import * as util from 'util';
|
||||
import { debugError, helper, assert } from '../helper';
|
||||
import { kBrowserCloseMessageId } from '../webkit/wkConnection';
|
||||
import { LaunchOptions, BrowserArgOptions, BrowserType, LaunchServerOptions, ConnectOptions } from './browserType';
|
||||
import { ConnectionTransport, SequenceNumberMixer } from '../transport';
|
||||
import { ConnectionTransport, SequenceNumberMixer, WebSocketTransport } from '../transport';
|
||||
import * as ws from 'ws';
|
||||
import { LaunchType } from '../browser';
|
||||
import { BrowserServer, WebSocketWrapper } from './browserServer';
|
||||
@ -85,14 +85,14 @@ export class WebKit implements BrowserType<WKBrowser> {
|
||||
let temporaryUserDataDir: string | null = null;
|
||||
if (!userDataDir) {
|
||||
userDataDir = await mkdtempAsync(WEBKIT_PROFILE_PATH);
|
||||
temporaryUserDataDir = userDataDir!;
|
||||
temporaryUserDataDir = userDataDir;
|
||||
}
|
||||
|
||||
const webkitArguments = [];
|
||||
if (!ignoreDefaultArgs)
|
||||
webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir!, port));
|
||||
webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir, port));
|
||||
else if (Array.isArray(ignoreDefaultArgs))
|
||||
webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir!, port).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1));
|
||||
webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir, port).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1));
|
||||
else
|
||||
webkitArguments.push(...args);
|
||||
|
||||
@ -103,7 +103,7 @@ export class WebKit implements BrowserType<WKBrowser> {
|
||||
const { launchedProcess, gracefullyClose } = await launchProcess({
|
||||
executablePath: webkitExecutable,
|
||||
args: webkitArguments,
|
||||
env: { ...env, CURL_COOKIE_JAR_PATH: path.join(userDataDir!, 'cookiejar.db') },
|
||||
env: { ...env, CURL_COOKIE_JAR_PATH: path.join(userDataDir, 'cookiejar.db') },
|
||||
handleSIGINT,
|
||||
handleSIGTERM,
|
||||
handleSIGHUP,
|
||||
@ -133,7 +133,7 @@ export class WebKit implements BrowserType<WKBrowser> {
|
||||
}
|
||||
|
||||
async connect(options: ConnectOptions): Promise<WKBrowser> {
|
||||
return await platform.connectToWebsocket(options.wsEndpoint, transport => {
|
||||
return await WebSocketTransport.connect(options.wsEndpoint, transport => {
|
||||
return WKBrowser.connect(transport, options.slowMo);
|
||||
});
|
||||
}
|
||||
@ -163,13 +163,13 @@ export class WebKit implements BrowserType<WKBrowser> {
|
||||
}
|
||||
}
|
||||
|
||||
const mkdtempAsync = platform.promisify(fs.mkdtemp);
|
||||
const mkdtempAsync = util.promisify(fs.mkdtemp);
|
||||
|
||||
const WEBKIT_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_dev_profile-');
|
||||
|
||||
function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number): WebSocketWrapper {
|
||||
const server = new ws.Server({ port });
|
||||
const guid = platform.guid();
|
||||
const guid = helper.guid();
|
||||
const idMixer = new SequenceNumberMixer<{id: number, socket: ws}>();
|
||||
const pendingBrowserContextCreations = new Set<number>();
|
||||
const pendingBrowserContextDeletions = new Map<number, string>();
|
||||
|
@ -15,6 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as WebSocket from 'ws';
|
||||
import { helper } from './helper';
|
||||
|
||||
export type ProtocolRequest = {
|
||||
id: number;
|
||||
method: string;
|
||||
@ -114,6 +117,57 @@ export class DeferWriteTransport implements ConnectionTransport {
|
||||
}
|
||||
}
|
||||
|
||||
export class WebSocketTransport implements ConnectionTransport {
|
||||
_ws: WebSocket;
|
||||
|
||||
onmessage?: (message: ProtocolResponse) => void;
|
||||
onclose?: () => void;
|
||||
|
||||
// 'onmessage' handler must be installed synchronously when 'onopen' callback is invoked to
|
||||
// avoid missing incoming messages.
|
||||
static connect<T>(url: string, onopen: (transport: ConnectionTransport) => Promise<T> | T): Promise<T> {
|
||||
const transport = new WebSocketTransport(url);
|
||||
return new Promise<T>((fulfill, reject) => {
|
||||
transport._ws.addEventListener('open', async () => fulfill(await onopen(transport)));
|
||||
transport._ws.addEventListener('error', event => reject(new Error('WebSocket error: ' + event.message)));
|
||||
});
|
||||
}
|
||||
|
||||
constructor(url: string) {
|
||||
this._ws = new WebSocket(url, [], {
|
||||
perMessageDeflate: false,
|
||||
maxPayload: 256 * 1024 * 1024, // 256Mb
|
||||
});
|
||||
// The 'ws' module in node sometimes sends us multiple messages in a single task.
|
||||
// In Web, all IO callbacks (e.g. WebSocket callbacks)
|
||||
// are dispatched into separate tasks, so there's no need
|
||||
// to do anything extra.
|
||||
const messageWrap: (cb: () => void) => void = helper.makeWaitForNextTask();
|
||||
|
||||
this._ws.addEventListener('message', event => {
|
||||
messageWrap(() => {
|
||||
if (this.onmessage)
|
||||
this.onmessage.call(null, JSON.parse(event.data));
|
||||
});
|
||||
});
|
||||
|
||||
this._ws.addEventListener('close', event => {
|
||||
if (this.onclose)
|
||||
this.onclose.call(null);
|
||||
});
|
||||
// Silently ignore all errors - we don't know what to do with them.
|
||||
this._ws.addEventListener('error', () => {});
|
||||
}
|
||||
|
||||
send(message: ProtocolRequest) {
|
||||
this._ws.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
close() {
|
||||
this._ws.close();
|
||||
}
|
||||
}
|
||||
|
||||
export class SequenceNumberMixer<V> {
|
||||
static _lastSequenceNumber = 1;
|
||||
private _values = new Map<number, V>();
|
||||
|
45
src/web.ts
45
src/web.ts
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CRBrowser as ChromiumBrowser } from './chromium/crBrowser';
|
||||
import { FFBrowser as FirefoxBrowser } from './firefox/ffBrowser';
|
||||
import { WKBrowser as WebKitBrowser } from './webkit/wkBrowser';
|
||||
import * as platform from './platform';
|
||||
|
||||
const connect = {
|
||||
chromium: {
|
||||
connect: async (url: string) => {
|
||||
return await platform.connectToWebsocket(url, transport => {
|
||||
return ChromiumBrowser.connect(transport, false);
|
||||
});
|
||||
}
|
||||
},
|
||||
webkit: {
|
||||
connect: async (url: string) => {
|
||||
return await platform.connectToWebsocket(url, transport => {
|
||||
return WebKitBrowser.connect(transport);
|
||||
});
|
||||
}
|
||||
},
|
||||
firefox: {
|
||||
connect: async (url: string) => {
|
||||
return await platform.connectToWebsocket(url, transport => {
|
||||
return FirefoxBrowser.connect(transport, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
export = connect;
|
@ -1,59 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: path.join(__dirname, 'web.ts'),
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
transpileOnly: true
|
||||
},
|
||||
exclude: [
|
||||
/node_modules/,
|
||||
/crypto/,
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: [ '.tsx', '.ts', '.js' ]
|
||||
},
|
||||
output: {
|
||||
filename: 'web.js',
|
||||
library: 'playwrightweb',
|
||||
libraryTarget: 'window',
|
||||
path: path.resolve(__dirname, '../')
|
||||
},
|
||||
externals: {
|
||||
'crypto': 'dummy',
|
||||
'events': 'dummy',
|
||||
'fs': 'dummy',
|
||||
'path': 'dummy',
|
||||
'debug': 'dummy',
|
||||
'buffer': 'dummy',
|
||||
'jpeg-js': 'dummy',
|
||||
'pngjs': 'dummy',
|
||||
'http': 'dummy',
|
||||
'https': 'dummy',
|
||||
'ws': 'dummy',
|
||||
}
|
||||
};
|
@ -21,16 +21,16 @@ import { Events } from '../events';
|
||||
import { assert, helper, RegisteredListener } from '../helper';
|
||||
import * as network from '../network';
|
||||
import { Page, PageBinding } from '../page';
|
||||
import * as platform from '../platform';
|
||||
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
||||
import * as types from '../types';
|
||||
import { Protocol } from './protocol';
|
||||
import { kPageProxyMessageReceived, PageProxyMessageReceivedPayload, WKConnection, WKSession } from './wkConnection';
|
||||
import { WKPage } from './wkPage';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Safari/605.1.15';
|
||||
|
||||
export class WKBrowser extends platform.EventEmitter implements Browser {
|
||||
export class WKBrowser extends EventEmitter implements Browser {
|
||||
private readonly _connection: WKConnection;
|
||||
private readonly _attachToDefaultContext: boolean;
|
||||
readonly _browserSession: WKSession;
|
||||
@ -186,7 +186,7 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
|
||||
await disconnected;
|
||||
}
|
||||
|
||||
_setDebugFunction(debugFunction: platform.DebuggerType) {
|
||||
_setDebugFunction(debugFunction: debug.IDebugger) {
|
||||
this._connection._debugProtocol = debugFunction;
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as debug from 'debug';
|
||||
import { EventEmitter } from 'events';
|
||||
import { assert } from '../helper';
|
||||
import * as platform from '../platform';
|
||||
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
||||
import { Protocol } from './protocol';
|
||||
|
||||
@ -34,7 +35,7 @@ export class WKConnection {
|
||||
private readonly _onDisconnect: () => void;
|
||||
private _lastId = 0;
|
||||
private _closed = false;
|
||||
_debugProtocol: platform.DebuggerType = platform.debug('pw:protocol');
|
||||
_debugProtocol = debug('pw:protocol');
|
||||
|
||||
readonly browserSession: WKSession;
|
||||
|
||||
@ -90,7 +91,7 @@ export class WKConnection {
|
||||
}
|
||||
}
|
||||
|
||||
export class WKSession extends platform.EventEmitter {
|
||||
export class WKSession extends EventEmitter {
|
||||
connection: WKConnection;
|
||||
errorText: string;
|
||||
readonly sessionId: string;
|
||||
@ -127,7 +128,7 @@ export class WKSession extends platform.EventEmitter {
|
||||
throw new Error(`Protocol error (${method}): ${this.errorText}`);
|
||||
const id = this.connection.nextMessageId();
|
||||
const messageObj = { id, method, params };
|
||||
platform.debug('pw:wrapped:' + this.sessionId)('SEND ► ' + JSON.stringify(messageObj, null, 2));
|
||||
debug('pw:wrapped:' + this.sessionId)('SEND ► ' + JSON.stringify(messageObj, null, 2));
|
||||
this._rawSend(messageObj);
|
||||
return new Promise<Protocol.CommandReturnValues[T]>((resolve, reject) => {
|
||||
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
|
||||
@ -146,7 +147,7 @@ export class WKSession extends platform.EventEmitter {
|
||||
}
|
||||
|
||||
dispatchMessage(object: any) {
|
||||
platform.debug('pw:wrapped:' + this.sessionId)('◀ RECV ' + JSON.stringify(object, null, 2));
|
||||
debug('pw:wrapped:' + this.sessionId)('◀ RECV ' + JSON.stringify(object, null, 2));
|
||||
if (object.id && this._callbacks.has(object.id)) {
|
||||
const callback = this._callbacks.get(object.id)!;
|
||||
this._callbacks.delete(object.id);
|
||||
|
@ -18,7 +18,6 @@
|
||||
import * as frames from '../frames';
|
||||
import { assert, debugError, helper } from '../helper';
|
||||
import * as network from '../network';
|
||||
import * as platform from '../platform';
|
||||
import { Protocol } from './protocol';
|
||||
import { WKSession } from './wkConnection';
|
||||
|
||||
@ -80,7 +79,7 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
||||
if (response.contentType)
|
||||
responseHeaders['content-type'] = response.contentType;
|
||||
if (responseBody && !('content-length' in responseHeaders))
|
||||
responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody));
|
||||
responseHeaders['content-length'] = String(Buffer.byteLength(responseBody));
|
||||
|
||||
await this._session.send('Network.interceptWithResponse', {
|
||||
requestId: this._requestId,
|
||||
@ -114,7 +113,7 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
||||
createResponse(responsePayload: Protocol.Network.Response): network.Response {
|
||||
const getResponseBody = async () => {
|
||||
const response = await this._session.send('Network.getResponseBody', { requestId: this._requestId });
|
||||
return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||
};
|
||||
return new network.Response(this.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), getResponseBody);
|
||||
}
|
||||
|
@ -30,11 +30,12 @@ import * as dialog from '../dialog';
|
||||
import { RawMouseImpl, RawKeyboardImpl } from './wkInput';
|
||||
import * as types from '../types';
|
||||
import * as accessibility from '../accessibility';
|
||||
import * as platform from '../platform';
|
||||
import { getAccessibilityTree } from './wkAccessibility';
|
||||
import { WKProvisionalPage } from './wkProvisionalPage';
|
||||
import { WKBrowserContext } from './wkBrowser';
|
||||
import { selectors } from '../selectors';
|
||||
import * as jpeg from 'jpeg-js';
|
||||
import * as png from 'pngjs';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
|
||||
@ -73,7 +74,7 @@ export class WKPage implements PageDelegate {
|
||||
this._workers = new WKWorkers(this._page);
|
||||
this._session = undefined as any as WKSession;
|
||||
this._browserContext = browserContext;
|
||||
this._page.on(Events.Page.FrameDetached, frame => this._removeContextsForFrame(frame, false));
|
||||
this._page.on(Events.Page.FrameDetached, (frame: frames.Frame) => this._removeContextsForFrame(frame, false));
|
||||
this._eventListeners = [
|
||||
helper.addEventListener(this._pageProxySession, 'Target.targetCreated', this._onTargetCreated.bind(this)),
|
||||
helper.addEventListener(this._pageProxySession, 'Target.targetDestroyed', this._onTargetDestroyed.bind(this)),
|
||||
@ -618,15 +619,15 @@ export class WKPage implements PageDelegate {
|
||||
await this._session.send('Page.setDefaultBackgroundColorOverride', { color });
|
||||
}
|
||||
|
||||
async takeScreenshot(format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<platform.BufferType> {
|
||||
async takeScreenshot(format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined): Promise<Buffer> {
|
||||
// TODO: documentRect does not include pageScale, while backend considers it does.
|
||||
// This brakes mobile screenshots of elements or full page.
|
||||
const rect = (documentRect || viewportRect)!;
|
||||
const result = await this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport' });
|
||||
const prefix = 'data:image/png;base64,';
|
||||
let buffer = platform.Buffer.from(result.dataURL.substr(prefix.length), 'base64');
|
||||
let buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64');
|
||||
if (format === 'jpeg')
|
||||
buffer = platform.pngToJpeg(buffer, quality);
|
||||
buffer = jpeg.encode(png.PNG.sync.read(buffer), quality).data;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
@ -225,8 +225,4 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
|
||||
loadTests('./chromium/oopif.spec.js');
|
||||
loadTests('./chromium/tracing.spec.js');
|
||||
});
|
||||
|
||||
describe('[Driver]', () => {
|
||||
loadTests('./web.spec.js');
|
||||
});
|
||||
};
|
||||
|
@ -1,93 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {PageTestSuite}
|
||||
*/
|
||||
module.exports.describe = function({testRunner, expect, defaultBrowserOptions, browserType, product, CHROMIUM, FFOX}) {
|
||||
const {describe, xdescribe, fdescribe} = testRunner;
|
||||
const {it, fit, xit, dit} = testRunner;
|
||||
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
||||
|
||||
describe('Web SDK', function() {
|
||||
beforeAll(async state => {
|
||||
state.controlledBrowserApp = await browserType.launchServer(defaultBrowserOptions);
|
||||
state.hostBrowser = await browserType.launch(defaultBrowserOptions);
|
||||
});
|
||||
|
||||
afterAll(async state => {
|
||||
await state.hostBrowser.close();
|
||||
state.hostBrowser = null;
|
||||
|
||||
await state.controlledBrowserApp.close();
|
||||
state.controlledBrowserApp = null;
|
||||
state.webUrl = null;
|
||||
});
|
||||
|
||||
beforeEach(async state => {
|
||||
state.page = await state.hostBrowser.newPage();
|
||||
state.page.on('console', message => console.log('TEST: ' + message.text()));
|
||||
await state.page.goto(state.sourceServer.PREFIX + '/test/assets/playwrightweb.html');
|
||||
await state.page.evaluate(([product, wsEndpoint]) => setup(product, wsEndpoint), [product.toLowerCase(), state.controlledBrowserApp.wsEndpoint()]);
|
||||
});
|
||||
|
||||
afterEach(async state => {
|
||||
await state.page.evaluate(() => teardown());
|
||||
await state.page.close();
|
||||
state.page = null;
|
||||
});
|
||||
|
||||
it('should navigate', async({page, server}) => {
|
||||
const url = await page.evaluate(async url => {
|
||||
await page.goto(url);
|
||||
return page.evaluate(() => window.location.href);
|
||||
}, server.EMPTY_PAGE);
|
||||
expect(url).toBe(server.EMPTY_PAGE);
|
||||
});
|
||||
|
||||
it('should evaluate handles', async({page, server}) => {
|
||||
const foo = await page.evaluateHandle(() => ({ x: 1, y: 'foo' }));
|
||||
const result = await page.evaluate(({ foo }) => {
|
||||
return foo;
|
||||
}, { foo });
|
||||
expect(result).toEqual({ x: 1, y: 'foo' });
|
||||
});
|
||||
|
||||
it('should receive events', async({page, server}) => {
|
||||
const logs = await page.evaluate(async () => {
|
||||
const logs = [];
|
||||
page.on('console', message => logs.push(message.text()));
|
||||
await page.evaluate(() => console.log('hello'));
|
||||
await page.evaluate(() => console.log('world'));
|
||||
return logs;
|
||||
});
|
||||
expect(logs).toEqual(['hello', 'world']);
|
||||
});
|
||||
|
||||
it('should take screenshot', async({page, server}) => {
|
||||
const { base64, bufferClassName } = await page.evaluate(async url => {
|
||||
await page.setViewportSize({width: 500, height: 500});
|
||||
await page.goto(url);
|
||||
const screenshot = await page.screenshot();
|
||||
return { base64: screenshot.toString('base64'), bufferClassName: screenshot.constructor.name };
|
||||
}, server.PREFIX + '/grid.html');
|
||||
const screenshot = Buffer.from(base64, 'base64');
|
||||
expect(screenshot).toBeGolden('screenshot-sanity.png');
|
||||
// Verify that we use web versions of node-specific classes.
|
||||
expect(bufferClassName).toBe('BufferImpl');
|
||||
});
|
||||
});
|
||||
};
|
@ -23,9 +23,8 @@ module.exports = { checkSources, expandPrefix };
|
||||
|
||||
/**
|
||||
* @param {!Array<!import('../Source')>} sources
|
||||
* @param {!Array<string>} externalDependencies
|
||||
*/
|
||||
function checkSources(sources, externalDependencies) {
|
||||
function checkSources(sources) {
|
||||
// special treatment for Events.js
|
||||
const classEvents = new Map();
|
||||
const eventsSources = sources.filter(source => source.name().startsWith('events.'));
|
||||
@ -108,33 +107,16 @@ function checkSources(sources, externalDependencies) {
|
||||
}
|
||||
if (fileName.endsWith('/api.ts') && ts.isExportSpecifier(node))
|
||||
apiClassNames.add(expandPrefix((node.propertyName || node.name).text));
|
||||
const isPlatform = fileName.endsWith('platform.ts');
|
||||
if (!fileName.includes('src/server/')) {
|
||||
// Only relative imports.
|
||||
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
||||
const module = node.moduleSpecifier.text;
|
||||
const isRelative = module.startsWith('.');
|
||||
const isPlatformDependency = isPlatform && externalDependencies.includes(module);
|
||||
const isServerDependency = path.resolve(path.dirname(fileName), module).includes('src/server');
|
||||
if (isServerDependency || (!isRelative && !isPlatformDependency)) {
|
||||
if (isServerDependency) {
|
||||
const lac = ts.getLineAndCharacterOfPosition(node.getSourceFile(), node.moduleSpecifier.pos);
|
||||
errors.push(`Disallowed import "${module}" at ${node.getSourceFile().fileName}:${lac.line + 1}`);
|
||||
}
|
||||
}
|
||||
// No references to external types.
|
||||
if (!isPlatform && ts.isTypeReferenceNode(node)) {
|
||||
const isPlatformReference = ts.isQualifiedName(node.typeName) && ts.isIdentifier(node.typeName.left) && node.typeName.left.escapedText === 'platform';
|
||||
if (!isPlatformReference) {
|
||||
const type = checker.getTypeAtLocation(node);
|
||||
if (type.symbol && type.symbol.valueDeclaration) {
|
||||
const source = type.symbol.valueDeclaration.getSourceFile();
|
||||
if (source.fileName.includes('@types')) {
|
||||
const lac = ts.getLineAndCharacterOfPosition(node.getSourceFile(), node.pos);
|
||||
errors.push(`Disallowed type reference "${type.symbol.escapedName}" at ${node.getSourceFile().fileName}:${lac.line + 1}:${lac.character + 1}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ const EXCLUDE_PROPERTIES = new Set([
|
||||
* @param {!Array<!Source>} mdSources
|
||||
* @return {!Promise<!Array<!Message>>}
|
||||
*/
|
||||
module.exports = async function lint(page, mdSources, jsSources, externalDependencies) {
|
||||
module.exports = async function lint(page, mdSources, jsSources) {
|
||||
const mdResult = await mdBuilder(page, mdSources);
|
||||
const jsResult = jsBuilder.checkSources(jsSources, externalDependencies);
|
||||
const jsResult = jsBuilder.checkSources(jsSources);
|
||||
const jsDocumentation = filterJSDocumentation(jsSources, jsResult.documentation);
|
||||
const mdDocumentation = mdResult.documentation;
|
||||
|
||||
|
@ -61,8 +61,7 @@ async function run() {
|
||||
const page = await browser.newPage();
|
||||
const checkPublicAPI = require('./check_public_api');
|
||||
const jsSources = await Source.readdir(path.join(PROJECT_DIR, 'src'));
|
||||
const externalDependencies = Object.keys(require('../../src/web.webpack.config').externals);
|
||||
messages.push(...await checkPublicAPI(page, mdSources, jsSources, externalDependencies));
|
||||
messages.push(...await checkPublicAPI(page, mdSources, jsSources));
|
||||
await browser.close();
|
||||
|
||||
for (const source of mdSources) {
|
||||
|
@ -37,7 +37,7 @@ let documentation;
|
||||
const {documentation: mdDocumentation} = await require('../doclint/check_public_api/MDBuilder')(page, [api]);
|
||||
await browser.close();
|
||||
const sources = await Source.readdir(path.join(PROJECT_DIR, 'src'));
|
||||
const {documentation: jsDocumentation} = await require('../doclint/check_public_api/JSBuilder').checkSources(sources, Object.keys(require('../../src/web.webpack.config').externals));
|
||||
const {documentation: jsDocumentation} = await require('../doclint/check_public_api/JSBuilder').checkSources(sources);
|
||||
documentation = mergeDocumentation(mdDocumentation, jsDocumentation);
|
||||
const handledClasses = new Set();
|
||||
|
||||
|
@ -20,7 +20,6 @@ const path = require('path');
|
||||
const files = [
|
||||
path.join('src', 'injected', 'zsSelectorEngine.webpack.config.js'),
|
||||
path.join('src', 'injected', 'selectorEvaluator.webpack.config.js'),
|
||||
path.join('src', 'web.webpack.config.js'),
|
||||
];
|
||||
|
||||
function runOne(runner, file) {
|
||||
|
Loading…
Reference in New Issue
Block a user