chore: unify frame lifecycle events between browsers (#172)

This commit is contained in:
Dmitry Gozman 2019-12-09 16:34:42 -08:00 committed by GitHub
parent e5a85e4e67
commit b4c89ca0f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 243 additions and 107 deletions

View File

@ -10,7 +10,7 @@
"playwright": { "playwright": {
"chromium_revision": "719491", "chromium_revision": "719491",
"firefox_revision": "1004", "firefox_revision": "1004",
"webkit_revision": "1016" "webkit_revision": "1022"
}, },
"scripts": { "scripts": {
"unit": "node test/test.js", "unit": "node test/test.js",

View File

@ -60,7 +60,6 @@ const frameDataSymbol = Symbol('frameData');
type FrameData = { type FrameData = {
id: string, id: string,
loaderId: string, loaderId: string,
lifecycleEvents: Set<string>,
}; };
export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate { export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate {
@ -142,14 +141,11 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
return (frame as any)[frameDataSymbol]; return (frame as any)[frameDataSymbol];
} }
async navigateFrame( async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}): Promise<network.Response | null> {
frame: frames.Frame,
url: string,
options: { referer?: string; timeout?: number; waitUntil?: string | string[]; } = {}): Promise<network.Response | null> {
assertNoLegacyNavigationOptions(options); assertNoLegacyNavigationOptions(options);
const { const {
referer = this._networkManager.extraHTTPHeaders()['referer'], referer = this._networkManager.extraHTTPHeaders()['referer'],
waitUntil = ['load'], waitUntil = (['load'] as frames.LifecycleEvent[]),
timeout = this._page._timeoutSettings.navigationTimeout(), timeout = this._page._timeoutSettings.navigationTimeout(),
} = options; } = options;
@ -181,13 +177,10 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
} }
} }
async waitForFrameNavigation( async waitForFrameNavigation(frame: frames.Frame, options: frames.NavigateOptions = {}): Promise<network.Response | null> {
frame: frames.Frame,
options: { timeout?: number; waitUntil?: string | string[]; } = {}
): Promise<network.Response | null> {
assertNoLegacyNavigationOptions(options); assertNoLegacyNavigationOptions(options);
const { const {
waitUntil = ['load'], waitUntil = (['load'] as frames.LifecycleEvent[]),
timeout = this._page._timeoutSettings.navigationTimeout(), timeout = this._page._timeoutSettings.navigationTimeout(),
} = options; } = options;
const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout); const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout);
@ -204,7 +197,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
async setFrameContent(frame: frames.Frame, html: string, options: frames.NavigateOptions = {}) { async setFrameContent(frame: frames.Frame, html: string, options: frames.NavigateOptions = {}) {
const { const {
waitUntil = ['load'], waitUntil = (['load'] as frames.LifecycleEvent[]),
timeout = this._page._timeoutSettings.navigationTimeout(), timeout = this._page._timeoutSettings.navigationTimeout(),
} = options; } = options;
const context = await frame._utilityContext(); const context = await frame._utilityContext();
@ -232,9 +225,12 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
const data = this._frameData(frame); const data = this._frameData(frame);
if (event.name === 'init') { if (event.name === 'init') {
data.loaderId = event.loaderId; data.loaderId = event.loaderId;
data.lifecycleEvents.clear(); frame._firedLifecycleEvents.clear();
} }
data.lifecycleEvents.add(event.name); if (event.name === 'load')
frame._firedLifecycleEvents.add('load');
else if (event.name === 'DOMContentLoaded')
frame._firedLifecycleEvents.add('domcontentloaded');
this.emit(FrameManagerEvents.LifecycleEvent, frame); this.emit(FrameManagerEvents.LifecycleEvent, frame);
} }
@ -242,9 +238,8 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
const frame = this._frames.get(frameId); const frame = this._frames.get(frameId);
if (!frame) if (!frame)
return; return;
const data = this._frameData(frame); frame._firedLifecycleEvents.add('domcontentloaded');
data.lifecycleEvents.add('DOMContentLoaded'); frame._firedLifecycleEvents.add('load');
data.lifecycleEvents.add('load');
this.emit(FrameManagerEvents.LifecycleEvent, frame); this.emit(FrameManagerEvents.LifecycleEvent, frame);
} }
@ -284,7 +279,6 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
const data: FrameData = { const data: FrameData = {
id: frameId, id: frameId,
loaderId: '', loaderId: '',
lifecycleEvents: new Set(),
}; };
frame[frameDataSymbol] = data; frame[frameDataSymbol] = data;
this._frames.set(frameId, frame); this._frames.set(frameId, frame);
@ -316,7 +310,6 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
const data: FrameData = { const data: FrameData = {
id: framePayload.id, id: framePayload.id,
loaderId: '', loaderId: '',
lifecycleEvents: new Set(),
}; };
frame[frameDataSymbol] = data; frame[frameDataSymbol] = data;
} }

View File

@ -24,7 +24,7 @@ import * as frames from '../frames';
import * as network from '../network'; import * as network from '../network';
export class LifecycleWatcher { export class LifecycleWatcher {
private _expectedLifecycle: string[]; private _expectedLifecycle: frames.LifecycleEvent[];
private _frameManager: FrameManager; private _frameManager: FrameManager;
private _frame: frames.Frame; private _frame: frames.Frame;
private _initialLoaderId: string; private _initialLoaderId: string;
@ -43,17 +43,12 @@ export class LifecycleWatcher {
private _maximumTimer: NodeJS.Timer; private _maximumTimer: NodeJS.Timer;
private _hasSameDocumentNavigation: boolean; private _hasSameDocumentNavigation: boolean;
constructor(frameManager: FrameManager, frame: frames.Frame, waitUntil: string | string[], timeout: number) { constructor(frameManager: FrameManager, frame: frames.Frame, waitUntil: frames.LifecycleEvent | frames.LifecycleEvent[], timeout: number) {
if (Array.isArray(waitUntil)) if (Array.isArray(waitUntil))
waitUntil = waitUntil.slice(); waitUntil = waitUntil.slice();
else if (typeof waitUntil === 'string') else if (typeof waitUntil === 'string')
waitUntil = [waitUntil]; waitUntil = [waitUntil];
this._expectedLifecycle = waitUntil.map(value => { this._expectedLifecycle = waitUntil.slice();
const protocolEvent = playwrightToProtocolLifecycle.get(value);
assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value);
return protocolEvent;
});
this._frameManager = frameManager; this._frameManager = frameManager;
this._frame = frame; this._frame = frame;
this._initialLoaderId = frameManager._frameData(frame).loaderId; this._initialLoaderId = frameManager._frameData(frame).loaderId;
@ -139,9 +134,9 @@ export class LifecycleWatcher {
} }
_checkLifecycleComplete() { _checkLifecycleComplete() {
const checkLifecycle = (frame: frames.Frame, expectedLifecycle: string[]): boolean => { const checkLifecycle = (frame: frames.Frame, expectedLifecycle: frames.LifecycleEvent[]): boolean => {
for (const event of expectedLifecycle) { for (const event of expectedLifecycle) {
if (!this._frameManager._frameData(frame).lifecycleEvents.has(event)) if (!frame._firedLifecycleEvents.has(event))
return false; return false;
} }
for (const child of frame.childFrames()) { for (const child of frame.childFrames()) {
@ -168,10 +163,3 @@ export class LifecycleWatcher {
clearTimeout(this._maximumTimer); clearTimeout(this._maximumTimer);
} }
} }
const playwrightToProtocolLifecycle = new Map([
['load', 'load'],
['domcontentloaded', 'DOMContentLoaded'],
['networkidle0', 'networkIdle'],
['networkidle2', 'networkAlmostIdle'],
]);

View File

@ -52,7 +52,6 @@ const frameDataSymbol = Symbol('frameData');
type FrameData = { type FrameData = {
frameId: string, frameId: string,
lastCommittedNavigationId: string, lastCommittedNavigationId: string,
firedEvents: Set<string>,
}; };
export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate { export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate {
@ -163,7 +162,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
frame._navigated(params.url, params.name); frame._navigated(params.url, params.name);
const data = this._frameData(frame); const data = this._frameData(frame);
data.lastCommittedNavigationId = params.navigationId; data.lastCommittedNavigationId = params.navigationId;
data.firedEvents.clear(); frame._firedLifecycleEvents.clear();
this.emit(FrameManagerEvents.FrameNavigated, frame); this.emit(FrameManagerEvents.FrameNavigated, frame);
this._page.emit(CommonEvents.Page.FrameNavigated, frame); this._page.emit(CommonEvents.Page.FrameNavigated, frame);
} }
@ -181,7 +180,6 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
const data: FrameData = { const data: FrameData = {
frameId: params.frameId, frameId: params.frameId,
lastCommittedNavigationId: '', lastCommittedNavigationId: '',
firedEvents: new Set(),
}; };
frame[frameDataSymbol] = data; frame[frameDataSymbol] = data;
if (!parentFrame) { if (!parentFrame) {
@ -203,12 +201,16 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
_onEventFired({frameId, name}) { _onEventFired({frameId, name}) {
const frame = this._frames.get(frameId); const frame = this._frames.get(frameId);
this._frameData(frame).firedEvents.add(name.toLowerCase()); if (name === 'load') {
if (frame === this._mainFrame) { frame._firedLifecycleEvents.add('load');
if (name === 'load') { if (frame === this._mainFrame) {
this.emit(FrameManagerEvents.Load); this.emit(FrameManagerEvents.Load);
this._page.emit(CommonEvents.Page.Load); this._page.emit(CommonEvents.Page.Load);
} else if (name === 'DOMContentLoaded') { }
}
if (name === 'DOMContentLoaded') {
frame._firedLifecycleEvents.add('domcontentloaded');
if (frame === this._mainFrame) {
this.emit(FrameManagerEvents.DOMContentLoaded); this.emit(FrameManagerEvents.DOMContentLoaded);
this._page.emit(CommonEvents.Page.DOMContentLoaded); this._page.emit(CommonEvents.Page.DOMContentLoaded);
} }
@ -261,7 +263,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
async waitForFrameNavigation(frame: frames.Frame, options: frames.NavigateOptions = {}) { async waitForFrameNavigation(frame: frames.Frame, options: frames.NavigateOptions = {}) {
const { const {
timeout = this._page._timeoutSettings.navigationTimeout(), timeout = this._page._timeoutSettings.navigationTimeout(),
waitUntil = ['load'], waitUntil = (['load'] as frames.LifecycleEvent[]),
} = options; } = options;
const normalizedWaitUntil = normalizeWaitUntil(waitUntil); const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
@ -306,7 +308,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}) { async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}) {
const { const {
timeout = this._page._timeoutSettings.navigationTimeout(), timeout = this._page._timeoutSettings.navigationTimeout(),
waitUntil = ['load'], waitUntil = (['load'] as frames.LifecycleEvent[]),
referer, referer,
} = options; } = options;
const normalizedWaitUntil = normalizeWaitUntil(waitUntil); const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
@ -388,7 +390,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
private async _go(action: () => Promise<{ navigationId: string | null, navigationURL: string | null }>, options: frames.NavigateOptions = {}) { private async _go(action: () => Promise<{ navigationId: string | null, navigationURL: string | null }>, options: frames.NavigateOptions = {}) {
const { const {
timeout = this._page._timeoutSettings.navigationTimeout(), timeout = this._page._timeoutSettings.navigationTimeout(),
waitUntil = ['load'], waitUntil = (['load'] as frames.LifecycleEvent[]),
} = options; } = options;
const frame = this.mainFrame(); const frame = this.mainFrame();
const normalizedWaitUntil = normalizeWaitUntil(waitUntil); const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
@ -434,7 +436,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
} }
} }
export function normalizeWaitUntil(waitUntil) { export function normalizeWaitUntil(waitUntil: frames.LifecycleEvent | frames.LifecycleEvent[]): frames.LifecycleEvent[] {
if (!Array.isArray(waitUntil)) if (!Array.isArray(waitUntil))
waitUntil = [waitUntil]; waitUntil = [waitUntil];
for (const condition of waitUntil) { for (const condition of waitUntil) {

View File

@ -20,6 +20,7 @@ import { JugglerSessionEvents } from './Connection';
import { FrameManagerEvents, FrameManager } from './FrameManager'; import { FrameManagerEvents, FrameManager } from './FrameManager';
import { NetworkManager, NetworkManagerEvents } from './NetworkManager'; import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
import * as frames from '../frames'; import * as frames from '../frames';
import * as network from '../network';
export class NextNavigationWatchdog { export class NextNavigationWatchdog {
private _frameManager: FrameManager; private _frameManager: FrameManager;
@ -75,14 +76,14 @@ export class NavigationWatchdog {
private _frameManager: FrameManager; private _frameManager: FrameManager;
private _navigatedFrame: frames.Frame; private _navigatedFrame: frames.Frame;
private _targetNavigationId: any; private _targetNavigationId: any;
private _firedEvents: any; private _firedEvents: frames.LifecycleEvent[];
private _targetURL: any; private _targetURL: any;
private _promise: Promise<unknown>; private _promise: Promise<unknown>;
private _resolveCallback: (value?: unknown) => void; private _resolveCallback: (value?: unknown) => void;
private _navigationRequest: any; private _navigationRequest: network.Request | null;
private _eventListeners: RegisteredListener[]; private _eventListeners: RegisteredListener[];
constructor(frameManager: FrameManager, navigatedFrame: frames.Frame, networkManager: NetworkManager, targetNavigationId, targetURL, firedEvents) { constructor(frameManager: FrameManager, navigatedFrame: frames.Frame, networkManager: NetworkManager, targetNavigationId, targetURL, firedEvents: frames.LifecycleEvent[]) {
this._frameManager = frameManager; this._frameManager = frameManager;
this._navigatedFrame = navigatedFrame; this._navigatedFrame = navigatedFrame;
this._targetNavigationId = targetNavigationId; this._targetNavigationId = targetNavigationId;
@ -113,17 +114,17 @@ export class NavigationWatchdog {
this._navigationRequest = request; this._navigationRequest = request;
} }
navigationResponse() { navigationResponse(): network.Response | null {
return this._navigationRequest ? this._navigationRequest.response() : null; return this._navigationRequest ? this._navigationRequest.response() : null;
} }
_checkNavigationComplete() { _checkNavigationComplete() {
const checkFiredEvents = (frame: frames.Frame, firedEvents) => { const checkFiredEvents = (frame: frames.Frame, firedEvents: frames.LifecycleEvent[]) => {
for (const subframe of frame.childFrames()) { for (const subframe of frame.childFrames()) {
if (!checkFiredEvents(subframe, firedEvents)) if (!checkFiredEvents(subframe, firedEvents))
return false; return false;
} }
return firedEvents.every(event => this._frameManager._frameData(frame).firedEvents.has(event)); return firedEvents.every(event => frame._firedLifecycleEvents.has(event));
}; };
if (this._navigatedFrame.isDetached()) if (this._navigatedFrame.isDetached())

View File

@ -37,7 +37,7 @@ type World = {
export type NavigateOptions = { export type NavigateOptions = {
timeout?: number, timeout?: number,
waitUntil?: string | string[], waitUntil?: LifecycleEvent | LifecycleEvent[],
}; };
export type GotoOptions = NavigateOptions & { export type GotoOptions = NavigateOptions & {
@ -50,8 +50,11 @@ export interface FrameDelegate {
setFrameContent(frame: Frame, html: string, options?: NavigateOptions): Promise<void>; setFrameContent(frame: Frame, html: string, options?: NavigateOptions): Promise<void>;
} }
export type LifecycleEvent = 'load' | 'domcontentloaded';
export class Frame { export class Frame {
_delegate: FrameDelegate; readonly _delegate: FrameDelegate;
readonly _firedLifecycleEvents: Set<LifecycleEvent>;
private _timeoutSettings: TimeoutSettings; private _timeoutSettings: TimeoutSettings;
private _parentFrame: Frame; private _parentFrame: Frame;
private _url = ''; private _url = '';
@ -62,6 +65,7 @@ export class Frame {
constructor(delegate: FrameDelegate, timeoutSettings: TimeoutSettings, parentFrame: Frame | null) { constructor(delegate: FrameDelegate, timeoutSettings: TimeoutSettings, parentFrame: Frame | null) {
this._delegate = delegate; this._delegate = delegate;
this._firedLifecycleEvents = new Set();
this._timeoutSettings = timeoutSettings; this._timeoutSettings = timeoutSettings;
this._parentFrame = parentFrame; this._parentFrame = parentFrame;

View File

@ -22,7 +22,7 @@ import { assert, debugError, helper, RegisteredListener } from '../helper';
import * as js from '../javascript'; import * as js from '../javascript';
import * as dom from '../dom'; import * as dom from '../dom';
import * as network from '../network'; import * as network from '../network';
import { TargetSession } from './Connection'; import { TargetSession, TargetSessionEvents } from './Connection';
import { Events } from './events'; import { Events } from './events';
import { Events as CommonEvents } from '../events'; import { Events as CommonEvents } from '../events';
import { ExecutionContextDelegate } from './ExecutionContext'; import { ExecutionContextDelegate } from './ExecutionContext';
@ -43,11 +43,13 @@ export const FrameManagerEvents = {
FrameAttached: Symbol('FrameAttached'), FrameAttached: Symbol('FrameAttached'),
FrameDetached: Symbol('FrameDetached'), FrameDetached: Symbol('FrameDetached'),
FrameNavigated: Symbol('FrameNavigated'), FrameNavigated: Symbol('FrameNavigated'),
LifecycleEvent: Symbol('LifecycleEvent'),
}; };
const frameDataSymbol = Symbol('frameData'); const frameDataSymbol = Symbol('frameData');
type FrameData = { type FrameData = {
id: string, id: string,
loaderId: string,
}; };
export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate { export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate {
@ -121,6 +123,8 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
helper.addEventListener(this._session, 'Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)), helper.addEventListener(this._session, 'Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)),
helper.addEventListener(this._session, 'Page.frameDetached', event => this._onFrameDetached(event.frameId)), helper.addEventListener(this._session, 'Page.frameDetached', event => this._onFrameDetached(event.frameId)),
helper.addEventListener(this._session, 'Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)), helper.addEventListener(this._session, 'Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)),
helper.addEventListener(this._session, 'Page.loadEventFired', event => this._onLifecycleEvent(event.frameId, 'load')),
helper.addEventListener(this._session, 'Page.domContentEventFired', event => this._onLifecycleEvent(event.frameId, 'domcontentloaded')),
helper.addEventListener(this._session, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)), helper.addEventListener(this._session, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),
helper.addEventListener(this._session, 'Page.loadEventFired', event => this._page.emit(Events.Page.Load)), helper.addEventListener(this._session, 'Page.loadEventFired', event => this._page.emit(Events.Page.Load)),
helper.addEventListener(this._session, 'Console.messageAdded', event => this._onConsoleMessage(event)), helper.addEventListener(this._session, 'Console.messageAdded', event => this._onConsoleMessage(event)),
@ -146,6 +150,29 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
const frame = this._frames.get(frameId); const frame = this._frames.get(frameId);
if (!frame) if (!frame)
return; return;
const hasDOMContentLoaded = frame._firedLifecycleEvents.has('domcontentloaded');
const hasLoad = frame._firedLifecycleEvents.has('load');
frame._firedLifecycleEvents.add('domcontentloaded');
frame._firedLifecycleEvents.add('load');
this.emit(FrameManagerEvents.LifecycleEvent, frame);
if (frame === this.mainFrame() && !hasDOMContentLoaded)
this._page.emit(CommonEvents.Page.DOMContentLoaded);
if (frame === this.mainFrame() && !hasLoad)
this._page.emit(CommonEvents.Page.Load);
}
_onLifecycleEvent(frameId: string, event: frames.LifecycleEvent) {
const frame = this._frames.get(frameId);
if (!frame)
return;
frame._firedLifecycleEvents.add(event);
this.emit(FrameManagerEvents.LifecycleEvent, frame);
if (frame === this.mainFrame()) {
if (event === 'load')
this._page.emit(CommonEvents.Page.Load);
if (event === 'domcontentloaded')
this._page.emit(CommonEvents.Page.DOMContentLoaded);
}
} }
_handleFrameTree(frameTree: Protocol.Page.FrameResourceTree) { _handleFrameTree(frameTree: Protocol.Page.FrameResourceTree) {
@ -187,6 +214,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
const frame = new frames.Frame(this, this._page._timeoutSettings, parentFrame); const frame = new frames.Frame(this, this._page._timeoutSettings, parentFrame);
const data: FrameData = { const data: FrameData = {
id: frameId, id: frameId,
loaderId: '',
}; };
frame[frameDataSymbol] = data; frame[frameDataSymbol] = data;
this._frames.set(frameId, frame); this._frames.set(frameId, frame);
@ -215,6 +243,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
frame = new frames.Frame(this, this._page._timeoutSettings, null); frame = new frames.Frame(this, this._page._timeoutSettings, null);
const data: FrameData = { const data: FrameData = {
id: framePayload.id, id: framePayload.id,
loaderId: framePayload.loaderId,
}; };
frame[frameDataSymbol] = data; frame[frameDataSymbol] = data;
this._frames.set(framePayload.id, frame); this._frames.set(framePayload.id, frame);
@ -228,6 +257,10 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
// Update frame payload. // Update frame payload.
frame._navigated(framePayload.url, framePayload.name); frame._navigated(framePayload.url, framePayload.name);
frame._firedLifecycleEvents.clear();
const data = this._frameData(frame);
data.loaderId = framePayload.loaderId;
for (const context of this._contextIdToContext.values()) { for (const context of this._contextIdToContext.values()) {
if (context.frame() === frame) { if (context.frame() === frame) {
const delegate = context._delegate as ExecutionContextDelegate; const delegate = context._delegate as ExecutionContextDelegate;
@ -292,30 +325,60 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
this._page.emit(CommonEvents.Page.FrameDetached, frame); this._page.emit(CommonEvents.Page.FrameDetached, frame);
} }
async navigateFrame(frame: frames.Frame, url: string, options: { referer?: string; timeout?: number; waitUntil?: string | Array<string>; } | undefined = {}): Promise<network.Response | null> { async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}): Promise<network.Response | null> {
const { const {
timeout = this._page._timeoutSettings.navigationTimeout(), timeout = this._page._timeoutSettings.navigationTimeout(),
waitUntil = (['load'] as frames.LifecycleEvent[])
} = options; } = options;
const watchDog = new NextNavigationWatchdog(this, frame, timeout); const watchDog = new NextNavigationWatchdog(this, frame, waitUntil, timeout);
await this._session.send('Page.navigate', {url}); await this._session.send('Page.navigate', {url, frameId: this._frameData(frame).id});
return watchDog.waitForNavigation(); const error = await Promise.race([
watchDog.timeoutOrTerminationPromise(),
watchDog.newDocumentNavigationPromise(),
watchDog.sameDocumentNavigationPromise(),
]);
watchDog.dispose();
if (error)
throw error;
return watchDog.navigationResponse();
} }
async waitForFrameNavigation(frame: frames.Frame, options?: frames.NavigateOptions): Promise<network.Response | null> { async waitForFrameNavigation(frame: frames.Frame, options: frames.NavigateOptions = {}): Promise<network.Response | null> {
// FIXME: this method only works for main frames. const {
const watchDog = new NextNavigationWatchdog(this, frame, 10000); timeout = this._page._timeoutSettings.navigationTimeout(),
return watchDog.waitForNavigation(); waitUntil = (['load'] as frames.LifecycleEvent[])
} = options;
const watchDog = new NextNavigationWatchdog(this, frame, waitUntil, timeout);
const error = await Promise.race([
watchDog.timeoutOrTerminationPromise(),
watchDog.newDocumentNavigationPromise(),
watchDog.sameDocumentNavigationPromise(),
]);
watchDog.dispose();
if (error)
throw error;
return watchDog.navigationResponse();
} }
async setFrameContent(frame: frames.Frame, html: string, options: { timeout?: number; waitUntil?: string | Array<string>; } | undefined = {}) { async setFrameContent(frame: frames.Frame, html: string, options: frames.NavigateOptions = {}) {
// We rely upon the fact that document.open() will trigger Page.loadEventFired. // We rely upon the fact that document.open() will trigger Page.loadEventFired.
const watchDog = new NextNavigationWatchdog(this, frame, 1000); const {
timeout = this._page._timeoutSettings.navigationTimeout(),
waitUntil = (['load'] as frames.LifecycleEvent[])
} = options;
const watchDog = new NextNavigationWatchdog(this, frame, waitUntil, timeout);
await frame.evaluate(html => { await frame.evaluate(html => {
document.open(); document.open();
document.write(html); document.write(html);
document.close(); document.close();
}, html); }, html);
await watchDog.waitForNavigation(); const error = await Promise.race([
watchDog.timeoutOrTerminationPromise(),
watchDog.lifecyclePromise(),
]);
watchDog.dispose();
if (error)
throw error;
} }
async _onConsoleMessage(event: Protocol.Console.messageAddedPayload) { async _onConsoleMessage(event: Protocol.Console.messageAddedPayload) {
@ -441,52 +504,90 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
class NextNavigationWatchdog { class NextNavigationWatchdog {
_frameManager: FrameManager; _frameManager: FrameManager;
_frame: frames.Frame; _frame: frames.Frame;
_newDocumentNavigationPromise: Promise<unknown>; _newDocumentNavigationPromise: Promise<Error | null>;
_newDocumentNavigationCallback: (value?: unknown) => void; _newDocumentNavigationCallback: (value?: unknown) => void;
_sameDocumentNavigationPromise: Promise<unknown>; _sameDocumentNavigationPromise: Promise<Error | null>;
_sameDocumentNavigationCallback: (value?: unknown) => void; _sameDocumentNavigationCallback: (value?: unknown) => void;
private _lifecyclePromise: Promise<void>;
private _lifecycleCallback: () => void;
private _terminationPromise: Promise<Error | null>;
private _terminationCallback: (err: Error | null) => void;
_navigationRequest: any; _navigationRequest: any;
_eventListeners: RegisteredListener[]; _eventListeners: RegisteredListener[];
_timeoutPromise: Promise<unknown>; _timeoutPromise: Promise<Error | null>;
_timeoutId: NodeJS.Timer; _timeoutId: NodeJS.Timer;
_hasSameDocumentNavigation = false;
_expectedLifecycle: frames.LifecycleEvent[];
_initialLoaderId: string;
_disconnectedListener: RegisteredListener;
constructor(frameManager: FrameManager, frame: frames.Frame, timeout) { constructor(frameManager: FrameManager, frame: frames.Frame, waitUntil: frames.LifecycleEvent | frames.LifecycleEvent[], timeout) {
if (Array.isArray(waitUntil))
waitUntil = waitUntil.slice();
else if (typeof waitUntil === 'string')
waitUntil = [waitUntil];
this._expectedLifecycle = waitUntil.slice();
this._frameManager = frameManager; this._frameManager = frameManager;
this._frame = frame; this._frame = frame;
this._initialLoaderId = frameManager._frameData(frame).loaderId;
this._newDocumentNavigationPromise = new Promise(fulfill => { this._newDocumentNavigationPromise = new Promise(fulfill => {
this._newDocumentNavigationCallback = fulfill; this._newDocumentNavigationCallback = fulfill;
}); });
this._sameDocumentNavigationPromise = new Promise(fulfill => { this._sameDocumentNavigationPromise = new Promise(fulfill => {
this._sameDocumentNavigationCallback = fulfill; this._sameDocumentNavigationCallback = fulfill;
}); });
this._lifecyclePromise = new Promise(fulfill => {
this._lifecycleCallback = fulfill;
});
/** @type {?Request} */ /** @type {?Request} */
this._navigationRequest = null; this._navigationRequest = null;
this._eventListeners = [ this._eventListeners = [
helper.addEventListener(frameManager._page, Events.Page.Load, event => this._newDocumentNavigationCallback()), helper.addEventListener(frameManager, FrameManagerEvents.LifecycleEvent, frame => this._onLifecycleEvent(frame)),
helper.addEventListener(frameManager, FrameManagerEvents.FrameNavigated, frame => this._onLifecycleEvent(frame)),
helper.addEventListener(frameManager, FrameManagerEvents.FrameNavigatedWithinDocument, frame => this._onSameDocumentNavigation(frame)), helper.addEventListener(frameManager, FrameManagerEvents.FrameNavigatedWithinDocument, frame => this._onSameDocumentNavigation(frame)),
helper.addEventListener(frameManager, FrameManagerEvents.TargetSwappedOnNavigation, event => this._onTargetReconnected()), helper.addEventListener(frameManager, FrameManagerEvents.TargetSwappedOnNavigation, event => this._onTargetReconnected()),
helper.addEventListener(frameManager, FrameManagerEvents.FrameDetached, frame => this._onFrameDetached(frame)),
helper.addEventListener(frameManager.networkManager(), NetworkManagerEvents.Request, this._onRequest.bind(this)), helper.addEventListener(frameManager.networkManager(), NetworkManagerEvents.Request, this._onRequest.bind(this)),
]; ];
this._registerDisconnectedListener();
const timeoutError = new TimeoutError('Navigation Timeout Exceeded: ' + timeout + 'ms'); const timeoutError = new TimeoutError('Navigation Timeout Exceeded: ' + timeout + 'ms');
let timeoutCallback; let timeoutCallback;
this._timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError)); this._timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
this._timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null; this._timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
this._terminationPromise = new Promise(fulfill => {
this._terminationCallback = fulfill;
});
} }
async waitForNavigation() { sameDocumentNavigationPromise(): Promise<Error | null> {
const error = await Promise.race([ return this._sameDocumentNavigationPromise;
this._timeoutPromise, }
this._newDocumentNavigationPromise,
this._sameDocumentNavigationPromise newDocumentNavigationPromise(): Promise<Error | null> {
]); return this._newDocumentNavigationPromise;
// TODO: handle exceptions }
this.dispose();
if (error) lifecyclePromise(): Promise<any> {
throw error; return this._lifecyclePromise;
return this.navigationResponse(); }
timeoutOrTerminationPromise(): Promise<Error | null> {
return Promise.race([this._timeoutPromise, this._terminationPromise]);
}
_registerDisconnectedListener() {
if (this._disconnectedListener)
helper.removeEventListeners([this._disconnectedListener]);
const session = this._frameManager._session;
this._disconnectedListener = helper.addEventListener(this._frameManager._session, TargetSessionEvents.Disconnected, () => {
// Session may change on swap out, check that it's current.
if (session === this._frameManager._session)
this._terminationCallback(new Error('Navigation failed because browser has disconnected!'));
});
} }
async _onTargetReconnected() { async _onTargetReconnected() {
this._registerDisconnectedListener();
// In case web process change we migh have missed load event. Check current ready // In case web process change we migh have missed load event. Check current ready
// state to mitigate that. // state to mitigate that.
try { try {
@ -504,9 +605,50 @@ class NextNavigationWatchdog {
} }
} }
_onLifecycleEvent(frame: frames.Frame) {
this._checkLifecycle();
}
_onSameDocumentNavigation(frame) { _onSameDocumentNavigation(frame) {
if (this._frame === frame) if (this._frame === frame)
this._hasSameDocumentNavigation = true;
this._checkLifecycle();
}
_checkLifecycle() {
const checkLifecycle = (frame: frames.Frame, expectedLifecycle: frames.LifecycleEvent[]): boolean => {
for (const event of expectedLifecycle) {
if (!frame._firedLifecycleEvents.has(event))
return false;
}
for (const child of frame.childFrames()) {
if (!checkLifecycle(child, expectedLifecycle))
return false;
}
return true;
};
if (this._frame.isDetached()) {
this._newDocumentNavigationCallback(new Error('Navigating frame was detached'));
this._sameDocumentNavigationCallback(new Error('Navigating frame was detached'));
return;
}
if (!checkLifecycle(this._frame, this._expectedLifecycle))
return;
this._lifecycleCallback();
if (this._hasSameDocumentNavigation)
this._sameDocumentNavigationCallback(); this._sameDocumentNavigationCallback();
if (this._frameManager._frameData(this._frame).loaderId !== this._initialLoaderId)
this._newDocumentNavigationCallback();
}
_onFrameDetached(frame: frames.Frame) {
if (this._frame === frame) {
this._terminationCallback.call(null, new Error('Navigating frame was detached'));
return;
}
this._checkLifecycle();
} }
_onRequest(request: network.Request) { _onRequest(request: network.Request) {
@ -520,6 +662,7 @@ class NextNavigationWatchdog {
} }
dispose() { dispose() {
// TODO: handle exceptions
helper.removeEventListeners(this._eventListeners); helper.removeEventListeners(this._eventListeners);
clearTimeout(this._timeoutId); clearTimeout(this._timeoutId);
} }

View File

@ -20,7 +20,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('Browser.version', function() { describe('Browser.version', function() {
it('should return whether we are in headless', async({browser}) => { it.skip(WEBKIT)('should return whether we are in headless', async({browser}) => {
const version = await browser.version(); const version = await browser.version();
expect(version.length).toBeGreaterThan(0); expect(version.length).toBeGreaterThan(0);
if (CHROME) if (CHROME)

View File

@ -197,7 +197,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
expect(error.message).toBe('No node found for selector: button.does-not-exist'); expect(error.message).toBe('No node found for selector: button.does-not-exist');
}); });
// @see https://github.com/GoogleChrome/puppeteer/issues/161 // @see https://github.com/GoogleChrome/puppeteer/issues/161
it('should not hang with touch-enabled viewports', async({page, server}) => { it.skip(WEBKIT)('should not hang with touch-enabled viewports', async({page, server}) => {
await page.setViewport(playwright.devices['iPhone 6'].viewport); await page.setViewport(playwright.devices['iPhone 6'].viewport);
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(100, 10); await page.mouse.move(100, 10);
@ -328,7 +328,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
expect(await page.evaluate(() => offsetY)).toBe(1910); expect(await page.evaluate(() => offsetY)).toBe(1910);
}); });
it('should update modifiers correctly', async({page, server}) => { it.skip(WEBKIT)('should update modifiers correctly', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
await page.click('button', { modifiers: ['Shift'] }); await page.click('button', { modifiers: ['Shift'] });
expect(await page.evaluate(() => shiftKey)).toBe(true); expect(await page.evaluate(() => shiftKey)).toBe(true);

View File

@ -70,7 +70,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
expect(newDimensions.width).toBe(Math.round(width + 104)); expect(newDimensions.width).toBe(Math.round(width + 104));
expect(newDimensions.height).toBe(Math.round(height + 104)); expect(newDimensions.height).toBe(Math.round(height + 104));
}); });
it('should select the text with mouse', async({page, server}) => { it.skip(WEBKIT)('should select the text with mouse', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html'); await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea'); await page.focus('textarea');
const text = 'This is the text that we are going to try to select. Let\'s see how it goes.'; const text = 'This is the text that we are going to try to select. Let\'s see how it goes.';
@ -103,7 +103,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
await page.hover('#button-6'); await page.hover('#button-6');
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6'); expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
}); });
it('should set modifier keys on click', async({page, server}) => { it.skip(WEBKIT)('should set modifier keys on click', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html'); await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() => document.querySelector('#button-3').addEventListener('mousedown', e => window.lastEvent = e, true)); await page.evaluate(() => document.querySelector('#button-3').addEventListener('mousedown', e => window.lastEvent = e, true));
const modifiers = {'Shift': 'shiftKey', 'Control': 'ctrlKey', 'Alt': 'altKey', 'Meta': 'metaKey'}; const modifiers = {'Shift': 'shiftKey', 'Control': 'ctrlKey', 'Alt': 'altKey', 'Meta': 'metaKey'};

View File

@ -26,7 +26,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(page.url()).toBe(server.EMPTY_PAGE); expect(page.url()).toBe(server.EMPTY_PAGE);
}); });
it.skip(WEBKIT)('should work with anchor navigation', async({page, server}) => { it('should work with anchor navigation', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(page.url()).toBe(server.EMPTY_PAGE); expect(page.url()).toBe(server.EMPTY_PAGE);
await page.goto(server.EMPTY_PAGE + '#foo'); await page.goto(server.EMPTY_PAGE + '#foo');
@ -72,7 +72,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'domcontentloaded'}); const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'domcontentloaded'});
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
}); });
it.skip(WEBKIT)('should work when page calls history API in beforeunload', async({page, server}) => { it('should work when page calls history API in beforeunload', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => { await page.evaluate(() => {
window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false); window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false);
@ -80,23 +80,23 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
const response = await page.goto(server.PREFIX + '/grid.html'); const response = await page.goto(server.PREFIX + '/grid.html');
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
}); });
it.skip(FFOX || WEBKIT)('should navigate to empty page with networkidle0', async({page, server}) => { xit('should navigate to empty page with networkidle0', async({page, server}) => {
const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle0'}); const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle0'});
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
}); });
it.skip(FFOX || WEBKIT)('should navigate to empty page with networkidle2', async({page, server}) => { xit('should navigate to empty page with networkidle2', async({page, server}) => {
const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle2'}); const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle2'});
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
}); });
it.skip(WEBKIT)('should fail when navigating to bad url', async({page, server}) => { it.skip(WEBKIT)('should fail when navigating to bad url', async({page, server}) => {
let error = null; let error = null;
await page.goto('asdfasdf').catch(e => error = e); await page.goto('asdfasdf').catch(e => error = e);
// FIXME: shows dialog in WebKit.
if (CHROME || WEBKIT) if (CHROME || WEBKIT)
expect(error.message).toContain('Cannot navigate to invalid URL'); expect(error.message).toContain('Cannot navigate to invalid URL');
else else
expect(error.message).toContain('Invalid url'); expect(error.message).toContain('Invalid url');
}); });
// FIXME: shows dialog in WebKit.
it.skip(WEBKIT)('should fail when navigating to bad SSL', async({page, httpsServer}) => { it.skip(WEBKIT)('should fail when navigating to bad SSL', async({page, httpsServer}) => {
// Make sure that network events do not emit 'undefined'. // Make sure that network events do not emit 'undefined'.
// @see https://crbug.com/750469 // @see https://crbug.com/750469
@ -105,23 +105,24 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
page.on('requestfailed', request => expect(request).toBeTruthy()); page.on('requestfailed', request => expect(request).toBeTruthy());
let error = null; let error = null;
await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e);
// FIXME: shows dialog in WebKit.
if (CHROME || WEBKIT) if (CHROME || WEBKIT)
expect(error.message).toContain('net::ERR_CERT_AUTHORITY_INVALID'); expect(error.message).toContain('net::ERR_CERT_AUTHORITY_INVALID');
else else
expect(error.message).toContain('SSL_ERROR_UNKNOWN'); expect(error.message).toContain('SSL_ERROR_UNKNOWN');
}); });
// FIXME: shows dialog in WebKit.
it.skip(WEBKIT)('should fail when navigating to bad SSL after redirects', async({page, server, httpsServer}) => { it.skip(WEBKIT)('should fail when navigating to bad SSL after redirects', async({page, server, httpsServer}) => {
server.setRedirect('/redirect/1.html', '/redirect/2.html'); server.setRedirect('/redirect/1.html', '/redirect/2.html');
server.setRedirect('/redirect/2.html', '/empty.html'); server.setRedirect('/redirect/2.html', '/empty.html');
let error = null; let error = null;
await page.goto(httpsServer.PREFIX + '/redirect/1.html').catch(e => error = e); await page.goto(httpsServer.PREFIX + '/redirect/1.html').catch(e => error = e);
// FIXME: shows dialog in WebKit.
if (CHROME || WEBKIT) if (CHROME || WEBKIT)
expect(error.message).toContain('net::ERR_CERT_AUTHORITY_INVALID'); expect(error.message).toContain('net::ERR_CERT_AUTHORITY_INVALID');
else else
expect(error.message).toContain('SSL_ERROR_UNKNOWN'); expect(error.message).toContain('SSL_ERROR_UNKNOWN');
}); });
it.skip(FFOX || WEBKIT)('should throw if networkidle is passed as an option', async({page, server}) => { xit('should throw if networkidle is passed as an option', async({page, server}) => {
let error = null; let error = null;
await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle'}).catch(err => error = err); await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle'}).catch(err => error = err);
expect(error.message).toContain('"networkidle" option is no longer supported'); expect(error.message).toContain('"networkidle" option is no longer supported');
@ -203,7 +204,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
expect(response.url()).toBe(server.EMPTY_PAGE); expect(response.url()).toBe(server.EMPTY_PAGE);
}); });
it.skip(FFOX || WEBKIT)('should wait for network idle to succeed navigation', async({page, server}) => { xit('should wait for network idle to succeed navigation', async({page, server}) => {
let responses = []; let responses = [];
// Hold on to a bunch of requests without answering. // Hold on to a bunch of requests without answering.
server.setRoute('/fetch-request-a.js', (req, res) => responses.push(res)); server.setRoute('/fetch-request-a.js', (req, res) => responses.push(res));
@ -270,6 +271,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
expect(warning).toBe(null); expect(warning).toBe(null);
}); });
it.skip(WEBKIT)('should not leak listeners during bad navigation', async({page, server}) => { it.skip(WEBKIT)('should not leak listeners during bad navigation', async({page, server}) => {
// FIXME: shows dialog in webkit.
let warning = null; let warning = null;
const warningHandler = w => warning = w; const warningHandler = w => warning = w;
process.on('warning', warningHandler); process.on('warning', warningHandler);
@ -347,7 +349,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
expect(response.url()).toContain('grid.html'); expect(response.url()).toContain('grid.html');
}); });
it.skip(WEBKIT)('should work with both domcontentloaded and load', async({page, server}) => { it('should work with both domcontentloaded and load', async({page, server}) => {
let response = null; let response = null;
server.setRoute('/one-style.css', (req, res) => response = res); server.setRoute('/one-style.css', (req, res) => response = res);
const navigationPromise = page.goto(server.PREFIX + '/one-style.html'); const navigationPromise = page.goto(server.PREFIX + '/one-style.html');
@ -436,11 +438,14 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
it.skip(WEBKIT)('should work when subframe issues window.stop()', async({page, server}) => { it.skip(WEBKIT)('should work when subframe issues window.stop()', async({page, server}) => {
server.setRoute('/frames/style.css', (req, res) => {}); server.setRoute('/frames/style.css', (req, res) => {});
const navigationPromise = page.goto(server.PREFIX + '/frames/one-frame.html'); const navigationPromise = page.goto(server.PREFIX + '/frames/one-frame.html');
const frame = await utils.waitEvent(page, 'frameattached'); let frame;
await new Promise(fulfill => { await new Promise(fulfill => {
page.on('framenavigated', f => { page.once('frameattached', attached => {
if (f === frame) frame = attached;
fulfill(); page.on('framenavigated', f => {
if (f === frame)
fulfill();
});
}); });
}); });
await Promise.all([ await Promise.all([
@ -484,7 +489,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
}); });
describe('Frame.goto', function() { describe('Frame.goto', function() {
it.skip(WEBKIT)('should navigate subframes', async({page, server}) => { it('should navigate subframes', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html'); await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames()[0].url()).toContain('/frames/one-frame.html'); expect(page.frames()[0].url()).toContain('/frames/one-frame.html');
expect(page.frames()[1].url()).toContain('/frames/frame.html'); expect(page.frames()[1].url()).toContain('/frames/frame.html');
@ -493,7 +498,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
expect(response.frame()).toBe(page.frames()[1]); expect(response.frame()).toBe(page.frames()[1]);
}); });
it.skip(WEBKIT)('should reject when frame detaches', async({page, server}) => { it('should reject when frame detaches', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html'); await page.goto(server.PREFIX + '/frames/one-frame.html');
server.setRoute('/empty.html', () => {}); server.setRoute('/empty.html', () => {});
@ -534,7 +539,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
}); });
describe('Frame.waitForNavigation', function() { describe('Frame.waitForNavigation', function() {
it.skip(WEBKIT)('should work', async({page, server}) => { it('should work', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html'); await page.goto(server.PREFIX + '/frames/one-frame.html');
const frame = page.frames()[1]; const frame = page.frames()[1];
const [response] = await Promise.all([ const [response] = await Promise.all([
@ -546,7 +551,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
expect(response.frame()).toBe(frame); expect(response.frame()).toBe(frame);
expect(page.url()).toContain('/frames/one-frame.html'); expect(page.url()).toContain('/frames/one-frame.html');
}); });
it.skip(WEBKIT)('should fail when frame detaches', async({page, server}) => { it('should fail when frame detaches', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html'); await page.goto(server.PREFIX + '/frames/one-frame.html');
const frame = page.frames()[1]; const frame = page.frames()[1];

View File

@ -80,7 +80,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
await page.evaluate(() => window.__FOO = 1); await page.evaluate(() => window.__FOO = 1);
await watchdog; await watchdog;
}); });
it.skip(WEBKIT)('should work when resolved right before execution context disposal', async({page, server}) => { it('should work when resolved right before execution context disposal', async({page, server}) => {
// FIXME: implement Page.addScriptToEvaluateOnNewDocument in WebKit. // FIXME: implement Page.addScriptToEvaluateOnNewDocument in WebKit.
await page.evaluateOnNewDocument(() => window.__RELOADED = true); await page.evaluateOnNewDocument(() => window.__RELOADED = true);
await page.waitForFunction(() => { await page.waitForFunction(() => {