feat(pw-web): introduce platform.ts to absract between node and browser platforms (#392)
This commit is contained in:
parent
f1b825e6a2
commit
9c966c8b19
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import { BrowserContext, BrowserContextOptions } from './browserContext';
|
import { BrowserContext, BrowserContextOptions } from './browserContext';
|
||||||
import { ChildProcess } from 'child_process';
|
import { ChildProcess } from 'child_process';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from './platform';
|
||||||
|
|
||||||
export class Browser extends EventEmitter {
|
export class Browser extends EventEmitter {
|
||||||
newContext(options?: BrowserContextOptions): Promise<BrowserContext> { throw new Error('Not implemented'); }
|
newContext(options?: BrowserContextOptions): Promise<BrowserContext> { throw new Error('Not implemented'); }
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,17 @@ import * as extract from 'extract-zip';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as ProxyAgent from 'https-proxy-agent';
|
import * as ProxyAgent from 'https-proxy-agent';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import * as platform from './platform';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { getProxyForUrl } from 'proxy-from-env';
|
import { getProxyForUrl } from 'proxy-from-env';
|
||||||
import * as removeRecursive from 'rimraf';
|
import * as removeRecursive from 'rimraf';
|
||||||
import * as URL from 'url';
|
import * as URL from 'url';
|
||||||
import { assert, helper } from './helper';
|
import { assert } from './helper';
|
||||||
|
|
||||||
const readdirAsync = helper.promisify(fs.readdir.bind(fs));
|
const readdirAsync = platform.promisify(fs.readdir.bind(fs));
|
||||||
const mkdirAsync = helper.promisify(fs.mkdir.bind(fs));
|
const mkdirAsync = platform.promisify(fs.mkdir.bind(fs));
|
||||||
const unlinkAsync = helper.promisify(fs.unlink.bind(fs));
|
const unlinkAsync = platform.promisify(fs.unlink.bind(fs));
|
||||||
const chmodAsync = helper.promisify(fs.chmod.bind(fs));
|
const chmodAsync = platform.promisify(fs.chmod.bind(fs));
|
||||||
|
|
||||||
function existsAsync(filePath) {
|
function existsAsync(filePath) {
|
||||||
let fulfill = null;
|
let fulfill = null;
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import { CRPage } from './crPage';
|
||||||
import * as browser from '../browser';
|
import * as browser from '../browser';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
|
import * as platform from '../platform';
|
||||||
import { CRWorker } from './features/crWorkers';
|
import { CRWorker } from './features/crWorkers';
|
||||||
import { ConnectionTransport } from '../transport';
|
import { ConnectionTransport } from '../transport';
|
||||||
import { readProtocolStream } from './crProtocolHelper';
|
import { readProtocolStream } from './crProtocolHelper';
|
||||||
|
|
@ -268,10 +269,10 @@ export class CRBrowser extends browser.Browser {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async stopTracing(): Promise<Buffer> {
|
async stopTracing(): Promise<platform.BufferType> {
|
||||||
assert(this._tracingClient, 'Tracing was not started.');
|
assert(this._tracingClient, 'Tracing was not started.');
|
||||||
let fulfill: (buffer: Buffer) => void;
|
let fulfill: (buffer: platform.BufferType) => void;
|
||||||
const contentPromise = new Promise<Buffer>(x => fulfill = x);
|
const contentPromise = new Promise<platform.BufferType>(x => fulfill = x);
|
||||||
this._tracingClient.once('Tracing.tracingComplete', event => {
|
this._tracingClient.once('Tracing.tracingComplete', event => {
|
||||||
readProtocolStream(this._tracingClient, event.stream, this._tracingPath).then(fulfill);
|
readProtocolStream(this._tracingClient, event.stream, this._tracingPath).then(fulfill);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,18 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as debug from 'debug';
|
import * as platform from '../platform';
|
||||||
import { EventEmitter } from 'events';
|
|
||||||
import { ConnectionTransport } from '../transport';
|
import { ConnectionTransport } from '../transport';
|
||||||
import { assert } from '../helper';
|
import { assert } from '../helper';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
|
||||||
const debugProtocol = debug('playwright:protocol');
|
const debugProtocol = platform.debug('playwright:protocol');
|
||||||
|
|
||||||
export const ConnectionEvents = {
|
export const ConnectionEvents = {
|
||||||
Disconnected: Symbol('ConnectionEvents.Disconnected')
|
Disconnected: Symbol('ConnectionEvents.Disconnected')
|
||||||
};
|
};
|
||||||
|
|
||||||
export class CRConnection extends EventEmitter {
|
export class CRConnection extends platform.EventEmitter {
|
||||||
private _lastId = 0;
|
private _lastId = 0;
|
||||||
private _transport: ConnectionTransport;
|
private _transport: ConnectionTransport;
|
||||||
private _sessions = new Map<string, CRSession>();
|
private _sessions = new Map<string, CRSession>();
|
||||||
|
|
@ -113,7 +112,7 @@ export const CRSessionEvents = {
|
||||||
Disconnected: Symbol('Events.CDPSession.Disconnected')
|
Disconnected: Symbol('Events.CDPSession.Disconnected')
|
||||||
};
|
};
|
||||||
|
|
||||||
export class CRSession extends EventEmitter {
|
export class CRSession extends platform.EventEmitter {
|
||||||
_connection: CRConnection;
|
_connection: CRConnection;
|
||||||
private _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
|
private _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
|
||||||
private _targetType: string;
|
private _targetType: string;
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,16 @@ 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 util from 'util';
|
||||||
|
import * as platform from '../platform';
|
||||||
import { BrowserFetcher, BrowserFetcherOptions } from '../browserFetcher';
|
import { BrowserFetcher, BrowserFetcherOptions } from '../browserFetcher';
|
||||||
import { TimeoutError } from '../errors';
|
import { TimeoutError } from '../errors';
|
||||||
import { assert, helper } from '../helper';
|
import { assert } from '../helper';
|
||||||
import { launchProcess, waitForLine } from '../processLauncher';
|
import { launchProcess, waitForLine } from '../processLauncher';
|
||||||
import { ConnectionTransport, PipeTransport, SlowMoTransport, WebSocketTransport } from '../transport';
|
import { ConnectionTransport, PipeTransport, SlowMoTransport, WebSocketTransport } from '../transport';
|
||||||
import { CRBrowser } from './crBrowser';
|
import { CRBrowser } from './crBrowser';
|
||||||
import { BrowserServer } from '../browser';
|
import { BrowserServer } from '../browser';
|
||||||
|
|
||||||
const mkdtempAsync = helper.promisify(fs.mkdtemp);
|
const mkdtempAsync = platform.promisify(fs.mkdtemp);
|
||||||
|
|
||||||
const CHROME_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_dev_profile-');
|
const CHROME_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_dev_profile-');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ 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';
|
||||||
|
|
||||||
export class CRNetworkManager {
|
export class CRNetworkManager {
|
||||||
|
|
@ -188,7 +189,7 @@ export class CRNetworkManager {
|
||||||
const remoteAddress: network.RemoteAddress = { ip: responsePayload.remoteIPAddress, port: responsePayload.remotePort };
|
const remoteAddress: network.RemoteAddress = { ip: responsePayload.remoteIPAddress, port: responsePayload.remotePort };
|
||||||
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 Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||||
};
|
};
|
||||||
return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), remoteAddress, getResponseBody);
|
return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), remoteAddress, getResponseBody);
|
||||||
}
|
}
|
||||||
|
|
@ -261,7 +262,7 @@ class InterceptableRequest implements network.RequestDelegate {
|
||||||
event.request.url, event.type.toLowerCase(), event.request.method, event.request.postData, headersObject(event.request.headers));
|
event.request.url, event.type.toLowerCase(), event.request.method, event.request.postData, headersObject(event.request.headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
async continue(overrides: { headers?: {[key: string]: string}; } = {}) {
|
async continue(overrides: { headers?: network.Headers; } = {}) {
|
||||||
await this._client.send('Fetch.continueRequest', {
|
await this._client.send('Fetch.continueRequest', {
|
||||||
requestId: this._interceptionId,
|
requestId: this._interceptionId,
|
||||||
headers: overrides.headers ? headersArray(overrides.headers) : undefined,
|
headers: overrides.headers ? headersArray(overrides.headers) : undefined,
|
||||||
|
|
@ -272,8 +273,8 @@ class InterceptableRequest implements network.RequestDelegate {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(response: { status: number; headers: {[key: string]: string}; contentType: string; body: (string | Buffer); }) {
|
async fulfill(response: { status: number; headers: network.Headers; contentType: string; body: (string | platform.BufferType); }) {
|
||||||
const responseBody = response.body && helper.isString(response.body) ? Buffer.from(/** @type {string} */(response.body)) : /** @type {?Buffer} */(response.body || null);
|
const responseBody = response.body && helper.isString(response.body) ? platform.Buffer.from(response.body) : (response.body || null);
|
||||||
|
|
||||||
const responseHeaders: { [s: string]: string; } = {};
|
const responseHeaders: { [s: string]: string; } = {};
|
||||||
if (response.headers) {
|
if (response.headers) {
|
||||||
|
|
@ -283,7 +284,7 @@ class InterceptableRequest implements network.RequestDelegate {
|
||||||
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(Buffer.byteLength(responseBody));
|
responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody));
|
||||||
|
|
||||||
await this._client.send('Fetch.fulfillRequest', {
|
await this._client.send('Fetch.fulfillRequest', {
|
||||||
requestId: this._interceptionId,
|
requestId: this._interceptionId,
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import { BrowserContext } from '../browserContext';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { ConsoleMessage } from '../console';
|
import { ConsoleMessage } from '../console';
|
||||||
import * as accessibility from '../accessibility';
|
import * as accessibility from '../accessibility';
|
||||||
|
import * as platform from '../platform';
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
|
|
||||||
|
|
@ -375,11 +376,11 @@ export class CRPage implements PageDelegate {
|
||||||
await this._client.send('Emulation.setDefaultBackgroundColorOverride', { color });
|
await this._client.send('Emulation.setDefaultBackgroundColorOverride', { color });
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeScreenshot(format: 'png' | 'jpeg', options: types.ScreenshotOptions): Promise<Buffer> {
|
async takeScreenshot(format: 'png' | 'jpeg', options: types.ScreenshotOptions): Promise<platform.BufferType> {
|
||||||
await this._client.send('Page.bringToFront', {});
|
await this._client.send('Page.bringToFront', {});
|
||||||
const clip = options.clip ? { ...options.clip, scale: 1 } : undefined;
|
const clip = options.clip ? { ...options.clip, scale: 1 } : undefined;
|
||||||
const result = await this._client.send('Page.captureScreenshot', { format, quality: options.quality, clip });
|
const result = await this._client.send('Page.captureScreenshot', { format, quality: options.quality, clip });
|
||||||
return Buffer.from(result.data, 'base64');
|
return platform.Buffer.from(result.data, 'base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetViewport(): Promise<void> {
|
async resetViewport(): Promise<void> {
|
||||||
|
|
@ -494,7 +495,7 @@ export class ChromiumPage extends Page {
|
||||||
this._networkManager = new CRNetworkManager(client, this);
|
this._networkManager = new CRNetworkManager(client, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async pdf(options?: PDFOptions): Promise<Buffer> {
|
async pdf(options?: PDFOptions): Promise<platform.BufferType> {
|
||||||
return this._pdf.generate(options);
|
return this._pdf.generate(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,11 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import * as fs from 'fs';
|
|
||||||
import {helper, assert, debugError} from '../helper';
|
import { assert, debugError } from '../helper';
|
||||||
import { CRSession } from './crConnection';
|
import { CRSession } from './crConnection';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
import * as platform from '../platform';
|
||||||
const openAsync = helper.promisify(fs.open);
|
|
||||||
const writeAsync = helper.promisify(fs.write);
|
|
||||||
const closeAsync = helper.promisify(fs.close);
|
|
||||||
|
|
||||||
|
|
||||||
export function getExceptionMessage(exceptionDetails: Protocol.Runtime.ExceptionDetails): string {
|
export function getExceptionMessage(exceptionDetails: Protocol.Runtime.ExceptionDetails): string {
|
||||||
if (exceptionDetails.exception)
|
if (exceptionDetails.exception)
|
||||||
|
|
@ -69,26 +65,26 @@ export async function releaseObject(client: CRSession, remoteObject: Protocol.Ru
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readProtocolStream(client: CRSession, handle: string, path: string | null): Promise<Buffer> {
|
export async function readProtocolStream(client: CRSession, handle: string, path: string | null): Promise<platform.BufferType> {
|
||||||
let eof = false;
|
let eof = false;
|
||||||
let file;
|
let fd;
|
||||||
if (path)
|
if (path)
|
||||||
file = await openAsync(path, 'w');
|
fd = await platform.openFdAsync(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 = Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
|
const buf = platform.Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
|
||||||
bufs.push(buf);
|
bufs.push(buf);
|
||||||
if (path)
|
if (path)
|
||||||
await writeAsync(file, buf);
|
await platform.writeFdAsync(fd, buf);
|
||||||
}
|
}
|
||||||
if (path)
|
if (path)
|
||||||
await closeAsync(file);
|
await platform.closeFdAsync(fd);
|
||||||
await client.send('IO.close', {handle});
|
await client.send('IO.close', {handle});
|
||||||
let resultBuffer = null;
|
let resultBuffer = null;
|
||||||
try {
|
try {
|
||||||
resultBuffer = Buffer.concat(bufs);
|
resultBuffer = platform.Buffer.concat(bufs);
|
||||||
} finally {
|
} finally {
|
||||||
return resultBuffer;
|
return resultBuffer;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
import { assert, helper } from '../../helper';
|
import { assert, helper } from '../../helper';
|
||||||
import { CRSession } from '../crConnection';
|
import { CRSession } from '../crConnection';
|
||||||
import { readProtocolStream } from '../crProtocolHelper';
|
import { readProtocolStream } from '../crProtocolHelper';
|
||||||
|
import * as platform from '../../platform';
|
||||||
|
|
||||||
export type PDFOptions = {
|
export type PDFOptions = {
|
||||||
scale?: number,
|
scale?: number,
|
||||||
|
|
@ -91,7 +92,7 @@ export class CRPDF {
|
||||||
this._client = client;
|
this._client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
async generate(options: PDFOptions = {}): Promise<Buffer> {
|
async generate(options: PDFOptions = {}): Promise<platform.BufferType> {
|
||||||
const {
|
const {
|
||||||
scale = 1,
|
scale = 1,
|
||||||
displayHeaderFooter = false,
|
displayHeaderFooter = false,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from '../../platform';
|
||||||
import { CRSession, CRConnection } from '../crConnection';
|
import { CRSession, CRConnection } from '../crConnection';
|
||||||
import { debugError } from '../../helper';
|
import { debugError } from '../../helper';
|
||||||
import { Protocol } from '../protocol';
|
import { Protocol } from '../protocol';
|
||||||
|
|
|
||||||
11
src/dom.ts
11
src/dom.ts
|
|
@ -23,10 +23,7 @@ import * as zsSelectorEngineSource from './generated/zsSelectorEngineSource';
|
||||||
import { assert, helper, debugError } from './helper';
|
import { assert, helper, debugError } from './helper';
|
||||||
import Injected from './injected/injected';
|
import Injected from './injected/injected';
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
import * as path from 'path';
|
import * as platform from './platform';
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
const readFileAsync = helper.promisify(fs.readFile);
|
|
||||||
|
|
||||||
export class FrameExecutionContext extends js.ExecutionContext {
|
export class FrameExecutionContext extends js.ExecutionContext {
|
||||||
readonly frame: frames.Frame;
|
readonly frame: frames.Frame;
|
||||||
|
|
@ -409,9 +406,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
const filePayloads = await Promise.all(files.map(async item => {
|
const filePayloads = await Promise.all(files.map(async item => {
|
||||||
if (typeof item === 'string') {
|
if (typeof item === 'string') {
|
||||||
const file: types.FilePayload = {
|
const file: types.FilePayload = {
|
||||||
name: path.basename(item),
|
name: platform.basename(item),
|
||||||
type: 'application/octet-stream',
|
type: 'application/octet-stream',
|
||||||
data: (await readFileAsync(item)).toString('base64')
|
data: await platform.readFileAsync(item, 'base64')
|
||||||
};
|
};
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
@ -445,7 +442,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<string | Buffer> {
|
async screenshot(options?: types.ElementScreenshotOptions): Promise<string | platform.BufferType> {
|
||||||
return this._page._screenshotter.screenshotElement(this, options);
|
return this._page._screenshotter.screenshotElement(this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {assert} from '../helper';
|
import {assert} from '../helper';
|
||||||
import {EventEmitter} from 'events';
|
import * as platform from '../platform';
|
||||||
import * as debug from 'debug';
|
|
||||||
import { ConnectionTransport } from '../transport';
|
import { ConnectionTransport } from '../transport';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
const debugProtocol = debug('playwright:protocol');
|
|
||||||
|
const debugProtocol = platform.debug('playwright:protocol');
|
||||||
|
|
||||||
export const ConnectionEvents = {
|
export const ConnectionEvents = {
|
||||||
Disconnected: Symbol('Disconnected'),
|
Disconnected: Symbol('Disconnected'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export class FFConnection extends EventEmitter {
|
export class FFConnection extends platform.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;
|
||||||
|
|
@ -131,7 +131,7 @@ export const FFSessionEvents = {
|
||||||
Disconnected: Symbol('Disconnected')
|
Disconnected: Symbol('Disconnected')
|
||||||
};
|
};
|
||||||
|
|
||||||
export class FFSession extends EventEmitter {
|
export class FFSession extends platform.EventEmitter {
|
||||||
_connection: FFConnection;
|
_connection: FFConnection;
|
||||||
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 _targetType: string;
|
private _targetType: string;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ 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';
|
||||||
|
|
||||||
export class FFNetworkManager {
|
export class FFNetworkManager {
|
||||||
private _session: FFSession;
|
private _session: FFSession;
|
||||||
|
|
@ -76,7 +77,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 Buffer.from(response.base64body, 'base64');
|
return platform.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)
|
||||||
|
|
@ -168,7 +169,7 @@ class InterceptableRequest implements network.RequestDelegate {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(response: { status: number; headers: {[key: string]: string}; contentType: string; body: (string | Buffer); }) {
|
async fulfill(response: { status: number; headers: network.Headers; contentType: string; body: (string | platform.BufferType); }) {
|
||||||
throw new Error('Fulfill is not supported in Firefox');
|
throw new Error('Fulfill is not supported in Firefox');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import { getAccessibilityTree } from './ffAccessibility';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
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';
|
||||||
|
|
||||||
export class FFPage implements PageDelegate {
|
export class FFPage implements PageDelegate {
|
||||||
readonly rawMouse: RawMouseImpl;
|
readonly rawMouse: RawMouseImpl;
|
||||||
|
|
@ -272,13 +273,13 @@ export class FFPage implements PageDelegate {
|
||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeScreenshot(format: 'png' | 'jpeg', options: types.ScreenshotOptions): Promise<Buffer> {
|
async takeScreenshot(format: 'png' | 'jpeg', options: types.ScreenshotOptions): Promise<platform.BufferType> {
|
||||||
const { data } = await this._session.send('Page.screenshot', {
|
const { data } = await this._session.send('Page.screenshot', {
|
||||||
mimeType: ('image/' + format) as ('image/png' | 'image/jpeg'),
|
mimeType: ('image/' + format) as ('image/png' | 'image/jpeg'),
|
||||||
fullPage: options.fullPage,
|
fullPage: options.fullPage,
|
||||||
clip: options.clip,
|
clip: options.clip,
|
||||||
});
|
});
|
||||||
return Buffer.from(data, 'base64');
|
return platform.Buffer.from(data, 'base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetViewport(): Promise<void> {
|
async resetViewport(): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as js from './javascript';
|
import * as js from './javascript';
|
||||||
import * as dom from './dom';
|
import * as dom from './dom';
|
||||||
import * as network from './network';
|
import * as network from './network';
|
||||||
|
|
@ -26,8 +25,7 @@ import { TimeoutError } from './errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
import { ConsoleMessage } from './console';
|
import { ConsoleMessage } from './console';
|
||||||
|
import * as platform from './platform';
|
||||||
const readFileAsync = helper.promisify(fs.readFile);
|
|
||||||
|
|
||||||
type ContextType = 'main' | 'utility';
|
type ContextType = 'main' | 'utility';
|
||||||
type ContextData = {
|
type ContextData = {
|
||||||
|
|
@ -507,7 +505,7 @@ export class Frame {
|
||||||
if (url !== null)
|
if (url !== null)
|
||||||
return (await context.evaluateHandle(addScriptUrl, url, type)).asElement();
|
return (await context.evaluateHandle(addScriptUrl, url, type)).asElement();
|
||||||
if (path !== null) {
|
if (path !== null) {
|
||||||
let contents = await readFileAsync(path, 'utf8');
|
let contents = await platform.readFileAsync(path, 'utf8');
|
||||||
contents += '//# sourceURL=' + path.replace(/\n/g, '');
|
contents += '//# sourceURL=' + path.replace(/\n/g, '');
|
||||||
return (await context.evaluateHandle(addScriptContent, contents, type)).asElement();
|
return (await context.evaluateHandle(addScriptContent, contents, type)).asElement();
|
||||||
}
|
}
|
||||||
|
|
@ -557,7 +555,7 @@ export class Frame {
|
||||||
return (await context.evaluateHandle(addStyleUrl, url)).asElement();
|
return (await context.evaluateHandle(addStyleUrl, url)).asElement();
|
||||||
|
|
||||||
if (path !== null) {
|
if (path !== null) {
|
||||||
let contents = await readFileAsync(path, 'utf8');
|
let contents = await platform.readFileAsync(path, 'utf8');
|
||||||
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
|
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
|
||||||
return (await context.evaluateHandle(addStyleContent, contents)).asElement();
|
return (await context.evaluateHandle(addStyleContent, contents)).asElement();
|
||||||
}
|
}
|
||||||
|
|
@ -909,7 +907,7 @@ class LifecycleWatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
_urlMatches(urlString: string): boolean {
|
_urlMatches(urlString: string): boolean {
|
||||||
return !this._urlMatch || helper.urlMatches(urlString, this._urlMatch);
|
return !this._urlMatch || platform.urlMatches(urlString, this._urlMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
setExpectedDocumentId(documentId: string, url: string) {
|
setExpectedDocumentId(documentId: string, url: string) {
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as debug from 'debug';
|
|
||||||
import * as types from './types';
|
|
||||||
import * as kurl from 'url';
|
|
||||||
import { TimeoutError } from './errors';
|
import { TimeoutError } from './errors';
|
||||||
|
import * as platform from './platform';
|
||||||
|
|
||||||
export const debugError = debug(`playwright:error`);
|
export const debugError = platform.debug(`playwright:error`);
|
||||||
|
|
||||||
export type RegisteredListener = {
|
export type RegisteredListener = {
|
||||||
emitter: NodeJS.EventEmitter;
|
emitter: platform.EventEmitterType;
|
||||||
eventName: (string | symbol);
|
eventName: (string | symbol);
|
||||||
handler: (...args: any[]) => void;
|
handler: (...args: any[]) => void;
|
||||||
};
|
};
|
||||||
|
|
@ -63,7 +61,7 @@ class Helper {
|
||||||
}
|
}
|
||||||
|
|
||||||
static addEventListener(
|
static addEventListener(
|
||||||
emitter: NodeJS.EventEmitter,
|
emitter: platform.EventEmitterType,
|
||||||
eventName: (string | symbol),
|
eventName: (string | symbol),
|
||||||
handler: (...args: any[]) => void): RegisteredListener {
|
handler: (...args: any[]) => void): RegisteredListener {
|
||||||
emitter.on(eventName, handler);
|
emitter.on(eventName, handler);
|
||||||
|
|
@ -71,7 +69,7 @@ class Helper {
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeEventListeners(listeners: Array<{
|
static removeEventListeners(listeners: Array<{
|
||||||
emitter: NodeJS.EventEmitter;
|
emitter: platform.EventEmitterType;
|
||||||
eventName: (string | symbol);
|
eventName: (string | symbol);
|
||||||
handler: (...args: any[]) => void;
|
handler: (...args: any[]) => void;
|
||||||
}>) {
|
}>) {
|
||||||
|
|
@ -88,24 +86,8 @@ class Helper {
|
||||||
return typeof obj === 'number' || obj instanceof Number;
|
return typeof obj === 'number' || obj instanceof Number;
|
||||||
}
|
}
|
||||||
|
|
||||||
static promisify(nodeFunction: Function): Function {
|
|
||||||
function promisified(...args) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
function callback(err, ...result) {
|
|
||||||
if (err)
|
|
||||||
return reject(err);
|
|
||||||
if (result.length === 1)
|
|
||||||
return resolve(result[0]);
|
|
||||||
return resolve(result);
|
|
||||||
}
|
|
||||||
nodeFunction.call(null, ...args, callback);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return promisified;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async waitForEvent(
|
static async waitForEvent(
|
||||||
emitter: NodeJS.EventEmitter,
|
emitter: platform.EventEmitterType,
|
||||||
eventName: (string | symbol),
|
eventName: (string | symbol),
|
||||||
predicate: Function,
|
predicate: Function,
|
||||||
timeout: number,
|
timeout: number,
|
||||||
|
|
@ -159,22 +141,6 @@ class Helper {
|
||||||
clearTimeout(timeoutTimer);
|
clearTimeout(timeoutTimer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static urlMatches(urlString: string, match: types.URLMatch | undefined): boolean {
|
|
||||||
if (match === undefined)
|
|
||||||
return true;
|
|
||||||
if (typeof match === 'string')
|
|
||||||
return match === urlString;
|
|
||||||
if (match instanceof RegExp)
|
|
||||||
return match.test(urlString);
|
|
||||||
assert(typeof match === 'function', 'url parameter should be string, RegExp or function');
|
|
||||||
|
|
||||||
try {
|
|
||||||
return match(new kurl.URL(urlString));
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assert(value: any, message?: string) {
|
export function assert(value: any, message?: string) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
import * as frames from './frames';
|
import * as frames from './frames';
|
||||||
import { assert } from './helper';
|
import { assert } from './helper';
|
||||||
|
import * as platform from './platform';
|
||||||
|
|
||||||
export type NetworkCookie = {
|
export type NetworkCookie = {
|
||||||
name: string,
|
name: string,
|
||||||
|
|
@ -202,7 +203,7 @@ export class Request {
|
||||||
await this._delegate.abort(errorCode);
|
await this._delegate.abort(errorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(response: { status: number; headers: {[key: string]: string}; contentType: string; body: (string | Buffer); }) { // Mocking responses for dataURL requests is not currently supported.
|
async fulfill(response: { status: number; headers: Headers; contentType: string; body: (string | platform.BufferType); }) { // Mocking responses for dataURL requests is not currently supported.
|
||||||
if (this.url().startsWith('data:'))
|
if (this.url().startsWith('data:'))
|
||||||
return;
|
return;
|
||||||
assert(this._delegate, 'Request Interception is not enabled!');
|
assert(this._delegate, 'Request Interception is not enabled!');
|
||||||
|
|
@ -226,11 +227,11 @@ export type RemoteAddress = {
|
||||||
port: number,
|
port: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
type GetResponseBodyCallback = () => Promise<Buffer>;
|
type GetResponseBodyCallback = () => Promise<platform.BufferType>;
|
||||||
|
|
||||||
export class Response {
|
export class Response {
|
||||||
private _request: Request;
|
private _request: Request;
|
||||||
private _contentPromise: Promise<Buffer> | null = null;
|
private _contentPromise: Promise<platform.BufferType> | null = null;
|
||||||
_finishedPromise: Promise<Error | null>;
|
_finishedPromise: Promise<Error | null>;
|
||||||
private _finishedPromiseCallback: any;
|
private _finishedPromiseCallback: any;
|
||||||
private _remoteAddress: RemoteAddress;
|
private _remoteAddress: RemoteAddress;
|
||||||
|
|
@ -282,7 +283,7 @@ export class Response {
|
||||||
return this._headers;
|
return this._headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer(): Promise<Buffer> {
|
buffer(): Promise<platform.BufferType> {
|
||||||
if (!this._contentPromise) {
|
if (!this._contentPromise) {
|
||||||
this._contentPromise = this._finishedPromise.then(async error => {
|
this._contentPromise = this._finishedPromise.then(async error => {
|
||||||
if (error)
|
if (error)
|
||||||
|
|
@ -314,8 +315,8 @@ export class Response {
|
||||||
|
|
||||||
export interface RequestDelegate {
|
export interface RequestDelegate {
|
||||||
abort(errorCode: string): Promise<void>;
|
abort(errorCode: string): Promise<void>;
|
||||||
fulfill(response: { status: number; headers: {[key: string]: string}; contentType: string; body: (string | Buffer); }): Promise<void>;
|
fulfill(response: { status: number; headers: Headers; contentType: string; body: (string | platform.BufferType); }): Promise<void>;
|
||||||
continue(overrides: { url?: string; method?: string; postData?: string; headers?: { [key: string]: string; }; }): Promise<void>;
|
continue(overrides: { url?: string; method?: string; postData?: string; headers?: Headers; }): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes.
|
// List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes.
|
||||||
|
|
|
||||||
12
src/page.ts
12
src/page.ts
|
|
@ -15,7 +15,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
|
||||||
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 } from './helper';
|
||||||
|
|
@ -30,6 +29,7 @@ import { BrowserContext } from './browserContext';
|
||||||
import { ConsoleMessage, ConsoleMessageLocation } from './console';
|
import { ConsoleMessage, ConsoleMessageLocation } from './console';
|
||||||
import Injected from './injected/injected';
|
import Injected from './injected/injected';
|
||||||
import * as accessibility from './accessibility';
|
import * as accessibility from './accessibility';
|
||||||
|
import * as platform from './platform';
|
||||||
|
|
||||||
export interface PageDelegate {
|
export interface PageDelegate {
|
||||||
readonly rawMouse: input.RawMouse;
|
readonly rawMouse: input.RawMouse;
|
||||||
|
|
@ -56,7 +56,7 @@ export interface PageDelegate {
|
||||||
getBoundingBoxForScreenshot(handle: dom.ElementHandle<Node>): Promise<types.Rect | null>;
|
getBoundingBoxForScreenshot(handle: dom.ElementHandle<Node>): Promise<types.Rect | null>;
|
||||||
canScreenshotOutsideViewport(): boolean;
|
canScreenshotOutsideViewport(): boolean;
|
||||||
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, options: types.ScreenshotOptions, viewport: types.Viewport): Promise<Buffer>;
|
takeScreenshot(format: string, options: types.ScreenshotOptions, viewport: types.Viewport): Promise<platform.BufferType>;
|
||||||
resetViewport(oldSize: types.Size): Promise<void>;
|
resetViewport(oldSize: types.Size): Promise<void>;
|
||||||
|
|
||||||
isElementHandle(remoteObject: any): boolean;
|
isElementHandle(remoteObject: any): boolean;
|
||||||
|
|
@ -87,7 +87,7 @@ export type FileChooser = {
|
||||||
multiple: boolean
|
multiple: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Page extends EventEmitter {
|
export class Page extends platform.EventEmitter {
|
||||||
private _closed = false;
|
private _closed = false;
|
||||||
private _closedCallback: () => void;
|
private _closedCallback: () => void;
|
||||||
private _closedPromise: Promise<void>;
|
private _closedPromise: Promise<void>;
|
||||||
|
|
@ -336,7 +336,7 @@ export class Page extends EventEmitter {
|
||||||
const { timeout = this._timeoutSettings.timeout() } = options;
|
const { timeout = this._timeoutSettings.timeout() } = options;
|
||||||
return helper.waitForEvent(this, Events.Page.Request, (request: network.Request) => {
|
return helper.waitForEvent(this, Events.Page.Request, (request: network.Request) => {
|
||||||
if (helper.isString(urlOrPredicate) || urlOrPredicate instanceof RegExp)
|
if (helper.isString(urlOrPredicate) || urlOrPredicate instanceof RegExp)
|
||||||
return helper.urlMatches(request.url(), urlOrPredicate);
|
return platform.urlMatches(request.url(), urlOrPredicate);
|
||||||
return urlOrPredicate(request);
|
return urlOrPredicate(request);
|
||||||
}, timeout, this._disconnectedPromise);
|
}, timeout, this._disconnectedPromise);
|
||||||
}
|
}
|
||||||
|
|
@ -345,7 +345,7 @@ export class Page extends EventEmitter {
|
||||||
const { timeout = this._timeoutSettings.timeout() } = options;
|
const { timeout = this._timeoutSettings.timeout() } = options;
|
||||||
return helper.waitForEvent(this, Events.Page.Response, (response: network.Response) => {
|
return helper.waitForEvent(this, Events.Page.Response, (response: network.Response) => {
|
||||||
if (helper.isString(urlOrPredicate) || urlOrPredicate instanceof RegExp)
|
if (helper.isString(urlOrPredicate) || urlOrPredicate instanceof RegExp)
|
||||||
return helper.urlMatches(response.url(), urlOrPredicate);
|
return platform.urlMatches(response.url(), urlOrPredicate);
|
||||||
return urlOrPredicate(response);
|
return urlOrPredicate(response);
|
||||||
}, timeout, this._disconnectedPromise);
|
}, timeout, this._disconnectedPromise);
|
||||||
}
|
}
|
||||||
|
|
@ -430,7 +430,7 @@ export class Page extends EventEmitter {
|
||||||
await this._delegate.authenticate(credentials);
|
await this._delegate.authenticate(credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
async screenshot(options?: types.ScreenshotOptions): Promise<Buffer> {
|
async screenshot(options?: types.ScreenshotOptions): Promise<platform.BufferType> {
|
||||||
return this._screenshotter.screenshotPage(options);
|
return this._screenshotter.screenshotPage(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
221
src/platform.ts
Normal file
221
src/platform.ts
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
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 mime from 'mime';
|
||||||
|
import * as jpeg from 'jpeg-js';
|
||||||
|
import * as png from 'pngjs';
|
||||||
|
|
||||||
|
import { assert, helper } from './helper';
|
||||||
|
import * as types from './types';
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
function callback(err, ...result) {
|
||||||
|
if (err)
|
||||||
|
return reject(err);
|
||||||
|
if (result.length === 1)
|
||||||
|
return resolve(result[0]);
|
||||||
|
return resolve(result);
|
||||||
|
}
|
||||||
|
nodeFunction.call(null, ...args, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return promisified;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>>();
|
||||||
|
|
||||||
|
addListener(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
on(event: string | symbol, listener: Listener): this {
|
||||||
|
return this.addListener(event, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
once(event: string | symbol, listener: Listener): this {
|
||||||
|
const wrapped = (...args: any[]) => {
|
||||||
|
this.removeListener(event, wrapped);
|
||||||
|
listener(...args);
|
||||||
|
};
|
||||||
|
return this.on(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;
|
||||||
|
|
||||||
|
type DebugType = typeof nodeDebug;
|
||||||
|
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 = NodeBuffer;
|
||||||
|
|
||||||
|
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 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 {
|
||||||
|
assertFileAccess();
|
||||||
|
return mime.getType(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function urlMatches(urlString: string, match: types.URLMatch | undefined): boolean {
|
||||||
|
if (match === undefined)
|
||||||
|
return true;
|
||||||
|
if (typeof match === 'string')
|
||||||
|
return match === urlString;
|
||||||
|
if (match instanceof RegExp)
|
||||||
|
return match.test(urlString);
|
||||||
|
assert(typeof match === 'function', 'url parameter should be string, RegExp or function');
|
||||||
|
|
||||||
|
try {
|
||||||
|
return match(new URL(urlString));
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pngToJpeg(buffer: Buffer): Buffer {
|
||||||
|
assert(isNode, 'Converting from png to jpeg is only supported in Node.js');
|
||||||
|
return jpeg.encode(png.PNG.sync.read(buffer)).data;
|
||||||
|
}
|
||||||
|
|
@ -21,8 +21,9 @@ import * as removeFolder from 'rimraf';
|
||||||
import { helper } from './helper';
|
import { helper } from './helper';
|
||||||
import * as readline from 'readline';
|
import * as readline from 'readline';
|
||||||
import { TimeoutError } from './errors';
|
import { TimeoutError } from './errors';
|
||||||
|
import * as platform from './platform';
|
||||||
|
|
||||||
const removeFolderAsync = helper.promisify(removeFolder);
|
const removeFolderAsync = platform.promisify(removeFolder);
|
||||||
|
|
||||||
export type LaunchProcessOptions = {
|
export type LaunchProcessOptions = {
|
||||||
executablePath: string,
|
executablePath: string,
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as mime from 'mime';
|
|
||||||
import * as dom from './dom';
|
import * as dom from './dom';
|
||||||
import { assert, helper } from './helper';
|
import { assert } from './helper';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
|
import * as platform from './platform';
|
||||||
const writeFileAsync = helper.promisify(fs.writeFile);
|
|
||||||
|
|
||||||
export class Screenshotter {
|
export class Screenshotter {
|
||||||
private _queue = new TaskQueue();
|
private _queue = new TaskQueue();
|
||||||
|
|
@ -39,7 +36,7 @@ export class Screenshotter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async screenshotPage(options: types.ScreenshotOptions = {}): Promise<Buffer> {
|
async screenshotPage(options: types.ScreenshotOptions = {}): Promise<platform.BufferType> {
|
||||||
const format = validateScreeshotOptions(options);
|
const format = validateScreeshotOptions(options);
|
||||||
return this._queue.postTask(async () => {
|
return this._queue.postTask(async () => {
|
||||||
let overridenViewport: types.Viewport | undefined;
|
let overridenViewport: types.Viewport | undefined;
|
||||||
|
|
@ -82,7 +79,7 @@ export class Screenshotter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async screenshotElement(handle: dom.ElementHandle, options: types.ElementScreenshotOptions = {}): Promise<Buffer> {
|
async screenshotElement(handle: dom.ElementHandle, options: types.ElementScreenshotOptions = {}): Promise<platform.BufferType> {
|
||||||
const format = validateScreeshotOptions(options);
|
const format = validateScreeshotOptions(options);
|
||||||
const rewrittenOptions: types.ScreenshotOptions = { ...options };
|
const rewrittenOptions: types.ScreenshotOptions = { ...options };
|
||||||
return this._queue.postTask(async () => {
|
return this._queue.postTask(async () => {
|
||||||
|
|
@ -121,7 +118,7 @@ export class Screenshotter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _screenshot(format: 'png' | 'jpeg', options: types.ScreenshotOptions, viewport: types.Viewport): Promise<Buffer> {
|
private async _screenshot(format: 'png' | 'jpeg', options: types.ScreenshotOptions, viewport: types.Viewport): Promise<platform.BufferType> {
|
||||||
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});
|
||||||
|
|
@ -129,7 +126,7 @@ export class Screenshotter {
|
||||||
if (shouldSetDefaultBackground)
|
if (shouldSetDefaultBackground)
|
||||||
await this._page._delegate.setBackgroundColor();
|
await this._page._delegate.setBackgroundColor();
|
||||||
if (options.path)
|
if (options.path)
|
||||||
await writeFileAsync(options.path, buffer);
|
await platform.writeFileAsync(options.path, buffer);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +165,7 @@ function validateScreeshotOptions(options: types.ScreenshotOptions): 'png' | 'jp
|
||||||
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 = mime.getType(options.path);
|
const mimeType = platform.getMimeType(options.path);
|
||||||
if (mimeType === 'image/png')
|
if (mimeType === 'image/png')
|
||||||
format = 'png';
|
format = 'png';
|
||||||
else if (mimeType === 'image/jpeg')
|
else if (mimeType === 'image/jpeg')
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
import * as js from './javascript';
|
import * as js from './javascript';
|
||||||
import * as dom from './dom';
|
import * as dom from './dom';
|
||||||
import * as kurl from 'url';
|
|
||||||
|
|
||||||
type Boxed<Args extends any[]> = { [Index in keyof Args]: Args[Index] | js.JSHandle<Args[Index]> };
|
type Boxed<Args extends any[]> = { [Index in keyof Args]: Args[Index] | js.JSHandle<Args[Index]> };
|
||||||
type PageFunction<Args extends any[], R = any> = string | ((...args: Args) => R | Promise<R>);
|
type PageFunction<Args extends any[], R = any> = string | ((...args: Args) => R | Promise<R>);
|
||||||
|
|
@ -63,7 +62,7 @@ export type Viewport = {
|
||||||
hasTouch?: boolean;
|
hasTouch?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type URLMatch = string | RegExp | ((url: kurl.URL) => boolean);
|
export type URLMatch = string | RegExp | ((url: URL) => boolean);
|
||||||
|
|
||||||
export type Credentials = {
|
export type Credentials = {
|
||||||
username: string;
|
username: string;
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from '../helper';
|
import { assert } from '../helper';
|
||||||
import * as debug from 'debug';
|
import * as platform from '../platform';
|
||||||
import { EventEmitter } from 'events';
|
|
||||||
import { ConnectionTransport } from '../transport';
|
import { ConnectionTransport } from '../transport';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
|
||||||
const debugProtocol = debug('playwright:protocol');
|
const debugProtocol = platform.debug('playwright:protocol');
|
||||||
const debugWrappedMessage = require('debug')('wrapped');
|
const debugWrappedMessage = platform.debug('wrapped');
|
||||||
|
|
||||||
export const WKConnectionEvents = {
|
export const WKConnectionEvents = {
|
||||||
PageProxyCreated: Symbol('ConnectionEvents.PageProxyCreated'),
|
PageProxyCreated: Symbol('ConnectionEvents.PageProxyCreated'),
|
||||||
|
|
@ -35,7 +34,7 @@ export const WKPageProxySessionEvents = {
|
||||||
DidCommitProvisionalTarget: Symbol('PageProxyEvents.DidCommitProvisionalTarget'),
|
DidCommitProvisionalTarget: Symbol('PageProxyEvents.DidCommitProvisionalTarget'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export class WKConnection extends EventEmitter {
|
export class WKConnection extends platform.EventEmitter {
|
||||||
private _lastId = 0;
|
private _lastId = 0;
|
||||||
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 _transport: ConnectionTransport;
|
private readonly _transport: ConnectionTransport;
|
||||||
|
|
@ -137,7 +136,7 @@ export const WKTargetSessionEvents = {
|
||||||
Disconnected: Symbol('TargetSessionEvents.Disconnected')
|
Disconnected: Symbol('TargetSessionEvents.Disconnected')
|
||||||
};
|
};
|
||||||
|
|
||||||
export class WKPageProxySession extends EventEmitter {
|
export class WKPageProxySession extends platform.EventEmitter {
|
||||||
_connection: WKConnection;
|
_connection: WKConnection;
|
||||||
private readonly _sessions = new Map<string, WKTargetSession>();
|
private readonly _sessions = new Map<string, WKTargetSession>();
|
||||||
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}>();
|
||||||
|
|
@ -215,7 +214,7 @@ export class WKPageProxySession extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WKTargetSession extends EventEmitter {
|
export class WKTargetSession extends platform.EventEmitter {
|
||||||
_pageProxySession: WKPageProxySession;
|
_pageProxySession: WKPageProxySession;
|
||||||
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;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ 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 types from '../types';
|
import * as types from '../types';
|
||||||
|
import * as platform from '../platform';
|
||||||
|
|
||||||
export class WKNetworkManager {
|
export class WKNetworkManager {
|
||||||
private _session: WKTargetSession;
|
private _session: WKTargetSession;
|
||||||
|
|
@ -106,7 +107,7 @@ export class WKNetworkManager {
|
||||||
const remoteAddress: network.RemoteAddress = { ip: '', port: 0 };
|
const remoteAddress: network.RemoteAddress = { ip: '', port: 0 };
|
||||||
const getResponseBody = async () => {
|
const getResponseBody = async () => {
|
||||||
const response = await this._session.send('Network.getResponseBody', { requestId: request._requestId });
|
const response = await this._session.send('Network.getResponseBody', { requestId: request._requestId });
|
||||||
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||||
};
|
};
|
||||||
return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), remoteAddress, getResponseBody);
|
return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), remoteAddress, getResponseBody);
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +209,7 @@ class InterceptableRequest implements network.RequestDelegate {
|
||||||
await this._session.send('Network.interceptAsError', { requestId: this._requestId, reason });
|
await this._session.send('Network.interceptAsError', { requestId: this._requestId, reason });
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(response: { status: number; headers: {[key: string]: string}; contentType: string; body: (string | Buffer); }) {
|
async fulfill(response: { status: number; headers: network.Headers; contentType: string; body: (string | platform.BufferType); }) {
|
||||||
await this._interceptedPromise;
|
await this._interceptedPromise;
|
||||||
|
|
||||||
const base64Encoded = !!response.body && !helper.isString(response.body);
|
const base64Encoded = !!response.body && !helper.isString(response.body);
|
||||||
|
|
@ -222,7 +223,7 @@ class InterceptableRequest implements network.RequestDelegate {
|
||||||
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(Buffer.byteLength(responseBody));
|
responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody));
|
||||||
|
|
||||||
await this._session.send('Network.interceptWithResponse', {
|
await this._session.send('Network.interceptWithResponse', {
|
||||||
requestId: this._requestId,
|
requestId: this._requestId,
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,8 @@ import { WKBrowser } from './wkBrowser';
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
import { RawMouseImpl, RawKeyboardImpl } from './wkInput';
|
import { RawMouseImpl, RawKeyboardImpl } from './wkInput';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import * as jpeg from 'jpeg-js';
|
|
||||||
import { PNG } from 'pngjs';
|
|
||||||
import * as accessibility from '../accessibility';
|
import * as accessibility from '../accessibility';
|
||||||
|
import * as platform from '../platform';
|
||||||
import { getAccessibilityTree } from './wkAccessibility';
|
import { getAccessibilityTree } from './wkAccessibility';
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
|
|
@ -386,13 +385,13 @@ export class WKPage implements PageDelegate {
|
||||||
this._session.send('Page.setDefaultBackgroundColorOverride', { color });
|
this._session.send('Page.setDefaultBackgroundColorOverride', { color });
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeScreenshot(format: string, options: types.ScreenshotOptions, viewport: types.Viewport): Promise<Buffer> {
|
async takeScreenshot(format: string, options: types.ScreenshotOptions, viewport: types.Viewport): Promise<platform.BufferType> {
|
||||||
const rect = options.clip || { x: 0, y: 0, width: viewport.width, height: viewport.height };
|
const rect = options.clip || { x: 0, y: 0, width: viewport.width, height: viewport.height };
|
||||||
const result = await this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: options.fullPage ? 'Page' : 'Viewport' });
|
const result = await this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: options.fullPage ? 'Page' : 'Viewport' });
|
||||||
const prefix = 'data:image/png;base64,';
|
const prefix = 'data:image/png;base64,';
|
||||||
let buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64');
|
let buffer = platform.Buffer.from(result.dataURL.substr(prefix.length), 'base64');
|
||||||
if (format === 'jpeg')
|
if (format === 'jpeg')
|
||||||
buffer = jpeg.encode(PNG.sync.read(buffer)).data;
|
buffer = platform.pngToJpeg(buffer);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const {helper} = require('../../lib/helper');
|
const utils = require('../utils');
|
||||||
const rmAsync = helper.promisify(require('rimraf'));
|
const rmAsync = utils.promisify(require('rimraf'));
|
||||||
const mkdtempAsync = helper.promisify(fs.mkdtemp);
|
const mkdtempAsync = utils.promisify(fs.mkdtemp);
|
||||||
const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-');
|
const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-');
|
||||||
|
|
||||||
module.exports.describe = function ({ testRunner, expect, defaultBrowserOptions, playwright }) {
|
module.exports.describe = function ({ testRunner, expect, defaultBrowserOptions, playwright }) {
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const {helper} = require('../lib/helper');
|
|
||||||
const rmAsync = helper.promisify(require('rimraf'));
|
|
||||||
const mkdtempAsync = helper.promisify(fs.mkdtemp);
|
|
||||||
const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-');
|
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
|
|
||||||
module.exports.describe = function({testRunner, expect, defaultBrowserOptions, playwright, WEBKIT}) {
|
module.exports.describe = function({testRunner, expect, defaultBrowserOptions, playwright, WEBKIT}) {
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,22 @@ function traceAPICoverage(apiCoverage, events, className, classType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const utils = module.exports = {
|
const utils = module.exports = {
|
||||||
|
promisify: function (nodeFunction) {
|
||||||
|
function promisified(...args) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
function callback(err, ...result) {
|
||||||
|
if (err)
|
||||||
|
return reject(err);
|
||||||
|
if (result.length === 1)
|
||||||
|
return resolve(result[0]);
|
||||||
|
return resolve(result);
|
||||||
|
}
|
||||||
|
nodeFunction.call(null, ...args, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return promisified;
|
||||||
|
},
|
||||||
|
|
||||||
recordAPICoverage: function(testRunner, api, events) {
|
recordAPICoverage: function(testRunner, api, events) {
|
||||||
const coverage = new Map();
|
const coverage = new Map();
|
||||||
for (const [className, classType] of Object.entries(api))
|
for (const [className, classType] of Object.entries(api))
|
||||||
|
|
@ -96,7 +112,7 @@ const utils = module.exports = {
|
||||||
for (const frame of page.frames()) {
|
for (const frame of page.frames()) {
|
||||||
if (!frames.has(frame))
|
if (!frames.has(frame))
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,6 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
const fs = require('fs');
|
|
||||||
const os = require('os');
|
|
||||||
const path = require('path');
|
|
||||||
const {helper} = require('../../lib/helper');
|
|
||||||
const rmAsync = helper.promisify(require('rimraf'));
|
|
||||||
const mkdtempAsync = helper.promisify(fs.mkdtemp);
|
|
||||||
const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-');
|
|
||||||
const utils = require('../utils');
|
|
||||||
|
|
||||||
module.exports.describe = function ({ testRunner, expect, playwright }) {
|
module.exports.describe = function ({ testRunner, expect, playwright }) {
|
||||||
const {describe, xdescribe, fdescribe} = testRunner;
|
const {describe, xdescribe, fdescribe} = testRunner;
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,12 @@ function checkSources(sources) {
|
||||||
});
|
});
|
||||||
const checker = program.getTypeChecker();
|
const checker = program.getTypeChecker();
|
||||||
const sourceFiles = program.getSourceFiles();
|
const sourceFiles = program.getSourceFiles();
|
||||||
|
const errors = [];
|
||||||
/** @type {!Array<!Documentation.Class>} */
|
/** @type {!Array<!Documentation.Class>} */
|
||||||
const classes = [];
|
const classes = [];
|
||||||
/** @type {!Map<string, string>} */
|
/** @type {!Map<string, string>} */
|
||||||
const inheritance = new Map();
|
const inheritance = new Map();
|
||||||
sourceFiles.filter(x => !x.fileName.includes('node_modules')).map(x => visit(x));
|
sourceFiles.filter(x => !x.fileName.includes('node_modules')).map(x => visit(x));
|
||||||
const errors = [];
|
|
||||||
const documentation = new Documentation(recreateClassesWithInheritance(classes, inheritance));
|
const documentation = new Documentation(recreateClassesWithInheritance(classes, inheritance));
|
||||||
|
|
||||||
return {errors, documentation};
|
return {errors, documentation};
|
||||||
|
|
@ -98,6 +98,30 @@ function checkSources(sources) {
|
||||||
excludeClasses.add(className);
|
excludeClasses.add(className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!node.getSourceFile().fileName.endsWith('platform.ts')) {
|
||||||
|
// Only relative imports.
|
||||||
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
||||||
|
const module = node.moduleSpecifier.text;
|
||||||
|
if (!module.startsWith('.')) {
|
||||||
|
const lac = ts.getLineAndCharacterOfPosition(node.getSourceFile(), node.moduleSpecifier.pos);
|
||||||
|
errors.push(`Disallowed import "${module}" at ${node.getSourceFile().fileName}:${lac.line + 1}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No references to external types.
|
||||||
|
if (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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue