chore: remove web mode (#1625)
This commit is contained in:
parent
cf49a9ee7b
commit
e241c1bef8
|
|
@ -47,6 +47,7 @@
|
||||||
"extract-zip": "^1.6.6",
|
"extract-zip": "^1.6.6",
|
||||||
"https-proxy-agent": "^3.0.0",
|
"https-proxy-agent": "^3.0.0",
|
||||||
"jpeg-js": "^0.3.6",
|
"jpeg-js": "^0.3.6",
|
||||||
|
"mime": "^2.4.4",
|
||||||
"pngjs": "^3.4.0",
|
"pngjs": "^3.4.0",
|
||||||
"progress": "^2.0.3",
|
"progress": "^2.0.3",
|
||||||
"proxy-from-env": "^1.1.0",
|
"proxy-from-env": "^1.1.0",
|
||||||
|
|
@ -56,6 +57,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/debug": "0.0.31",
|
"@types/debug": "0.0.31",
|
||||||
"@types/extract-zip": "^1.6.2",
|
"@types/extract-zip": "^1.6.2",
|
||||||
|
"@types/mime": "^2.0.1",
|
||||||
"@types/node": "^8.10.34",
|
"@types/node": "^8.10.34",
|
||||||
"@types/pngjs": "^3.4.0",
|
"@types/pngjs": "^3.4.0",
|
||||||
"@types/proxy-from-env": "^1.0.0",
|
"@types/proxy-from-env": "^1.0.0",
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BrowserContext, BrowserContextOptions } from './browserContext';
|
import { BrowserContext, BrowserContextOptions } from './browserContext';
|
||||||
import * as platform from './platform';
|
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
export interface Browser extends platform.EventEmitterType {
|
export interface Browser extends EventEmitter {
|
||||||
newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
|
newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
|
||||||
contexts(): BrowserContext[];
|
contexts(): BrowserContext[];
|
||||||
newPage(options?: BrowserContextOptions): Promise<Page>;
|
newPage(options?: BrowserContextOptions): Promise<Page>;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import { Events as CommonEvents } from '../events';
|
||||||
import { assert, debugError, helper } from '../helper';
|
import { assert, debugError, helper } from '../helper';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import { Page, PageBinding, Worker } from '../page';
|
import { Page, PageBinding, Worker } from '../page';
|
||||||
import * as platform from '../platform';
|
|
||||||
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { ConnectionEvents, CRConnection, CRSession } from './crConnection';
|
import { ConnectionEvents, CRConnection, CRSession } from './crConnection';
|
||||||
|
|
@ -30,8 +29,9 @@ import { readProtocolStream } from './crProtocolHelper';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { CRExecutionContext } from './crExecutionContext';
|
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;
|
readonly _connection: CRConnection;
|
||||||
_session: CRSession;
|
_session: CRSession;
|
||||||
private _clientRootSessionPromise: Promise<CRSession> | null = null;
|
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.');
|
assert(this._tracingClient, 'Tracing was not started.');
|
||||||
const [event] = await Promise.all([
|
const [event] = await Promise.all([
|
||||||
new Promise(f => this._tracingClient!.once('Tracing.tracingComplete', f)),
|
new Promise(f => this._tracingClient!.once('Tracing.tracingComplete', f)),
|
||||||
|
|
@ -242,7 +242,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
||||||
return this._clientRootSessionPromise;
|
return this._clientRootSessionPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
_setDebugFunction(debugFunction: platform.DebuggerType) {
|
_setDebugFunction(debugFunction: debug.IDebugger) {
|
||||||
this._connection._debugProtocol = debugFunction;
|
this._connection._debugProtocol = debugFunction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from '../helper';
|
import { assert } from '../helper';
|
||||||
import * as platform from '../platform';
|
import * as debug from 'debug';
|
||||||
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
export const ConnectionEvents = {
|
export const ConnectionEvents = {
|
||||||
Disconnected: Symbol('ConnectionEvents.Disconnected')
|
Disconnected: Symbol('ConnectionEvents.Disconnected')
|
||||||
|
|
@ -28,13 +29,13 @@ export const ConnectionEvents = {
|
||||||
// should ignore.
|
// should ignore.
|
||||||
export const kBrowserCloseMessageId = -9999;
|
export const kBrowserCloseMessageId = -9999;
|
||||||
|
|
||||||
export class CRConnection extends platform.EventEmitter {
|
export class CRConnection extends EventEmitter {
|
||||||
private _lastId = 0;
|
private _lastId = 0;
|
||||||
private readonly _transport: ConnectionTransport;
|
private readonly _transport: ConnectionTransport;
|
||||||
private readonly _sessions = new Map<string, CRSession>();
|
private readonly _sessions = new Map<string, CRSession>();
|
||||||
readonly rootSession: CRSession;
|
readonly rootSession: CRSession;
|
||||||
_closed = false;
|
_closed = false;
|
||||||
_debugProtocol: platform.DebuggerType;
|
_debugProtocol: debug.IDebugger;
|
||||||
|
|
||||||
constructor(transport: ConnectionTransport) {
|
constructor(transport: ConnectionTransport) {
|
||||||
super();
|
super();
|
||||||
|
|
@ -43,7 +44,7 @@ export class CRConnection extends platform.EventEmitter {
|
||||||
this._transport.onclose = this._onClose.bind(this);
|
this._transport.onclose = this._onClose.bind(this);
|
||||||
this.rootSession = new CRSession(this, '', 'browser', '');
|
this.rootSession = new CRSession(this, '', 'browser', '');
|
||||||
this._sessions.set('', this.rootSession);
|
this._sessions.set('', this.rootSession);
|
||||||
this._debugProtocol = platform.debug('pw:protocol');
|
this._debugProtocol = debug('pw:protocol');
|
||||||
(this._debugProtocol as any).color = '34';
|
(this._debugProtocol as any).color = '34';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,7 +119,7 @@ export const CRSessionEvents = {
|
||||||
Disconnected: Symbol('Events.CDPSession.Disconnected')
|
Disconnected: Symbol('Events.CDPSession.Disconnected')
|
||||||
};
|
};
|
||||||
|
|
||||||
export class CRSession extends platform.EventEmitter {
|
export class CRSession extends EventEmitter {
|
||||||
_connection: CRConnection | null;
|
_connection: CRConnection | null;
|
||||||
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
|
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
|
||||||
private readonly _targetType: string;
|
private readonly _targetType: string;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import { assert, debugError, helper, RegisteredListener } from '../helper';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import * as platform from '../platform';
|
|
||||||
import { Credentials } from '../types';
|
import { Credentials } from '../types';
|
||||||
import { CRPage } from './crPage';
|
import { CRPage } from './crPage';
|
||||||
|
|
||||||
|
|
@ -191,7 +190,7 @@ export class CRNetworkManager {
|
||||||
_createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response {
|
_createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response {
|
||||||
const getResponseBody = async () => {
|
const getResponseBody = async () => {
|
||||||
const response = await this._client.send('Network.getResponseBody', { requestId: request._requestId });
|
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);
|
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) {
|
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; } = {};
|
const responseHeaders: { [s: string]: string; } = {};
|
||||||
if (response.headers) {
|
if (response.headers) {
|
||||||
|
|
@ -291,7 +290,7 @@ class InterceptableRequest implements network.RouteDelegate {
|
||||||
if (response.contentType)
|
if (response.contentType)
|
||||||
responseHeaders['content-type'] = response.contentType;
|
responseHeaders['content-type'] = response.contentType;
|
||||||
if (responseBody && !('content-length' in responseHeaders))
|
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', {
|
await this._client.send('Fetch.fulfillRequest', {
|
||||||
requestId: this._interceptionId!,
|
requestId: this._interceptionId!,
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ import { CRPDF } from './crPdf';
|
||||||
import { CRBrowserContext } from './crBrowser';
|
import { CRBrowserContext } from './crBrowser';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { ConsoleMessage } from '../console';
|
import { ConsoleMessage } from '../console';
|
||||||
import * as platform from '../platform';
|
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
|
|
||||||
|
|
@ -455,7 +454,7 @@ export class CRPage implements PageDelegate {
|
||||||
await this._client.send('Emulation.setDefaultBackgroundColorOverride', { color });
|
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');
|
const { visualViewport } = await this._client.send('Page.getLayoutMetrics');
|
||||||
if (!documentRect) {
|
if (!documentRect) {
|
||||||
documentRect = {
|
documentRect = {
|
||||||
|
|
@ -472,7 +471,7 @@ export class CRPage implements PageDelegate {
|
||||||
// ignore current page scale.
|
// ignore current page scale.
|
||||||
const clip = { ...documentRect, scale: viewportRect ? visualViewport.scale : 1 };
|
const clip = { ...documentRect, scale: viewportRect ? visualViewport.scale : 1 };
|
||||||
const result = await this._client.send('Page.captureScreenshot', { format, quality, clip });
|
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> {
|
async resetViewport(): Promise<void> {
|
||||||
|
|
@ -587,7 +586,7 @@ export class CRPage implements PageDelegate {
|
||||||
await this._client.send('Page.enable').catch(e => {});
|
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);
|
return this._pdf.generate(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert, helper } from '../helper';
|
import { assert, helper } from '../helper';
|
||||||
import * as platform from '../platform';
|
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { CRSession } from './crConnection';
|
import { CRSession } from './crConnection';
|
||||||
import { readProtocolStream } from './crProtocolHelper';
|
import { readProtocolStream } from './crProtocolHelper';
|
||||||
|
|
@ -77,7 +76,7 @@ export class CRPDF {
|
||||||
this._client = client;
|
this._client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
async generate(options: types.PDFOptions = {}): Promise<platform.BufferType> {
|
async generate(options: types.PDFOptions = {}): Promise<Buffer> {
|
||||||
const {
|
const {
|
||||||
scale = 1,
|
scale = 1,
|
||||||
displayHeaderFooter = false,
|
displayHeaderFooter = false,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
import { assert } from '../helper';
|
import { assert } from '../helper';
|
||||||
import { CRSession } from './crConnection';
|
import { CRSession } from './crConnection';
|
||||||
import { Protocol } from './protocol';
|
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 {
|
export function getExceptionMessage(exceptionDetails: Protocol.Runtime.ExceptionDetails): string {
|
||||||
if (exceptionDetails.exception)
|
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 => {});
|
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 eof = false;
|
||||||
let fd: number | undefined;
|
let fd: number | undefined;
|
||||||
if (path)
|
if (path)
|
||||||
fd = await platform.openFdAsync(path, 'w');
|
fd = await util.promisify(fs.open)(path, 'w');
|
||||||
const bufs = [];
|
const bufs = [];
|
||||||
while (!eof) {
|
while (!eof) {
|
||||||
const response = await client.send('IO.read', {handle});
|
const response = await client.send('IO.read', {handle});
|
||||||
eof = response.eof;
|
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);
|
bufs.push(buf);
|
||||||
if (path)
|
if (path)
|
||||||
await platform.writeFdAsync(fd!, buf);
|
await util.promisify(fs.write)(fd!, buf);
|
||||||
}
|
}
|
||||||
if (path)
|
if (path)
|
||||||
await platform.closeFdAsync(fd!);
|
await util.promisify(fs.close)(fd!);
|
||||||
await client.send('IO.close', {handle});
|
await client.send('IO.close', {handle});
|
||||||
return platform.Buffer.concat(bufs);
|
return Buffer.concat(bufs);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toConsoleMessageLocation(stackTrace: Protocol.Runtime.StackTrace | undefined) {
|
export function toConsoleMessageLocation(stackTrace: Protocol.Runtime.StackTrace | undefined) {
|
||||||
|
|
|
||||||
19
src/dom.ts
19
src/dom.ts
|
|
@ -14,15 +14,18 @@
|
||||||
* limitations under the License.
|
* 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 * as frames from './frames';
|
||||||
|
import { assert, debugError, helper } from './helper';
|
||||||
|
import Injected from './injected/injected';
|
||||||
import * as input from './input';
|
import * as input from './input';
|
||||||
import * as js from './javascript';
|
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 { Page } from './page';
|
||||||
import * as platform from './platform';
|
|
||||||
import { selectors } from './selectors';
|
import { selectors } from './selectors';
|
||||||
|
import * as types from './types';
|
||||||
|
|
||||||
export type PointerActionOptions = {
|
export type PointerActionOptions = {
|
||||||
modifiers?: input.Modifier[];
|
modifiers?: input.Modifier[];
|
||||||
|
|
@ -257,9 +260,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
for (const item of ff) {
|
for (const item of ff) {
|
||||||
if (typeof item === 'string') {
|
if (typeof item === 'string') {
|
||||||
const file: types.FilePayload = {
|
const file: types.FilePayload = {
|
||||||
name: platform.basename(item),
|
name: path.basename(item),
|
||||||
type: platform.getMimeType(item),
|
type: mime.getType(item) || 'application/octet-stream',
|
||||||
data: await platform.readFileAsync(item, 'base64')
|
data: await util.promisify(fs.readFile)(item, 'base64')
|
||||||
};
|
};
|
||||||
filePayloads.push(file);
|
filePayloads.push(file);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -316,7 +319,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
return this._page._delegate.getBoundingBox(this);
|
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);
|
return this._page._screenshotter.screenshotElement(this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from './platform';
|
import { EventEmitter } from 'events';
|
||||||
import { helper } from './helper';
|
import { helper } from './helper';
|
||||||
|
|
||||||
export class ExtendedEventEmitter extends EventEmitter {
|
export class ExtendedEventEmitter extends EventEmitter {
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,15 @@ import { Events } from '../events';
|
||||||
import { assert, helper, RegisteredListener } from '../helper';
|
import { assert, helper, RegisteredListener } from '../helper';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import { Page, PageBinding } from '../page';
|
import { Page, PageBinding } from '../page';
|
||||||
import * as platform from '../platform';
|
|
||||||
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { ConnectionEvents, FFConnection } from './ffConnection';
|
import { ConnectionEvents, FFConnection } from './ffConnection';
|
||||||
import { headersArray } from './ffNetworkManager';
|
import { headersArray } from './ffNetworkManager';
|
||||||
import { FFPage } from './ffPage';
|
import { FFPage } from './ffPage';
|
||||||
import { Protocol } from './protocol';
|
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;
|
_connection: FFConnection;
|
||||||
readonly _ffPages: Map<string, FFPage>;
|
readonly _ffPages: Map<string, FFPage>;
|
||||||
readonly _defaultContext: FFBrowserContext;
|
readonly _defaultContext: FFBrowserContext;
|
||||||
|
|
@ -147,7 +147,7 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
|
||||||
await disconnected;
|
await disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
_setDebugFunction(debugFunction: platform.DebuggerType) {
|
_setDebugFunction(debugFunction: debug.IDebugger) {
|
||||||
this._connection._debugProtocol = debugFunction;
|
this._connection._debugProtocol = debugFunction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {assert} from '../helper';
|
import * as debug from 'debug';
|
||||||
import * as platform from '../platform';
|
import { EventEmitter } from 'events';
|
||||||
|
import { assert } from '../helper';
|
||||||
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
|
||||||
|
|
@ -28,12 +29,12 @@ export const ConnectionEvents = {
|
||||||
// should ignore.
|
// should ignore.
|
||||||
export const kBrowserCloseMessageId = -9999;
|
export const kBrowserCloseMessageId = -9999;
|
||||||
|
|
||||||
export class FFConnection extends platform.EventEmitter {
|
export class FFConnection extends EventEmitter {
|
||||||
private _lastId: number;
|
private _lastId: number;
|
||||||
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
|
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
|
||||||
private _transport: ConnectionTransport;
|
private _transport: ConnectionTransport;
|
||||||
readonly _sessions: Map<string, FFSession>;
|
readonly _sessions: Map<string, FFSession>;
|
||||||
_debugProtocol: platform.DebuggerType = platform.debug('pw:protocol');
|
_debugProtocol = debug('pw:protocol');
|
||||||
_closed: boolean;
|
_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;
|
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')
|
Disconnected: Symbol('Disconnected')
|
||||||
};
|
};
|
||||||
|
|
||||||
export class FFSession extends platform.EventEmitter {
|
export class FFSession extends EventEmitter {
|
||||||
_connection: FFConnection;
|
_connection: FFConnection;
|
||||||
_disposed = false;
|
_disposed = false;
|
||||||
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
|
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 { Page } from '../page';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import * as platform from '../platform';
|
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
|
||||||
export class FFNetworkManager {
|
export class FFNetworkManager {
|
||||||
|
|
@ -73,7 +72,7 @@ export class FFNetworkManager {
|
||||||
});
|
});
|
||||||
if (response.evicted)
|
if (response.evicted)
|
||||||
throw new Error(`Response body for ${request.request.method()} ${request.request.url()} was 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 = {};
|
const headers: network.Headers = {};
|
||||||
for (const {name, value} of event.headers)
|
for (const {name, value} of event.headers)
|
||||||
|
|
@ -171,7 +170,7 @@ class InterceptableRequest implements network.RouteDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(response: network.FulfillResponse) {
|
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; } = {};
|
const responseHeaders: { [s: string]: string; } = {};
|
||||||
if (response.headers) {
|
if (response.headers) {
|
||||||
|
|
@ -181,7 +180,7 @@ class InterceptableRequest implements network.RouteDelegate {
|
||||||
if (response.contentType)
|
if (response.contentType)
|
||||||
responseHeaders['content-type'] = response.contentType;
|
responseHeaders['content-type'] = response.contentType;
|
||||||
if (responseBody && !('content-length' in responseHeaders))
|
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', {
|
await this._session.send('Network.fulfillInterceptedRequest', {
|
||||||
requestId: this._id,
|
requestId: this._id,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import { Events } from '../events';
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import { assert, debugError, helper, RegisteredListener } from '../helper';
|
import { assert, debugError, helper, RegisteredListener } from '../helper';
|
||||||
import { Page, PageBinding, PageDelegate, Worker } from '../page';
|
import { Page, PageBinding, PageDelegate, Worker } from '../page';
|
||||||
import * as platform from '../platform';
|
|
||||||
import { kScreenshotDuringNavigationError } from '../screenshotter';
|
import { kScreenshotDuringNavigationError } from '../screenshotter';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { getAccessibilityTree } from './ffAccessibility';
|
import { getAccessibilityTree } from './ffAccessibility';
|
||||||
|
|
@ -340,7 +339,7 @@ export class FFPage implements PageDelegate {
|
||||||
throw new Error('Not implemented');
|
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) {
|
if (!documentRect) {
|
||||||
const context = await this._page.mainFrame()._utilityContext();
|
const context = await this._page.mainFrame()._utilityContext();
|
||||||
const scrollOffset = await context.evaluateInternal(() => ({ x: window.scrollX, y: window.scrollY }));
|
const scrollOffset = await context.evaluateInternal(() => ({ x: window.scrollX, y: window.scrollY }));
|
||||||
|
|
@ -361,7 +360,7 @@ export class FFPage implements PageDelegate {
|
||||||
e.message = kScreenshotDuringNavigationError;
|
e.message = kScreenshotDuringNavigationError;
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
return platform.Buffer.from(data, 'base64');
|
return Buffer.from(data, 'base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetViewport(): Promise<void> {
|
async resetViewport(): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,18 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as types from './types';
|
import * as fs from 'fs';
|
||||||
import * as js from './javascript';
|
import * as util from 'util';
|
||||||
|
import { ConsoleMessage } from './console';
|
||||||
import * as dom from './dom';
|
import * as dom from './dom';
|
||||||
import * as network from './network';
|
|
||||||
import { helper, assert, RegisteredListener } from './helper';
|
|
||||||
import { TimeoutError } from './errors';
|
import { TimeoutError } from './errors';
|
||||||
import { Events } from './events';
|
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 { Page } from './page';
|
||||||
import { ConsoleMessage } from './console';
|
|
||||||
import * as platform from './platform';
|
|
||||||
import { selectors } from './selectors';
|
import { selectors } from './selectors';
|
||||||
|
import * as types from './types';
|
||||||
|
|
||||||
type ContextType = 'main' | 'utility';
|
type ContextType = 'main' | 'utility';
|
||||||
type ContextData = {
|
type ContextData = {
|
||||||
|
|
@ -109,7 +110,7 @@ export class FrameManager {
|
||||||
await this._page._delegate.inputActionEpilogue();
|
await this._page._delegate.inputActionEpilogue();
|
||||||
await barrier.waitFor();
|
await barrier.waitFor();
|
||||||
// Resolve in the next task, after all waitForNavigations.
|
// Resolve in the next task, after all waitForNavigations.
|
||||||
await new Promise(platform.makeWaitForNextTask());
|
await new Promise(helper.makeWaitForNextTask());
|
||||||
return result;
|
return result;
|
||||||
} finally {
|
} finally {
|
||||||
this._pendingNavigationBarriers.delete(barrier);
|
this._pendingNavigationBarriers.delete(barrier);
|
||||||
|
|
@ -549,7 +550,7 @@ export class Frame {
|
||||||
if (url !== null)
|
if (url !== null)
|
||||||
return (await context.evaluateHandleInternal(addScriptUrl, { url, type })).asElement()!;
|
return (await context.evaluateHandleInternal(addScriptUrl, { url, type })).asElement()!;
|
||||||
if (path !== null) {
|
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, '');
|
contents += '//# sourceURL=' + path.replace(/\n/g, '');
|
||||||
return (await context.evaluateHandleInternal(addScriptContent, { content: contents, type })).asElement()!;
|
return (await context.evaluateHandleInternal(addScriptContent, { content: contents, type })).asElement()!;
|
||||||
}
|
}
|
||||||
|
|
@ -598,7 +599,7 @@ export class Frame {
|
||||||
return (await context.evaluateHandleInternal(addStyleUrl, url)).asElement()!;
|
return (await context.evaluateHandleInternal(addStyleUrl, url)).asElement()!;
|
||||||
|
|
||||||
if (path !== null) {
|
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, '') + '*/';
|
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
|
||||||
return (await context.evaluateHandleInternal(addStyleContent, contents)).asElement()!;
|
return (await context.evaluateHandleInternal(addStyleContent, contents)).asElement()!;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,24 @@
|
||||||
* limitations under the License.
|
* 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 { TimeoutError } from './errors';
|
||||||
import * as platform from './platform';
|
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
|
|
||||||
export const debugError = platform.debug(`pw:error`);
|
export const debugError = debug(`pw:error`);
|
||||||
|
|
||||||
export type RegisteredListener = {
|
export type RegisteredListener = {
|
||||||
emitter: platform.EventEmitterType;
|
emitter: EventEmitter;
|
||||||
eventName: (string | symbol);
|
eventName: (string | symbol);
|
||||||
handler: (...args: any[]) => void;
|
handler: (...args: any[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Listener = (...args: any[]) => void;
|
||||||
|
|
||||||
class Helper {
|
class Helper {
|
||||||
static evaluationString(fun: Function | string, ...args: any[]): string {
|
static evaluationString(fun: Function | string, ...args: any[]): string {
|
||||||
if (Helper.isString(fun)) {
|
if (Helper.isString(fun)) {
|
||||||
|
|
@ -47,7 +53,7 @@ class Helper {
|
||||||
if (fun.content !== undefined) {
|
if (fun.content !== undefined) {
|
||||||
fun = fun.content;
|
fun = fun.content;
|
||||||
} else if (fun.path !== undefined) {
|
} 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)
|
if (addSourceUrl)
|
||||||
contents += '//# sourceURL=' + fun.path.replace(/\n/g, '');
|
contents += '//# sourceURL=' + fun.path.replace(/\n/g, '');
|
||||||
fun = contents;
|
fun = contents;
|
||||||
|
|
@ -59,7 +65,7 @@ class Helper {
|
||||||
}
|
}
|
||||||
|
|
||||||
static installApiHooks(className: string, classType: any) {
|
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)) {
|
for (const methodName of Reflect.ownKeys(classType.prototype)) {
|
||||||
const method = Reflect.get(classType.prototype, methodName);
|
const method = Reflect.get(classType.prototype, methodName);
|
||||||
if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function')
|
if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function')
|
||||||
|
|
@ -102,7 +108,7 @@ class Helper {
|
||||||
}
|
}
|
||||||
|
|
||||||
static addEventListener(
|
static addEventListener(
|
||||||
emitter: platform.EventEmitterType,
|
emitter: EventEmitter,
|
||||||
eventName: (string | symbol),
|
eventName: (string | symbol),
|
||||||
handler: (...args: any[]) => void): RegisteredListener {
|
handler: (...args: any[]) => void): RegisteredListener {
|
||||||
emitter.on(eventName, handler);
|
emitter.on(eventName, handler);
|
||||||
|
|
@ -110,7 +116,7 @@ class Helper {
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeEventListeners(listeners: Array<{
|
static removeEventListeners(listeners: Array<{
|
||||||
emitter: platform.EventEmitterType;
|
emitter: EventEmitter;
|
||||||
eventName: (string | symbol);
|
eventName: (string | symbol);
|
||||||
handler: (...args: any[]) => void;
|
handler: (...args: any[]) => void;
|
||||||
}>) {
|
}>) {
|
||||||
|
|
@ -140,7 +146,7 @@ class Helper {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async waitForEvent(
|
static async waitForEvent(
|
||||||
emitter: platform.EventEmitterType,
|
emitter: EventEmitter,
|
||||||
eventName: (string | symbol),
|
eventName: (string | symbol),
|
||||||
predicate: Function,
|
predicate: Function,
|
||||||
timeout: number,
|
timeout: number,
|
||||||
|
|
@ -296,6 +302,45 @@ class Helper {
|
||||||
assert(typeof match === 'function', 'url parameter should be string, RegExp or function');
|
assert(typeof match === 'function', 'url parameter should be string, RegExp or function');
|
||||||
return match(url);
|
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 {
|
export function assert(value: any, message?: string): asserts value {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
import * as dom from './dom';
|
import * as dom from './dom';
|
||||||
import * as platform from './platform';
|
import { helper } from './helper';
|
||||||
|
|
||||||
export interface ExecutionContextDelegate {
|
export interface ExecutionContextDelegate {
|
||||||
evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: string | Function, ...args: any[]): Promise<any>;
|
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 handles: (Promise<JSHandle | T>)[] = [];
|
||||||
const toDispose: Promise<JSHandle>[] = [];
|
const toDispose: Promise<JSHandle>[] = [];
|
||||||
const pushHandle = (handle: Promise<JSHandle | T>): string => {
|
const pushHandle = (handle: Promise<JSHandle | T>): string => {
|
||||||
const guid = platform.guid();
|
const guid = helper.guid();
|
||||||
guids.push(guid);
|
guids.push(guid);
|
||||||
handles.push(handle);
|
handles.push(handle);
|
||||||
return guid;
|
return guid;
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,11 @@
|
||||||
* limitations under the License.
|
* 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 * as frames from './frames';
|
||||||
import { assert, helper } from './helper';
|
import { assert, helper } from './helper';
|
||||||
import * as platform from './platform';
|
|
||||||
|
|
||||||
export type NetworkCookie = {
|
export type NetworkCookie = {
|
||||||
name: string,
|
name: string,
|
||||||
|
|
@ -227,8 +229,8 @@ export class Route {
|
||||||
response = {
|
response = {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
headers: response.headers,
|
headers: response.headers,
|
||||||
contentType: platform.getMimeType(response.path),
|
contentType: mime.getType(response.path) || 'application/octet-stream',
|
||||||
body: await platform.readFileBuffer(response.path)
|
body: await util.promisify(fs.readFile)(response.path)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
await this._delegate.fulfill(response);
|
await this._delegate.fulfill(response);
|
||||||
|
|
@ -242,11 +244,11 @@ export class Route {
|
||||||
|
|
||||||
export type RouteHandler = (route: Route, request: Request) => void;
|
export type RouteHandler = (route: Route, request: Request) => void;
|
||||||
|
|
||||||
type GetResponseBodyCallback = () => Promise<platform.BufferType>;
|
type GetResponseBodyCallback = () => Promise<Buffer>;
|
||||||
|
|
||||||
export class Response {
|
export class Response {
|
||||||
private _request: Request;
|
private _request: Request;
|
||||||
private _contentPromise: Promise<platform.BufferType> | null = null;
|
private _contentPromise: Promise<Buffer> | null = null;
|
||||||
_finishedPromise: Promise<Error | null>;
|
_finishedPromise: Promise<Error | null>;
|
||||||
private _finishedPromiseCallback: any;
|
private _finishedPromiseCallback: any;
|
||||||
private _status: number;
|
private _status: number;
|
||||||
|
|
@ -296,7 +298,7 @@ export class Response {
|
||||||
return this._finishedPromise;
|
return this._finishedPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
body(): Promise<platform.BufferType> {
|
body(): Promise<Buffer> {
|
||||||
if (!this._contentPromise) {
|
if (!this._contentPromise) {
|
||||||
this._contentPromise = this._finishedPromise.then(async error => {
|
this._contentPromise = this._finishedPromise.then(async error => {
|
||||||
if (error)
|
if (error)
|
||||||
|
|
@ -330,7 +332,7 @@ export type FulfillResponse = {
|
||||||
status?: number,
|
status?: number,
|
||||||
headers?: Headers,
|
headers?: Headers,
|
||||||
contentType?: string,
|
contentType?: string,
|
||||||
body?: string | platform.BufferType,
|
body?: string | Buffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface RouteDelegate {
|
export interface RouteDelegate {
|
||||||
|
|
|
||||||
18
src/page.ts
18
src/page.ts
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
import * as dom from './dom';
|
import * as dom from './dom';
|
||||||
import * as frames from './frames';
|
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 input from './input';
|
||||||
import * as js from './javascript';
|
import * as js from './javascript';
|
||||||
import * as network from './network';
|
import * as network from './network';
|
||||||
|
|
@ -28,8 +28,8 @@ import { Events } from './events';
|
||||||
import { BrowserContext, BrowserContextBase } from './browserContext';
|
import { BrowserContext, BrowserContextBase } from './browserContext';
|
||||||
import { ConsoleMessage, ConsoleMessageLocation } from './console';
|
import { ConsoleMessage, ConsoleMessageLocation } from './console';
|
||||||
import * as accessibility from './accessibility';
|
import * as accessibility from './accessibility';
|
||||||
import * as platform from './platform';
|
|
||||||
import { ExtendedEventEmitter } from './extendedEventEmitter';
|
import { ExtendedEventEmitter } from './extendedEventEmitter';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
export interface PageDelegate {
|
export interface PageDelegate {
|
||||||
readonly rawMouse: input.RawMouse;
|
readonly rawMouse: input.RawMouse;
|
||||||
|
|
@ -55,7 +55,7 @@ export interface PageDelegate {
|
||||||
canScreenshotOutsideViewport(): boolean;
|
canScreenshotOutsideViewport(): boolean;
|
||||||
resetViewport(): Promise<void>; // Only called if canScreenshotOutsideViewport() returns false.
|
resetViewport(): Promise<void>; // Only called if canScreenshotOutsideViewport() returns false.
|
||||||
setBackgroundColor(color?: { r: number; g: number; b: number; a: number; }): Promise<void>;
|
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;
|
isElementHandle(remoteObject: any): boolean;
|
||||||
adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>>;
|
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>;
|
scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<void>;
|
||||||
|
|
||||||
getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}>;
|
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;
|
coverage?: () => any;
|
||||||
|
|
||||||
// Work around Chrome's non-associated input and protocol.
|
// Work around Chrome's non-associated input and protocol.
|
||||||
|
|
@ -106,7 +106,7 @@ export class Page extends ExtendedEventEmitter {
|
||||||
readonly _frameManager: frames.FrameManager;
|
readonly _frameManager: frames.FrameManager;
|
||||||
readonly accessibility: accessibility.Accessibility;
|
readonly accessibility: accessibility.Accessibility;
|
||||||
private _workers = new Map<string, Worker>();
|
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 coverage: any;
|
||||||
readonly _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = [];
|
readonly _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = [];
|
||||||
_ownedContext: BrowserContext | undefined;
|
_ownedContext: BrowserContext | undefined;
|
||||||
|
|
@ -408,7 +408,7 @@ export class Page extends ExtendedEventEmitter {
|
||||||
route.continue();
|
route.continue();
|
||||||
}
|
}
|
||||||
|
|
||||||
async screenshot(options?: types.ScreenshotOptions): Promise<platform.BufferType> {
|
async screenshot(options?: types.ScreenshotOptions): Promise<Buffer> {
|
||||||
return this._screenshotter.screenshotPage(options);
|
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 (event === Events.Page.FileChooser) {
|
||||||
if (!this.listenerCount(event))
|
if (!this.listenerCount(event))
|
||||||
this._delegate.setFileChooserIntercepted(true);
|
this._delegate.setFileChooserIntercepted(true);
|
||||||
|
|
@ -515,7 +515,7 @@ export class Page extends ExtendedEventEmitter {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeListener(event: string | symbol, listener: platform.Listener): this {
|
removeListener(event: string | symbol, listener: Listener): this {
|
||||||
super.removeListener(event, listener);
|
super.removeListener(event, listener);
|
||||||
if (event === Events.Page.FileChooser && !this.listenerCount(event))
|
if (event === Events.Page.FileChooser && !this.listenerCount(event))
|
||||||
this._delegate.setFileChooserIntercepted(false);
|
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 _url: string;
|
||||||
private _executionContextPromise: Promise<js.ExecutionContext>;
|
private _executionContextPromise: Promise<js.ExecutionContext>;
|
||||||
private _executionContextCallback: (value?: js.ExecutionContext) => void;
|
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.
|
* 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 * as dom from './dom';
|
||||||
import { assert, helper } from './helper';
|
import { assert, helper } from './helper';
|
||||||
import * as types from './types';
|
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
import * as platform from './platform';
|
import * as types from './types';
|
||||||
|
|
||||||
export class Screenshotter {
|
export class Screenshotter {
|
||||||
private _queue = new TaskQueue();
|
private _queue = new TaskQueue();
|
||||||
|
|
@ -78,7 +80,7 @@ export class Screenshotter {
|
||||||
return fullPageSize;
|
return fullPageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
async screenshotPage(options: types.ScreenshotOptions = {}): Promise<platform.BufferType> {
|
async screenshotPage(options: types.ScreenshotOptions = {}): Promise<Buffer> {
|
||||||
const format = validateScreenshotOptions(options);
|
const format = validateScreenshotOptions(options);
|
||||||
return this._queue.postTask(async () => {
|
return this._queue.postTask(async () => {
|
||||||
const { viewportSize, originalViewportSize } = await this._originalViewportSize();
|
const { viewportSize, originalViewportSize } = await this._originalViewportSize();
|
||||||
|
|
@ -102,7 +104,7 @@ export class Screenshotter {
|
||||||
}).catch(rewriteError);
|
}).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);
|
const format = validateScreenshotOptions(options);
|
||||||
return this._queue.postTask(async () => {
|
return this._queue.postTask(async () => {
|
||||||
const { viewportSize, originalViewportSize } = await this._originalViewportSize();
|
const { viewportSize, originalViewportSize } = await this._originalViewportSize();
|
||||||
|
|
@ -138,7 +140,7 @@ export class Screenshotter {
|
||||||
}).catch(rewriteError);
|
}).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';
|
const shouldSetDefaultBackground = options.omitBackground && format === 'png';
|
||||||
if (shouldSetDefaultBackground)
|
if (shouldSetDefaultBackground)
|
||||||
await this._page._delegate.setBackgroundColor({ r: 0, g: 0, b: 0, a: 0});
|
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();
|
await this._page._delegate.resetViewport();
|
||||||
}
|
}
|
||||||
if (options.path)
|
if (options.path)
|
||||||
await platform.writeFileAsync(options.path, buffer);
|
await util.promisify(fs.writeFile)(options.path, buffer);
|
||||||
return 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);
|
assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type);
|
||||||
format = options.type;
|
format = options.type;
|
||||||
} else if (options.path) {
|
} else if (options.path) {
|
||||||
const mimeType = platform.getMimeType(options.path);
|
const mimeType = mime.getType(options.path);
|
||||||
if (mimeType === 'image/png')
|
if (mimeType === 'image/png')
|
||||||
format = 'png';
|
format = 'png';
|
||||||
else if (mimeType === 'image/jpeg')
|
else if (mimeType === 'image/jpeg')
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,9 @@ import * as path from 'path';
|
||||||
import { getProxyForUrl } from 'proxy-from-env';
|
import { getProxyForUrl } from 'proxy-from-env';
|
||||||
import * as URL from 'url';
|
import * as URL from 'url';
|
||||||
import { assert } from '../helper';
|
import { assert } from '../helper';
|
||||||
import * as platform from '../platform';
|
|
||||||
|
|
||||||
const unlinkAsync = platform.promisify(fs.unlink.bind(fs));
|
const unlinkAsync = util.promisify(fs.unlink.bind(fs));
|
||||||
const chmodAsync = platform.promisify(fs.chmod.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 existsAsync = (path: string): Promise<boolean> => new Promise(resolve => fs.stat(path, err => resolve(!err)));
|
||||||
|
|
||||||
const DEFAULT_DOWNLOAD_HOSTS = {
|
const DEFAULT_DOWNLOAD_HOSTS = {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChildProcess, execSync } from 'child_process';
|
import { ChildProcess, execSync } from 'child_process';
|
||||||
import * as platform from '../platform';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
export class WebSocketWrapper {
|
export class WebSocketWrapper {
|
||||||
readonly wsEndpoint: string;
|
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 _process: ChildProcess;
|
||||||
private _gracefullyClose: () => Promise<void>;
|
private _gracefullyClose: () => Promise<void>;
|
||||||
private _webSocketWrapper: WebSocketWrapper | null;
|
private _webSocketWrapper: WebSocketWrapper | null;
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import * as util from 'util';
|
||||||
import { debugError, helper, assert } from '../helper';
|
import { debugError, helper, assert } from '../helper';
|
||||||
import { CRBrowser } from '../chromium/crBrowser';
|
import { CRBrowser } from '../chromium/crBrowser';
|
||||||
import * as platform from '../platform';
|
|
||||||
import * as ws from 'ws';
|
import * as ws from 'ws';
|
||||||
import { launchProcess } from '../server/processLauncher';
|
import { launchProcess } from '../server/processLauncher';
|
||||||
import { kBrowserCloseMessageId } from '../chromium/crConnection';
|
import { kBrowserCloseMessageId } from '../chromium/crConnection';
|
||||||
|
|
@ -29,7 +29,7 @@ import { LaunchOptions, BrowserArgOptions, BrowserType, ConnectOptions, LaunchSe
|
||||||
import { LaunchType } from '../browser';
|
import { LaunchType } from '../browser';
|
||||||
import { BrowserServer, WebSocketWrapper } from './browserServer';
|
import { BrowserServer, WebSocketWrapper } from './browserServer';
|
||||||
import { Events } from '../events';
|
import { Events } from '../events';
|
||||||
import { ConnectionTransport, ProtocolRequest } from '../transport';
|
import { ConnectionTransport, ProtocolRequest, WebSocketTransport } from '../transport';
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
|
|
||||||
export class Chromium implements BrowserType<CRBrowser> {
|
export class Chromium implements BrowserType<CRBrowser> {
|
||||||
|
|
@ -85,14 +85,14 @@ export class Chromium implements BrowserType<CRBrowser> {
|
||||||
let temporaryUserDataDir: string | null = null;
|
let temporaryUserDataDir: string | null = null;
|
||||||
if (!userDataDir) {
|
if (!userDataDir) {
|
||||||
userDataDir = await mkdtempAsync(CHROMIUM_PROFILE_PATH);
|
userDataDir = await mkdtempAsync(CHROMIUM_PROFILE_PATH);
|
||||||
temporaryUserDataDir = userDataDir!;
|
temporaryUserDataDir = userDataDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chromeArguments = [];
|
const chromeArguments = [];
|
||||||
if (!ignoreDefaultArgs)
|
if (!ignoreDefaultArgs)
|
||||||
chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir!));
|
chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir));
|
||||||
else if (Array.isArray(ignoreDefaultArgs))
|
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
|
else
|
||||||
chromeArguments.push(...args);
|
chromeArguments.push(...args);
|
||||||
|
|
||||||
|
|
@ -133,7 +133,7 @@ export class Chromium implements BrowserType<CRBrowser> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(options: ConnectOptions): Promise<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);
|
return CRBrowser.connect(transport, false, options.slowMo);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +178,7 @@ export class Chromium implements BrowserType<CRBrowser> {
|
||||||
|
|
||||||
function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number): WebSocketWrapper {
|
function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number): WebSocketWrapper {
|
||||||
const server = new ws.Server({ port });
|
const server = new ws.Server({ port });
|
||||||
const guid = platform.guid();
|
const guid = helper.guid();
|
||||||
|
|
||||||
const awaitingBrowserTarget = new Map<number, ws>();
|
const awaitingBrowserTarget = new Map<number, ws>();
|
||||||
const sessionToSocket = new Map<string, 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-');
|
const CHROMIUM_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_dev_profile-');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import * as util from 'util';
|
||||||
import * as ws from 'ws';
|
import * as ws from 'ws';
|
||||||
import { LaunchType } from '../browser';
|
import { LaunchType } from '../browser';
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
|
|
@ -26,13 +27,12 @@ import { Events } from '../events';
|
||||||
import { FFBrowser } from '../firefox/ffBrowser';
|
import { FFBrowser } from '../firefox/ffBrowser';
|
||||||
import { kBrowserCloseMessageId } from '../firefox/ffConnection';
|
import { kBrowserCloseMessageId } from '../firefox/ffConnection';
|
||||||
import { debugError, helper, assert } from '../helper';
|
import { debugError, helper, assert } from '../helper';
|
||||||
import * as platform from '../platform';
|
|
||||||
import { BrowserServer, WebSocketWrapper } from './browserServer';
|
import { BrowserServer, WebSocketWrapper } from './browserServer';
|
||||||
import { BrowserArgOptions, BrowserType, LaunchOptions, LaunchServerOptions, ConnectOptions } from './browserType';
|
import { BrowserArgOptions, BrowserType, LaunchOptions, LaunchServerOptions, ConnectOptions } from './browserType';
|
||||||
import { launchProcess, waitForLine } from './processLauncher';
|
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> {
|
export class Firefox implements BrowserType<FFBrowser> {
|
||||||
private _executablePath: (string|undefined);
|
private _executablePath: (string|undefined);
|
||||||
|
|
@ -50,7 +50,7 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||||
async launch(options: LaunchOptions = {}): Promise<FFBrowser> {
|
async launch(options: LaunchOptions = {}): Promise<FFBrowser> {
|
||||||
assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
|
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 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);
|
return FFBrowser.connect(transport, false, options.slowMo);
|
||||||
});
|
});
|
||||||
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
|
// 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,
|
slowMo = 0,
|
||||||
} = options;
|
} = options;
|
||||||
const browserServer = await this._launchServer(options, 'persistent', userDataDir);
|
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);
|
return FFBrowser.connect(transport, true, slowMo);
|
||||||
});
|
});
|
||||||
await helper.waitWithTimeout(browser._firstPagePromise, 'first page', timeout);
|
await helper.waitWithTimeout(browser._firstPagePromise, 'first page', timeout);
|
||||||
|
|
@ -103,9 +103,9 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ignoreDefaultArgs)
|
if (!ignoreDefaultArgs)
|
||||||
firefoxArguments.push(...this._defaultArgs(options, launchType, userDataDir!, 0));
|
firefoxArguments.push(...this._defaultArgs(options, launchType, userDataDir, 0));
|
||||||
else if (Array.isArray(ignoreDefaultArgs))
|
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
|
else
|
||||||
firefoxArguments.push(...args);
|
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.
|
// We try to gracefully close to prevent crash reporting and core dumps.
|
||||||
// Note that it's fine to reuse the pipe transport, since
|
// Note that it's fine to reuse the pipe transport, since
|
||||||
// our connection ignores kBrowserCloseMessageId.
|
// 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 };
|
const message = { method: 'Browser.close', params: {}, id: kBrowserCloseMessageId };
|
||||||
await transport.send(message);
|
await transport.send(message);
|
||||||
},
|
},
|
||||||
|
|
@ -149,14 +149,14 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||||
|
|
||||||
let browserServer: BrowserServer | undefined = undefined;
|
let browserServer: BrowserServer | undefined = undefined;
|
||||||
let browserWSEndpoint: string | 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;
|
browserWSEndpoint = webSocketWrapper.wsEndpoint;
|
||||||
browserServer = new BrowserServer(launchedProcess, gracefullyClose, webSocketWrapper);
|
browserServer = new BrowserServer(launchedProcess, gracefullyClose, webSocketWrapper);
|
||||||
return browserServer;
|
return browserServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(options: ConnectOptions): Promise<FFBrowser> {
|
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);
|
return FFBrowser.connect(transport, false, options.slowMo);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +197,7 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||||
|
|
||||||
function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number): WebSocketWrapper {
|
function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number): WebSocketWrapper {
|
||||||
const server = new ws.Server({ port });
|
const server = new ws.Server({ port });
|
||||||
const guid = platform.guid();
|
const guid = helper.guid();
|
||||||
const idMixer = new SequenceNumberMixer<{id: number, socket: ws}>();
|
const idMixer = new SequenceNumberMixer<{id: number, socket: ws}>();
|
||||||
const pendingBrowserContextCreations = new Set<number>();
|
const pendingBrowserContextCreations = new Set<number>();
|
||||||
const pendingBrowserContextDeletions = new Map<number, string>();
|
const pendingBrowserContextDeletions = new Map<number, string>();
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,12 @@
|
||||||
|
|
||||||
import { debugError, helper, RegisteredListener } from '../helper';
|
import { debugError, helper, RegisteredListener } from '../helper';
|
||||||
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
||||||
import { makeWaitForNextTask } from '../platform';
|
|
||||||
|
|
||||||
export class PipeTransport implements ConnectionTransport {
|
export class PipeTransport implements ConnectionTransport {
|
||||||
private _pipeWrite: NodeJS.WritableStream | null;
|
private _pipeWrite: NodeJS.WritableStream | null;
|
||||||
private _pendingMessage = '';
|
private _pendingMessage = '';
|
||||||
private _eventListeners: RegisteredListener[];
|
private _eventListeners: RegisteredListener[];
|
||||||
private _waitForNextTask = makeWaitForNextTask();
|
private _waitForNextTask = helper.makeWaitForNextTask();
|
||||||
private readonly _closeCallback: () => void;
|
private readonly _closeCallback: () => void;
|
||||||
|
|
||||||
onmessage?: (message: ProtocolResponse) => void;
|
onmessage?: (message: ProtocolResponse) => void;
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import * as stream from 'stream';
|
import * as debug from 'debug';
|
||||||
import * as removeFolder from 'rimraf';
|
|
||||||
import { helper } from '../helper';
|
|
||||||
import * as readline from 'readline';
|
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 { 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 = {
|
export type LaunchProcessOptions = {
|
||||||
executablePath: string,
|
executablePath: string,
|
||||||
|
|
@ -48,9 +49,9 @@ let lastLaunchedId = 0;
|
||||||
|
|
||||||
export async function launchProcess(options: LaunchProcessOptions): Promise<LaunchResult> {
|
export async function launchProcess(options: LaunchProcessOptions): Promise<LaunchResult> {
|
||||||
const id = ++lastLaunchedId;
|
const id = ++lastLaunchedId;
|
||||||
const debugBrowser = platform.debug(`pw:browser:proc:[${id}]`);
|
const debugBrowser = debug(`pw:browser:proc:[${id}]`);
|
||||||
const debugBrowserOut = platform.debug(`pw:browser:out:[${id}]`);
|
const debugBrowserOut = debug(`pw:browser:out:[${id}]`);
|
||||||
const debugBrowserErr = platform.debug(`pw:browser:err:[${id}]`);
|
const debugBrowserErr = debug(`pw:browser:err:[${id}]`);
|
||||||
(debugBrowser as any).color = '33';
|
(debugBrowser as any).color = '33';
|
||||||
(debugBrowserOut as any).color = '178';
|
(debugBrowserOut as any).color = '178';
|
||||||
(debugBrowserErr as any).color = '160';
|
(debugBrowserErr as any).color = '160';
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,12 @@ import { PipeTransport } from './pipeTransport';
|
||||||
import { launchProcess } from './processLauncher';
|
import { launchProcess } from './processLauncher';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as platform from '../platform';
|
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
|
import * as util from 'util';
|
||||||
import { debugError, helper, assert } from '../helper';
|
import { debugError, helper, assert } from '../helper';
|
||||||
import { kBrowserCloseMessageId } from '../webkit/wkConnection';
|
import { kBrowserCloseMessageId } from '../webkit/wkConnection';
|
||||||
import { LaunchOptions, BrowserArgOptions, BrowserType, LaunchServerOptions, ConnectOptions } from './browserType';
|
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 * as ws from 'ws';
|
||||||
import { LaunchType } from '../browser';
|
import { LaunchType } from '../browser';
|
||||||
import { BrowserServer, WebSocketWrapper } from './browserServer';
|
import { BrowserServer, WebSocketWrapper } from './browserServer';
|
||||||
|
|
@ -85,14 +85,14 @@ export class WebKit implements BrowserType<WKBrowser> {
|
||||||
let temporaryUserDataDir: string | null = null;
|
let temporaryUserDataDir: string | null = null;
|
||||||
if (!userDataDir) {
|
if (!userDataDir) {
|
||||||
userDataDir = await mkdtempAsync(WEBKIT_PROFILE_PATH);
|
userDataDir = await mkdtempAsync(WEBKIT_PROFILE_PATH);
|
||||||
temporaryUserDataDir = userDataDir!;
|
temporaryUserDataDir = userDataDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
const webkitArguments = [];
|
const webkitArguments = [];
|
||||||
if (!ignoreDefaultArgs)
|
if (!ignoreDefaultArgs)
|
||||||
webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir!, port));
|
webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir, port));
|
||||||
else if (Array.isArray(ignoreDefaultArgs))
|
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
|
else
|
||||||
webkitArguments.push(...args);
|
webkitArguments.push(...args);
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ export class WebKit implements BrowserType<WKBrowser> {
|
||||||
const { launchedProcess, gracefullyClose } = await launchProcess({
|
const { launchedProcess, gracefullyClose } = await launchProcess({
|
||||||
executablePath: webkitExecutable,
|
executablePath: webkitExecutable,
|
||||||
args: webkitArguments,
|
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,
|
handleSIGINT,
|
||||||
handleSIGTERM,
|
handleSIGTERM,
|
||||||
handleSIGHUP,
|
handleSIGHUP,
|
||||||
|
|
@ -133,7 +133,7 @@ export class WebKit implements BrowserType<WKBrowser> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(options: ConnectOptions): Promise<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);
|
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-');
|
const WEBKIT_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_dev_profile-');
|
||||||
|
|
||||||
function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number): WebSocketWrapper {
|
function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number): WebSocketWrapper {
|
||||||
const server = new ws.Server({ port });
|
const server = new ws.Server({ port });
|
||||||
const guid = platform.guid();
|
const guid = helper.guid();
|
||||||
const idMixer = new SequenceNumberMixer<{id: number, socket: ws}>();
|
const idMixer = new SequenceNumberMixer<{id: number, socket: ws}>();
|
||||||
const pendingBrowserContextCreations = new Set<number>();
|
const pendingBrowserContextCreations = new Set<number>();
|
||||||
const pendingBrowserContextDeletions = new Map<number, string>();
|
const pendingBrowserContextDeletions = new Map<number, string>();
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as WebSocket from 'ws';
|
||||||
|
import { helper } from './helper';
|
||||||
|
|
||||||
export type ProtocolRequest = {
|
export type ProtocolRequest = {
|
||||||
id: number;
|
id: number;
|
||||||
method: string;
|
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> {
|
export class SequenceNumberMixer<V> {
|
||||||
static _lastSequenceNumber = 1;
|
static _lastSequenceNumber = 1;
|
||||||
private _values = new Map<number, V>();
|
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 { assert, helper, RegisteredListener } from '../helper';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import { Page, PageBinding } from '../page';
|
import { Page, PageBinding } from '../page';
|
||||||
import * as platform from '../platform';
|
|
||||||
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { kPageProxyMessageReceived, PageProxyMessageReceivedPayload, WKConnection, WKSession } from './wkConnection';
|
import { kPageProxyMessageReceived, PageProxyMessageReceivedPayload, WKConnection, WKSession } from './wkConnection';
|
||||||
import { WKPage } from './wkPage';
|
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';
|
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 _connection: WKConnection;
|
||||||
private readonly _attachToDefaultContext: boolean;
|
private readonly _attachToDefaultContext: boolean;
|
||||||
readonly _browserSession: WKSession;
|
readonly _browserSession: WKSession;
|
||||||
|
|
@ -186,7 +186,7 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
|
||||||
await disconnected;
|
await disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
_setDebugFunction(debugFunction: platform.DebuggerType) {
|
_setDebugFunction(debugFunction: debug.IDebugger) {
|
||||||
this._connection._debugProtocol = debugFunction;
|
this._connection._debugProtocol = debugFunction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as debug from 'debug';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
import { assert } from '../helper';
|
import { assert } from '../helper';
|
||||||
import * as platform from '../platform';
|
|
||||||
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
|
||||||
|
|
@ -34,7 +35,7 @@ export class WKConnection {
|
||||||
private readonly _onDisconnect: () => void;
|
private readonly _onDisconnect: () => void;
|
||||||
private _lastId = 0;
|
private _lastId = 0;
|
||||||
private _closed = false;
|
private _closed = false;
|
||||||
_debugProtocol: platform.DebuggerType = platform.debug('pw:protocol');
|
_debugProtocol = debug('pw:protocol');
|
||||||
|
|
||||||
readonly browserSession: WKSession;
|
readonly browserSession: WKSession;
|
||||||
|
|
||||||
|
|
@ -90,7 +91,7 @@ export class WKConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WKSession extends platform.EventEmitter {
|
export class WKSession extends EventEmitter {
|
||||||
connection: WKConnection;
|
connection: WKConnection;
|
||||||
errorText: string;
|
errorText: string;
|
||||||
readonly sessionId: string;
|
readonly sessionId: string;
|
||||||
|
|
@ -127,7 +128,7 @@ export class WKSession extends platform.EventEmitter {
|
||||||
throw new Error(`Protocol error (${method}): ${this.errorText}`);
|
throw new Error(`Protocol error (${method}): ${this.errorText}`);
|
||||||
const id = this.connection.nextMessageId();
|
const id = this.connection.nextMessageId();
|
||||||
const messageObj = { id, method, params };
|
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);
|
this._rawSend(messageObj);
|
||||||
return new Promise<Protocol.CommandReturnValues[T]>((resolve, reject) => {
|
return new Promise<Protocol.CommandReturnValues[T]>((resolve, reject) => {
|
||||||
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
|
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
|
||||||
|
|
@ -146,7 +147,7 @@ export class WKSession extends platform.EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchMessage(object: any) {
|
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)) {
|
if (object.id && this._callbacks.has(object.id)) {
|
||||||
const callback = this._callbacks.get(object.id)!;
|
const callback = this._callbacks.get(object.id)!;
|
||||||
this._callbacks.delete(object.id);
|
this._callbacks.delete(object.id);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import { assert, debugError, helper } from '../helper';
|
import { assert, debugError, helper } from '../helper';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import * as platform from '../platform';
|
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { WKSession } from './wkConnection';
|
import { WKSession } from './wkConnection';
|
||||||
|
|
||||||
|
|
@ -80,7 +79,7 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
||||||
if (response.contentType)
|
if (response.contentType)
|
||||||
responseHeaders['content-type'] = response.contentType;
|
responseHeaders['content-type'] = response.contentType;
|
||||||
if (responseBody && !('content-length' in responseHeaders))
|
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', {
|
await this._session.send('Network.interceptWithResponse', {
|
||||||
requestId: this._requestId,
|
requestId: this._requestId,
|
||||||
|
|
@ -114,7 +113,7 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
||||||
createResponse(responsePayload: Protocol.Network.Response): network.Response {
|
createResponse(responsePayload: Protocol.Network.Response): network.Response {
|
||||||
const getResponseBody = async () => {
|
const getResponseBody = async () => {
|
||||||
const response = await this._session.send('Network.getResponseBody', { requestId: this._requestId });
|
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);
|
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 { RawMouseImpl, RawKeyboardImpl } from './wkInput';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import * as accessibility from '../accessibility';
|
import * as accessibility from '../accessibility';
|
||||||
import * as platform from '../platform';
|
|
||||||
import { getAccessibilityTree } from './wkAccessibility';
|
import { getAccessibilityTree } from './wkAccessibility';
|
||||||
import { WKProvisionalPage } from './wkProvisionalPage';
|
import { WKProvisionalPage } from './wkProvisionalPage';
|
||||||
import { WKBrowserContext } from './wkBrowser';
|
import { WKBrowserContext } from './wkBrowser';
|
||||||
import { selectors } from '../selectors';
|
import { selectors } from '../selectors';
|
||||||
|
import * as jpeg from 'jpeg-js';
|
||||||
|
import * as png from 'pngjs';
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
|
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
|
||||||
|
|
@ -73,7 +74,7 @@ export class WKPage implements PageDelegate {
|
||||||
this._workers = new WKWorkers(this._page);
|
this._workers = new WKWorkers(this._page);
|
||||||
this._session = undefined as any as WKSession;
|
this._session = undefined as any as WKSession;
|
||||||
this._browserContext = browserContext;
|
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 = [
|
this._eventListeners = [
|
||||||
helper.addEventListener(this._pageProxySession, 'Target.targetCreated', this._onTargetCreated.bind(this)),
|
helper.addEventListener(this._pageProxySession, 'Target.targetCreated', this._onTargetCreated.bind(this)),
|
||||||
helper.addEventListener(this._pageProxySession, 'Target.targetDestroyed', this._onTargetDestroyed.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 });
|
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.
|
// TODO: documentRect does not include pageScale, while backend considers it does.
|
||||||
// This brakes mobile screenshots of elements or full page.
|
// This brakes mobile screenshots of elements or full page.
|
||||||
const rect = (documentRect || viewportRect)!;
|
const rect = (documentRect || viewportRect)!;
|
||||||
const result = await this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport' });
|
const result = await this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport' });
|
||||||
const prefix = 'data:image/png;base64,';
|
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')
|
if (format === 'jpeg')
|
||||||
buffer = platform.pngToJpeg(buffer, quality);
|
buffer = jpeg.encode(png.PNG.sync.read(buffer), quality).data;
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -225,8 +225,4 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
|
||||||
loadTests('./chromium/oopif.spec.js');
|
loadTests('./chromium/oopif.spec.js');
|
||||||
loadTests('./chromium/tracing.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<!import('../Source')>} sources
|
||||||
* @param {!Array<string>} externalDependencies
|
|
||||||
*/
|
*/
|
||||||
function checkSources(sources, externalDependencies) {
|
function checkSources(sources) {
|
||||||
// special treatment for Events.js
|
// special treatment for Events.js
|
||||||
const classEvents = new Map();
|
const classEvents = new Map();
|
||||||
const eventsSources = sources.filter(source => source.name().startsWith('events.'));
|
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))
|
if (fileName.endsWith('/api.ts') && ts.isExportSpecifier(node))
|
||||||
apiClassNames.add(expandPrefix((node.propertyName || node.name).text));
|
apiClassNames.add(expandPrefix((node.propertyName || node.name).text));
|
||||||
const isPlatform = fileName.endsWith('platform.ts');
|
|
||||||
if (!fileName.includes('src/server/')) {
|
if (!fileName.includes('src/server/')) {
|
||||||
// Only relative imports.
|
// Only relative imports.
|
||||||
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
||||||
const module = node.moduleSpecifier.text;
|
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');
|
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);
|
const lac = ts.getLineAndCharacterOfPosition(node.getSourceFile(), node.moduleSpecifier.pos);
|
||||||
errors.push(`Disallowed import "${module}" at ${node.getSourceFile().fileName}:${lac.line + 1}`);
|
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);
|
ts.forEachChild(node, visit);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@ const EXCLUDE_PROPERTIES = new Set([
|
||||||
* @param {!Array<!Source>} mdSources
|
* @param {!Array<!Source>} mdSources
|
||||||
* @return {!Promise<!Array<!Message>>}
|
* @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 mdResult = await mdBuilder(page, mdSources);
|
||||||
const jsResult = jsBuilder.checkSources(jsSources, externalDependencies);
|
const jsResult = jsBuilder.checkSources(jsSources);
|
||||||
const jsDocumentation = filterJSDocumentation(jsSources, jsResult.documentation);
|
const jsDocumentation = filterJSDocumentation(jsSources, jsResult.documentation);
|
||||||
const mdDocumentation = mdResult.documentation;
|
const mdDocumentation = mdResult.documentation;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,7 @@ async function run() {
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
const checkPublicAPI = require('./check_public_api');
|
const checkPublicAPI = require('./check_public_api');
|
||||||
const jsSources = await Source.readdir(path.join(PROJECT_DIR, 'src'));
|
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));
|
||||||
messages.push(...await checkPublicAPI(page, mdSources, jsSources, externalDependencies));
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
|
|
||||||
for (const source of mdSources) {
|
for (const source of mdSources) {
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ let documentation;
|
||||||
const {documentation: mdDocumentation} = await require('../doclint/check_public_api/MDBuilder')(page, [api]);
|
const {documentation: mdDocumentation} = await require('../doclint/check_public_api/MDBuilder')(page, [api]);
|
||||||
await browser.close();
|
await browser.close();
|
||||||
const sources = await Source.readdir(path.join(PROJECT_DIR, 'src'));
|
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);
|
documentation = mergeDocumentation(mdDocumentation, jsDocumentation);
|
||||||
const handledClasses = new Set();
|
const handledClasses = new Set();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ const path = require('path');
|
||||||
const files = [
|
const files = [
|
||||||
path.join('src', 'injected', 'zsSelectorEngine.webpack.config.js'),
|
path.join('src', 'injected', 'zsSelectorEngine.webpack.config.js'),
|
||||||
path.join('src', 'injected', 'selectorEvaluator.webpack.config.js'),
|
path.join('src', 'injected', 'selectorEvaluator.webpack.config.js'),
|
||||||
path.join('src', 'web.webpack.config.js'),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function runOne(runner, file) {
|
function runOne(runner, file) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue