From aa4c893b092b5bc56768fcaea5261a45f0ebe3e1 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 15 Jul 2020 18:48:19 -0700 Subject: [PATCH] feat(rpc): implement waitForNavigation on the client (#2949) Drive-by: fix electron issues, exposed by the test using waitForNavigation. Drive-by: mark some tests skip(CHANNEL) that were mistakenly marked skip(USES_HOOKS). --- src/frames.ts | 35 +++++++++++----- src/rpc/channels.ts | 7 ++-- src/rpc/client/electron.ts | 5 ++- src/rpc/client/frame.ts | 62 ++++++++++++++++++++++++---- src/rpc/client/network.ts | 4 ++ src/rpc/client/page.ts | 8 +--- src/rpc/client/waiter.ts | 24 ++++++++++- src/rpc/server/electronDispatcher.ts | 10 +++-- src/rpc/server/frameDispatcher.ts | 10 ++++- src/rpc/server/pageDispatcher.ts | 5 --- src/server/electron.ts | 2 +- test/autowaiting.spec.js | 2 +- test/chromium/session.spec.js | 2 +- test/electron/electron.spec.js | 2 +- test/evaluation.spec.js | 4 +- test/navigation.spec.js | 16 +++---- test/page.spec.js | 4 +- test/queryselector.spec.js | 2 +- test/waittask.spec.js | 4 +- 19 files changed, 144 insertions(+), 64 deletions(-) diff --git a/src/frames.ts b/src/frames.ts index 526da04a46..1cb81b9093 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -53,8 +53,16 @@ type ConsoleTagHandler = () => void; export type FunctionWithSource = (source: { context: BrowserContext, page: Page, frame: Frame}, ...args: any) => any; export const kNavigationEvent = Symbol('navigation'); -type NavigationEvent = { - documentInfo?: DocumentInfo, // Undefined for same-document navigations. +export type NavigationEvent = { + // New frame url after navigation. + url: string, + // New frame name after navigation. + name: string, + // Information about the new document for cross-document navigations. + // Undefined for same-document navigations. + newDocument?: DocumentInfo, + // Error for cross-document navigations if any. When error is present, + // the navigation did not commit. error?: Error, }; export const kAddLifecycleEvent = Symbol('addLifecycle'); @@ -172,9 +180,9 @@ export class FrameManager { else frame._currentDocument = { documentId, request: undefined }; frame._pendingDocument = undefined; - const navigationEvent: NavigationEvent = { documentInfo: frame._currentDocument }; - frame._eventEmitter.emit(kNavigationEvent, navigationEvent); frame._onClearLifecycle(); + const navigationEvent: NavigationEvent = { url, name, newDocument: frame._currentDocument }; + frame._eventEmitter.emit(kNavigationEvent, navigationEvent); if (!initial) { this._page._logger.info(` navigated to "${url}"`); this._page.emit(Events.Page.FrameNavigated, frame); @@ -186,7 +194,7 @@ export class FrameManager { if (!frame) return; frame._url = url; - const navigationEvent: NavigationEvent = {}; + const navigationEvent: NavigationEvent = { url, name: frame._name }; frame._eventEmitter.emit(kNavigationEvent, navigationEvent); this._page._logger.info(` navigated to "${url}"`); this._page.emit(Events.Page.FrameNavigated, frame); @@ -198,7 +206,12 @@ export class FrameManager { return; if (documentId !== undefined && frame._pendingDocument.documentId !== documentId) return; - const navigationEvent: NavigationEvent = { documentInfo: frame._pendingDocument, error: new Error(errorText) }; + const navigationEvent: NavigationEvent = { + url: frame._url, + name: frame._name, + newDocument: frame._pendingDocument, + error: new Error(errorText), + }; frame._pendingDocument = undefined; frame._eventEmitter.emit(kNavigationEvent, navigationEvent); } @@ -410,7 +423,7 @@ export class Frame { } url = helper.completeUserURL(url); - const sameDocument = helper.waitForEvent(progress, this._eventEmitter, kNavigationEvent, (e: NavigationEvent) => !e.documentInfo); + const sameDocument = helper.waitForEvent(progress, this._eventEmitter, kNavigationEvent, (e: NavigationEvent) => !e.newDocument); const navigateResult = await this._page._delegate.navigateFrame(this, url, referer); let event: NavigationEvent; @@ -419,10 +432,10 @@ export class Frame { event = await helper.waitForEvent(progress, this._eventEmitter, kNavigationEvent, (event: NavigationEvent) => { // We are interested either in this specific document, or any other document that // did commit and replaced the expected document. - return event.documentInfo && (event.documentInfo.documentId === navigateResult.newDocumentId || !event.error); + return event.newDocument && (event.newDocument.documentId === navigateResult.newDocumentId || !event.error); }).promise; - if (event.documentInfo!.documentId !== navigateResult.newDocumentId) { + if (event.newDocument!.documentId !== navigateResult.newDocumentId) { // This is just a sanity check. In practice, new navigation should // cancel the previous one and report "request cancelled"-like error. throw new Error('Navigation interrupted by another one'); @@ -436,7 +449,7 @@ export class Frame { if (!this._subtreeLifecycleEvents.has(waitUntil)) await helper.waitForEvent(progress, this._eventEmitter, kAddLifecycleEvent, (e: types.LifecycleEvent) => e === waitUntil).promise; - const request = event.documentInfo ? event.documentInfo.request : undefined; + const request = event.newDocument ? event.newDocument.request : undefined; return request ? request._finalRequest().response() : null; }); } @@ -460,7 +473,7 @@ export class Frame { if (!this._subtreeLifecycleEvents.has(waitUntil)) await helper.waitForEvent(progress, this._eventEmitter, kAddLifecycleEvent, (e: types.LifecycleEvent) => e === waitUntil).promise; - const request = navigationEvent.documentInfo ? navigationEvent.documentInfo.request : undefined; + const request = navigationEvent.newDocument ? navigationEvent.newDocument.request : undefined; return request ? request._finalRequest().response() : null; }); } diff --git a/src/rpc/channels.ts b/src/rpc/channels.ts index 8d246f3058..4d09618b5f 100644 --- a/src/rpc/channels.ts +++ b/src/rpc/channels.ts @@ -119,7 +119,6 @@ export interface PageChannel extends Channel { on(event: 'fileChooser', callback: (params: { element: ElementHandleChannel, isMultiple: boolean }) => void): this; on(event: 'frameAttached', callback: (params: { frame: FrameChannel }) => void): this; on(event: 'frameDetached', callback: (params: { frame: FrameChannel }) => void): this; - on(event: 'frameNavigated', callback: (params: { frame: FrameChannel, url: string, name: string }) => void): this; on(event: 'load', callback: () => void): this; on(event: 'pageError', callback: (params: { error: types.Error }) => void): this; on(event: 'popup', callback: (params: { page: PageChannel }) => void): this; @@ -173,8 +172,11 @@ export type PageInitializer = { isClosed: boolean }; +export type FrameNavigatedEvent = { url: string, name: string, newDocument?: { request?: RequestChannel }, error?: string }; + export interface FrameChannel extends Channel { on(event: 'loadstate', callback: (params: { add?: types.LifecycleEvent, remove?: types.LifecycleEvent }) => void): this; + on(event: 'navigated', callback: (params: FrameNavigatedEvent) => void): this; evalOnSelector(params: { selector: string; expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>; evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>; @@ -206,7 +208,6 @@ export interface FrameChannel extends Channel { type(params: { selector: string, text: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions): Promise; uncheck(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions): Promise; waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions): Promise<{ handle: JSHandleChannel }>; - waitForNavigation(params: types.WaitForNavigationOptions): Promise<{ response: ResponseChannel | null }>; waitForSelector(params: { selector: string } & types.WaitForElementOptions): Promise<{ element: ElementHandleChannel | null }>; } export type FrameInitializer = { @@ -403,7 +404,7 @@ export type ElectronInitializer = {}; export interface ElectronApplicationChannel extends Channel { on(event: 'close', callback: () => void): this; - on(event: 'window', callback: (params: { page: PageChannel }) => void): this; + on(event: 'window', callback: (params: { page: PageChannel, browserWindow: JSHandleChannel }) => void): this; newBrowserWindow(params: { arg: any }): Promise<{ page: PageChannel }>; evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }>; diff --git a/src/rpc/client/electron.ts b/src/rpc/client/electron.ts index 0dd5879202..341276ce2d 100644 --- a/src/rpc/client/electron.ts +++ b/src/rpc/client/electron.ts @@ -24,6 +24,7 @@ import { ElectronEvents } from '../../server/electron'; import { TimeoutSettings } from '../../timeoutSettings'; import { Waiter } from './waiter'; import { TimeoutError } from '../../errors'; +import { Events } from '../../events'; export class Electron extends ChannelOwner { static from(electron: ElectronChannel): Electron { @@ -53,10 +54,12 @@ export class ElectronApplication extends ChannelOwner { + this._channel.on('window', ({ page, browserWindow }) => { const window = Page.from(page); + (window as any).browserWindow = JSHandle.from(browserWindow); this._windows.add(window); this.emit(ElectronEvents.ElectronApplication.Window, window); + window.once(Events.Page.Close, () => this._windows.delete(window)); }); this._channel.on('close', () => { this.emit(ElectronEvents.ElectronApplication.Close); diff --git a/src/rpc/client/frame.ts b/src/rpc/client/frame.ts index 4ab9dfaab5..5c29ee1a33 100644 --- a/src/rpc/client/frame.ts +++ b/src/rpc/client/frame.ts @@ -15,9 +15,9 @@ * limitations under the License. */ -import { assertMaxArguments } from '../../helper'; +import { assertMaxArguments, helper } from '../../helper'; import * as types from '../../types'; -import { FrameChannel, FrameInitializer } from '../channels'; +import { FrameChannel, FrameInitializer, FrameNavigatedEvent } from '../channels'; import { BrowserContext } from './browserContext'; import { ChannelOwner } from './channelOwner'; import { ElementHandle, convertSelectOptionValues, convertInputFiles } from './elementHandle'; @@ -75,6 +75,13 @@ export class Frame extends ChannelOwner { if (event.remove) this._loadStates.delete(event.remove); }); + this._channel.on('navigated', event => { + this._url = event.url; + this._name = event.name; + this._eventEmitter.emit('navigated', event); + if (!event.error && this._page) + this._page.emit(Events.Page.FrameNavigated, this); + }); } private _apiName(method: string) { @@ -87,9 +94,48 @@ export class Frame extends ChannelOwner { }); } + private _setupNavigationWaiter(): Waiter { + const waiter = new Waiter(); + waiter.rejectOnEvent(this._page!, Events.Page.Close, new Error('Navigation failed because page was closed!')); + waiter.rejectOnEvent(this._page!, Events.Page.Crash, new Error('Navigation failed because page crashed!')); + waiter.rejectOnEvent(this._page!, Events.Page.FrameDetached, new Error('Navigating frame was detached!'), frame => frame === this); + return waiter; + } + async waitForNavigation(options: types.WaitForNavigationOptions = {}): Promise { return this._wrapApiCall(this._apiName('waitForNavigation'), async () => { - return network.Response.fromNullable((await this._channel.waitForNavigation({ ...options })).response); + const waitUntil = verifyLoadState(options.waitUntil === undefined ? 'load' : options.waitUntil); + const timeout = this._page!._timeoutSettings.navigationTimeout(options); + const waiter = this._setupNavigationWaiter(); + waiter.rejectOnTimeout(timeout, new TimeoutError(`Timeout ${timeout}ms exceeded.`)); + + const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : ''; + waiter.log(`waiting for navigation${toUrl} until "${waitUntil}"`); + + const navigatedEvent = await waiter.waitForEvent(this._eventEmitter, 'navigated', event => { + // Any failed navigation results in a rejection. + if (event.error) + return true; + waiter.log(` navigated to "${event.url}"`); + return helper.urlMatches(event.url, options.url); + }); + if (navigatedEvent.error) { + const e = new Error(navigatedEvent.error); + e.stack = ''; + await waiter.waitForPromise(Promise.reject(e)); + } + + if (!this._loadStates.has(waitUntil)) { + await waiter.waitForEvent(this._eventEmitter, 'loadstate', s => { + waiter.log(` "${s}" event fired`); + return s === waitUntil; + }); + } + + const request = navigatedEvent.newDocument ? network.Request.fromNullable(navigatedEvent.newDocument.request || null) : null; + const response = request ? await waiter.waitForPromise(request._finalRequest().response()) : null; + waiter.dispose(); + return response; }); } @@ -99,12 +145,12 @@ export class Frame extends ChannelOwner { return; return this._wrapApiCall(this._apiName('waitForLoadState'), async () => { const timeout = this._page!._timeoutSettings.navigationTimeout(options); - const waiter = new Waiter(); - waiter.rejectOnEvent(this._page!, Events.Page.Close, new Error('Navigation failed because page was closed!')); - waiter.rejectOnEvent(this._page!, Events.Page.Crash, new Error('Navigation failed because page crashed!')); - waiter.rejectOnEvent(this._page!, Events.Page.FrameDetached, new Error('Navigating frame was detached!'), frame => frame === this); + const waiter = this._setupNavigationWaiter(); waiter.rejectOnTimeout(timeout, new TimeoutError(`Timeout ${timeout}ms exceeded.`)); - await waiter.waitForEvent(this._eventEmitter, 'loadstate', s => s === state); + await waiter.waitForEvent(this._eventEmitter, 'loadstate', s => { + waiter.log(` "${s}" event fired`); + return s === state; + }); waiter.dispose(); }); } diff --git a/src/rpc/client/network.ts b/src/rpc/client/network.ts index 0034ed4875..0119f9a81c 100644 --- a/src/rpc/client/network.ts +++ b/src/rpc/client/network.ts @@ -132,6 +132,10 @@ export class Request extends ChannelOwner { errorText: this._failureText }; } + + _finalRequest(): Request { + return this._redirectedTo ? this._redirectedTo._finalRequest() : this; + } } export class Route extends ChannelOwner { diff --git a/src/rpc/client/page.ts b/src/rpc/client/page.ts index e34696c86a..e9fb8026c1 100644 --- a/src/rpc/client/page.ts +++ b/src/rpc/client/page.ts @@ -75,6 +75,7 @@ export class Page extends ChannelOwner { constructor(parent: ChannelOwner, type: string, guid: string, initializer: PageInitializer) { super(parent, type, guid, initializer); + this.setMaxListeners(0); this._browserContext = parent as BrowserContext; this._timeoutSettings = new TimeoutSettings(this._browserContext._timeoutSettings); @@ -98,7 +99,6 @@ export class Page extends ChannelOwner { this._channel.on('fileChooser', ({ element, isMultiple }) => this.emit(Events.Page.FileChooser, new FileChooser(this, ElementHandle.from(element), isMultiple))); this._channel.on('frameAttached', ({ frame }) => this._onFrameAttached(Frame.from(frame))); this._channel.on('frameDetached', ({ frame }) => this._onFrameDetached(Frame.from(frame))); - this._channel.on('frameNavigated', ({ frame, url, name }) => this._onFrameNavigated(Frame.from(frame), url, name)); this._channel.on('load', () => this.emit(Events.Page.Load)); this._channel.on('pageError', ({ error }) => this.emit(Events.Page.PageError, parseError(error))); this._channel.on('popup', ({ page }) => this.emit(Events.Page.Popup, Page.from(page))); @@ -136,12 +136,6 @@ export class Page extends ChannelOwner { this.emit(Events.Page.FrameDetached, frame); } - private _onFrameNavigated(frame: Frame, url: string, name: string) { - frame._url = url; - frame._name = name; - this.emit(Events.Page.FrameNavigated, frame); - } - private _onRoute(route: Route, request: Request) { for (const {url, handler} of this._routes) { if (helper.urlMatches(request.url(), url)) { diff --git a/src/rpc/client/waiter.ts b/src/rpc/client/waiter.ts index 62eff42f83..a9e062cb4d 100644 --- a/src/rpc/client/waiter.ts +++ b/src/rpc/client/waiter.ts @@ -15,14 +15,17 @@ */ import { EventEmitter } from 'events'; +import { rewriteErrorMessage } from '../../utils/stackTrace'; export class Waiter { private _dispose: (() => void)[] = []; private _failures: Promise[] = []; + // TODO: can/should we move these logs into wrapApiCall? + private _logs: string[] = []; async waitForEvent(emitter: EventEmitter, event: string, predicate?: (arg: T) => boolean): Promise { const { promise, dispose } = waitForEvent(emitter, event, predicate); - return this._wait(promise, dispose); + return this.waitForPromise(promise, dispose); } rejectOnEvent(emitter: EventEmitter, event: string, error: Error, predicate?: (arg: T) => boolean) { @@ -42,7 +45,7 @@ export class Waiter { dispose(); } - private async _wait(promise: Promise, dispose?: () => void): Promise { + async waitForPromise(promise: Promise, dispose?: () => void): Promise { try { const result = await Promise.race([promise, ...this._failures]); if (dispose) @@ -52,10 +55,15 @@ export class Waiter { if (dispose) dispose(); this.dispose(); + rewriteErrorMessage(e, e.message + formatLogRecording(this._logs) + kLoggingNote); throw e; } } + log(s: string) { + this._logs.push(s); + } + private _rejectOn(promise: Promise, dispose?: () => void) { this._failures.push(promise); if (dispose) @@ -89,3 +97,15 @@ function waitForTimeout(timeout: number): { promise: Promise, dispose: () const dispose = () => clearTimeout(timeoutId); return { promise, dispose }; } + +const kLoggingNote = `\nNote: use DEBUG=pw:api environment variable and rerun to capture Playwright logs.`; + +function formatLogRecording(log: string[]): string { + if (!log.length) + return ''; + const header = ` logs `; + const headerLength = 60; + const leftLength = (headerLength - header.length) / 2; + const rightLength = headerLength - header.length - leftLength; + return `\n${'='.repeat(leftLength)}${header}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`; +} diff --git a/src/rpc/server/electronDispatcher.ts b/src/rpc/server/electronDispatcher.ts index e72169f52b..1166f11a0f 100644 --- a/src/rpc/server/electronDispatcher.ts +++ b/src/rpc/server/electronDispatcher.ts @@ -15,11 +15,10 @@ */ import { Dispatcher, DispatcherScope, lookupDispatcher } from './dispatcher'; -import { Electron, ElectronApplication, ElectronEvents } from '../../server/electron'; +import { Electron, ElectronApplication, ElectronEvents, ElectronPage } from '../../server/electron'; import { ElectronApplicationChannel, ElectronApplicationInitializer, PageChannel, JSHandleChannel, ElectronInitializer, ElectronChannel, ElectronLaunchOptions } from '../channels'; import { BrowserContextDispatcher } from './browserContextDispatcher'; import { BrowserContextBase } from '../../browserContext'; -import { Page } from '../../page'; import { PageDispatcher } from './pageDispatcher'; import { parseArgument } from './jsHandleDispatcher'; import { createHandle } from './elementHandlerDispatcher'; @@ -42,8 +41,11 @@ export class ElectronApplicationDispatcher extends Dispatcher this._dispatchEvent('close')); - electronApplication.on(ElectronEvents.ElectronApplication.Window, (page: Page) => { - this._dispatchEvent('window', { page: lookupDispatcher(page) }); + electronApplication.on(ElectronEvents.ElectronApplication.Window, (page: ElectronPage) => { + this._dispatchEvent('window', { + page: lookupDispatcher(page), + browserWindow: createHandle(this._scope, page.browserWindow), + }); }); } diff --git a/src/rpc/server/frameDispatcher.ts b/src/rpc/server/frameDispatcher.ts index 370ec8a8f3..30197edbe3 100644 --- a/src/rpc/server/frameDispatcher.ts +++ b/src/rpc/server/frameDispatcher.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { Frame, kAddLifecycleEvent, kRemoveLifecycleEvent } from '../../frames'; +import { Frame, kAddLifecycleEvent, kRemoveLifecycleEvent, kNavigationEvent, NavigationEvent } from '../../frames'; import * as types from '../../types'; import { ElementHandleChannel, FrameChannel, FrameInitializer, JSHandleChannel, ResponseChannel } from '../channels'; import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher'; import { convertSelectOptionValues, ElementHandleDispatcher, createHandle, convertInputFiles } from './elementHandlerDispatcher'; import { parseArgument, serializeResult } from './jsHandleDispatcher'; -import { ResponseDispatcher } from './networkDispatchers'; +import { ResponseDispatcher, RequestDispatcher } from './networkDispatchers'; export class FrameDispatcher extends Dispatcher implements FrameChannel { private _frame: Frame; @@ -44,6 +44,12 @@ export class FrameDispatcher extends Dispatcher impleme frame._eventEmitter.on(kRemoveLifecycleEvent, (event: types.LifecycleEvent) => { this._dispatchEvent('loadstate', { remove: event }); }); + frame._eventEmitter.on(kNavigationEvent, (event: NavigationEvent) => { + const params = { url: event.url, name: event.name, error: event.error ? event.error.message : undefined }; + if (event.newDocument) + (params as any).newDocument = { request: RequestDispatcher.fromNullable(this._scope, event.newDocument.request || null) }; + this._dispatchEvent('navigated', params); + }); } async goto(params: { url: string } & types.GotoOptions): Promise<{ response: ResponseChannel | null }> { diff --git a/src/rpc/server/pageDispatcher.ts b/src/rpc/server/pageDispatcher.ts index c5f2ac67f8..33b0dbdbfa 100644 --- a/src/rpc/server/pageDispatcher.ts +++ b/src/rpc/server/pageDispatcher.ts @@ -57,7 +57,6 @@ export class PageDispatcher extends Dispatcher implements })); page.on(Events.Page.FrameAttached, frame => this._onFrameAttached(frame)); page.on(Events.Page.FrameDetached, frame => this._onFrameDetached(frame)); - page.on(Events.Page.FrameNavigated, frame => this._onFrameNavigated(frame)); page.on(Events.Page.Load, () => this._dispatchEvent('load')); page.on(Events.Page.PageError, error => this._dispatchEvent('pageError', { error: serializeError(error) })); page.on(Events.Page.Popup, page => this._dispatchEvent('popup', { page: lookupDispatcher(page) })); @@ -219,10 +218,6 @@ export class PageDispatcher extends Dispatcher implements this._dispatchEvent('frameAttached', { frame: FrameDispatcher.from(this._scope, frame) }); } - _onFrameNavigated(frame: Frame) { - this._dispatchEvent('frameNavigated', { frame: lookupDispatcher(frame), url: frame.url(), name: frame.name() }); - } - _onFrameDetached(frame: Frame) { this._dispatchEvent('frameDetached', { frame: lookupDispatcher(frame) }); } diff --git a/src/server/electron.ts b/src/server/electron.ts index 4a15480a8e..fb03e13f28 100644 --- a/src/server/electron.ts +++ b/src/server/electron.ts @@ -52,7 +52,7 @@ export const ElectronEvents = { } }; -interface ElectronPage extends Page { +export interface ElectronPage extends Page { browserWindow: js.JSHandle; _browserWindowId: number; } diff --git a/test/autowaiting.spec.js b/test/autowaiting.spec.js index 0f03d6bf96..827e7ee79d 100644 --- a/test/autowaiting.spec.js +++ b/test/autowaiting.spec.js @@ -166,7 +166,7 @@ describe('Auto waiting', () => { await page.setContent(`empty.html`); await Promise.all([ page.click('a').then(() => page.waitForLoadState('load')).then(() => messages.push('clickload')), - page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')), + page.waitForEvent('framenavigated').then(() => page.waitForLoadState('domcontentloaded')).then(() => messages.push('domcontentloaded')), ]); expect(messages.join('|')).toBe('route|domcontentloaded|clickload'); }); diff --git a/test/chromium/session.spec.js b/test/chromium/session.spec.js index f8edb2a58c..ba31ed8fcb 100644 --- a/test/chromium/session.spec.js +++ b/test/chromium/session.spec.js @@ -66,7 +66,7 @@ describe('ChromiumBrowserContext.createSession', function() { } expect(error.message).toContain(CHANNEL ? 'Target browser or context has been closed' : 'Session closed.'); }); - it.skip(USES_HOOKS)('should throw nice errors', async function({page, browser}) { + it.skip(CHANNEL)('should throw nice errors', async function({page, browser}) { const client = await page.context().newCDPSession(page); const error = await theSourceOfTheProblems().catch(error => error); expect(error.stack).toContain('theSourceOfTheProblems'); diff --git a/test/electron/electron.spec.js b/test/electron/electron.spec.js index 08fef8672e..32efad7c57 100644 --- a/test/electron/electron.spec.js +++ b/test/electron/electron.spec.js @@ -55,7 +55,7 @@ describe('Electron', function() { await page.goto('data:text/html,Hello World 2'); expect(await page.title()).toBe('Hello World 2'); }); - it.skip(CHANNEL)('should create multiple windows', async ({ application }) => { + it('should create multiple windows', async ({ application }) => { const createPage = async ordinal => { const page = await application.newBrowserWindow({ width: 800, height: 600 }); await Promise.all([ diff --git a/test/evaluation.spec.js b/test/evaluation.spec.js index ed197d9724..ab3960b807 100644 --- a/test/evaluation.spec.js +++ b/test/evaluation.spec.js @@ -17,7 +17,7 @@ const utils = require('./utils'); const path = require('path'); -const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = utils.testOptions(browserType); +const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS, CHANNEL} = utils.testOptions(browserType); describe('Page.evaluate', function() { it('should work', async({page, server}) => { @@ -373,7 +373,7 @@ describe('Page.evaluate', function() { }); expect(result).toBe(undefined); }); - it.slow().skip(USES_HOOKS)('should transfer 100Mb of data from page to node.js', async({page, server}) => { + it.slow().skip(CHANNEL)('should transfer 100Mb of data from page to node.js', async({page, server}) => { const a = await page.evaluate(() => Array(100 * 1024 * 1024 + 1).join('a')); expect(a.length).toBe(100 * 1024 * 1024); }); diff --git a/test/navigation.spec.js b/test/navigation.spec.js index 96a54dd671..999e11ac7e 100644 --- a/test/navigation.spec.js +++ b/test/navigation.spec.js @@ -460,18 +460,14 @@ describe('Page.goto', function() { return Promise.all([ server.waitForRequest(suffix), frame._page.waitForRequest(server.PREFIX + suffix), - ]) + ]); } let responses = {}; // Hold on to a bunch of requests without answering. server.setRoute('/fetch-request-a.js', (req, res) => responses.a = res); - const initialFetchResourcesRequested = Promise.all([ - waitForRequest('/fetch-request-a.js'), - ]); - - let secondFetchResourceRequested; + const firstFetchResourceRequested = waitForRequest('/fetch-request-a.js'); server.setRoute('/fetch-request-d.js', (req, res) => responses.d = res); - secondFetchResourceRequested = waitForRequest('/fetch-request-d.js'); + const secondFetchResourceRequested = waitForRequest('/fetch-request-d.js'); const waitForLoadPromise = isSetContent ? Promise.resolve() : frame.waitForNavigation({ waitUntil: 'load' }); @@ -487,8 +483,8 @@ describe('Page.goto', function() { await waitForLoadPromise; expect(actionFinished).toBe(false); - // Wait for the initial three resources to be requested. - await initialFetchResourcesRequested; + // Wait for the initial resource to be requested. + await firstFetchResourceRequested; expect(actionFinished).toBe(false); expect(responses.a).toBeTruthy(); @@ -564,7 +560,7 @@ describe('Page.goto', function() { }); }); -describe.skip(CHANNEL)('Page.waitForNavigation', function() { +describe('Page.waitForNavigation', function() { it('should work', async({page, server}) => { await page.goto(server.EMPTY_PAGE); const [response] = await Promise.all([ diff --git a/test/page.spec.js b/test/page.spec.js index f3f1e4f60e..41ba6c2392 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -18,7 +18,7 @@ const path = require('path'); const util = require('util'); const vm = require('vm'); -const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS} = require('./utils').testOptions(browserType); +const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = require('./utils').testOptions(browserType); describe('Page.close', function() { it('should reject all promises when page is closed', async({context}) => { @@ -101,7 +101,7 @@ describe('Page.Events.Load', function() { }); }); -describe.skip(USES_HOOKS)('Async stacks', () => { +describe.skip(CHANNEL)('Async stacks', () => { it('should work', async({page, server}) => { server.setRoute('/empty.html', (req, res) => { req.socket.end(); diff --git a/test/queryselector.spec.js b/test/queryselector.spec.js index bfb0254700..c7f8b46be2 100644 --- a/test/queryselector.spec.js +++ b/test/queryselector.spec.js @@ -525,7 +525,7 @@ describe('text selector', () => { expect(await page.$$eval(`text=lowo`, els => els.map(e => e.outerHTML).join(''))).toBe('
helloworld
helloworld'); }); - it.skip(CHANNEL)('create', async ({page}) => { + it('create', async ({playwright, page}) => { await page.setContent(`
yo
"ya
ye ye
`); expect(await playwright.selectors._createSelector('text', await page.$('div'))).toBe('yo'); expect(await playwright.selectors._createSelector('text', await page.$('div:nth-child(2)'))).toBe('"\\"ya"'); diff --git a/test/waittask.spec.js b/test/waittask.spec.js index 6ae957c5bf..de08285b19 100644 --- a/test/waittask.spec.js +++ b/test/waittask.spec.js @@ -16,7 +16,7 @@ */ const utils = require('./utils'); -const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = utils.testOptions(browserType); +const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = utils.testOptions(browserType); async function giveItTimeToLog(frame) { await frame.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)))); @@ -458,7 +458,7 @@ describe('Frame.waitForSelector', function() { await page.setContent(`
anything
`); expect(await page.evaluate(x => x.textContent, await waitForSelector)).toBe('anything'); }); - it.skip(USES_HOOKS)('should have correct stack trace for timeout', async({page, server}) => { + it.skip(CHANNEL)('should have correct stack trace for timeout', async({page, server}) => { let error; await page.waitForSelector('.zombo', { timeout: 10 }).catch(e => error = e); expect(error.stack).toContain('waittask.spec.js');