feat: introduce BrowserServer (#308)
This commit is contained in:
parent
14da8f531d
commit
331f0e603a
43
src/browser.ts
Normal file
43
src/browser.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { BrowserContext, BrowserContextOptions } from './browserContext';
|
||||
import { ChildProcess } from 'child_process';
|
||||
|
||||
export interface Browser {
|
||||
newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
|
||||
disconnect(): void;
|
||||
isConnected(): boolean;
|
||||
|
||||
browserContexts(): BrowserContext[];
|
||||
defaultContext(): BrowserContext;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
export class BrowserServer<T extends Browser> {
|
||||
private _browser: T;
|
||||
private _process: ChildProcess;
|
||||
private _wsEndpoint: string;
|
||||
|
||||
constructor(browser: T, process: ChildProcess, wsEndpoint: string) {
|
||||
this._browser = browser;
|
||||
this._process = process;
|
||||
this._wsEndpoint = wsEndpoint;
|
||||
}
|
||||
|
||||
async connect(): Promise<T> {
|
||||
return this._browser;
|
||||
}
|
||||
|
||||
process(): ChildProcess {
|
||||
return this._process;
|
||||
}
|
||||
|
||||
wsEndpoint(): string {
|
||||
return this._wsEndpoint;
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
await this._browser.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as childProcess from 'child_process';
|
||||
import { EventEmitter } from 'events';
|
||||
import { Events } from './events';
|
||||
import { assert, helper } from '../helper';
|
||||
|
|
@ -24,44 +23,41 @@ import { Connection, ConnectionEvents, CDPSession } from './Connection';
|
|||
import { Page } from '../page';
|
||||
import { Target } from './Target';
|
||||
import { Protocol } from './protocol';
|
||||
import { Chromium } from './features/chromium';
|
||||
import { FrameManager } from './FrameManager';
|
||||
import * as browser from '../browser';
|
||||
import * as network from '../network';
|
||||
import { Permissions } from './features/permissions';
|
||||
import { Overrides } from './features/overrides';
|
||||
import { Worker } from './features/workers';
|
||||
import { ConnectionTransport } from '../transport';
|
||||
import { readProtocolStream } from './protocolHelper';
|
||||
|
||||
export class Browser extends EventEmitter {
|
||||
private _process: childProcess.ChildProcess;
|
||||
export class Browser extends EventEmitter implements browser.Browser {
|
||||
_connection: Connection;
|
||||
_client: CDPSession;
|
||||
private _defaultContext: BrowserContext;
|
||||
private _contexts = new Map<string, BrowserContext>();
|
||||
_targets = new Map<string, Target>();
|
||||
readonly chromium: Chromium;
|
||||
|
||||
private _tracingRecording = false;
|
||||
private _tracingPath = '';
|
||||
private _tracingClient: CDPSession | undefined;
|
||||
|
||||
static async create(
|
||||
browserWSEndpoint: string,
|
||||
transport: ConnectionTransport,
|
||||
process: childProcess.ChildProcess | null) {
|
||||
transport: ConnectionTransport) {
|
||||
const connection = new Connection(transport);
|
||||
|
||||
const { browserContextIds } = await connection.rootSession.send('Target.getBrowserContexts');
|
||||
const browser = new Browser(browserWSEndpoint, connection, browserContextIds, process);
|
||||
const browser = new Browser(connection, browserContextIds);
|
||||
await connection.rootSession.send('Target.setDiscoverTargets', { discover: true });
|
||||
await browser.waitForTarget(t => t.type() === 'page');
|
||||
return browser;
|
||||
}
|
||||
|
||||
constructor(
|
||||
browserWSEndpoint: string,
|
||||
connection: Connection,
|
||||
contextIds: string[],
|
||||
process: childProcess.ChildProcess | null) {
|
||||
constructor(connection: Connection, contextIds: string[]) {
|
||||
super();
|
||||
this._connection = connection;
|
||||
this._client = connection.rootSession;
|
||||
this._process = process;
|
||||
this.chromium = new Chromium(this, browserWSEndpoint);
|
||||
|
||||
this._defaultContext = this._createBrowserContext(null, {});
|
||||
for (const contextId of contextIds)
|
||||
|
|
@ -139,10 +135,6 @@ export class Browser extends EventEmitter {
|
|||
return context;
|
||||
}
|
||||
|
||||
process(): childProcess.ChildProcess | null {
|
||||
return this._process;
|
||||
}
|
||||
|
||||
async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
|
||||
const { browserContextId } = await this._client.send('Target.createBrowserContext');
|
||||
const context = this._createBrowserContext(browserContextId, options);
|
||||
|
|
@ -167,8 +159,8 @@ export class Browser extends EventEmitter {
|
|||
assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
|
||||
this._targets.set(event.targetInfo.targetId, target);
|
||||
|
||||
if (await target._initializedPromise)
|
||||
this.chromium.emit(Events.Chromium.TargetCreated, target);
|
||||
if (target._isInitialized || await target._initializedPromise)
|
||||
this.emit(Events.Browser.TargetCreated, target);
|
||||
}
|
||||
|
||||
async _targetDestroyed(event: { targetId: string; }) {
|
||||
|
|
@ -177,7 +169,7 @@ export class Browser extends EventEmitter {
|
|||
this._targets.delete(event.targetId);
|
||||
target._didClose();
|
||||
if (await target._initializedPromise)
|
||||
this.chromium.emit(Events.Chromium.TargetDestroyed, target);
|
||||
this.emit(Events.Browser.TargetDestroyed, target);
|
||||
}
|
||||
|
||||
_targetInfoChanged(event: Protocol.Target.targetInfoChangedPayload) {
|
||||
|
|
@ -187,7 +179,7 @@ export class Browser extends EventEmitter {
|
|||
const wasInitialized = target._isInitialized;
|
||||
target._targetInfoChanged(event.targetInfo);
|
||||
if (wasInitialized && previousURL !== target.url())
|
||||
this.chromium.emit(Events.Chromium.TargetChanged, target);
|
||||
this.emit(Events.Browser.TargetChanged, target);
|
||||
}
|
||||
|
||||
async _closePage(page: Page) {
|
||||
|
|
@ -202,7 +194,7 @@ export class Browser extends EventEmitter {
|
|||
await (page._delegate as FrameManager)._client.send('Target.activateTarget', {targetId: Target.fromPage(page)._targetId});
|
||||
}
|
||||
|
||||
async _waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise<Target> {
|
||||
async waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise<Target> {
|
||||
const {
|
||||
timeout = 30000
|
||||
} = options;
|
||||
|
|
@ -211,15 +203,15 @@ export class Browser extends EventEmitter {
|
|||
return existingTarget;
|
||||
let resolve: (target: Target) => void;
|
||||
const targetPromise = new Promise<Target>(x => resolve = x);
|
||||
this.chromium.on(Events.Chromium.TargetCreated, check);
|
||||
this.chromium.on(Events.Chromium.TargetChanged, check);
|
||||
this.on(Events.Browser.TargetCreated, check);
|
||||
this.on(Events.Browser.TargetChanged, check);
|
||||
try {
|
||||
if (!timeout)
|
||||
return await targetPromise;
|
||||
return await helper.waitWithTimeout(targetPromise, 'target', timeout);
|
||||
} finally {
|
||||
this.chromium.removeListener(Events.Chromium.TargetCreated, check);
|
||||
this.chromium.removeListener(Events.Chromium.TargetChanged, check);
|
||||
this.removeListener(Events.Browser.TargetCreated, check);
|
||||
this.removeListener(Events.Browser.TargetChanged, check);
|
||||
}
|
||||
|
||||
function check(target: Target) {
|
||||
|
|
@ -233,6 +225,62 @@ export class Browser extends EventEmitter {
|
|||
this.disconnect();
|
||||
}
|
||||
|
||||
browserTarget(): Target {
|
||||
return [...this._targets.values()].find(t => t.type() === 'browser');
|
||||
}
|
||||
|
||||
serviceWorker(target: Target): Promise<Worker | null> {
|
||||
return target._worker();
|
||||
}
|
||||
|
||||
async startTracing(page: Page | undefined, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) {
|
||||
assert(!this._tracingRecording, 'Cannot start recording trace while already recording trace.');
|
||||
this._tracingClient = page ? (page._delegate as FrameManager)._client : this._client;
|
||||
|
||||
const defaultCategories = [
|
||||
'-*', 'devtools.timeline', 'v8.execute', 'disabled-by-default-devtools.timeline',
|
||||
'disabled-by-default-devtools.timeline.frame', 'toplevel',
|
||||
'blink.console', 'blink.user_timing', 'latencyInfo', 'disabled-by-default-devtools.timeline.stack',
|
||||
'disabled-by-default-v8.cpu_profiler', 'disabled-by-default-v8.cpu_profiler.hires'
|
||||
];
|
||||
const {
|
||||
path = null,
|
||||
screenshots = false,
|
||||
categories = defaultCategories,
|
||||
} = options;
|
||||
|
||||
if (screenshots)
|
||||
categories.push('disabled-by-default-devtools.screenshot');
|
||||
|
||||
this._tracingPath = path;
|
||||
this._tracingRecording = true;
|
||||
await this._tracingClient.send('Tracing.start', {
|
||||
transferMode: 'ReturnAsStream',
|
||||
categories: categories.join(',')
|
||||
});
|
||||
}
|
||||
|
||||
async stopTracing(): Promise<Buffer> {
|
||||
assert(this._tracingClient, 'Tracing was not started.');
|
||||
let fulfill: (buffer: Buffer) => void;
|
||||
const contentPromise = new Promise<Buffer>(x => fulfill = x);
|
||||
this._tracingClient.once('Tracing.tracingComplete', event => {
|
||||
readProtocolStream(this._tracingClient, event.stream, this._tracingPath).then(fulfill);
|
||||
});
|
||||
await this._tracingClient.send('Tracing.end');
|
||||
this._tracingRecording = false;
|
||||
return contentPromise;
|
||||
}
|
||||
|
||||
targets(context?: BrowserContext): Target[] {
|
||||
const targets = this._allTargets();
|
||||
return context ? targets.filter(t => t.browserContext() === context) : targets;
|
||||
}
|
||||
|
||||
pageTarget(page: Page): Target {
|
||||
return Target.fromPage(page);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this._connection.dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,18 +16,16 @@
|
|||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as URL from 'url';
|
||||
import { Browser } from './Browser';
|
||||
import * as util from 'util';
|
||||
import { BrowserFetcher, BrowserFetcherOptions } from '../browserFetcher';
|
||||
import { TimeoutError } from '../errors';
|
||||
import { assert, helper } from '../helper';
|
||||
import { ConnectionTransport, WebSocketTransport, PipeTransport, SlowMoTransport } from '../transport';
|
||||
import * as util from 'util';
|
||||
import { launchProcess, waitForLine } from '../processLauncher';
|
||||
import { ConnectionTransport, PipeTransport, SlowMoTransport, WebSocketTransport } from '../transport';
|
||||
import { Browser } from './Browser';
|
||||
import { BrowserServer } from '../browser';
|
||||
|
||||
const mkdtempAsync = helper.promisify(fs.mkdtemp);
|
||||
|
||||
|
|
@ -69,7 +67,7 @@ export class Launcher {
|
|||
this._preferredRevision = preferredRevision;
|
||||
}
|
||||
|
||||
async launch(options: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) = {}): Promise<Browser> {
|
||||
async launch(options: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) = {}): Promise<BrowserServer<Browser>> {
|
||||
const {
|
||||
ignoreDefaultArgs = false,
|
||||
args = [],
|
||||
|
|
@ -139,9 +137,9 @@ export class Launcher {
|
|||
} else {
|
||||
transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);
|
||||
}
|
||||
browser = await Browser.create(browserWSEndpoint, SlowMoTransport.wrap(transport, slowMo), launchedProcess);
|
||||
await browser._waitForTarget(t => t.type() === 'page');
|
||||
return browser;
|
||||
|
||||
browser = await Browser.create(SlowMoTransport.wrap(transport, slowMo));
|
||||
return new BrowserServer(browser, launchedProcess, browserWSEndpoint);
|
||||
} catch (e) {
|
||||
if (browser)
|
||||
await browser.close();
|
||||
|
|
@ -178,26 +176,6 @@ export class Launcher {
|
|||
return this._resolveExecutablePath().executablePath;
|
||||
}
|
||||
|
||||
async connect(options: (ConnectionOptions & {
|
||||
browserWSEndpoint?: string;
|
||||
browserURL?: string;
|
||||
transport?: ConnectionTransport; })): Promise<Browser> {
|
||||
assert(Number(!!options.browserWSEndpoint) + Number(!!options.browserURL) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to playwright.connect');
|
||||
|
||||
let transport: ConnectionTransport | undefined;
|
||||
let connectionURL: string = '';
|
||||
if (options.transport) {
|
||||
transport = options.transport;
|
||||
} else if (options.browserWSEndpoint) {
|
||||
connectionURL = options.browserWSEndpoint;
|
||||
transport = await WebSocketTransport.create(options.browserWSEndpoint);
|
||||
} else if (options.browserURL) {
|
||||
connectionURL = await getWSEndpoint(options.browserURL);
|
||||
transport = await WebSocketTransport.create(connectionURL);
|
||||
}
|
||||
return Browser.create(connectionURL, SlowMoTransport.wrap(transport, options.slowMo), null);
|
||||
}
|
||||
|
||||
_resolveExecutablePath(): { executablePath: string; missingText: string | null; } {
|
||||
const browserFetcher = createBrowserFetcher(this._projectRoot);
|
||||
const revisionInfo = browserFetcher.revisionInfo(this._preferredRevision);
|
||||
|
|
@ -207,36 +185,6 @@ export class Launcher {
|
|||
|
||||
}
|
||||
|
||||
function getWSEndpoint(browserURL: 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 = URL.resolve(browserURL, '/json/version');
|
||||
const protocol = endpointURL.startsWith('https') ? https : http;
|
||||
const requestOptions = Object.assign(URL.parse(endpointURL), { method: 'GET' });
|
||||
const request = protocol.request(requestOptions, 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(JSON.parse(data).webSocketDebuggerUrl));
|
||||
});
|
||||
|
||||
request.on('error', reject);
|
||||
request.end();
|
||||
|
||||
return promise.catch(e => {
|
||||
e.message = `Failed to fetch browser webSocket url from ${endpointURL}: ` + e.message;
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
export type LauncherChromeArgOptions = {
|
||||
headless?: boolean,
|
||||
args?: string[],
|
||||
|
|
|
|||
|
|
@ -15,12 +15,17 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Browser } from './Browser';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as URL from 'url';
|
||||
import * as browsers from '../browser';
|
||||
import { BrowserFetcher, BrowserFetcherOptions, BrowserFetcherRevisionInfo, OnProgressCallback } from '../browserFetcher';
|
||||
import { ConnectionTransport } from '../transport';
|
||||
import { DeviceDescriptors, DeviceDescriptor } from '../deviceDescriptors';
|
||||
import { DeviceDescriptor, DeviceDescriptors } from '../deviceDescriptors';
|
||||
import * as Errors from '../errors';
|
||||
import { Launcher, ConnectionOptions, LauncherChromeArgOptions, LauncherLaunchOptions, createBrowserFetcher } from './Launcher';
|
||||
import { assert } from '../helper';
|
||||
import { ConnectionTransport, WebSocketTransport, SlowMoTransport } from '../transport';
|
||||
import { ConnectionOptions, createBrowserFetcher, Launcher, LauncherChromeArgOptions, LauncherLaunchOptions } from './Launcher';
|
||||
import { Browser } from './Browser';
|
||||
|
||||
type Devices = { [name: string]: DeviceDescriptor } & DeviceDescriptor[];
|
||||
|
||||
|
|
@ -42,15 +47,33 @@ export class Playwright {
|
|||
return revisionInfo;
|
||||
}
|
||||
|
||||
launch(options?: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) | undefined): Promise<Browser> {
|
||||
async launch(options?: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) | undefined): Promise<browsers.Browser> {
|
||||
const server = await this._launcher.launch(options);
|
||||
return server.connect();
|
||||
}
|
||||
|
||||
async launchServer(options: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) = {}): Promise<browsers.BrowserServer<Browser>> {
|
||||
return this._launcher.launch(options);
|
||||
}
|
||||
|
||||
connect(options: (ConnectionOptions & {
|
||||
async connect(options: (ConnectionOptions & {
|
||||
browserWSEndpoint?: string;
|
||||
browserURL?: string;
|
||||
transport?: ConnectionTransport; })): Promise<Browser> {
|
||||
return this._launcher.connect(options);
|
||||
assert(Number(!!options.browserWSEndpoint) + Number(!!options.browserURL) + Number(!!options.transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to playwright.connect');
|
||||
|
||||
let transport: ConnectionTransport | undefined;
|
||||
let connectionURL: string = '';
|
||||
if (options.transport) {
|
||||
transport = options.transport;
|
||||
} else if (options.browserWSEndpoint) {
|
||||
connectionURL = options.browserWSEndpoint;
|
||||
transport = await WebSocketTransport.create(options.browserWSEndpoint);
|
||||
} else if (options.browserURL) {
|
||||
connectionURL = await getWSEndpoint(options.browserURL);
|
||||
transport = await WebSocketTransport.create(connectionURL);
|
||||
}
|
||||
return Browser.create(SlowMoTransport.wrap(transport, options.slowMo));
|
||||
}
|
||||
|
||||
executablePath(): string {
|
||||
|
|
@ -76,3 +99,33 @@ export class Playwright {
|
|||
return createBrowserFetcher(this._projectRoot, options);
|
||||
}
|
||||
}
|
||||
|
||||
function getWSEndpoint(browserURL: 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 = URL.resolve(browserURL, '/json/version');
|
||||
const protocol = endpointURL.startsWith('https') ? https : http;
|
||||
const requestOptions = Object.assign(URL.parse(endpointURL), { method: 'GET' });
|
||||
const request = protocol.request(requestOptions, 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(JSON.parse(data).webSocketDebuggerUrl));
|
||||
});
|
||||
|
||||
request.on('error', reject);
|
||||
request.end();
|
||||
|
||||
return promise.catch(e => {
|
||||
e.message = `Failed to fetch browser webSocket url from ${endpointURL}: ` + e.message;
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@ export { Request, Response } from '../network';
|
|||
export { BrowserContext } from '../browserContext';
|
||||
export { CDPSession } from './Connection';
|
||||
export { Accessibility } from './features/accessibility';
|
||||
export { Chromium } from './features/chromium';
|
||||
export { Coverage } from './features/coverage';
|
||||
export { Interception } from './features/interception';
|
||||
export { Overrides } from './features/overrides';
|
||||
|
|
|
|||
|
|
@ -17,10 +17,7 @@
|
|||
|
||||
export const Events = {
|
||||
Browser: {
|
||||
Disconnected: 'disconnected'
|
||||
},
|
||||
|
||||
Chromium: {
|
||||
Disconnected: 'disconnected',
|
||||
TargetCreated: 'targetcreated',
|
||||
TargetDestroyed: 'targetdestroyed',
|
||||
TargetChanged: 'targetchanged',
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
* Modifications 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 { EventEmitter } from 'events';
|
||||
import { assert } from '../../helper';
|
||||
import { Browser } from '../Browser';
|
||||
import { BrowserContext } from '../../browserContext';
|
||||
import { CDPSession } from '../Connection';
|
||||
import { Page } from '../../page';
|
||||
import { readProtocolStream } from '../protocolHelper';
|
||||
import { Target } from '../Target';
|
||||
import { Worker } from './workers';
|
||||
import { FrameManager } from '../FrameManager';
|
||||
|
||||
export class Chromium extends EventEmitter {
|
||||
private _client: CDPSession;
|
||||
private _recording = false;
|
||||
private _path = '';
|
||||
private _tracingClient: CDPSession | undefined;
|
||||
private _browser: Browser;
|
||||
private _browserWSEndpoint: string;
|
||||
|
||||
constructor(browser: Browser, browserWSEndpoint: string) {
|
||||
super();
|
||||
this._browserWSEndpoint = browserWSEndpoint;
|
||||
this._client = browser._client;
|
||||
this._browser = browser;
|
||||
}
|
||||
|
||||
browserTarget(): Target {
|
||||
return [...this._browser._targets.values()].find(t => t.type() === 'browser');
|
||||
}
|
||||
|
||||
serviceWorker(target: Target): Promise<Worker | null> {
|
||||
return target._worker();
|
||||
}
|
||||
|
||||
async startTracing(page: Page | undefined, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) {
|
||||
assert(!this._recording, 'Cannot start recording trace while already recording trace.');
|
||||
this._tracingClient = page ? (page._delegate as FrameManager)._client : this._client;
|
||||
|
||||
const defaultCategories = [
|
||||
'-*', 'devtools.timeline', 'v8.execute', 'disabled-by-default-devtools.timeline',
|
||||
'disabled-by-default-devtools.timeline.frame', 'toplevel',
|
||||
'blink.console', 'blink.user_timing', 'latencyInfo', 'disabled-by-default-devtools.timeline.stack',
|
||||
'disabled-by-default-v8.cpu_profiler', 'disabled-by-default-v8.cpu_profiler.hires'
|
||||
];
|
||||
const {
|
||||
path = null,
|
||||
screenshots = false,
|
||||
categories = defaultCategories,
|
||||
} = options;
|
||||
|
||||
if (screenshots)
|
||||
categories.push('disabled-by-default-devtools.screenshot');
|
||||
|
||||
this._path = path;
|
||||
this._recording = true;
|
||||
await this._tracingClient.send('Tracing.start', {
|
||||
transferMode: 'ReturnAsStream',
|
||||
categories: categories.join(',')
|
||||
});
|
||||
}
|
||||
|
||||
async stopTracing(): Promise<Buffer> {
|
||||
assert(this._tracingClient, 'Tracing was not started.');
|
||||
let fulfill: (buffer: Buffer) => void;
|
||||
const contentPromise = new Promise<Buffer>(x => fulfill = x);
|
||||
this._tracingClient.once('Tracing.tracingComplete', event => {
|
||||
readProtocolStream(this._tracingClient, event.stream, this._path).then(fulfill);
|
||||
});
|
||||
await this._tracingClient.send('Tracing.end');
|
||||
this._recording = false;
|
||||
return contentPromise;
|
||||
}
|
||||
|
||||
targets(context?: BrowserContext): Target[] {
|
||||
const targets = this._browser._allTargets();
|
||||
return context ? targets.filter(t => t.browserContext() === context) : targets;
|
||||
}
|
||||
|
||||
pageTarget(page: Page): Target {
|
||||
return Target.fromPage(page);
|
||||
}
|
||||
|
||||
waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise<Target> {
|
||||
return this._browser._waitForTarget(predicate, options);
|
||||
}
|
||||
|
||||
wsEndpoint(): string {
|
||||
return this._browserWSEndpoint;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import { helper, RegisteredListener, assert } from '../helper';
|
||||
import { Connection, ConnectionEvents, JugglerSessionEvents } from './Connection';
|
||||
import { Events } from './events';
|
||||
|
|
@ -24,35 +23,29 @@ import { Events as CommonEvents } from '../events';
|
|||
import { Permissions } from './features/permissions';
|
||||
import { Page } from '../page';
|
||||
import { FrameManager } from './FrameManager';
|
||||
import { Firefox } from './features/firefox';
|
||||
import * as browser from '../browser';
|
||||
import * as network from '../network';
|
||||
import { BrowserContext, BrowserContextOptions } from '../browserContext';
|
||||
import { ConnectionTransport } from '../transport';
|
||||
|
||||
export class Browser extends EventEmitter {
|
||||
export class Browser extends EventEmitter implements browser.Browser {
|
||||
_connection: Connection;
|
||||
private _process: ChildProcess;
|
||||
_targets: Map<string, Target>;
|
||||
private _defaultContext: BrowserContext;
|
||||
private _contexts: Map<string, BrowserContext>;
|
||||
private _eventListeners: RegisteredListener[];
|
||||
readonly firefox: Firefox;
|
||||
readonly _browserWSEndpoint: string;
|
||||
|
||||
static async create(browserWSEndpoint: string, transport: ConnectionTransport, process: ChildProcess | null) {
|
||||
static async create(transport: ConnectionTransport) {
|
||||
const connection = new Connection(transport);
|
||||
const {browserContextIds} = await connection.send('Target.getBrowserContexts');
|
||||
const browser = new Browser(browserWSEndpoint, connection, browserContextIds, process);
|
||||
const browser = new Browser(connection, browserContextIds);
|
||||
await connection.send('Target.enable');
|
||||
return browser;
|
||||
}
|
||||
|
||||
constructor(browserWSEndpoint: string, connection: Connection, browserContextIds: Array<string>, process: ChildProcess | null) {
|
||||
constructor(connection: Connection, browserContextIds: Array<string>) {
|
||||
super();
|
||||
this._connection = connection;
|
||||
this._process = process;
|
||||
this.firefox = new Firefox(browserWSEndpoint);
|
||||
|
||||
this._targets = new Map();
|
||||
|
||||
this._defaultContext = this._createBrowserContext(null, {});
|
||||
|
|
@ -95,10 +88,6 @@ export class Browser extends EventEmitter {
|
|||
return this._defaultContext;
|
||||
}
|
||||
|
||||
process(): ChildProcess | null {
|
||||
return this._process;
|
||||
}
|
||||
|
||||
async _waitForTarget(predicate: (target: Target) => boolean, options: { timeout?: number; } = {}): Promise<Target> {
|
||||
const {
|
||||
timeout = 30000
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { assert } from '../helper';
|
|||
import { TimeoutError } from '../errors';
|
||||
import { WebSocketTransport, SlowMoTransport } from '../transport';
|
||||
import { launchProcess, waitForLine } from '../processLauncher';
|
||||
import { BrowserServer } from '../browser';
|
||||
|
||||
const mkdtempAsync = util.promisify(fs.mkdtemp);
|
||||
const writeFileAsync = util.promisify(fs.writeFile);
|
||||
|
|
@ -58,7 +59,7 @@ export class Launcher {
|
|||
return firefoxArguments;
|
||||
}
|
||||
|
||||
async launch(options: any = {}): Promise<Browser> {
|
||||
async launch(options: any = {}): Promise<BrowserServer<Browser>> {
|
||||
const {
|
||||
ignoreDefaultArgs = false,
|
||||
args = [],
|
||||
|
|
@ -122,9 +123,9 @@ export class Launcher {
|
|||
const match = await waitForLine(launchedProcess, launchedProcess.stdout, /^Juggler listening on (ws:\/\/.*)$/, timeout, timeoutError);
|
||||
const url = match[1];
|
||||
const transport = await WebSocketTransport.create(url);
|
||||
browser = await Browser.create(url, SlowMoTransport.wrap(transport, slowMo), launchedProcess);
|
||||
browser = await Browser.create(SlowMoTransport.wrap(transport, slowMo));
|
||||
await browser._waitForTarget(t => t.type() === 'page');
|
||||
return browser;
|
||||
return new BrowserServer(browser, launchedProcess, url);
|
||||
} catch (e) {
|
||||
if (browser)
|
||||
await browser.close();
|
||||
|
|
@ -132,15 +133,6 @@ export class Launcher {
|
|||
}
|
||||
}
|
||||
|
||||
async connect(options: any = {}): Promise<Browser> {
|
||||
const {
|
||||
browserWSEndpoint,
|
||||
slowMo = 0,
|
||||
} = options;
|
||||
const transport = await WebSocketTransport.create(browserWSEndpoint);
|
||||
return await Browser.create(browserWSEndpoint, SlowMoTransport.wrap(transport, slowMo), null);
|
||||
}
|
||||
|
||||
executablePath(): string {
|
||||
return this._resolveExecutablePath().executablePath;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as browsers from '../browser';
|
||||
import { Browser } from './Browser';
|
||||
import { BrowserFetcher, BrowserFetcherOptions, OnProgressCallback, BrowserFetcherRevisionInfo } from '../browserFetcher';
|
||||
import { ConnectionTransport } from '../transport';
|
||||
import { WebSocketTransport, SlowMoTransport } from '../transport';
|
||||
import { DeviceDescriptors, DeviceDescriptor } from '../deviceDescriptors';
|
||||
import * as Errors from '../errors';
|
||||
import { Launcher, createBrowserFetcher } from './Launcher';
|
||||
|
|
@ -41,15 +43,18 @@ export class Playwright {
|
|||
return revisionInfo;
|
||||
}
|
||||
|
||||
launch(options: any): Promise<Browser> {
|
||||
async launch(options: any): Promise<Browser> {
|
||||
const server = await this._launcher.launch(options);
|
||||
return server.connect();
|
||||
}
|
||||
|
||||
async launchServer(options: any): Promise<browsers.BrowserServer<Browser>> {
|
||||
return this._launcher.launch(options);
|
||||
}
|
||||
|
||||
connect(options: (any & {
|
||||
browserWSEndpoint?: string;
|
||||
browserURL?: string;
|
||||
transport?: ConnectionTransport; })): Promise<Browser> {
|
||||
return this._launcher.connect(options);
|
||||
async connect(options: { slowMo?: number, browserWSEndpoint: string }): Promise<Browser> {
|
||||
const transport = await WebSocketTransport.create(options.browserWSEndpoint);
|
||||
return Browser.create(SlowMoTransport.wrap(transport, options.slowMo || 0));
|
||||
}
|
||||
|
||||
executablePath(): string {
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
|
||||
export class Firefox {
|
||||
private _browserWSEndpoint: string;
|
||||
|
||||
constructor(browserWSEndpoint: string) {
|
||||
this._browserWSEndpoint = browserWSEndpoint;
|
||||
}
|
||||
|
||||
wsEndpoint(): string {
|
||||
return this._browserWSEndpoint;
|
||||
}
|
||||
}
|
||||
|
|
@ -76,6 +76,7 @@ export async function launchProcess(options: LaunchProcessOptions, attemptToGrac
|
|||
const waitForProcessToClose = new Promise((fulfill, reject) => {
|
||||
spawnedProcess.once('exit', () => {
|
||||
processClosed = true;
|
||||
helper.removeEventListeners(listeners);
|
||||
// Cleanup as processes exit.
|
||||
if (options.tempDir) {
|
||||
removeFolderAsync(options.tempDir)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export interface ConnectionTransport {
|
|||
|
||||
export class WebSocketTransport implements ConnectionTransport {
|
||||
private _ws: WebSocket;
|
||||
|
||||
onmessage?: (message: string) => void;
|
||||
onclose?: () => void;
|
||||
|
||||
|
|
@ -36,12 +37,12 @@ export class WebSocketTransport implements ConnectionTransport {
|
|||
perMessageDeflate: false,
|
||||
maxPayload: 256 * 1024 * 1024, // 256Mb
|
||||
});
|
||||
ws.addEventListener('open', () => resolve(new WebSocketTransport(ws)));
|
||||
ws.addEventListener('open', () => resolve(new WebSocketTransport(ws, url)));
|
||||
ws.addEventListener('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
constructor(ws: WebSocket) {
|
||||
constructor(ws: WebSocket, url: string) {
|
||||
this._ws = ws;
|
||||
this._ws.addEventListener('message', event => {
|
||||
if (this.onmessage)
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as childProcess from 'child_process';
|
||||
import { EventEmitter } from 'events';
|
||||
import { helper, RegisteredListener, debugError, assert } from '../helper';
|
||||
import * as browser from '../browser';
|
||||
import * as network from '../network';
|
||||
import { Connection, ConnectionEvents, TargetSession } from './Connection';
|
||||
import { Page } from '../page';
|
||||
|
|
@ -27,8 +27,7 @@ import { Events } from '../events';
|
|||
import { BrowserContext, BrowserContextOptions } from '../browserContext';
|
||||
import { ConnectionTransport } from '../transport';
|
||||
|
||||
export class Browser extends EventEmitter {
|
||||
private readonly _process: childProcess.ChildProcess;
|
||||
export class Browser extends EventEmitter implements browser.Browser {
|
||||
readonly _connection: Connection;
|
||||
private _defaultContext: BrowserContext;
|
||||
private _contexts = new Map<string, BrowserContext>();
|
||||
|
|
@ -36,12 +35,9 @@ export class Browser extends EventEmitter {
|
|||
private _eventListeners: RegisteredListener[];
|
||||
private _privateEvents = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
transport: ConnectionTransport,
|
||||
process: childProcess.ChildProcess | null) {
|
||||
constructor(transport: ConnectionTransport) {
|
||||
super();
|
||||
this._connection = new Connection(transport);
|
||||
this._process = process;
|
||||
|
||||
/** @type {!Map<string, !Target>} */
|
||||
this._targets = new Map();
|
||||
|
|
@ -63,10 +59,6 @@ export class Browser extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
process(): childProcess.ChildProcess | null {
|
||||
return this._process;
|
||||
}
|
||||
|
||||
async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
|
||||
const { browserContextId } = await this._connection.send('Browser.createContext');
|
||||
const context = this._createBrowserContext(browserContextId, options);
|
||||
|
|
@ -84,15 +76,11 @@ export class Browser extends EventEmitter {
|
|||
return this._defaultContext;
|
||||
}
|
||||
|
||||
targets(): Target[] {
|
||||
return Array.from(this._targets.values());
|
||||
}
|
||||
|
||||
async _waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise<Target> {
|
||||
const {
|
||||
timeout = 30000
|
||||
} = options;
|
||||
const existingTarget = this.targets().find(predicate);
|
||||
const existingTarget = Array.from(this._targets.values()).find(predicate);
|
||||
if (existingTarget)
|
||||
return existingTarget;
|
||||
let resolve : (a: Target) => void;
|
||||
|
|
@ -185,7 +173,7 @@ export class Browser extends EventEmitter {
|
|||
_createBrowserContext(browserContextId: string | undefined, options: BrowserContextOptions): BrowserContext {
|
||||
const context = new BrowserContext({
|
||||
pages: async (): Promise<Page[]> => {
|
||||
const targets = this.targets().filter(target => target._browserContext === context && target._type === 'page');
|
||||
const targets = Array.from(this._targets.values()).filter(target => target._browserContext === context && target._type === 'page');
|
||||
const pages = await Promise.all(targets.map(target => target.page()));
|
||||
return pages.filter(page => !!page);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import * as path from 'path';
|
|||
import * as util from 'util';
|
||||
import * as os from 'os';
|
||||
import { launchProcess } from '../processLauncher';
|
||||
import { BrowserServer } from '../browser';
|
||||
|
||||
const DEFAULT_ARGS = [
|
||||
];
|
||||
|
|
@ -46,7 +47,7 @@ export class Launcher {
|
|||
return webkitArguments;
|
||||
}
|
||||
|
||||
async launch(options: LauncherLaunchOptions = {}): Promise<Browser> {
|
||||
async launch(options: LauncherLaunchOptions = {}): Promise<BrowserServer<Browser>> {
|
||||
const {
|
||||
ignoreDefaultArgs = false,
|
||||
args = [],
|
||||
|
|
@ -96,9 +97,9 @@ export class Launcher {
|
|||
let browser: Browser | undefined;
|
||||
try {
|
||||
const transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);
|
||||
browser = new Browser(SlowMoTransport.wrap(transport, slowMo), launchedProcess);
|
||||
browser = new Browser(SlowMoTransport.wrap(transport, slowMo));
|
||||
await browser._waitForTarget(t => t._type === 'page');
|
||||
return browser;
|
||||
return new BrowserServer(browser, launchedProcess, '');
|
||||
} catch (e) {
|
||||
if (browser)
|
||||
await browser.close();
|
||||
|
|
|
|||
|
|
@ -14,11 +14,13 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Browser } from './Browser';
|
||||
|
||||
import * as browsers from '../browser';
|
||||
import { BrowserFetcher, BrowserFetcherOptions, OnProgressCallback, BrowserFetcherRevisionInfo } from '../browserFetcher';
|
||||
import { DeviceDescriptors } from '../deviceDescriptors';
|
||||
import * as Errors from '../errors';
|
||||
import { Launcher, LauncherLaunchOptions, createBrowserFetcher } from './Launcher';
|
||||
import { Browser } from './Browser';
|
||||
|
||||
export class Playwright {
|
||||
private _projectRoot: string;
|
||||
|
|
@ -38,7 +40,12 @@ export class Playwright {
|
|||
return revisionInfo;
|
||||
}
|
||||
|
||||
launch(options: (LauncherLaunchOptions) | undefined): Promise<Browser> {
|
||||
async launch(options: (LauncherLaunchOptions) | undefined): Promise<Browser> {
|
||||
const server = await this._launcher.launch(options);
|
||||
return server.connect();
|
||||
}
|
||||
|
||||
async launchServer(options: (LauncherLaunchOptions) | undefined): Promise<browsers.BrowserServer<Browser>> {
|
||||
return this._launcher.launch(options);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
|
|||
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
||||
|
||||
describe('Browser.process', function() {
|
||||
it('should return child_process instance', async function({browser}) {
|
||||
const process = await browser.process();
|
||||
it('should return child_process instance', async function({browserServer}) {
|
||||
const process = await browserServer.process();
|
||||
expect(process.pid).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,14 +7,8 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
|
|||
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
||||
|
||||
describe('CrBrowser', function() {
|
||||
it('should not return child_process for remote browser', async function({browser}) {
|
||||
const browserWSEndpoint = browser.chromium.wsEndpoint();
|
||||
const remoteBrowser = await playwright.connect({browserWSEndpoint});
|
||||
expect(remoteBrowser.process()).toBe(null);
|
||||
remoteBrowser.disconnect();
|
||||
});
|
||||
it('should close all belonging targets once closing context', async function({browser, newContext}) {
|
||||
const targets = async () => (await browser.chromium.targets()).filter(t => t.type() === 'page');
|
||||
const targets = async () => (await browser.targets()).filter(t => t.type() === 'page');
|
||||
expect((await targets()).length).toBe(1);
|
||||
|
||||
const context = await newContext();
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
||||
|
||||
describe('Chromium', function() {
|
||||
it('should work across sessions', async function({browser, newContext}) {
|
||||
it('should work across sessions', async function({browserServer, server, browser, newContext}) {
|
||||
expect(browser.browserContexts().length).toBe(2);
|
||||
await newContext();
|
||||
expect(browser.browserContexts().length).toBe(3);
|
||||
const remoteBrowser = await playwright.connect({
|
||||
browserWSEndpoint: browser.chromium.wsEndpoint()
|
||||
browserWSEndpoint: browserServer.wsEndpoint()
|
||||
});
|
||||
const contexts = remoteBrowser.browserContexts();
|
||||
expect(contexts.length).toBe(3);
|
||||
|
|
@ -38,7 +38,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
describe('Target', function() {
|
||||
it('Chromium.targets should return all of the targets', async({page, server, browser}) => {
|
||||
// The pages will be the testing page and the original newtab page
|
||||
const targets = browser.chromium.targets();
|
||||
const targets = browser.targets();
|
||||
expect(targets.some(target => target.type() === 'page' &&
|
||||
target.url() === 'about:blank')).toBeTruthy('Missing blank page');
|
||||
expect(targets.some(target => target.type() === 'browser')).toBeTruthy('Missing browser target');
|
||||
|
|
@ -51,7 +51,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
expect(allPages[0]).not.toBe(allPages[1]);
|
||||
});
|
||||
it('should contain browser target', async({browser}) => {
|
||||
const targets = browser.chromium.targets();
|
||||
const targets = browser.targets();
|
||||
const browserTarget = targets.find(target => target.type() === 'browser');
|
||||
expect(browserTarget).toBeTruthy();
|
||||
});
|
||||
|
|
@ -63,7 +63,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
});
|
||||
it('should report when a new page is created and closed', async({browser, page, server, context}) => {
|
||||
const [otherPage] = await Promise.all([
|
||||
browser.chromium.waitForTarget(target => target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html').then(target => target.page()),
|
||||
browser.waitForTarget(target => target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html').then(target => target.page()),
|
||||
page.evaluate(url => window.open(url), server.CROSS_PROCESS_PREFIX + '/empty.html'),
|
||||
]);
|
||||
expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX);
|
||||
|
|
@ -74,32 +74,32 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
expect(allPages).toContain(page);
|
||||
expect(allPages).toContain(otherPage);
|
||||
|
||||
const closePagePromise = new Promise(fulfill => browser.chromium.once('targetdestroyed', target => fulfill(target.page())));
|
||||
const closePagePromise = new Promise(fulfill => browser.once('targetdestroyed', target => fulfill(target.page())));
|
||||
await otherPage.close();
|
||||
expect(await closePagePromise).toBe(otherPage);
|
||||
|
||||
allPages = await Promise.all(browser.chromium.targets().map(target => target.page()));
|
||||
allPages = await Promise.all(browser.targets().map(target => target.page()));
|
||||
expect(allPages).toContain(page);
|
||||
expect(allPages).not.toContain(otherPage);
|
||||
});
|
||||
it('should report when a service worker is created and destroyed', async({browser, page, server, context}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const createdTarget = new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target)));
|
||||
const createdTarget = new Promise(fulfill => browser.once('targetcreated', target => fulfill(target)));
|
||||
|
||||
await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html');
|
||||
|
||||
expect((await createdTarget).type()).toBe('service_worker');
|
||||
expect((await createdTarget).url()).toBe(server.PREFIX + '/serviceworkers/empty/sw.js');
|
||||
|
||||
const destroyedTarget = new Promise(fulfill => browser.chromium.once('targetdestroyed', target => fulfill(target)));
|
||||
const destroyedTarget = new Promise(fulfill => browser.once('targetdestroyed', target => fulfill(target)));
|
||||
await page.evaluate(() => window.registrationPromise.then(registration => registration.unregister()));
|
||||
expect(await destroyedTarget).toBe(await createdTarget);
|
||||
});
|
||||
it('should create a worker from a service worker', async({browser, page, server, context}) => {
|
||||
await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html');
|
||||
|
||||
const target = await browser.chromium.waitForTarget(target => target.type() === 'service_worker');
|
||||
const worker = await browser.chromium.serviceWorker(target);
|
||||
const target = await browser.waitForTarget(target => target.type() === 'service_worker');
|
||||
const worker = await browser.serviceWorker(target);
|
||||
expect(await worker.evaluate(() => self.toString())).toBe('[object ServiceWorkerGlobalScope]');
|
||||
});
|
||||
it('should create a worker from a shared worker', async({browser, page, server, context}) => {
|
||||
|
|
@ -107,38 +107,38 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
await page.evaluate(() => {
|
||||
new SharedWorker('data:text/javascript,console.log("hi")');
|
||||
});
|
||||
const target = await browser.chromium.waitForTarget(target => target.type() === 'shared_worker');
|
||||
const worker = await browser.chromium.serviceWorker(target);
|
||||
const target = await browser.waitForTarget(target => target.type() === 'shared_worker');
|
||||
const worker = await browser.serviceWorker(target);
|
||||
expect(await worker.evaluate(() => self.toString())).toBe('[object SharedWorkerGlobalScope]');
|
||||
});
|
||||
it('should report when a target url changes', async({browser, page, server, context}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
let changedTarget = new Promise(fulfill => browser.chromium.once('targetchanged', target => fulfill(target)));
|
||||
let changedTarget = new Promise(fulfill => browser.once('targetchanged', target => fulfill(target)));
|
||||
await page.goto(server.CROSS_PROCESS_PREFIX + '/');
|
||||
expect((await changedTarget).url()).toBe(server.CROSS_PROCESS_PREFIX + '/');
|
||||
|
||||
changedTarget = new Promise(fulfill => browser.chromium.once('targetchanged', target => fulfill(target)));
|
||||
changedTarget = new Promise(fulfill => browser.once('targetchanged', target => fulfill(target)));
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
expect((await changedTarget).url()).toBe(server.EMPTY_PAGE);
|
||||
});
|
||||
it('should not report uninitialized pages', async({browser, page, server, context}) => {
|
||||
let targetChanged = false;
|
||||
const listener = () => targetChanged = true;
|
||||
browser.chromium.on('targetchanged', listener);
|
||||
const targetPromise = new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target)));
|
||||
browser.on('targetchanged', listener);
|
||||
const targetPromise = new Promise(fulfill => browser.once('targetcreated', target => fulfill(target)));
|
||||
const newPagePromise = context.newPage();
|
||||
const target = await targetPromise;
|
||||
expect(target.url()).toBe('about:blank');
|
||||
|
||||
const newPage = await newPagePromise;
|
||||
const targetPromise2 = new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target)));
|
||||
const targetPromise2 = new Promise(fulfill => browser.once('targetcreated', target => fulfill(target)));
|
||||
const evaluatePromise = newPage.evaluate(() => window.open('about:blank'));
|
||||
const target2 = await targetPromise2;
|
||||
expect(target2.url()).toBe('about:blank');
|
||||
await evaluatePromise;
|
||||
await newPage.close();
|
||||
expect(targetChanged).toBe(false, 'target should not be reported as changed');
|
||||
browser.chromium.removeListener('targetchanged', listener);
|
||||
browser.removeListener('targetchanged', listener);
|
||||
});
|
||||
it('should not crash while redirecting if original request was missed', async({browser, page, server, context}) => {
|
||||
let serverResponse = null;
|
||||
|
|
@ -149,7 +149,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
server.waitForRequest('/one-style.css')
|
||||
]);
|
||||
// Connect to the opened page.
|
||||
const target = await browser.chromium.waitForTarget(target => target.url().includes('one-style.html'));
|
||||
const target = await browser.waitForTarget(target => target.url().includes('one-style.html'));
|
||||
const newPage = await target.page();
|
||||
// Issue a redirect.
|
||||
serverResponse.writeHead(302, { location: '/injectedstyle.css' });
|
||||
|
|
@ -162,12 +162,12 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
it('should have an opener', async({browser, page, server, context}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const [createdTarget] = await Promise.all([
|
||||
new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target))),
|
||||
new Promise(fulfill => browser.once('targetcreated', target => fulfill(target))),
|
||||
page.goto(server.PREFIX + '/popup/window-open.html')
|
||||
]);
|
||||
expect((await createdTarget.page()).url()).toBe(server.PREFIX + '/popup/popup.html');
|
||||
expect(createdTarget.opener()).toBe(browser.chromium.pageTarget(page));
|
||||
expect(browser.chromium.pageTarget(page).opener()).toBe(null);
|
||||
expect(createdTarget.opener()).toBe(browser.pageTarget(page));
|
||||
expect(browser.pageTarget(page).opener()).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
it('should wait for a target', async function({browser, server, newContext}) {
|
||||
const context = await newContext();
|
||||
let resolved = false;
|
||||
const targetPromise = browser.chromium.waitForTarget(target => target.browserContext() === context && target.url() === server.EMPTY_PAGE);
|
||||
const targetPromise = browser.waitForTarget(target => target.browserContext() === context && target.url() === server.EMPTY_PAGE);
|
||||
targetPromise.then(() => resolved = true);
|
||||
const page = await context.newPage();
|
||||
expect(resolved).toBe(false);
|
||||
|
|
@ -185,13 +185,13 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
});
|
||||
it('should timeout waiting for a non-existent target', async function({browser, server, newContext}) {
|
||||
const context = await newContext();
|
||||
const error = await browser.chromium.waitForTarget(target => target.browserContext() === context && target.url() === server.EMPTY_PAGE, {timeout: 1}).catch(e => e);
|
||||
const error = await browser.waitForTarget(target => target.browserContext() === context && target.url() === server.EMPTY_PAGE, {timeout: 1}).catch(e => e);
|
||||
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
|
||||
await context.close();
|
||||
});
|
||||
it('should wait for a target', async function({browser, server}) {
|
||||
let resolved = false;
|
||||
const targetPromise = browser.chromium.waitForTarget(target => target.url() === server.EMPTY_PAGE);
|
||||
const targetPromise = browser.waitForTarget(target => target.url() === server.EMPTY_PAGE);
|
||||
targetPromise.then(() => resolved = true);
|
||||
const page = await browser.defaultContext().newPage();
|
||||
expect(resolved).toBe(false);
|
||||
|
|
@ -202,7 +202,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
});
|
||||
it('should timeout waiting for a non-existent target', async function({browser, server}) {
|
||||
let error = null;
|
||||
await browser.chromium.waitForTarget(target => target.url() === server.EMPTY_PAGE, {
|
||||
await browser.waitForTarget(target => target.url() === server.EMPTY_PAGE, {
|
||||
timeout: 1
|
||||
}).catch(e => error = e);
|
||||
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
|
||||
|
|
@ -210,9 +210,9 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
it('should fire target events', async function({browser, newContext, server}) {
|
||||
const context = await newContext();
|
||||
const events = [];
|
||||
browser.chromium.on('targetcreated', target => events.push('CREATED: ' + target.url()));
|
||||
browser.chromium.on('targetchanged', target => events.push('CHANGED: ' + target.url()));
|
||||
browser.chromium.on('targetdestroyed', target => events.push('DESTROYED: ' + target.url()));
|
||||
browser.on('targetcreated', target => events.push('CREATED: ' + target.url()));
|
||||
browser.on('targetchanged', target => events.push('CHANGED: ' + target.url()));
|
||||
browser.on('targetdestroyed', target => events.push('DESTROYED: ' + target.url()));
|
||||
const page = await context.newPage();
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await page.close();
|
||||
|
|
|
|||
|
|
@ -14,10 +14,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const {helper} = require('../../lib/helper');
|
||||
|
||||
const utils = require('../utils');
|
||||
|
||||
module.exports.describe = function({testRunner, expect, defaultBrowserOptions, playwright, FFOX, CHROME, WEBKIT}) {
|
||||
|
|
@ -27,39 +24,41 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
|
||||
describe('Playwright.connect', function() {
|
||||
it('should be able to connect multiple times to the same browser', async({server}) => {
|
||||
const originalBrowser = await playwright.launch(defaultBrowserOptions);
|
||||
const browser = await playwright.connect({
|
||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||
const local = await browserServer.connect();
|
||||
const remote = await playwright.connect({
|
||||
...defaultBrowserOptions,
|
||||
browserWSEndpoint: originalBrowser.chromium.wsEndpoint()
|
||||
browserWSEndpoint: browserServer.wsEndpoint()
|
||||
});
|
||||
const page = await browser.defaultContext().newPage();
|
||||
const page = await remote.defaultContext().newPage();
|
||||
expect(await page.evaluate(() => 7 * 8)).toBe(56);
|
||||
browser.disconnect();
|
||||
remote.disconnect();
|
||||
|
||||
const secondPage = await originalBrowser.defaultContext().newPage();
|
||||
const secondPage = await local.defaultContext().newPage();
|
||||
expect(await secondPage.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work');
|
||||
await originalBrowser.close();
|
||||
await browserServer.close();
|
||||
});
|
||||
it('should be able to close remote browser', async({server}) => {
|
||||
const originalBrowser = await playwright.launch(defaultBrowserOptions);
|
||||
const remoteBrowser = await playwright.connect({
|
||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||
const local = await browserServer.connect();
|
||||
const remote = await playwright.connect({
|
||||
...defaultBrowserOptions,
|
||||
browserWSEndpoint: originalBrowser.chromium.wsEndpoint()
|
||||
browserWSEndpoint: browserServer.wsEndpoint()
|
||||
});
|
||||
await Promise.all([
|
||||
utils.waitEvent(originalBrowser, 'disconnected'),
|
||||
remoteBrowser.close(),
|
||||
utils.waitEvent(local, 'disconnected'),
|
||||
remote.close(),
|
||||
]);
|
||||
});
|
||||
it('should be able to reconnect to a disconnected browser', async({server}) => {
|
||||
const originalBrowser = await playwright.launch(defaultBrowserOptions);
|
||||
const browserWSEndpoint = originalBrowser.chromium.wsEndpoint();
|
||||
const page = await originalBrowser.defaultContext().newPage();
|
||||
it('should be able to reconnect to a browser', async({server}) => {
|
||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||
const browser = await browserServer.connect();
|
||||
const browserWSEndpoint = browserServer.wsEndpoint();
|
||||
const page = await browser.defaultContext().newPage();
|
||||
await page.goto(server.PREFIX + '/frames/nested-frames.html');
|
||||
originalBrowser.disconnect();
|
||||
|
||||
const browser = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint});
|
||||
const pages = await browser.defaultContext().pages();
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint});
|
||||
const pages = await remote.defaultContext().pages();
|
||||
const restoredPage = pages.find(page => page.url() === server.PREFIX + '/frames/nested-frames.html');
|
||||
expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([
|
||||
'http://localhost:<PORT>/frames/nested-frames.html',
|
||||
|
|
@ -69,57 +68,61 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
' http://localhost:<PORT>/frames/frame.html (uno)',
|
||||
]);
|
||||
expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56);
|
||||
await browser.close();
|
||||
await remote.close();
|
||||
});
|
||||
// @see https://github.com/GoogleChrome/puppeteer/issues/4197#issuecomment-481793410
|
||||
it('should be able to connect to the same page simultaneously', async({server}) => {
|
||||
const browserOne = await playwright.launch(defaultBrowserOptions);
|
||||
const browserTwo = await playwright.connect({ ...defaultBrowserOptions, browserWSEndpoint: browserOne.chromium.wsEndpoint() });
|
||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||
const local = await browserServer.connect();
|
||||
const remote = await playwright.connect({ ...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint() });
|
||||
const [page1, page2] = await Promise.all([
|
||||
new Promise(x => browserOne.chromium.once('targetcreated', target => x(target.page()))),
|
||||
browserTwo.defaultContext().newPage(),
|
||||
new Promise(x => local.once('targetcreated', target => x(target.page()))),
|
||||
remote.defaultContext().newPage(),
|
||||
]);
|
||||
expect(await page1.evaluate(() => 7 * 8)).toBe(56);
|
||||
expect(await page2.evaluate(() => 7 * 6)).toBe(42);
|
||||
await browserOne.close();
|
||||
await local.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Browser.disconnect', function() {
|
||||
it('should reject navigation when browser closes', async({server}) => {
|
||||
server.setRoute('/one-style.css', () => {});
|
||||
const browser = await playwright.launch(defaultBrowserOptions);
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.chromium.wsEndpoint()});
|
||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||
const local = await browserServer.connect();
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
|
||||
const page = await remote.defaultContext().newPage();
|
||||
const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(e => e);
|
||||
await server.waitForRequest('/one-style.css');
|
||||
remote.disconnect();
|
||||
const error = await navigationPromise;
|
||||
expect(error.message).toBe('Navigation failed because browser has disconnected!');
|
||||
await browser.close();
|
||||
await local.close();
|
||||
});
|
||||
it('should reject waitForSelector when browser closes', async({server}) => {
|
||||
server.setRoute('/empty.html', () => {});
|
||||
const browser = await playwright.launch(defaultBrowserOptions);
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.chromium.wsEndpoint()});
|
||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||
const local = await browserServer.connect();
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
|
||||
const page = await remote.defaultContext().newPage();
|
||||
const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e);
|
||||
remote.disconnect();
|
||||
const error = await watchdog;
|
||||
expect(error.message).toContain('Protocol error');
|
||||
await browser.close();
|
||||
await local.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Browser.close', function() {
|
||||
it('should terminate network waiters', async({context, server}) => {
|
||||
const browser = await playwright.launch(defaultBrowserOptions);
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.chromium.wsEndpoint()});
|
||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||
const local = await browserServer.connect();
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browserServer.wsEndpoint()});
|
||||
const newPage = await remote.defaultContext().newPage();
|
||||
const results = await Promise.all([
|
||||
newPage.waitForRequest(server.EMPTY_PAGE).catch(e => e),
|
||||
newPage.waitForResponse(server.EMPTY_PAGE).catch(e => e),
|
||||
browser.close()
|
||||
local.close()
|
||||
]);
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const message = results[i].message;
|
||||
|
|
@ -131,13 +134,14 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
|
||||
describe('Browser.isConnected', () => {
|
||||
it('should set the browser connected state', async () => {
|
||||
const browser = await playwright.launch(defaultBrowserOptions);
|
||||
const browserWSEndpoint = browser.chromium.wsEndpoint();
|
||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||
const local = await browserServer.connect();
|
||||
const browserWSEndpoint = browserServer.wsEndpoint();
|
||||
const newBrowser = await playwright.connect({browserWSEndpoint});
|
||||
expect(newBrowser.isConnected()).toBe(true);
|
||||
newBrowser.disconnect();
|
||||
expect(newBrowser.isConnected()).toBe(false);
|
||||
await browser.close();
|
||||
await browserServer.close();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -48,14 +48,14 @@ module.exports.describe = function({testRunner, expect, playwright, defaultBrows
|
|||
it('background_page target type should be available', async() => {
|
||||
const browserWithExtension = await playwright.launch(extensionOptions);
|
||||
const page = await browserWithExtension.defaultContext().newPage();
|
||||
const backgroundPageTarget = await browserWithExtension.chromium.waitForTarget(target => target.type() === 'background_page');
|
||||
const backgroundPageTarget = await browserWithExtension.waitForTarget(target => target.type() === 'background_page');
|
||||
await page.close();
|
||||
await browserWithExtension.close();
|
||||
expect(backgroundPageTarget).toBeTruthy();
|
||||
});
|
||||
it('target.page() should return a background_page', async({}) => {
|
||||
const browserWithExtension = await playwright.launch(extensionOptions);
|
||||
const backgroundPageTarget = await browserWithExtension.chromium.waitForTarget(target => target.type() === 'background_page');
|
||||
const backgroundPageTarget = await browserWithExtension.waitForTarget(target => target.type() === 'background_page');
|
||||
const page = await backgroundPageTarget.page();
|
||||
expect(await page.evaluate(() => 2 * 3)).toBe(6);
|
||||
expect(await page.evaluate(() => window.MAGIC)).toBe(42);
|
||||
|
|
@ -121,7 +121,7 @@ module.exports.describe = function({testRunner, expect, playwright, defaultBrows
|
|||
const context = await browser.newContext();
|
||||
await Promise.all([
|
||||
context.newPage(),
|
||||
browser.chromium.waitForTarget(target => target.browserContext() === context && target.url().includes('devtools://')),
|
||||
browser.waitForTarget(target => target.browserContext() === context && target.url().includes('devtools://')),
|
||||
]);
|
||||
await browser.close();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -68,15 +68,15 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
it('should filter out ignored default arguments', async() => {
|
||||
// Make sure we launch with `--enable-automation` by default.
|
||||
const defaultArgs = playwright.defaultArgs(defaultBrowserOptions);
|
||||
const browser = await playwright.launch(Object.assign({}, defaultBrowserOptions, {
|
||||
const browserServer = await playwright.launchServer(Object.assign({}, defaultBrowserOptions, {
|
||||
// Ignore first and third default argument.
|
||||
ignoreDefaultArgs: [ defaultArgs[0], defaultArgs[2] ],
|
||||
}));
|
||||
const spawnargs = browser.process().spawnargs;
|
||||
const spawnargs = browserServer.process().spawnargs;
|
||||
expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1);
|
||||
expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1);
|
||||
expect(spawnargs.indexOf(defaultArgs[2])).toBe(-1);
|
||||
await browser.close();
|
||||
await browserServer.close();
|
||||
});
|
||||
});
|
||||
describe('Playwright.launch |browserURL| option', function() {
|
||||
|
|
@ -98,16 +98,16 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
originalBrowser.close();
|
||||
});
|
||||
it('should throw when using both browserWSEndpoint and browserURL', async({server}) => {
|
||||
const originalBrowser = await playwright.launch(Object.assign({}, defaultBrowserOptions, {
|
||||
const browserServer = await playwright.launchServer(Object.assign({}, defaultBrowserOptions, {
|
||||
args: ['--remote-debugging-port=21222']
|
||||
}));
|
||||
const browserURL = 'http://127.0.0.1:21222';
|
||||
|
||||
let error = null;
|
||||
await playwright.connect({browserURL, browserWSEndpoint: originalBrowser.chromium.wsEndpoint()}).catch(e => error = e);
|
||||
await playwright.connect({browserURL, browserWSEndpoint: browserServer.wsEndpoint()}).catch(e => error = e);
|
||||
expect(error.message).toContain('Exactly one of browserWSEndpoint, browserURL or transport');
|
||||
|
||||
originalBrowser.close();
|
||||
browserServer.close();
|
||||
});
|
||||
it('should throw when trying to connect to non-existing browser', async({server}) => {
|
||||
const originalBrowser = await playwright.launch(Object.assign({}, defaultBrowserOptions, {
|
||||
|
|
@ -160,30 +160,33 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
describe('Playwright.launch |pipe| option', function() {
|
||||
it('should support the pipe option', async() => {
|
||||
const options = Object.assign({pipe: true}, defaultBrowserOptions);
|
||||
const browser = await playwright.launch(options);
|
||||
const browserServer = await playwright.launchServer(options);
|
||||
const browser = await browserServer.connect();
|
||||
expect((await browser.defaultContext().pages()).length).toBe(1);
|
||||
expect(browser.chromium.wsEndpoint()).toBe('');
|
||||
expect(browserServer.wsEndpoint()).toBe('');
|
||||
const page = await browser.defaultContext().newPage();
|
||||
expect(await page.evaluate('11 * 11')).toBe(121);
|
||||
await page.close();
|
||||
await browser.close();
|
||||
await browserServer.close();
|
||||
});
|
||||
it('should support the pipe argument', async() => {
|
||||
const options = Object.assign({}, defaultBrowserOptions);
|
||||
options.args = ['--remote-debugging-pipe'].concat(options.args || []);
|
||||
const browser = await playwright.launch(options);
|
||||
expect(browser.chromium.wsEndpoint()).toBe('');
|
||||
const browserServer = await playwright.launchServer(options);
|
||||
const browser = await browserServer.connect();
|
||||
expect(browserServer.wsEndpoint()).toBe('');
|
||||
const page = await browser.defaultContext().newPage();
|
||||
expect(await page.evaluate('11 * 11')).toBe(121);
|
||||
await page.close();
|
||||
await browser.close();
|
||||
await browserServer.close();
|
||||
});
|
||||
it('should fire "disconnected" when closing with pipe', async() => {
|
||||
const options = Object.assign({pipe: true}, defaultBrowserOptions);
|
||||
const browser = await playwright.launch(options);
|
||||
const browserServer = await playwright.launchServer(options);
|
||||
const browser = await browserServer.connect();
|
||||
const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve));
|
||||
// Emulate user exiting browser.
|
||||
browser.process().kill();
|
||||
browserServer.process().kill();
|
||||
await disconnectedEventPromise;
|
||||
});
|
||||
});
|
||||
|
|
@ -193,9 +196,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
it('should work', async({server}) => {
|
||||
const browser = await playwright.launch(defaultBrowserOptions);
|
||||
const events = [];
|
||||
browser.chromium.on('targetcreated', () => events.push('CREATED'));
|
||||
browser.chromium.on('targetchanged', () => events.push('CHANGED'));
|
||||
browser.chromium.on('targetdestroyed', () => events.push('DESTROYED'));
|
||||
browser.on('targetcreated', () => events.push('CREATED'));
|
||||
browser.on('targetchanged', () => events.push('CHANGED'));
|
||||
browser.on('targetdestroyed', () => events.push('DESTROYED'));
|
||||
const page = await browser.defaultContext().newPage();
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await page.close();
|
||||
|
|
@ -206,8 +209,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
|
||||
describe('Browser.Events.disconnected', function() {
|
||||
it('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async() => {
|
||||
const originalBrowser = await playwright.launch(defaultBrowserOptions);
|
||||
const browserWSEndpoint = originalBrowser.chromium.wsEndpoint();
|
||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||
const originalBrowser = await browserServer.connect();
|
||||
const browserWSEndpoint = browserServer.wsEndpoint();
|
||||
const remoteBrowser1 = await playwright.connect({browserWSEndpoint});
|
||||
const remoteBrowser2 = await playwright.connect({browserWSEndpoint});
|
||||
|
||||
|
|
@ -230,7 +234,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
await Promise.all([
|
||||
waitEvent(remoteBrowser1, 'disconnected'),
|
||||
waitEvent(originalBrowser, 'disconnected'),
|
||||
originalBrowser.close(),
|
||||
browserServer.close(),
|
||||
]);
|
||||
|
||||
expect(disconnectedOriginal).toBe(1);
|
||||
|
|
|
|||
|
|
@ -54,5 +54,5 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
|
||||
|
||||
function oopifs(browser) {
|
||||
return browser.chromium.targets().filter(target => target._targetInfo.type === 'iframe');
|
||||
return browser.targets().filter(target => target._targetInfo.type === 'iframe');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
|
|||
|
||||
describe('Chromium.createCDPSession', function() {
|
||||
it('should work', async function({page, browser, server}) {
|
||||
const client = await browser.chromium.pageTarget(page).createCDPSession();
|
||||
const client = await browser.pageTarget(page).createCDPSession();
|
||||
|
||||
await Promise.all([
|
||||
client.send('Runtime.enable'),
|
||||
|
|
@ -33,7 +33,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
|
|||
expect(foo).toBe('bar');
|
||||
});
|
||||
it('should send events', async function({page, browser, server}) {
|
||||
const client = await browser.chromium.pageTarget(page).createCDPSession();
|
||||
const client = await browser.pageTarget(page).createCDPSession();
|
||||
await client.send('Network.enable');
|
||||
const events = [];
|
||||
client.on('Network.requestWillBeSent', event => events.push(event));
|
||||
|
|
@ -41,7 +41,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
|
|||
expect(events.length).toBe(1);
|
||||
});
|
||||
it('should enable and disable domains independently', async function({page, browser, server}) {
|
||||
const client = await browser.chromium.pageTarget(page).createCDPSession();
|
||||
const client = await browser.pageTarget(page).createCDPSession();
|
||||
await client.send('Runtime.enable');
|
||||
await client.send('Debugger.enable');
|
||||
// JS coverage enables and then disables Debugger domain.
|
||||
|
|
@ -56,7 +56,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
|
|||
expect(event.url).toBe('foo.js');
|
||||
});
|
||||
it('should be able to detach session', async function({page, browser, server}) {
|
||||
const client = await browser.chromium.pageTarget(page).createCDPSession();
|
||||
const client = await browser.pageTarget(page).createCDPSession();
|
||||
await client.send('Runtime.enable');
|
||||
const evalResponse = await client.send('Runtime.evaluate', {expression: '1 + 2', returnByValue: true});
|
||||
expect(evalResponse.result.value).toBe(3);
|
||||
|
|
@ -70,7 +70,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
|
|||
expect(error.message).toContain('Session closed.');
|
||||
});
|
||||
it('should throw nice errors', async function({page, browser}) {
|
||||
const client = await browser.chromium.pageTarget(page).createCDPSession();
|
||||
const client = await browser.pageTarget(page).createCDPSession();
|
||||
const error = await theSourceOfTheProblems().catch(error => error);
|
||||
expect(error.stack).toContain('theSourceOfTheProblems');
|
||||
expect(error.message).toContain('ThisCommand.DoesNotExist');
|
||||
|
|
|
|||
|
|
@ -38,55 +38,55 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||
}
|
||||
});
|
||||
it('should output a trace', async({browser, page, server, outputFile}) => {
|
||||
await browser.chromium.startTracing(page, {screenshots: true, path: outputFile});
|
||||
await browser.startTracing(page, {screenshots: true, path: outputFile});
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
await browser.chromium.stopTracing();
|
||||
await browser.stopTracing();
|
||||
expect(fs.existsSync(outputFile)).toBe(true);
|
||||
});
|
||||
it('should run with custom categories if provided', async({browser, page, outputFile}) => {
|
||||
await browser.chromium.startTracing(page, {path: outputFile, categories: ['disabled-by-default-v8.cpu_profiler.hires']});
|
||||
await browser.chromium.stopTracing();
|
||||
await browser.startTracing(page, {path: outputFile, categories: ['disabled-by-default-v8.cpu_profiler.hires']});
|
||||
await browser.stopTracing();
|
||||
|
||||
const traceJson = JSON.parse(fs.readFileSync(outputFile).toString());
|
||||
expect(traceJson.metadata['trace-config']).toContain('disabled-by-default-v8.cpu_profiler.hires');
|
||||
});
|
||||
it('should throw if tracing on two pages', async({browser, page, server, outputFile}) => {
|
||||
await browser.chromium.startTracing(page, {path: outputFile});
|
||||
await browser.startTracing(page, {path: outputFile});
|
||||
const newPage = await browser.defaultContext().newPage();
|
||||
let error = null;
|
||||
await browser.chromium.startTracing(newPage, {path: outputFile}).catch(e => error = e);
|
||||
await browser.startTracing(newPage, {path: outputFile}).catch(e => error = e);
|
||||
await newPage.close();
|
||||
expect(error).toBeTruthy();
|
||||
await browser.chromium.stopTracing();
|
||||
await browser.stopTracing();
|
||||
});
|
||||
it('should return a buffer', async({browser, page, server, outputFile}) => {
|
||||
await browser.chromium.startTracing(page, {screenshots: true, path: outputFile});
|
||||
await browser.startTracing(page, {screenshots: true, path: outputFile});
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
const trace = await browser.chromium.stopTracing();
|
||||
const trace = await browser.stopTracing();
|
||||
const buf = fs.readFileSync(outputFile);
|
||||
expect(trace.toString()).toEqual(buf.toString());
|
||||
});
|
||||
it('should work without options', async({browser, page, server, outputFile}) => {
|
||||
await browser.chromium.startTracing(page);
|
||||
await browser.startTracing(page);
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
const trace = await browser.chromium.stopTracing();
|
||||
const trace = await browser.stopTracing();
|
||||
expect(trace).toBeTruthy();
|
||||
});
|
||||
it('should return null in case of Buffer error', async({browser, page, server}) => {
|
||||
await browser.chromium.startTracing(page, {screenshots: true});
|
||||
await browser.startTracing(page, {screenshots: true});
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
const oldBufferConcat = Buffer.concat;
|
||||
Buffer.concat = bufs => {
|
||||
throw 'error';
|
||||
};
|
||||
const trace = await browser.chromium.stopTracing();
|
||||
const trace = await browser.stopTracing();
|
||||
expect(trace).toEqual(null);
|
||||
Buffer.concat = oldBufferConcat;
|
||||
});
|
||||
it('should support a buffer without a path', async({browser, page, server}) => {
|
||||
await browser.chromium.startTracing(page, {screenshots: true});
|
||||
await browser.startTracing(page, {screenshots: true});
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
const trace = await browser.chromium.stopTracing();
|
||||
const trace = await browser.stopTracing();
|
||||
expect(trace.toString()).toContain('screenshot');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
module.exports.describe = function({testRunner, expect, headless, playwright, FFOX, CHROME, WEBKIT}) {
|
||||
const {describe, xdescribe, fdescribe} = testRunner;
|
||||
const {it, fit, xit} = testRunner;
|
||||
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
||||
|
||||
describe('Browser.process', function() {
|
||||
it('should not return child_process for remote browser', async function({browser}) {
|
||||
const browserWSEndpoint = browser.firefox.wsEndpoint();
|
||||
const remoteBrowser = await playwright.connect({browserWSEndpoint});
|
||||
expect(remoteBrowser.process()).toBe(null);
|
||||
remoteBrowser.disconnect();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -65,15 +65,15 @@ module.exports.describe = function ({ testRunner, expect, defaultBrowserOptions,
|
|||
it('should filter out ignored default arguments', async() => {
|
||||
// Make sure we launch with `--enable-automation` by default.
|
||||
const defaultArgs = playwright.defaultArgs(defaultBrowserOptions);
|
||||
const browser = await playwright.launch(Object.assign({}, defaultBrowserOptions, {
|
||||
const browserServer = await playwright.launchServer(Object.assign({}, defaultBrowserOptions, {
|
||||
// Ignore first and third default argument.
|
||||
ignoreDefaultArgs: [ defaultArgs[0], defaultArgs[2] ],
|
||||
}));
|
||||
const spawnargs = browser.process().spawnargs;
|
||||
const spawnargs = browserServer.process().spawnargs;
|
||||
expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1);
|
||||
expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1);
|
||||
expect(spawnargs.indexOf(defaultArgs[2])).toBe(-1);
|
||||
await browser.close();
|
||||
await browserServer.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
7
test/fixtures/closeme.js
vendored
7
test/fixtures/closeme.js
vendored
|
|
@ -1,8 +1,5 @@
|
|||
(async() => {
|
||||
const [, , playwrightRoot, options] = process.argv;
|
||||
const browser = await require(playwrightRoot).launch(JSON.parse(options));
|
||||
if (browser.chromium)
|
||||
console.log(browser.chromium.wsEndpoint());
|
||||
else if (browser.firefox)
|
||||
console.log(browser.firefox.wsEndpoint());
|
||||
const browserServer = await require(playwrightRoot).launchServer(JSON.parse(options));
|
||||
console.log(browserServer.wsEndpoint());
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -90,12 +90,14 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
|
|||
|
||||
describe('Browser', function() {
|
||||
beforeAll(async state => {
|
||||
state.browser = await playwright.launch(defaultBrowserOptions);
|
||||
state.browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||
state.browser = await state.browserServer.connect();
|
||||
});
|
||||
|
||||
afterAll(async state => {
|
||||
await state.browser.close();
|
||||
await state.browserServer.close();
|
||||
state.browser = null;
|
||||
state.browserServer = null;
|
||||
});
|
||||
|
||||
beforeEach(async(state, test) => {
|
||||
|
|
@ -104,7 +106,7 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
|
|||
|
||||
let rl;
|
||||
if (!WEBKIT) {
|
||||
rl = require('readline').createInterface({ input: state.browser.process().stderr });
|
||||
rl = require('readline').createInterface({ input: state.browserServer.process().stderr });
|
||||
test.output = '';
|
||||
rl.on('line', onLine);
|
||||
}
|
||||
|
|
@ -188,9 +190,6 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
|
|||
if (CHROME) {
|
||||
testRunner.loadTests(require('./chromium/browser.spec.js'), testOptions);
|
||||
}
|
||||
if (FFOX) {
|
||||
testRunner.loadTests(require('./firefox/browser.spec.js'), testOptions);
|
||||
}
|
||||
});
|
||||
|
||||
// Top-level tests that launch Browser themselves.
|
||||
|
|
|
|||
|
|
@ -50,24 +50,25 @@ afterEach(async state => {
|
|||
|
||||
describe('Playwright-Web', () => {
|
||||
it('should work over web socket', async({page, serverConfig}) => {
|
||||
const browser2 = await playwright.launch();
|
||||
const browserServer = await playwright.launchServer();
|
||||
// Use in-page playwright to create a new page and navigate it to the EMPTY_PAGE
|
||||
await page.evaluate(async(browserWSEndpoint, serverConfig) => {
|
||||
const playwright = require('playwright');
|
||||
const browser = await playwright.connect({browserWSEndpoint});
|
||||
const page = await browser.defaultContext().newPage();
|
||||
await page.goto(serverConfig.EMPTY_PAGE);
|
||||
}, browser2.wsEndpoint(), serverConfig);
|
||||
const pageURLs = (await browser2.pages()).map(page => page.url()).sort();
|
||||
}, browserServer.wsEndpoint(), serverConfig);
|
||||
const browser = await browserServer.connect();
|
||||
const pageURLs = (await browser.defaultContext().pages()).map(page => page.url()).sort();
|
||||
expect(pageURLs).toEqual([
|
||||
'about:blank',
|
||||
serverConfig.EMPTY_PAGE
|
||||
]);
|
||||
await browser2.close();
|
||||
await browserServer.close();
|
||||
});
|
||||
it('should work over exposed DevTools protocol', async({browser, page, serverConfig}) => {
|
||||
// Expose devtools protocol binding into page.
|
||||
const session = await browser.chromium.browserTarget().createCDPSession();
|
||||
const session = await browser.browserTarget().createCDPSession();
|
||||
const pageInfo = (await session.send('Target.getTargets')).targetInfos.find(info => info.attached);
|
||||
await session.send('Target.exposeDevToolsProtocol', {targetId: pageInfo.targetId});
|
||||
await session.detach();
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ async function generateChromeProtocol(revision) {
|
|||
if (revision.local && fs.existsSync(outputPath))
|
||||
return;
|
||||
const playwright = await require('../../chromium');
|
||||
const browser = await playwright.launch({executablePath: revision.executablePath});
|
||||
const origin = browser.chromium.wsEndpoint().match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1];
|
||||
const page = await browser.defaultContext().newPage();
|
||||
const browserServer = await playwright.launchServer({executablePath: revision.executablePath});
|
||||
const origin = browserServer.wsEndpoint().match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1];
|
||||
const page = (await browserServer.connect()).defaultContext().newPage();
|
||||
await page.goto(`http://${origin}/json/protocol`);
|
||||
const json = JSON.parse(await page.evaluate(() => document.documentElement.innerText));
|
||||
await browser.close();
|
||||
await browserServer.close();
|
||||
fs.writeFileSync(outputPath, jsonToTS(json));
|
||||
console.log(`Wrote protocol.ts to ${path.relative(process.cwd(), outputPath)}`);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue