mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(connect): allow multiple webkit connections over web socket (#863)
This commit is contained in:
parent
f49d63ff0c
commit
a547aa7984
14
docs/api.md
14
docs/api.md
@ -147,7 +147,6 @@ See [ChromiumBrowser], [FirefoxBrowser] and [WebKitBrowser] for browser-specific
|
|||||||
- [event: 'disconnected'](#event-disconnected)
|
- [event: 'disconnected'](#event-disconnected)
|
||||||
- [browser.browserContexts()](#browserbrowsercontexts)
|
- [browser.browserContexts()](#browserbrowsercontexts)
|
||||||
- [browser.close()](#browserclose)
|
- [browser.close()](#browserclose)
|
||||||
- [browser.disconnect()](#browserdisconnect)
|
|
||||||
- [browser.isConnected()](#browserisconnected)
|
- [browser.isConnected()](#browserisconnected)
|
||||||
- [browser.newContext(options)](#browsernewcontextoptions)
|
- [browser.newContext(options)](#browsernewcontextoptions)
|
||||||
- [browser.newPage(url, [options])](#browsernewpageurl-options)
|
- [browser.newPage(url, [options])](#browsernewpageurl-options)
|
||||||
@ -168,12 +167,11 @@ a single instance of [BrowserContext].
|
|||||||
#### browser.close()
|
#### browser.close()
|
||||||
- returns: <[Promise]>
|
- returns: <[Promise]>
|
||||||
|
|
||||||
Closes browser and all of its pages (if any were opened). The [Browser] object itself is considered to be disposed and cannot be used anymore.
|
In case this browser is obtained using [browserType.launch](#browsertypelaunchoptions), closes the browser and all of its pages (if any were opened).
|
||||||
|
|
||||||
#### browser.disconnect()
|
In case this browser is obtained using [browserType.connect](#browsertypeconnectoptions), clears all created contexts belonging to this browser and disconnects from the browser server.
|
||||||
- returns: <[Promise]>
|
|
||||||
|
|
||||||
Disconnects Browser from the browser application, but leaves the application process running. After calling `disconnect`, the [Browser] object is considered disposed and cannot be used anymore.
|
The [Browser] object itself is considered to be disposed and cannot be used anymore.
|
||||||
|
|
||||||
#### browser.isConnected()
|
#### browser.isConnected()
|
||||||
|
|
||||||
@ -3468,12 +3466,11 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
|
|||||||
|
|
||||||
<!-- GEN:toc -->
|
<!-- GEN:toc -->
|
||||||
- [browserType.connect(options)](#browsertypeconnectoptions)
|
- [browserType.connect(options)](#browsertypeconnectoptions)
|
||||||
- [browserType.defaultArgs([options])](#browsertypedefaultargsoptions)
|
|
||||||
- [browserType.devices](#browsertypedevices)
|
- [browserType.devices](#browsertypedevices)
|
||||||
- [browserType.errors](#browsertypeerrors)
|
- [browserType.errors](#browsertypeerrors)
|
||||||
- [browserType.executablePath()](#browsertypeexecutablepath)
|
- [browserType.executablePath()](#browsertypeexecutablepath)
|
||||||
- [browserType.launch([options])](#browsertypelaunchoptions)
|
- [browserType.launch([options])](#browsertypelaunchoptions)
|
||||||
- [browserType.launchPersistent([options])](#browsertypelaunchpersistentoptions)
|
- [browserType.launchPersistent(userDataDir, [options])](#browsertypelaunchpersistentuserdatadir-options)
|
||||||
- [browserType.launchServer([options])](#browsertypelaunchserveroptions)
|
- [browserType.launchServer([options])](#browsertypelaunchserveroptions)
|
||||||
- [browserType.name()](#browsertypename)
|
- [browserType.name()](#browsertypename)
|
||||||
<!-- GEN:stop -->
|
<!-- GEN:stop -->
|
||||||
@ -3648,7 +3645,6 @@ await browser.stopTracing();
|
|||||||
- [event: 'disconnected'](#event-disconnected)
|
- [event: 'disconnected'](#event-disconnected)
|
||||||
- [browser.browserContexts()](#browserbrowsercontexts)
|
- [browser.browserContexts()](#browserbrowsercontexts)
|
||||||
- [browser.close()](#browserclose)
|
- [browser.close()](#browserclose)
|
||||||
- [browser.disconnect()](#browserdisconnect)
|
|
||||||
- [browser.isConnected()](#browserisconnected)
|
- [browser.isConnected()](#browserisconnected)
|
||||||
- [browser.newContext(options)](#browsernewcontextoptions)
|
- [browser.newContext(options)](#browsernewcontextoptions)
|
||||||
- [browser.newPage(url, [options])](#browsernewpageurl-options)
|
- [browser.newPage(url, [options])](#browsernewpageurl-options)
|
||||||
@ -3816,7 +3812,6 @@ Firefox browser instance does not expose Firefox-specific features.
|
|||||||
- [event: 'disconnected'](#event-disconnected)
|
- [event: 'disconnected'](#event-disconnected)
|
||||||
- [browser.browserContexts()](#browserbrowsercontexts)
|
- [browser.browserContexts()](#browserbrowsercontexts)
|
||||||
- [browser.close()](#browserclose)
|
- [browser.close()](#browserclose)
|
||||||
- [browser.disconnect()](#browserdisconnect)
|
|
||||||
- [browser.isConnected()](#browserisconnected)
|
- [browser.isConnected()](#browserisconnected)
|
||||||
- [browser.newContext(options)](#browsernewcontextoptions)
|
- [browser.newContext(options)](#browsernewcontextoptions)
|
||||||
- [browser.newPage(url, [options])](#browsernewpageurl-options)
|
- [browser.newPage(url, [options])](#browsernewpageurl-options)
|
||||||
@ -3833,7 +3828,6 @@ WebKit browser instance does not expose WebKit-specific features.
|
|||||||
- [event: 'disconnected'](#event-disconnected)
|
- [event: 'disconnected'](#event-disconnected)
|
||||||
- [browser.browserContexts()](#browserbrowsercontexts)
|
- [browser.browserContexts()](#browserbrowsercontexts)
|
||||||
- [browser.close()](#browserclose)
|
- [browser.close()](#browserclose)
|
||||||
- [browser.disconnect()](#browserdisconnect)
|
|
||||||
- [browser.isConnected()](#browserisconnected)
|
- [browser.isConnected()](#browserisconnected)
|
||||||
- [browser.newContext(options)](#browsernewcontextoptions)
|
- [browser.newContext(options)](#browsernewcontextoptions)
|
||||||
- [browser.newPage(url, [options])](#browsernewpageurl-options)
|
- [browser.newPage(url, [options])](#browsernewpageurl-options)
|
||||||
|
@ -12,7 +12,7 @@ API consists of a single `connect` function, similar to [browserType.connect(opt
|
|||||||
async function usePlaywright() {
|
async function usePlaywright() {
|
||||||
const browser = await window.playwrightweb.chromium.connect(options); // or 'firefox', 'webkit'
|
const browser = await window.playwrightweb.chromium.connect(options); // or 'firefox', 'webkit'
|
||||||
// ... drive automation ...
|
// ... drive automation ...
|
||||||
await browser.disconnect();
|
await browser.close();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
@ -23,7 +23,6 @@ export interface Browser extends platform.EventEmitterType {
|
|||||||
browserContexts(): BrowserContext[];
|
browserContexts(): BrowserContext[];
|
||||||
pages(): Promise<Page[]>;
|
pages(): Promise<Page[]>;
|
||||||
newPage(url?: string, options?: BrowserContextOptions): Promise<Page>;
|
newPage(url?: string, options?: BrowserContextOptions): Promise<Page>;
|
||||||
disconnect(): Promise<void>;
|
|
||||||
isConnected(): boolean;
|
isConnected(): boolean;
|
||||||
close(): Promise<void>;
|
close(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -44,28 +44,23 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
|||||||
|
|
||||||
static async connect(transport: ConnectionTransport, slowMo?: number): Promise<CRBrowser> {
|
static async connect(transport: ConnectionTransport, slowMo?: number): Promise<CRBrowser> {
|
||||||
const connection = new CRConnection(SlowMoTransport.wrap(transport, slowMo));
|
const connection = new CRConnection(SlowMoTransport.wrap(transport, slowMo));
|
||||||
const { browserContextIds } = await connection.rootSession.send('Target.getBrowserContexts');
|
const browser = new CRBrowser(connection);
|
||||||
const browser = new CRBrowser(connection, browserContextIds);
|
|
||||||
await connection.rootSession.send('Target.setDiscoverTargets', { discover: true });
|
await connection.rootSession.send('Target.setDiscoverTargets', { discover: true });
|
||||||
await browser.waitForTarget(t => t.type() === 'page');
|
await browser.waitForTarget(t => t.type() === 'page');
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(connection: CRConnection, contextIds: string[]) {
|
constructor(connection: CRConnection) {
|
||||||
super();
|
super();
|
||||||
this._connection = connection;
|
this._connection = connection;
|
||||||
this._client = connection.rootSession;
|
this._client = connection.rootSession;
|
||||||
|
|
||||||
this._defaultContext = this._createBrowserContext(null, {});
|
this._defaultContext = this._createBrowserContext(null, {});
|
||||||
for (const contextId of contextIds)
|
|
||||||
this._contexts.set(contextId, this._createBrowserContext(contextId, {}));
|
|
||||||
|
|
||||||
this._connection.on(ConnectionEvents.Disconnected, () => this.emit(CommonEvents.Browser.Disconnected));
|
this._connection.on(ConnectionEvents.Disconnected, () => this.emit(CommonEvents.Browser.Disconnected));
|
||||||
this._client.on('Target.targetCreated', this._targetCreated.bind(this));
|
this._client.on('Target.targetCreated', this._targetCreated.bind(this));
|
||||||
this._client.on('Target.targetDestroyed', this._targetDestroyed.bind(this));
|
this._client.on('Target.targetDestroyed', this._targetDestroyed.bind(this));
|
||||||
this._client.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this));
|
this._client.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
_createBrowserContext(contextId: string | null, options: BrowserContextOptions): BrowserContext {
|
_createBrowserContext(contextId: string | null, options: BrowserContextOptions): BrowserContext {
|
||||||
const context = new BrowserContext({
|
const context = new BrowserContext({
|
||||||
pages: async (): Promise<Page[]> => {
|
pages: async (): Promise<Page[]> => {
|
||||||
@ -245,7 +240,8 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
|||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
const disconnected = new Promise(f => this._connection.once(ConnectionEvents.Disconnected, f));
|
const disconnected = new Promise(f => this._connection.once(ConnectionEvents.Disconnected, f));
|
||||||
await this._connection.rootSession.send('Browser.close');
|
await Promise.all(this.browserContexts().map(context => context.close()));
|
||||||
|
this._connection.close();
|
||||||
await disconnected;
|
await disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,12 +301,6 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
|
|||||||
return CRTarget.fromPage(page);
|
return CRTarget.fromPage(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnect() {
|
|
||||||
const disconnected = new Promise(f => this.once(CommonEvents.Browser.Disconnected, f));
|
|
||||||
this._connection.close();
|
|
||||||
await disconnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
isConnected(): boolean {
|
isConnected(): boolean {
|
||||||
return !this._connection._closed;
|
return !this._connection._closed;
|
||||||
}
|
}
|
||||||
|
@ -37,23 +37,19 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
|
|||||||
|
|
||||||
static async connect(transport: ConnectionTransport, slowMo?: number): Promise<FFBrowser> {
|
static async connect(transport: ConnectionTransport, slowMo?: number): Promise<FFBrowser> {
|
||||||
const connection = new FFConnection(SlowMoTransport.wrap(transport, slowMo));
|
const connection = new FFConnection(SlowMoTransport.wrap(transport, slowMo));
|
||||||
const { browserContextIds } = await connection.send('Target.getBrowserContexts');
|
const browser = new FFBrowser(connection);
|
||||||
const browser = new FFBrowser(connection, browserContextIds);
|
|
||||||
await connection.send('Target.enable');
|
await connection.send('Target.enable');
|
||||||
await browser._waitForTarget(t => t.type() === 'page');
|
await browser._waitForTarget(t => t.type() === 'page');
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(connection: FFConnection, browserContextIds: Array<string>) {
|
constructor(connection: FFConnection) {
|
||||||
super();
|
super();
|
||||||
this._connection = connection;
|
this._connection = connection;
|
||||||
this._targets = new Map();
|
this._targets = new Map();
|
||||||
|
|
||||||
this._defaultContext = this._createBrowserContext(null, {});
|
this._defaultContext = this._createBrowserContext(null, {});
|
||||||
this._contexts = new Map();
|
this._contexts = new Map();
|
||||||
for (const browserContextId of browserContextIds)
|
|
||||||
this._contexts.set(browserContextId, this._createBrowserContext(browserContextId, {}));
|
|
||||||
|
|
||||||
this._connection.on(ConnectionEvents.Disconnected, () => this.emit(Events.Browser.Disconnected));
|
this._connection.on(ConnectionEvents.Disconnected, () => this.emit(Events.Browser.Disconnected));
|
||||||
|
|
||||||
this._eventListeners = [
|
this._eventListeners = [
|
||||||
@ -63,12 +59,6 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnect() {
|
|
||||||
const disconnected = new Promise(f => this.once(Events.Browser.Disconnected, f));
|
|
||||||
this._connection.close();
|
|
||||||
await disconnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
isConnected(): boolean {
|
isConnected(): boolean {
|
||||||
return !this._connection._closed;
|
return !this._connection._closed;
|
||||||
}
|
}
|
||||||
@ -154,9 +144,10 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
|
await Promise.all(this.browserContexts().map(context => context.close()));
|
||||||
helper.removeEventListeners(this._eventListeners);
|
helper.removeEventListeners(this._eventListeners);
|
||||||
const disconnected = new Promise(f => this._connection.once(ConnectionEvents.Disconnected, f));
|
const disconnected = new Promise(f => this.once(Events.Browser.Disconnected, f));
|
||||||
await this._connection.send('Browser.close');
|
this._connection.close();
|
||||||
await disconnected;
|
await disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,14 +276,22 @@ export function fetchUrl(url: string): Promise<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class WebSocketTransport implements ConnectionTransport {
|
export class WebSocketTransport implements ConnectionTransport {
|
||||||
private _ws: WebSocket;
|
private _ws: WebSocket;
|
||||||
|
|
||||||
onmessage?: (message: string) => void;
|
onmessage?: (message: string) => void;
|
||||||
onclose?: () => void;
|
onclose?: () => void;
|
||||||
|
private _connect: Promise<void>;
|
||||||
|
|
||||||
constructor(ws: WebSocket) {
|
constructor(url: string) {
|
||||||
this._ws = ws;
|
this._ws = (isNode ? new NodeWebSocket(url, [], {
|
||||||
|
perMessageDeflate: false,
|
||||||
|
maxPayload: 256 * 1024 * 1024, // 256Mb
|
||||||
|
}) : new WebSocket(url)) as WebSocket;
|
||||||
|
this._connect = new Promise((fulfill, reject) => {
|
||||||
|
this._ws.addEventListener('open', () => fulfill());
|
||||||
|
this._ws.addEventListener('error', event => reject(new Error(event.toString())));
|
||||||
|
});
|
||||||
this._ws.addEventListener('message', event => {
|
this._ws.addEventListener('message', event => {
|
||||||
if (this.onmessage)
|
if (this.onmessage)
|
||||||
this.onmessage.call(null, event.data);
|
this.onmessage.call(null, event.data);
|
||||||
@ -296,7 +304,8 @@ class WebSocketTransport implements ConnectionTransport {
|
|||||||
this._ws.addEventListener('error', () => {});
|
this._ws.addEventListener('error', () => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
send(message: string) {
|
async send(message: string) {
|
||||||
|
await this._connect;
|
||||||
this._ws.send(message);
|
this._ws.send(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,14 +313,3 @@ class WebSocketTransport implements ConnectionTransport {
|
|||||||
this._ws.close();
|
this._ws.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createWebSocketTransport(url: string): Promise<ConnectionTransport> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const ws = (isNode ? new NodeWebSocket(url, [], {
|
|
||||||
perMessageDeflate: false,
|
|
||||||
maxPayload: 256 * 1024 * 1024, // 256Mb
|
|
||||||
}) : new WebSocket(url)) as WebSocket;
|
|
||||||
ws.addEventListener('open', () => resolve(new WebSocketTransport(ws)));
|
|
||||||
ws.addEventListener('error', reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
@ -122,9 +122,9 @@ export class Chromium implements BrowserType {
|
|||||||
// We try to gracefully close to prevent crash reporting and core dumps.
|
// We try to gracefully close to prevent crash reporting and core dumps.
|
||||||
// Note that it's fine to reuse the pipe transport, since
|
// Note that it's fine to reuse the pipe transport, since
|
||||||
// our connection ignores kBrowserCloseMessageId.
|
// our connection ignores kBrowserCloseMessageId.
|
||||||
const t = transport || await platform.createWebSocketTransport(browserWSEndpoint!);
|
const t = transport || new platform.WebSocketTransport(browserWSEndpoint!);
|
||||||
const message = { method: 'Browser.close', id: kBrowserCloseMessageId };
|
const message = { method: 'Browser.close', id: kBrowserCloseMessageId };
|
||||||
t.send(JSON.stringify(message));
|
await t.send(JSON.stringify(message));
|
||||||
},
|
},
|
||||||
onkill: (exitCode, signal) => {
|
onkill: (exitCode, signal) => {
|
||||||
if (browserServer)
|
if (browserServer)
|
||||||
@ -147,7 +147,7 @@ export class Chromium implements BrowserType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async connect(options: ConnectOptions): Promise<CRBrowser> {
|
async connect(options: ConnectOptions): Promise<CRBrowser> {
|
||||||
const transport = await platform.createWebSocketTransport(options.wsEndpoint);
|
const transport = new platform.WebSocketTransport(options.wsEndpoint);
|
||||||
return CRBrowser.connect(transport, options.slowMo);
|
return CRBrowser.connect(transport, options.slowMo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ export class Firefox implements BrowserType {
|
|||||||
return browserContext;
|
return browserContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _launchServer(options: LaunchOptions = {}, launchType: LaunchType, userDataDir?: string, port?: number): Promise<{ browserServer: BrowserServer, transport?: ConnectionTransport }> {
|
private async _launchServer(options: LaunchOptions = {}, connectionType: LaunchType, userDataDir?: string, port?: number): Promise<{ browserServer: BrowserServer, transport?: ConnectionTransport }> {
|
||||||
const {
|
const {
|
||||||
ignoreDefaultArgs = false,
|
ignoreDefaultArgs = false,
|
||||||
args = [],
|
args = [],
|
||||||
@ -128,9 +128,9 @@ export class Firefox implements BrowserType {
|
|||||||
// We try to gracefully close to prevent crash reporting and core dumps.
|
// We try to gracefully close to prevent crash reporting and core dumps.
|
||||||
// Note that it's fine to reuse the pipe transport, since
|
// Note that it's fine to reuse the pipe transport, since
|
||||||
// our connection ignores kBrowserCloseMessageId.
|
// our connection ignores kBrowserCloseMessageId.
|
||||||
const transport = await platform.createWebSocketTransport(browserWSEndpoint);
|
const transport = new platform.WebSocketTransport(browserWSEndpoint);
|
||||||
const message = { method: 'Browser.close', params: {}, id: kBrowserCloseMessageId };
|
const message = { method: 'Browser.close', params: {}, id: kBrowserCloseMessageId };
|
||||||
transport.send(JSON.stringify(message));
|
await transport.send(JSON.stringify(message));
|
||||||
},
|
},
|
||||||
onkill: (exitCode, signal) => {
|
onkill: (exitCode, signal) => {
|
||||||
if (browserServer)
|
if (browserServer)
|
||||||
@ -141,12 +141,12 @@ export class Firefox implements BrowserType {
|
|||||||
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Firefox!`);
|
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Firefox!`);
|
||||||
const match = await waitForLine(launchedProcess, launchedProcess.stdout, /^Juggler listening on (ws:\/\/.*)$/, timeout, timeoutError);
|
const match = await waitForLine(launchedProcess, launchedProcess.stdout, /^Juggler listening on (ws:\/\/.*)$/, timeout, timeoutError);
|
||||||
const browserWSEndpoint = match[1];
|
const browserWSEndpoint = match[1];
|
||||||
browserServer = new BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? browserWSEndpoint : null);
|
browserServer = new BrowserServer(launchedProcess, gracefullyClose, connectionType === 'server' ? browserWSEndpoint : null);
|
||||||
return { browserServer, transport: launchType === 'server' ? undefined : await platform.createWebSocketTransport(browserWSEndpoint) };
|
return { browserServer, transport: connectionType === 'server' ? undefined : new platform.WebSocketTransport(browserWSEndpoint) };
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(options: ConnectOptions): Promise<FFBrowser> {
|
async connect(options: ConnectOptions): Promise<FFBrowser> {
|
||||||
const transport = await platform.createWebSocketTransport(options.wsEndpoint);
|
const transport = new platform.WebSocketTransport(options.wsEndpoint);
|
||||||
return FFBrowser.connect(transport, options.slowMo);
|
return FFBrowser.connect(transport, options.slowMo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ export class WebKit implements BrowserType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async connect(options: ConnectOptions): Promise<WKBrowser> {
|
async connect(options: ConnectOptions): Promise<WKBrowser> {
|
||||||
const transport = await platform.createWebSocketTransport(options.wsEndpoint);
|
const transport = new platform.WebSocketTransport(options.wsEndpoint);
|
||||||
return WKBrowser.connect(transport, options.slowMo);
|
return WKBrowser.connect(transport, options.slowMo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,36 +236,160 @@ function getMacVersion(): string {
|
|||||||
return cachedMacVersion;
|
return cachedMacVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SequenceNumberMixer<V> {
|
||||||
|
static _lastSequenceNumber = 1;
|
||||||
|
private _values = new Map<number, V>();
|
||||||
|
|
||||||
|
generate(value: V): number {
|
||||||
|
const sequenceNumber = ++SequenceNumberMixer._lastSequenceNumber;
|
||||||
|
this._values.set(sequenceNumber, value);
|
||||||
|
return sequenceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
take(sequenceNumber: number): V | undefined {
|
||||||
|
const value = this._values.get(sequenceNumber);
|
||||||
|
this._values.delete(sequenceNumber);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number) {
|
function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number) {
|
||||||
const server = new ws.Server({ port });
|
const server = new ws.Server({ port });
|
||||||
let socket: ws | undefined;
|
|
||||||
const guid = uuidv4();
|
const guid = uuidv4();
|
||||||
|
const idMixer = new SequenceNumberMixer<{id: number, socket: ws}>();
|
||||||
|
const pendingBrowserContextCreations = new Set<number>();
|
||||||
|
const pendingBrowserContextDeletions = new Map<number, string>();
|
||||||
|
const browserContextIds = new Map<string, ws>();
|
||||||
|
const pageProxyIds = new Map<string, ws>();
|
||||||
|
const sockets = new Set<ws>();
|
||||||
|
|
||||||
server.on('connection', (s, req) => {
|
|
||||||
if (req.url !== '/' + guid) {
|
|
||||||
s.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (socket) {
|
|
||||||
s.close(undefined, 'Multiple connections are not supported');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
socket = s;
|
|
||||||
s.on('message', message => transport.send(Buffer.from(message).toString()));
|
|
||||||
transport.onmessage = message => {
|
transport.onmessage = message => {
|
||||||
// We are not notified when socket starts closing, and sending messages to a closing
|
const parsedMessage = JSON.parse(message);
|
||||||
// socket throws an error.
|
if ('id' in parsedMessage) {
|
||||||
if (s.readyState !== ws.CLOSING)
|
if (parsedMessage.id === -9999)
|
||||||
s.send(message);
|
return;
|
||||||
|
// Process command response.
|
||||||
|
const value = idMixer.take(parsedMessage.id);
|
||||||
|
if (!value)
|
||||||
|
return;
|
||||||
|
const { id, socket } = value;
|
||||||
|
|
||||||
|
if (!socket || socket.readyState === ws.CLOSING) {
|
||||||
|
if (pendingBrowserContextCreations.has(id)) {
|
||||||
|
transport.send(JSON.stringify({
|
||||||
|
id: ++SequenceNumberMixer._lastSequenceNumber,
|
||||||
|
method: 'Browser.deleteContext',
|
||||||
|
params: { browserContextId: parsedMessage.result.browserContextId }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingBrowserContextCreations.has(parsedMessage.id)) {
|
||||||
|
// Browser.createContext response -> establish context attribution.
|
||||||
|
browserContextIds.set(parsedMessage.result.browserContextId, socket);
|
||||||
|
pendingBrowserContextCreations.delete(parsedMessage.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedContextId = pendingBrowserContextDeletions.get(parsedMessage.id);
|
||||||
|
if (deletedContextId) {
|
||||||
|
// Browser.deleteContext response -> remove context attribution.
|
||||||
|
browserContextIds.delete(deletedContextId);
|
||||||
|
pendingBrowserContextDeletions.delete(parsedMessage.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedMessage.id = id;
|
||||||
|
socket.send(JSON.stringify(parsedMessage));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process notification response.
|
||||||
|
const { method, params, pageProxyId } = parsedMessage;
|
||||||
|
if (pageProxyId) {
|
||||||
|
const socket = pageProxyIds.get(pageProxyId);
|
||||||
|
if (!socket || socket.readyState === ws.CLOSING) {
|
||||||
|
// Drop unattributed messages on the floor.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket.send(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (method === 'Browser.pageProxyCreated') {
|
||||||
|
const socket = browserContextIds.get(params.pageProxyInfo.browserContextId);
|
||||||
|
if (!socket || socket.readyState === ws.CLOSING) {
|
||||||
|
// Drop unattributed messages on the floor.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pageProxyIds.set(params.pageProxyInfo.pageProxyId, socket);
|
||||||
|
socket.send(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (method === 'Browser.pageProxyDestroyed') {
|
||||||
|
const socket = pageProxyIds.get(params.pageProxyId);
|
||||||
|
pageProxyIds.delete(params.pageProxyId);
|
||||||
|
if (socket && socket.readyState !== ws.CLOSING)
|
||||||
|
socket.send(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (method === 'Browser.provisionalLoadFailed') {
|
||||||
|
const socket = pageProxyIds.get(params.pageProxyId);
|
||||||
|
if (socket && socket.readyState !== ws.CLOSING)
|
||||||
|
socket!.send(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
s.on('close', () => {
|
|
||||||
socket = undefined;
|
server.on('connection', (socket: ws, req) => {
|
||||||
transport.onmessage = undefined;
|
if (req.url !== '/' + guid) {
|
||||||
|
socket.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sockets.add(socket);
|
||||||
|
// Following two messages are reporting the default browser context and the default page.
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
method: 'Browser.pageProxyCreated',
|
||||||
|
params: { pageProxyInfo: { pageProxyId: '5', browserContextId: '0000000000000002' } }
|
||||||
|
}));
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
method: 'Target.targetCreated',
|
||||||
|
params: {
|
||||||
|
targetInfo: { targetId: 'page-6', type: 'page', isPaused: false }
|
||||||
|
},
|
||||||
|
pageProxyId: '5'
|
||||||
|
}));
|
||||||
|
|
||||||
|
socket.on('message', (message: string) => {
|
||||||
|
const parsedMessage = JSON.parse(Buffer.from(message).toString());
|
||||||
|
const { id, method, params } = parsedMessage;
|
||||||
|
const seqNum = idMixer.generate({ id, socket });
|
||||||
|
transport.send(JSON.stringify({ ...parsedMessage, id: seqNum }));
|
||||||
|
if (method === 'Browser.createContext')
|
||||||
|
pendingBrowserContextCreations.add(seqNum);
|
||||||
|
if (method === 'Browser.deleteContext')
|
||||||
|
pendingBrowserContextDeletions.set(seqNum, params.browserContextId);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('close', () => {
|
||||||
|
for (const [pageProxyId, s] of pageProxyIds) {
|
||||||
|
if (s === socket)
|
||||||
|
pageProxyIds.delete(pageProxyId);
|
||||||
|
}
|
||||||
|
for (const [browserContextId, s] of browserContextIds) {
|
||||||
|
if (s === socket) {
|
||||||
|
transport.send(JSON.stringify({
|
||||||
|
id: ++SequenceNumberMixer._lastSequenceNumber,
|
||||||
|
method: 'Browser.deleteContext',
|
||||||
|
params: { browserContextId }
|
||||||
|
}));
|
||||||
|
browserContextIds.delete(browserContextId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sockets.delete(socket);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
transport.onclose = () => {
|
transport.onclose = () => {
|
||||||
if (socket)
|
for (const socket of sockets)
|
||||||
socket.close(undefined, 'Browser disconnected');
|
socket.close(undefined, 'Browser disconnected');
|
||||||
server.close();
|
server.close();
|
||||||
transport.onmessage = undefined;
|
transport.onmessage = undefined;
|
||||||
|
@ -22,19 +22,19 @@ import * as platform from './platform';
|
|||||||
const connect = {
|
const connect = {
|
||||||
chromium: {
|
chromium: {
|
||||||
connect: async (url: string) => {
|
connect: async (url: string) => {
|
||||||
const transport = await platform.createWebSocketTransport(url);
|
const transport = new platform.WebSocketTransport(url);
|
||||||
return ChromiumBrowser.connect(transport);
|
return ChromiumBrowser.connect(transport);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
webkit: {
|
webkit: {
|
||||||
connect: async (url: string) => {
|
connect: async (url: string) => {
|
||||||
const transport = await platform.createWebSocketTransport(url);
|
const transport = new platform.WebSocketTransport(url);
|
||||||
return WebKitBrowser.connect(transport);
|
return WebKitBrowser.connect(transport);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
firefox: {
|
firefox: {
|
||||||
connect: async (url: string) => {
|
connect: async (url: string) => {
|
||||||
const transport = await platform.createWebSocketTransport(url);
|
const transport = new platform.WebSocketTransport(url);
|
||||||
return FirefoxBrowser.connect(transport);
|
return FirefoxBrowser.connect(transport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,12 +157,6 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
|
|||||||
pageProxy.handleProvisionalLoadFailed(event);
|
pageProxy.handleProvisionalLoadFailed(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnect() {
|
|
||||||
const disconnected = new Promise(f => this.once(Events.Browser.Disconnected, f));
|
|
||||||
this._connection.close();
|
|
||||||
await disconnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
isConnected(): boolean {
|
isConnected(): boolean {
|
||||||
return !this._connection.isClosed();
|
return !this._connection.isClosed();
|
||||||
}
|
}
|
||||||
@ -170,7 +164,8 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
|
|||||||
async close() {
|
async close() {
|
||||||
helper.removeEventListeners(this._eventListeners);
|
helper.removeEventListeners(this._eventListeners);
|
||||||
const disconnected = new Promise(f => this.once(Events.Browser.Disconnected, f));
|
const disconnected = new Promise(f => this.once(Events.Browser.Disconnected, f));
|
||||||
await this._browserSession.send('Browser.close');
|
await Promise.all(this.browserContexts().map(context => context.close()));
|
||||||
|
this._connection.close();
|
||||||
await disconnected;
|
await disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,6 @@ async function setup(product, wsEndpoint) {
|
|||||||
}
|
}
|
||||||
async function teardown() {
|
async function teardown() {
|
||||||
await window.context.close();
|
await window.context.close();
|
||||||
await window.browser.disconnect();
|
await window.browser.close();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -114,7 +114,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
|
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
|
||||||
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
||||||
expect(remote.isConnected()).toBe(true);
|
expect(remote.isConnected()).toBe(true);
|
||||||
await remote.disconnect();
|
await remote.close();
|
||||||
expect(remote.isConnected()).toBe(false);
|
expect(remote.isConnected()).toBe(false);
|
||||||
await browserServer.close();
|
await browserServer.close();
|
||||||
});
|
});
|
||||||
@ -140,7 +140,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
const page = await remote.newPage();
|
const page = await remote.newPage();
|
||||||
const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(e => e);
|
const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(e => e);
|
||||||
await server.waitForRequest('/one-style.css');
|
await server.waitForRequest('/one-style.css');
|
||||||
await remote.disconnect();
|
await remote.close();
|
||||||
const error = await navigationPromise;
|
const error = await navigationPromise;
|
||||||
expect(error.message).toBe('Navigation failed because browser has disconnected!');
|
expect(error.message).toBe('Navigation failed because browser has disconnected!');
|
||||||
await browserServer.close();
|
await browserServer.close();
|
||||||
@ -155,7 +155,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
// Make sure the previous waitForSelector has time to make it to the browser before we disconnect.
|
// Make sure the previous waitForSelector has time to make it to the browser before we disconnect.
|
||||||
await page.waitForSelector('body');
|
await page.waitForSelector('body');
|
||||||
|
|
||||||
await remote.disconnect();
|
await remote.close();
|
||||||
const error = await watchdog;
|
const error = await watchdog;
|
||||||
expect(error.message).toContain('Protocol error');
|
expect(error.message).toContain('Protocol error');
|
||||||
await browserServer.close();
|
await browserServer.close();
|
||||||
@ -164,7 +164,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
|
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
|
||||||
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
||||||
const page = await remote.newPage();
|
const page = await remote.newPage();
|
||||||
await remote.disconnect();
|
await remote.close();
|
||||||
const error = await page.evaluate('1 + 1').catch(e => e);
|
const error = await page.evaluate('1 + 1').catch(e => e);
|
||||||
expect(error.message).toContain('has been closed');
|
expect(error.message).toContain('has been closed');
|
||||||
await browserServer.close();
|
await browserServer.close();
|
||||||
@ -187,14 +187,6 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
expect(message).not.toContain('Timeout');
|
expect(message).not.toContain('Timeout');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should be able to close remote browser', async({server}) => {
|
|
||||||
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
|
|
||||||
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
|
||||||
await Promise.all([
|
|
||||||
new Promise(f => browserServer.once('close', f)),
|
|
||||||
remote.close(),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Playwright.launch |webSocket| option', function() {
|
describe('Playwright.launch |webSocket| option', function() {
|
||||||
@ -219,25 +211,22 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Playwright.connect', function() {
|
describe('Playwright.connect', function() {
|
||||||
it.skip(WEBKIT)('should be able to reconnect to a browser', async({server}) => {
|
it('should be able to reconnect to a browser', async({server}) => {
|
||||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||||
|
{
|
||||||
const browser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
const browser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
||||||
const browserContext = await browser.newContext();
|
const browserContext = await browser.newContext();
|
||||||
const page = await browserContext.newPage();
|
const page = await browserContext.newPage();
|
||||||
await page.goto(server.PREFIX + '/frames/nested-frames.html');
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await browser.disconnect();
|
await browser.close();
|
||||||
|
}
|
||||||
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
{
|
||||||
const pages = await remote.pages();
|
const browser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
||||||
const restoredPage = pages.find(page => page.url() === server.PREFIX + '/frames/nested-frames.html');
|
const browserContext = await browser.newContext();
|
||||||
expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([
|
const page = await browserContext.newPage();
|
||||||
'http://localhost:<PORT>/frames/nested-frames.html',
|
await page.goto(server.EMPTY_PAGE);
|
||||||
' http://localhost:<PORT>/frames/frame.html (aframe)',
|
await browser.close();
|
||||||
' http://localhost:<PORT>/frames/two-frames.html (2frames)',
|
}
|
||||||
' http://localhost:<PORT>/frames/frame.html (dos)',
|
|
||||||
' http://localhost:<PORT>/frames/frame.html (uno)',
|
|
||||||
]);
|
|
||||||
expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56);
|
|
||||||
await browserServer.close();
|
await browserServer.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -25,13 +25,17 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
describe('BrowserContext', function() {
|
describe('BrowserContext', function() {
|
||||||
it('should work across sessions', async () => {
|
it('should work across sessions', async () => {
|
||||||
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||||
const browser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
const browser1 = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
||||||
expect(browser.browserContexts().length).toBe(0);
|
expect(browser1.browserContexts().length).toBe(0);
|
||||||
await browser.newContext();
|
await browser1.newContext();
|
||||||
expect(browser.browserContexts().length).toBe(1);
|
expect(browser1.browserContexts().length).toBe(1);
|
||||||
const remoteBrowser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
|
||||||
const contexts = remoteBrowser.browserContexts();
|
const browser2 = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
||||||
expect(contexts.length).toBe(1);
|
expect(browser2.browserContexts().length).toBe(0);
|
||||||
|
await browser2.newContext();
|
||||||
|
expect(browser2.browserContexts().length).toBe(1);
|
||||||
|
|
||||||
|
expect(browser1.browserContexts().length).toBe(1);
|
||||||
await browserServer.close();
|
await browserServer.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -53,7 +57,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
utils.waitEvent(remoteBrowser2, 'disconnected'),
|
utils.waitEvent(remoteBrowser2, 'disconnected'),
|
||||||
remoteBrowser2.disconnect(),
|
remoteBrowser2.close(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(disconnectedOriginal).toBe(0);
|
expect(disconnectedOriginal).toBe(0);
|
||||||
@ -74,37 +78,29 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
|
|||||||
|
|
||||||
describe('Playwright.connect', function() {
|
describe('Playwright.connect', function() {
|
||||||
it('should be able to connect multiple times to the same browser', async({server}) => {
|
it('should be able to connect multiple times to the same browser', async({server}) => {
|
||||||
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
|
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||||
const local = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
|
||||||
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
|
||||||
const page = await remote.newPage();
|
|
||||||
expect(await page.evaluate(() => 7 * 8)).toBe(56);
|
|
||||||
remote.disconnect();
|
|
||||||
|
|
||||||
const secondPage = await local.newPage();
|
|
||||||
expect(await secondPage.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work');
|
|
||||||
await browserServer.close();
|
|
||||||
});
|
|
||||||
it('should be able to close remote browser', async({server}) => {
|
|
||||||
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
|
|
||||||
const local = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
|
||||||
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
|
||||||
await Promise.all([
|
|
||||||
utils.waitEvent(local, 'disconnected'),
|
|
||||||
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 browserServer = await playwright.launchServer({...defaultBrowserOptions });
|
|
||||||
const browser1 = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
const browser1 = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
||||||
const page1 = await browser1.newPage();
|
|
||||||
await page1.goto(server.EMPTY_PAGE);
|
|
||||||
const browser2 = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
const browser2 = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
||||||
const page2 = (await browser2.pages()).find(page => page.url() === server.EMPTY_PAGE);
|
const page1 = await browser1.newPage();
|
||||||
expect(await page1.evaluate(() => 7 * 8)).toBe(56);
|
expect(await page1.evaluate(() => 7 * 8)).toBe(56);
|
||||||
expect(await page2.evaluate(() => 7 * 6)).toBe(42);
|
browser1.close();
|
||||||
|
|
||||||
|
const page2 = await browser2.newPage();
|
||||||
|
expect(await page2.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work');
|
||||||
await browserServer.close();
|
await browserServer.close();
|
||||||
});
|
});
|
||||||
|
it('should not be able to close remote browser', async() => {
|
||||||
|
const browserServer = await playwright.launchServer(defaultBrowserOptions);
|
||||||
|
{
|
||||||
|
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
||||||
|
await remote.newContext();
|
||||||
|
await remote.close();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
|
||||||
|
await remote.newContext();
|
||||||
|
await remote.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -200,6 +200,7 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
|
|||||||
testRunner.loadTests(require('./fixtures.spec.js'), testOptions);
|
testRunner.loadTests(require('./fixtures.spec.js'), testOptions);
|
||||||
testRunner.loadTests(require('./launcher.spec.js'), testOptions);
|
testRunner.loadTests(require('./launcher.spec.js'), testOptions);
|
||||||
testRunner.loadTests(require('./headful.spec.js'), testOptions);
|
testRunner.loadTests(require('./headful.spec.js'), testOptions);
|
||||||
|
testRunner.loadTests(require('./multiclient.spec.js'), testOptions);
|
||||||
|
|
||||||
if (CHROMIUM) {
|
if (CHROMIUM) {
|
||||||
testRunner.loadTests(require('./chromium/launcher.spec.js'), testOptions);
|
testRunner.loadTests(require('./chromium/launcher.spec.js'), testOptions);
|
||||||
@ -208,9 +209,5 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
|
|||||||
testRunner.loadTests(require('./chromium/tracing.spec.js'), testOptions);
|
testRunner.loadTests(require('./chromium/tracing.spec.js'), testOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CHROMIUM || FFOX) {
|
|
||||||
testRunner.loadTests(require('./multiclient.spec.js'), testOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
testRunner.loadTests(require('./web.spec.js'), testOptions);
|
testRunner.loadTests(require('./web.spec.js'), testOptions);
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user