chore: remove Page._didDisconnect (#27317)
Instead of having `didClose` based on page creation/destruction and `didDisconnect` based on session lifetime, we make session lifetime being managed by the `CRPage`/`FFPage`/`WKPage` instead.
This commit is contained in:
parent
7da1dfd21c
commit
c8143748e6
|
|
@ -193,7 +193,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
const [, ...otherPages] = this.pages();
|
const [, ...otherPages] = this.pages();
|
||||||
for (const p of otherPages)
|
for (const p of otherPages)
|
||||||
await p.close(metadata);
|
await p.close(metadata);
|
||||||
if (page && page._crashedScope.isClosed()) {
|
if (page && page.hasCrashed()) {
|
||||||
await page.close(metadata);
|
await page.close(metadata);
|
||||||
page = undefined;
|
page = undefined;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ export class CRBrowser extends Browser {
|
||||||
super(parent, options);
|
super(parent, options);
|
||||||
this._connection = connection;
|
this._connection = connection;
|
||||||
this._session = this._connection.rootSession;
|
this._session = this._connection.rootSession;
|
||||||
this._connection.on(ConnectionEvents.Disconnected, () => this._didClose());
|
this._connection.on(ConnectionEvents.Disconnected, () => this._didDisconnect());
|
||||||
this._session.on('Target.attachedToTarget', this._onAttachedToTarget.bind(this));
|
this._session.on('Target.attachedToTarget', this._onAttachedToTarget.bind(this));
|
||||||
this._session.on('Target.detachedFromTarget', this._onDetachedFromTarget.bind(this));
|
this._session.on('Target.detachedFromTarget', this._onDetachedFromTarget.bind(this));
|
||||||
this._session.on('Browser.downloadWillBegin', this._onDownloadWillBegin.bind(this));
|
this._session.on('Browser.downloadWillBegin', this._onDownloadWillBegin.bind(this));
|
||||||
|
|
@ -149,7 +149,7 @@ export class CRBrowser extends Browser {
|
||||||
_onAttachedToTarget({ targetInfo, sessionId, waitingForDebugger }: Protocol.Target.attachedToTargetPayload) {
|
_onAttachedToTarget({ targetInfo, sessionId, waitingForDebugger }: Protocol.Target.attachedToTargetPayload) {
|
||||||
if (targetInfo.type === 'browser')
|
if (targetInfo.type === 'browser')
|
||||||
return;
|
return;
|
||||||
const session = this._connection.session(sessionId)!;
|
const session = this._session.createChildSession(sessionId);
|
||||||
assert(targetInfo.browserContextId, 'targetInfo: ' + JSON.stringify(targetInfo, null, 2));
|
assert(targetInfo.browserContextId, 'targetInfo: ' + JSON.stringify(targetInfo, null, 2));
|
||||||
let context = this._contexts.get(targetInfo.browserContextId) || null;
|
let context = this._contexts.get(targetInfo.browserContextId) || null;
|
||||||
if (!context) {
|
if (!context) {
|
||||||
|
|
@ -234,6 +234,19 @@ export class CRBrowser extends Browser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _didDisconnect() {
|
||||||
|
for (const crPage of this._crPages.values())
|
||||||
|
crPage.didClose();
|
||||||
|
this._crPages.clear();
|
||||||
|
for (const backgroundPage of this._backgroundPages.values())
|
||||||
|
backgroundPage.didClose();
|
||||||
|
this._backgroundPages.clear();
|
||||||
|
for (const serviceWorker of this._serviceWorkers.values())
|
||||||
|
serviceWorker.didClose();
|
||||||
|
this._serviceWorkers.clear();
|
||||||
|
this._didClose();
|
||||||
|
}
|
||||||
|
|
||||||
private _findOwningPage(frameId: string) {
|
private _findOwningPage(frameId: string) {
|
||||||
for (const crPage of this._crPages.values()) {
|
for (const crPage of this._crPages.values()) {
|
||||||
const frame = crPage._page._frameManager.frame(frameId);
|
const frame = crPage._page._frameManager.frame(frameId);
|
||||||
|
|
@ -593,6 +606,6 @@ export class CRBrowserContext extends BrowserContext {
|
||||||
|
|
||||||
const rootSession = await this._browser._clientRootSession();
|
const rootSession = await this._browser._clientRootSession();
|
||||||
const { sessionId } = await rootSession.send('Target.attachToTarget', { targetId, flatten: true });
|
const { sessionId } = await rootSession.send('Target.attachToTarget', { targetId, flatten: true });
|
||||||
return this._browser._connection.session(sessionId)!;
|
return rootSession.createChildSession(sessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,10 @@ export const kBrowserCloseMessageId = -9999;
|
||||||
export class CRConnection extends EventEmitter {
|
export class CRConnection extends EventEmitter {
|
||||||
private _lastId = 0;
|
private _lastId = 0;
|
||||||
private readonly _transport: ConnectionTransport;
|
private readonly _transport: ConnectionTransport;
|
||||||
private readonly _sessions = new Map<string, CRSession>();
|
readonly _sessions = new Map<string, CRSession>();
|
||||||
private readonly _protocolLogger: ProtocolLogger;
|
private readonly _protocolLogger: ProtocolLogger;
|
||||||
private readonly _browserLogsCollector: RecentLogsCollector;
|
private readonly _browserLogsCollector: RecentLogsCollector;
|
||||||
|
_browserDisconnectedLogs: string | undefined;
|
||||||
readonly rootSession: CRSession;
|
readonly rootSession: CRSession;
|
||||||
_closed = false;
|
_closed = false;
|
||||||
|
|
||||||
|
|
@ -49,21 +50,13 @@ export class CRConnection extends EventEmitter {
|
||||||
this._transport = transport;
|
this._transport = transport;
|
||||||
this._protocolLogger = protocolLogger;
|
this._protocolLogger = protocolLogger;
|
||||||
this._browserLogsCollector = browserLogsCollector;
|
this._browserLogsCollector = browserLogsCollector;
|
||||||
this.rootSession = new CRSession(this, '', 'browser', '');
|
this.rootSession = new CRSession(this, null, '');
|
||||||
this._sessions.set('', this.rootSession);
|
this._sessions.set('', this.rootSession);
|
||||||
this._transport.onmessage = this._onMessage.bind(this);
|
this._transport.onmessage = this._onMessage.bind(this);
|
||||||
// onclose should be set last, since it can be immediately called.
|
// onclose should be set last, since it can be immediately called.
|
||||||
this._transport.onclose = this._onClose.bind(this);
|
this._transport.onclose = this._onClose.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromSession(session: CRSession): CRConnection {
|
|
||||||
return session._connection!;
|
|
||||||
}
|
|
||||||
|
|
||||||
session(sessionId: string): CRSession | null {
|
|
||||||
return this._sessions.get(sessionId) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_rawSend(sessionId: string, method: string, params: any): number {
|
_rawSend(sessionId: string, method: string, params: any): number {
|
||||||
const id = ++this._lastId;
|
const id = ++this._lastId;
|
||||||
const message: ProtocolRequest = { id, method, params };
|
const message: ProtocolRequest = { id, method, params };
|
||||||
|
|
@ -78,18 +71,6 @@ export class CRConnection extends EventEmitter {
|
||||||
this._protocolLogger('receive', message);
|
this._protocolLogger('receive', message);
|
||||||
if (message.id === kBrowserCloseMessageId)
|
if (message.id === kBrowserCloseMessageId)
|
||||||
return;
|
return;
|
||||||
if (message.method === 'Target.attachedToTarget') {
|
|
||||||
const sessionId = message.params.sessionId;
|
|
||||||
const rootSessionId = message.sessionId || '';
|
|
||||||
const session = new CRSession(this, rootSessionId, message.params.targetInfo.type, sessionId);
|
|
||||||
this._sessions.set(sessionId, session);
|
|
||||||
} else if (message.method === 'Target.detachedFromTarget') {
|
|
||||||
const session = this._sessions.get(message.params.sessionId);
|
|
||||||
if (session) {
|
|
||||||
session._onClosed(undefined);
|
|
||||||
this._sessions.delete(message.params.sessionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const session = this._sessions.get(message.sessionId || '');
|
const session = this._sessions.get(message.sessionId || '');
|
||||||
if (session)
|
if (session)
|
||||||
session._onMessage(message);
|
session._onMessage(message);
|
||||||
|
|
@ -99,10 +80,8 @@ export class CRConnection extends EventEmitter {
|
||||||
this._closed = true;
|
this._closed = true;
|
||||||
this._transport.onmessage = undefined;
|
this._transport.onmessage = undefined;
|
||||||
this._transport.onclose = undefined;
|
this._transport.onclose = undefined;
|
||||||
const browserDisconnectedLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
|
this._browserDisconnectedLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
|
||||||
for (const session of this._sessions.values())
|
this.rootSession.dispose();
|
||||||
session._onClosed(browserDisconnectedLogs);
|
|
||||||
this._sessions.clear();
|
|
||||||
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
|
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,14 +90,9 @@ export class CRConnection extends EventEmitter {
|
||||||
this._transport.close();
|
this._transport.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSession(targetInfo: Protocol.Target.TargetInfo): Promise<CRSession> {
|
|
||||||
const { sessionId } = await this.rootSession.send('Target.attachToTarget', { targetId: targetInfo.targetId, flatten: true });
|
|
||||||
return this._sessions.get(sessionId)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createBrowserSession(): Promise<CRSession> {
|
async createBrowserSession(): Promise<CRSession> {
|
||||||
const { sessionId } = await this.rootSession.send('Target.attachToBrowserTarget');
|
const { sessionId } = await this.rootSession.send('Target.attachToBrowserTarget');
|
||||||
return this._sessions.get(sessionId)!;
|
return this.rootSession.createChildSession(sessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,14 +101,13 @@ export const CRSessionEvents = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class CRSession extends EventEmitter {
|
export class CRSession extends EventEmitter {
|
||||||
_connection: CRConnection | null;
|
private readonly _connection: CRConnection;
|
||||||
_eventListener?: (method: string, params?: Object) => void;
|
_eventListener?: (method: string, params?: Object) => void;
|
||||||
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: ProtocolError) => void, error: ProtocolError, method: string}>();
|
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: ProtocolError) => void, error: ProtocolError, method: string}>();
|
||||||
private readonly _targetType: string;
|
|
||||||
private readonly _sessionId: string;
|
private readonly _sessionId: string;
|
||||||
private readonly _rootSessionId: string;
|
private readonly _parentSession: CRSession | null;
|
||||||
private _crashed: boolean = false;
|
private _crashed: boolean = false;
|
||||||
private _browserDisconnectedLogs: string | undefined;
|
private _closed = false;
|
||||||
override on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
override on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||||
override addListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
override addListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||||
override off: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
override off: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||||
|
|
@ -142,13 +115,12 @@ export class CRSession extends EventEmitter {
|
||||||
override once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
override once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||||
readonly guid: string;
|
readonly guid: string;
|
||||||
|
|
||||||
constructor(connection: CRConnection, rootSessionId: string, targetType: string, sessionId: string) {
|
constructor(connection: CRConnection, parentSession: CRSession | null, sessionId: string) {
|
||||||
super();
|
super();
|
||||||
this.guid = `cdp-session@${sessionId}`;
|
this.guid = `cdp-session@${sessionId}`;
|
||||||
this.setMaxListeners(0);
|
this.setMaxListeners(0);
|
||||||
this._connection = connection;
|
this._connection = connection;
|
||||||
this._rootSessionId = rootSessionId;
|
this._parentSession = parentSession;
|
||||||
this._targetType = targetType;
|
|
||||||
this._sessionId = sessionId;
|
this._sessionId = sessionId;
|
||||||
|
|
||||||
this.on = super.on;
|
this.on = super.on;
|
||||||
|
|
@ -162,16 +134,28 @@ export class CRSession extends EventEmitter {
|
||||||
this._crashed = true;
|
this._crashed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createChildSession(sessionId: string): CRSession {
|
||||||
|
const session = new CRSession(this._connection, this, sessionId);
|
||||||
|
this._connection._sessions.set(sessionId, session);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _closedErrorMessage() {
|
||||||
|
if (this._crashed)
|
||||||
|
return 'Target crashed';
|
||||||
|
if (this._connection._browserDisconnectedLogs !== undefined)
|
||||||
|
return `Browser closed.` + this._connection._browserDisconnectedLogs;
|
||||||
|
if (this._closed)
|
||||||
|
return `Target closed`;
|
||||||
|
}
|
||||||
|
|
||||||
async send<T extends keyof Protocol.CommandParameters>(
|
async send<T extends keyof Protocol.CommandParameters>(
|
||||||
method: T,
|
method: T,
|
||||||
params?: Protocol.CommandParameters[T]
|
params?: Protocol.CommandParameters[T]
|
||||||
): Promise<Protocol.CommandReturnValues[T]> {
|
): Promise<Protocol.CommandReturnValues[T]> {
|
||||||
if (this._crashed)
|
const closedErrorMessage = this._closedErrorMessage();
|
||||||
throw new ProtocolError(true, 'Target crashed');
|
if (closedErrorMessage)
|
||||||
if (this._browserDisconnectedLogs !== undefined)
|
throw new ProtocolError(true, closedErrorMessage);
|
||||||
throw new ProtocolError(true, `Browser closed.` + this._browserDisconnectedLogs);
|
|
||||||
if (!this._connection)
|
|
||||||
throw new ProtocolError(true, `Target closed`);
|
|
||||||
const id = this._connection._rawSend(this._sessionId, method, params);
|
const id = this._connection._rawSend(this._sessionId, method, params);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this._callbacks.set(id, { resolve, reject, error: new ProtocolError(false), method });
|
this._callbacks.set(id, { resolve, reject, error: new ProtocolError(false), method });
|
||||||
|
|
@ -203,23 +187,23 @@ export class CRSession extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async detach() {
|
async detach() {
|
||||||
if (!this._connection)
|
if (this._closed)
|
||||||
throw new Error(`Session already detached. Most likely the ${this._targetType} has been closed.`);
|
throw new Error(`Session already detached. Most likely the page has been closed.`);
|
||||||
const rootSession = this._connection.session(this._rootSessionId);
|
if (!this._parentSession)
|
||||||
if (!rootSession)
|
throw new Error('Root session cannot be closed');
|
||||||
throw new Error('Root session has been closed');
|
await this._parentSession.send('Target.detachFromTarget', { sessionId: this._sessionId });
|
||||||
await rootSession.send('Target.detachFromTarget', { sessionId: this._sessionId });
|
this.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onClosed(browserDisconnectedLogs: string | undefined) {
|
dispose() {
|
||||||
this._browserDisconnectedLogs = browserDisconnectedLogs;
|
this._closed = true;
|
||||||
const errorMessage = browserDisconnectedLogs !== undefined ? 'Browser closed.' + browserDisconnectedLogs : 'Target closed';
|
this._connection._sessions.delete(this._sessionId);
|
||||||
|
const errorMessage = this._closedErrorMessage()!;
|
||||||
for (const callback of this._callbacks.values()) {
|
for (const callback of this._callbacks.values()) {
|
||||||
callback.error.sessionClosed = true;
|
callback.error.sessionClosed = true;
|
||||||
callback.reject(rewriteErrorMessage(callback.error, errorMessage));
|
callback.reject(rewriteErrorMessage(callback.error, errorMessage));
|
||||||
}
|
}
|
||||||
this._callbacks.clear();
|
this._callbacks.clear();
|
||||||
this._connection = null;
|
|
||||||
Promise.resolve().then(() => this.emit(CRSessionEvents.Disconnected));
|
Promise.resolve().then(() => this.emit(CRSessionEvents.Disconnected));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ import type * as channels from '@protocol/channels';
|
||||||
import { getAccessibilityTree } from './crAccessibility';
|
import { getAccessibilityTree } from './crAccessibility';
|
||||||
import { CRBrowserContext } from './crBrowser';
|
import { CRBrowserContext } from './crBrowser';
|
||||||
import type { CRSession } from './crConnection';
|
import type { CRSession } from './crConnection';
|
||||||
import { CRConnection, CRSessionEvents } from './crConnection';
|
|
||||||
import { CRCoverage } from './crCoverage';
|
import { CRCoverage } from './crCoverage';
|
||||||
import { DragManager } from './crDragDrop';
|
import { DragManager } from './crDragDrop';
|
||||||
import { CRExecutionContext } from './crExecutionContext';
|
import { CRExecutionContext } from './crExecutionContext';
|
||||||
|
|
@ -93,7 +92,6 @@ export class CRPage implements PageDelegate {
|
||||||
this._page = new Page(this, browserContext);
|
this._page = new Page(this, browserContext);
|
||||||
this._mainFrameSession = new FrameSession(this, client, targetId, null);
|
this._mainFrameSession = new FrameSession(this, client, targetId, null);
|
||||||
this._sessions.set(targetId, this._mainFrameSession);
|
this._sessions.set(targetId, this._mainFrameSession);
|
||||||
client.once(CRSessionEvents.Disconnected, () => this._page._didDisconnect());
|
|
||||||
if (opener && !browserContext._options.noDefaultViewport) {
|
if (opener && !browserContext._options.noDefaultViewport) {
|
||||||
const features = opener._nextWindowOpenPopupFeatures.shift() || [];
|
const features = opener._nextWindowOpenPopupFeatures.shift() || [];
|
||||||
const viewportSize = helper.getViewportSizeFromWindowFeatures(features);
|
const viewportSize = helper.getViewportSizeFromWindowFeatures(features);
|
||||||
|
|
@ -407,6 +405,7 @@ class FrameSession {
|
||||||
private _evaluateOnNewDocumentIdentifiers: string[] = [];
|
private _evaluateOnNewDocumentIdentifiers: string[] = [];
|
||||||
private _exposedBindingNames: string[] = [];
|
private _exposedBindingNames: string[] = [];
|
||||||
private _metricsOverride: Protocol.Emulation.setDeviceMetricsOverrideParameters | undefined;
|
private _metricsOverride: Protocol.Emulation.setDeviceMetricsOverrideParameters | undefined;
|
||||||
|
private _workerSessions = new Map<string, CRSession>();
|
||||||
|
|
||||||
constructor(crPage: CRPage, client: CRSession, targetId: string, parentSession: FrameSession | null) {
|
constructor(crPage: CRPage, client: CRSession, targetId: string, parentSession: FrameSession | null) {
|
||||||
this._client = client;
|
this._client = client;
|
||||||
|
|
@ -421,9 +420,6 @@ class FrameSession {
|
||||||
this._firstNonInitialNavigationCommittedFulfill = f;
|
this._firstNonInitialNavigationCommittedFulfill = f;
|
||||||
this._firstNonInitialNavigationCommittedReject = r;
|
this._firstNonInitialNavigationCommittedReject = r;
|
||||||
});
|
});
|
||||||
client.once(CRSessionEvents.Disconnected, () => {
|
|
||||||
this._firstNonInitialNavigationCommittedReject(new Error('Page closed'));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_isMainFrame(): boolean {
|
_isMainFrame(): boolean {
|
||||||
|
|
@ -578,6 +574,7 @@ class FrameSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
|
this._firstNonInitialNavigationCommittedReject(new Error('Page closed'));
|
||||||
for (const childSession of this._childSessions)
|
for (const childSession of this._childSessions)
|
||||||
childSession.dispose();
|
childSession.dispose();
|
||||||
if (this._parentSession)
|
if (this._parentSession)
|
||||||
|
|
@ -585,6 +582,7 @@ class FrameSession {
|
||||||
eventsHelper.removeEventListeners(this._eventListeners);
|
eventsHelper.removeEventListeners(this._eventListeners);
|
||||||
this._networkManager.dispose();
|
this._networkManager.dispose();
|
||||||
this._crPage._sessions.delete(this._targetId);
|
this._crPage._sessions.delete(this._targetId);
|
||||||
|
this._client.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _navigate(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult> {
|
async _navigate(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult> {
|
||||||
|
|
@ -719,7 +717,7 @@ class FrameSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onAttachedToTarget(event: Protocol.Target.attachedToTargetPayload) {
|
_onAttachedToTarget(event: Protocol.Target.attachedToTargetPayload) {
|
||||||
const session = CRConnection.fromSession(this._client).session(event.sessionId)!;
|
const session = this._client.createChildSession(event.sessionId);
|
||||||
|
|
||||||
if (event.targetInfo.type === 'iframe') {
|
if (event.targetInfo.type === 'iframe') {
|
||||||
// Frame id equals target id.
|
// Frame id equals target id.
|
||||||
|
|
@ -745,6 +743,7 @@ class FrameSession {
|
||||||
const url = event.targetInfo.url;
|
const url = event.targetInfo.url;
|
||||||
const worker = new Worker(this._page, url);
|
const worker = new Worker(this._page, url);
|
||||||
this._page._addWorker(event.sessionId, worker);
|
this._page._addWorker(event.sessionId, worker);
|
||||||
|
this._workerSessions.set(event.sessionId, session);
|
||||||
session.once('Runtime.executionContextCreated', async event => {
|
session.once('Runtime.executionContextCreated', async event => {
|
||||||
worker._createExecutionContext(new CRExecutionContext(session, event.context));
|
worker._createExecutionContext(new CRExecutionContext(session, event.context));
|
||||||
});
|
});
|
||||||
|
|
@ -763,7 +762,11 @@ class FrameSession {
|
||||||
|
|
||||||
_onDetachedFromTarget(event: Protocol.Target.detachedFromTargetPayload) {
|
_onDetachedFromTarget(event: Protocol.Target.detachedFromTargetPayload) {
|
||||||
// This might be a worker...
|
// This might be a worker...
|
||||||
this._page._removeWorker(event.sessionId);
|
const workerSession = this._workerSessions.get(event.sessionId);
|
||||||
|
if (workerSession) {
|
||||||
|
workerSession.dispose();
|
||||||
|
this._page._removeWorker(event.sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
// ... or an oopif.
|
// ... or an oopif.
|
||||||
const childFrameSession = this._crPage._sessions.get(event.targetId!);
|
const childFrameSession = this._crPage._sessions.get(event.targetId!);
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,11 @@ export class CRServiceWorker extends Worker {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override didClose() {
|
||||||
|
this._session.dispose();
|
||||||
|
super.didClose();
|
||||||
|
}
|
||||||
|
|
||||||
async updateOffline(initial: boolean): Promise<void> {
|
async updateOffline(initial: boolean): Promise<void> {
|
||||||
if (!this._isNetworkInspectionEnabled())
|
if (!this._isNetworkInspectionEnabled())
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,9 @@ export class FFBrowser extends Browser {
|
||||||
for (const video of this._idToVideo.values())
|
for (const video of this._idToVideo.values())
|
||||||
video.artifact.reportFinished(kBrowserClosedError);
|
video.artifact.reportFinished(kBrowserClosedError);
|
||||||
this._idToVideo.clear();
|
this._idToVideo.clear();
|
||||||
|
for (const ffPage of this._ffPages.values())
|
||||||
|
ffPage.didClose();
|
||||||
|
this._ffPages.clear();
|
||||||
this._didClose();
|
this._didClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,9 +126,6 @@ export class FFConnection extends EventEmitter {
|
||||||
this._transport.onmessage = undefined;
|
this._transport.onmessage = undefined;
|
||||||
this._transport.onclose = undefined;
|
this._transport.onclose = undefined;
|
||||||
const formattedBrowserLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
|
const formattedBrowserLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
|
||||||
for (const session of this._sessions.values())
|
|
||||||
session.dispose();
|
|
||||||
this._sessions.clear();
|
|
||||||
for (const callback of this._callbacks.values()) {
|
for (const callback of this._callbacks.values()) {
|
||||||
const error = rewriteErrorMessage(callback.error, `Protocol error (${callback.method}): Browser closed.` + formattedBrowserLogs);
|
const error = rewriteErrorMessage(callback.error, `Protocol error (${callback.method}): Browser closed.` + formattedBrowserLogs);
|
||||||
error.sessionClosed = true;
|
error.sessionClosed = true;
|
||||||
|
|
@ -150,10 +147,6 @@ export class FFConnection extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FFSessionEvents = {
|
|
||||||
Disconnected: Symbol('Disconnected')
|
|
||||||
};
|
|
||||||
|
|
||||||
export class FFSession extends EventEmitter {
|
export class FFSession extends EventEmitter {
|
||||||
_connection: FFConnection;
|
_connection: FFConnection;
|
||||||
_disposed = false;
|
_disposed = false;
|
||||||
|
|
@ -228,7 +221,6 @@ export class FFSession extends EventEmitter {
|
||||||
this._callbacks.clear();
|
this._callbacks.clear();
|
||||||
this._disposed = true;
|
this._disposed = true;
|
||||||
this._connection._sessions.delete(this._sessionId);
|
this._connection._sessions.delete(this._sessionId);
|
||||||
Promise.resolve().then(() => this.emit(FFSessionEvents.Disconnected));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import { Page, Worker } from '../page';
|
||||||
import type * as types from '../types';
|
import type * as types from '../types';
|
||||||
import { getAccessibilityTree } from './ffAccessibility';
|
import { getAccessibilityTree } from './ffAccessibility';
|
||||||
import type { FFBrowserContext } from './ffBrowser';
|
import type { FFBrowserContext } from './ffBrowser';
|
||||||
import { FFSession, FFSessionEvents } from './ffConnection';
|
import { FFSession } from './ffConnection';
|
||||||
import { FFExecutionContext } from './ffExecutionContext';
|
import { FFExecutionContext } from './ffExecutionContext';
|
||||||
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './ffInput';
|
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './ffInput';
|
||||||
import { FFNetworkManager } from './ffNetworkManager';
|
import { FFNetworkManager } from './ffNetworkManager';
|
||||||
|
|
@ -100,10 +100,6 @@ export class FFPage implements PageDelegate {
|
||||||
eventsHelper.addEventListener(this._session, 'Page.screencastFrame', this._onScreencastFrame.bind(this)),
|
eventsHelper.addEventListener(this._session, 'Page.screencastFrame', this._onScreencastFrame.bind(this)),
|
||||||
|
|
||||||
];
|
];
|
||||||
session.once(FFSessionEvents.Disconnected, () => {
|
|
||||||
this._markAsError(new Error('Page closed'));
|
|
||||||
this._page._didDisconnect();
|
|
||||||
});
|
|
||||||
this._session.once('Page.ready', async () => {
|
this._session.once('Page.ready', async () => {
|
||||||
await this._page.initOpener(this._opener);
|
await this._page.initOpener(this._opener);
|
||||||
if (this._initializationFailed)
|
if (this._initializationFailed)
|
||||||
|
|
@ -346,6 +342,7 @@ export class FFPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
didClose() {
|
didClose() {
|
||||||
|
this._markAsError(new Error('Page closed'));
|
||||||
this._session.dispose();
|
this._session.dispose();
|
||||||
eventsHelper.removeEventListeners(this._eventListeners);
|
eventsHelper.removeEventListeners(this._eventListeners);
|
||||||
this._networkManager.dispose();
|
this._networkManager.dispose();
|
||||||
|
|
|
||||||
|
|
@ -617,8 +617,7 @@ export class Frame extends SdkObject {
|
||||||
async raceNavigationAction(progress: Progress, options: types.GotoOptions, action: () => Promise<network.Response | null>): Promise<network.Response | null> {
|
async raceNavigationAction(progress: Progress, options: types.GotoOptions, action: () => Promise<network.Response | null>): Promise<network.Response | null> {
|
||||||
return LongStandingScope.raceMultiple([
|
return LongStandingScope.raceMultiple([
|
||||||
this._detachedScope,
|
this._detachedScope,
|
||||||
this._page._disconnectedScope,
|
this._page.openScope,
|
||||||
this._page._crashedScope,
|
|
||||||
], action().catch(e => {
|
], action().catch(e => {
|
||||||
if (e instanceof NavigationAbortedError && e.documentId) {
|
if (e instanceof NavigationAbortedError && e.documentId) {
|
||||||
const data = this._redirectedNavigations.get(e.documentId);
|
const data = this._redirectedNavigations.get(e.documentId);
|
||||||
|
|
@ -1055,8 +1054,7 @@ export class Frame extends SdkObject {
|
||||||
// We need this to show expected/received values in time.
|
// We need this to show expected/received values in time.
|
||||||
const actionPromise = new Promise(f => setTimeout(f, timeout));
|
const actionPromise = new Promise(f => setTimeout(f, timeout));
|
||||||
await LongStandingScope.raceMultiple([
|
await LongStandingScope.raceMultiple([
|
||||||
this._page._disconnectedScope,
|
this._page.openScope,
|
||||||
this._page._crashedScope,
|
|
||||||
this._detachedScope,
|
this._detachedScope,
|
||||||
], actionPromise);
|
], actionPromise);
|
||||||
}
|
}
|
||||||
|
|
@ -1704,8 +1702,7 @@ class SignalBarrier {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
await LongStandingScope.raceMultiple([
|
await LongStandingScope.raceMultiple([
|
||||||
frame._page._disconnectedScope,
|
frame._page.openScope,
|
||||||
frame._page._crashedScope,
|
|
||||||
frame._detachedScope,
|
frame._detachedScope,
|
||||||
], waiter.promise).catch(() => {});
|
], waiter.promise).catch(() => {});
|
||||||
waiter.dispose();
|
waiter.dispose();
|
||||||
|
|
|
||||||
|
|
@ -136,11 +136,9 @@ export class Page extends SdkObject {
|
||||||
|
|
||||||
private _closedState: 'open' | 'closing' | 'closed' = 'open';
|
private _closedState: 'open' | 'closing' | 'closed' = 'open';
|
||||||
private _closedPromise = new ManualPromise<void>();
|
private _closedPromise = new ManualPromise<void>();
|
||||||
private _disconnected = false;
|
|
||||||
private _initialized = false;
|
private _initialized = false;
|
||||||
private _eventsToEmitAfterInitialized: { event: string | symbol, args: any[] }[] = [];
|
private _eventsToEmitAfterInitialized: { event: string | symbol, args: any[] }[] = [];
|
||||||
readonly _disconnectedScope = new LongStandingScope();
|
private _crashed = false;
|
||||||
readonly _crashedScope = new LongStandingScope();
|
|
||||||
readonly openScope = new LongStandingScope();
|
readonly openScope = new LongStandingScope();
|
||||||
readonly _browserContext: BrowserContext;
|
readonly _browserContext: BrowserContext;
|
||||||
readonly keyboard: input.Keyboard;
|
readonly keyboard: input.Keyboard;
|
||||||
|
|
@ -284,18 +282,9 @@ export class Page extends SdkObject {
|
||||||
this._frameManager.dispose();
|
this._frameManager.dispose();
|
||||||
this._frameThrottler.dispose();
|
this._frameThrottler.dispose();
|
||||||
this.emit(Page.Events.Crash);
|
this.emit(Page.Events.Crash);
|
||||||
this._crashedScope.close('Page crashed');
|
this._crashed = true;
|
||||||
this.instrumentation.onPageClose(this);
|
this.instrumentation.onPageClose(this);
|
||||||
this.openScope.close('Page closed');
|
this.openScope.close('Page crashed');
|
||||||
}
|
|
||||||
|
|
||||||
_didDisconnect() {
|
|
||||||
this._frameManager.dispose();
|
|
||||||
this._frameThrottler.dispose();
|
|
||||||
assert(!this._disconnected, 'Page disconnected twice');
|
|
||||||
this._disconnected = true;
|
|
||||||
this._disconnectedScope.close('Page closed');
|
|
||||||
this.openScope.close('Page closed');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onFileChooserOpened(handle: dom.ElementHandle) {
|
async _onFileChooserOpened(handle: dom.ElementHandle) {
|
||||||
|
|
@ -366,7 +355,7 @@ export class Page extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onBindingCalled(payload: string, context: dom.FrameExecutionContext) {
|
async _onBindingCalled(payload: string, context: dom.FrameExecutionContext) {
|
||||||
if (this._disconnected || this._closedState === 'closed')
|
if (this._closedState === 'closed')
|
||||||
return;
|
return;
|
||||||
await PageBinding.dispatch(this, payload, context);
|
await PageBinding.dispatch(this, payload, context);
|
||||||
}
|
}
|
||||||
|
|
@ -612,7 +601,6 @@ export class Page extends SdkObject {
|
||||||
const runBeforeUnload = !!options && !!options.runBeforeUnload;
|
const runBeforeUnload = !!options && !!options.runBeforeUnload;
|
||||||
if (this._closedState !== 'closing') {
|
if (this._closedState !== 'closing') {
|
||||||
this._closedState = 'closing';
|
this._closedState = 'closing';
|
||||||
assert(!this._disconnected, 'Target closed');
|
|
||||||
// This might throw if the browser context containing the page closes
|
// This might throw if the browser context containing the page closes
|
||||||
// while we are trying to close the page.
|
// while we are trying to close the page.
|
||||||
await this._delegate.closePage(runBeforeUnload).catch(e => debugLogger.log('error', e));
|
await this._delegate.closePage(runBeforeUnload).catch(e => debugLogger.log('error', e));
|
||||||
|
|
@ -632,8 +620,12 @@ export class Page extends SdkObject {
|
||||||
return this._closedState === 'closed';
|
return this._closedState === 'closed';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasCrashed() {
|
||||||
|
return this._crashed;
|
||||||
|
}
|
||||||
|
|
||||||
isClosedOrClosingOrCrashed() {
|
isClosedOrClosingOrCrashed() {
|
||||||
return this._closedState !== 'open' || this._crashedScope.isClosed();
|
return this._closedState !== 'open' || this._crashed;
|
||||||
}
|
}
|
||||||
|
|
||||||
_addWorker(workerId: string, worker: Worker) {
|
_addWorker(workerId: string, worker: Worker) {
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,8 @@ export class WKBrowser extends Browser {
|
||||||
|
|
||||||
_onDisconnect() {
|
_onDisconnect() {
|
||||||
for (const wkPage of this._wkPages.values())
|
for (const wkPage of this._wkPages.values())
|
||||||
wkPage.dispose(true);
|
wkPage.didClose();
|
||||||
|
this._wkPages.clear();
|
||||||
for (const video of this._idToVideo.values())
|
for (const video of this._idToVideo.values())
|
||||||
video.artifact.reportFinished(kBrowserClosedError);
|
video.artifact.reportFinished(kBrowserClosedError);
|
||||||
this._idToVideo.clear();
|
this._idToVideo.clear();
|
||||||
|
|
@ -178,7 +179,6 @@ export class WKBrowser extends Browser {
|
||||||
if (!wkPage)
|
if (!wkPage)
|
||||||
return;
|
return;
|
||||||
wkPage.didClose();
|
wkPage.didClose();
|
||||||
wkPage.dispose(false);
|
|
||||||
this._wkPages.delete(pageProxyId);
|
this._wkPages.delete(pageProxyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,8 @@ export class WKConnection {
|
||||||
private readonly _transport: ConnectionTransport;
|
private readonly _transport: ConnectionTransport;
|
||||||
private readonly _onDisconnect: () => void;
|
private readonly _onDisconnect: () => void;
|
||||||
private readonly _protocolLogger: ProtocolLogger;
|
private readonly _protocolLogger: ProtocolLogger;
|
||||||
readonly _browserLogsCollector: RecentLogsCollector;
|
private readonly _browserLogsCollector: RecentLogsCollector;
|
||||||
|
_browserDisconnectedLogs: string | undefined;
|
||||||
private _lastId = 0;
|
private _lastId = 0;
|
||||||
private _closed = false;
|
private _closed = false;
|
||||||
readonly browserSession: WKSession;
|
readonly browserSession: WKSession;
|
||||||
|
|
@ -83,7 +84,8 @@ export class WKConnection {
|
||||||
this._closed = true;
|
this._closed = true;
|
||||||
this._transport.onmessage = undefined;
|
this._transport.onmessage = undefined;
|
||||||
this._transport.onclose = undefined;
|
this._transport.onclose = undefined;
|
||||||
this.browserSession.dispose(true);
|
this._browserDisconnectedLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
|
||||||
|
this.browserSession.dispose();
|
||||||
this._onDisconnect();
|
this._onDisconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,9 +158,9 @@ export class WKSession extends EventEmitter {
|
||||||
return this._disposed;
|
return this._disposed;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(disconnected: boolean) {
|
dispose() {
|
||||||
if (disconnected)
|
if (this.connection._browserDisconnectedLogs)
|
||||||
this.errorText = 'Browser closed.' + helper.formatBrowserLogs(this.connection._browserLogsCollector.recentLogs());
|
this.errorText = 'Browser closed.' + this.connection._browserDisconnectedLogs;
|
||||||
for (const callback of this._callbacks.values()) {
|
for (const callback of this._callbacks.values()) {
|
||||||
callback.error.sessionClosed = true;
|
callback.error.sessionClosed = true;
|
||||||
callback.reject(rewriteErrorMessage(callback.error, this.errorText));
|
callback.reject(rewriteErrorMessage(callback.error, this.errorText));
|
||||||
|
|
|
||||||
|
|
@ -247,11 +247,11 @@ export class WKPage implements PageDelegate {
|
||||||
private _onTargetDestroyed(event: Protocol.Target.targetDestroyedPayload) {
|
private _onTargetDestroyed(event: Protocol.Target.targetDestroyedPayload) {
|
||||||
const { targetId, crashed } = event;
|
const { targetId, crashed } = event;
|
||||||
if (this._provisionalPage && this._provisionalPage._session.sessionId === targetId) {
|
if (this._provisionalPage && this._provisionalPage._session.sessionId === targetId) {
|
||||||
this._provisionalPage._session.dispose(false);
|
this._provisionalPage._session.dispose();
|
||||||
this._provisionalPage.dispose();
|
this._provisionalPage.dispose();
|
||||||
this._provisionalPage = null;
|
this._provisionalPage = null;
|
||||||
} else if (this._session.sessionId === targetId) {
|
} else if (this._session.sessionId === targetId) {
|
||||||
this._session.dispose(false);
|
this._session.dispose();
|
||||||
eventsHelper.removeEventListeners(this._sessionListeners);
|
eventsHelper.removeEventListeners(this._sessionListeners);
|
||||||
if (crashed) {
|
if (crashed) {
|
||||||
this._session.markAsCrashed();
|
this._session.markAsCrashed();
|
||||||
|
|
@ -261,22 +261,18 @@ export class WKPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
didClose() {
|
didClose() {
|
||||||
this._page._didClose();
|
this._pageProxySession.dispose();
|
||||||
}
|
|
||||||
|
|
||||||
dispose(disconnected: boolean) {
|
|
||||||
this._pageProxySession.dispose(disconnected);
|
|
||||||
eventsHelper.removeEventListeners(this._sessionListeners);
|
eventsHelper.removeEventListeners(this._sessionListeners);
|
||||||
eventsHelper.removeEventListeners(this._eventListeners);
|
eventsHelper.removeEventListeners(this._eventListeners);
|
||||||
if (this._session)
|
if (this._session)
|
||||||
this._session.dispose(disconnected);
|
this._session.dispose();
|
||||||
if (this._provisionalPage) {
|
if (this._provisionalPage) {
|
||||||
this._provisionalPage._session.dispose(disconnected);
|
this._provisionalPage._session.dispose();
|
||||||
this._provisionalPage.dispose();
|
this._provisionalPage.dispose();
|
||||||
this._provisionalPage = null;
|
this._provisionalPage = null;
|
||||||
}
|
}
|
||||||
this._page._didDisconnect();
|
|
||||||
this._firstNonInitialNavigationCommittedReject(new Error('Page closed'));
|
this._firstNonInitialNavigationCommittedReject(new Error('Page closed'));
|
||||||
|
this._page._didClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchMessageToSession(message: any) {
|
dispatchMessageToSession(message: any) {
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export class WKWorkers {
|
||||||
const workerSession = this._workerSessions.get(event.workerId)!;
|
const workerSession = this._workerSessions.get(event.workerId)!;
|
||||||
if (!workerSession)
|
if (!workerSession)
|
||||||
return;
|
return;
|
||||||
workerSession.dispose(false);
|
workerSession.dispose();
|
||||||
this._workerSessions.delete(event.workerId);
|
this._workerSessions.delete(event.workerId);
|
||||||
this._page._removeWorker(event.workerId);
|
this._page._removeWorker(event.workerId);
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -97,10 +97,10 @@ it('should not stall on evaluate when dismissing beforeunload', async ({ page, s
|
||||||
await page.click('body');
|
await page.click('body');
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
page.waitForEvent('dialog').then(dialog => dialog.dismiss()),
|
||||||
page.evaluate(() => {
|
page.evaluate(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}),
|
}),
|
||||||
page.waitForEvent('dialog').then(dialog => dialog.dismiss()),
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,3 +49,15 @@ test('version should work', async function({ browser, browserName }) {
|
||||||
else
|
else
|
||||||
expect(version.match(/^\d+\.\d+/)).toBeTruthy();
|
expect(version.match(/^\d+\.\d+/)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should dispatch page.on(close) upon browser.close and reject evaluate', async ({ browserType }) => {
|
||||||
|
const browser = await browserType.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
let closed = false;
|
||||||
|
page.on('close', () => closed = true);
|
||||||
|
const promise = page.evaluate(() => new Promise<void>(() => {})).catch(e => e);
|
||||||
|
await browser.close();
|
||||||
|
expect(closed).toBe(true);
|
||||||
|
const error = await promise;
|
||||||
|
expect(error.message).toMatch(/(Target|Browser) closed/);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,11 @@ it('should close page with active dialog', async ({ page }) => {
|
||||||
await page.close();
|
await page.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not accept after close', async ({ page, mode }) => {
|
it('should not accept dialog after close', async ({ page, mode }) => {
|
||||||
it.fixme(mode.startsWith('service2'), 'Times out');
|
it.fixme(mode.startsWith('service2'), 'Times out');
|
||||||
|
const promise = page.waitForEvent('dialog');
|
||||||
page.evaluate(() => alert()).catch(() => {});
|
page.evaluate(() => alert()).catch(() => {});
|
||||||
const dialog = await page.waitForEvent('dialog');
|
const dialog = await promise;
|
||||||
await page.close();
|
await page.close();
|
||||||
const e = await dialog.dismiss().catch(e => e);
|
const e = await dialog.dismiss().catch(e => e);
|
||||||
expect(e.message).toContain('Target page, context or browser has been closed');
|
expect(e.message).toContain('Target page, context or browser has been closed');
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,9 @@ it('should report requests and responses handled by service worker', async ({ pa
|
||||||
|
|
||||||
await page.goto(server.PREFIX + '/serviceworkers/fetchdummy/sw.html');
|
await page.goto(server.PREFIX + '/serviceworkers/fetchdummy/sw.html');
|
||||||
await page.evaluate(() => window['activationPromise']);
|
await page.evaluate(() => window['activationPromise']);
|
||||||
const [swResponse, request] = await Promise.all([
|
const [request, swResponse] = await Promise.all([
|
||||||
page.evaluate(() => window['fetchDummy']('foo')),
|
|
||||||
page.waitForEvent('request'),
|
page.waitForEvent('request'),
|
||||||
|
page.evaluate(() => window['fetchDummy']('foo')),
|
||||||
]);
|
]);
|
||||||
expect(swResponse).toBe('responseFromServiceWorker:foo');
|
expect(swResponse).toBe('responseFromServiceWorker:foo');
|
||||||
expect(request.url()).toBe(server.PREFIX + '/serviceworkers/fetchdummy/foo');
|
expect(request.url()).toBe(server.PREFIX + '/serviceworkers/fetchdummy/foo');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue