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).
This commit is contained in:
parent
824f6491d3
commit
aa4c893b09
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<void>;
|
||||
uncheck(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
|
||||
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 }>;
|
||||
|
|
|
|||
|
|
@ -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<ElectronChannel, ElectronInitializer> {
|
||||
static from(electron: ElectronChannel): Electron {
|
||||
|
|
@ -53,10 +54,12 @@ export class ElectronApplication extends ChannelOwner<ElectronApplicationChannel
|
|||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: ElectronApplicationInitializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
this._context = BrowserContext.from(initializer.context);
|
||||
this._channel.on('window', ({page}) => {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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<FrameChannel, FrameInitializer> {
|
|||
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<FrameChannel, FrameInitializer> {
|
|||
});
|
||||
}
|
||||
|
||||
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<Frame>(this._page!, Events.Page.FrameDetached, new Error('Navigating frame was detached!'), frame => frame === this);
|
||||
return waiter;
|
||||
}
|
||||
|
||||
async waitForNavigation(options: types.WaitForNavigationOptions = {}): Promise<network.Response | null> {
|
||||
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<FrameNavigatedEvent>(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<types.LifecycleEvent>(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<FrameChannel, FrameInitializer> {
|
|||
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<Frame>(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<types.LifecycleEvent>(this._eventEmitter, 'loadstate', s => s === state);
|
||||
await waiter.waitForEvent<types.LifecycleEvent>(this._eventEmitter, 'loadstate', s => {
|
||||
waiter.log(` "${s}" event fired`);
|
||||
return s === state;
|
||||
});
|
||||
waiter.dispose();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,6 +132,10 @@ export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
|
|||
errorText: this._failureText
|
||||
};
|
||||
}
|
||||
|
||||
_finalRequest(): Request {
|
||||
return this._redirectedTo ? this._redirectedTo._finalRequest() : this;
|
||||
}
|
||||
}
|
||||
|
||||
export class Route extends ChannelOwner<RouteChannel, RouteInitializer> {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
|
|||
|
||||
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<PageChannel, PageInitializer> {
|
|||
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<PageChannel, PageInitializer> {
|
|||
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)) {
|
||||
|
|
|
|||
|
|
@ -15,14 +15,17 @@
|
|||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { rewriteErrorMessage } from '../../utils/stackTrace';
|
||||
|
||||
export class Waiter {
|
||||
private _dispose: (() => void)[] = [];
|
||||
private _failures: Promise<any>[] = [];
|
||||
// TODO: can/should we move these logs into wrapApiCall?
|
||||
private _logs: string[] = [];
|
||||
|
||||
async waitForEvent<T = void>(emitter: EventEmitter, event: string, predicate?: (arg: T) => boolean): Promise<T> {
|
||||
const { promise, dispose } = waitForEvent(emitter, event, predicate);
|
||||
return this._wait(promise, dispose);
|
||||
return this.waitForPromise(promise, dispose);
|
||||
}
|
||||
|
||||
rejectOnEvent<T = void>(emitter: EventEmitter, event: string, error: Error, predicate?: (arg: T) => boolean) {
|
||||
|
|
@ -42,7 +45,7 @@ export class Waiter {
|
|||
dispose();
|
||||
}
|
||||
|
||||
private async _wait<T>(promise: Promise<T>, dispose?: () => void): Promise<T> {
|
||||
async waitForPromise<T>(promise: Promise<T>, dispose?: () => void): Promise<T> {
|
||||
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<any>, dispose?: () => void) {
|
||||
this._failures.push(promise);
|
||||
if (dispose)
|
||||
|
|
@ -89,3 +97,15 @@ function waitForTimeout(timeout: number): { promise: Promise<void>, 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)}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ElectronApplicatio
|
|||
});
|
||||
|
||||
electronApplication.on(ElectronEvents.ElectronApplication.Close, () => this._dispatchEvent('close'));
|
||||
electronApplication.on(ElectronEvents.ElectronApplication.Window, (page: Page) => {
|
||||
this._dispatchEvent('window', { page: lookupDispatcher<PageDispatcher>(page) });
|
||||
electronApplication.on(ElectronEvents.ElectronApplication.Window, (page: ElectronPage) => {
|
||||
this._dispatchEvent('window', {
|
||||
page: lookupDispatcher<PageDispatcher>(page),
|
||||
browserWindow: createHandle(this._scope, page.browserWindow),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Frame, FrameInitializer> implements FrameChannel {
|
||||
private _frame: Frame;
|
||||
|
|
@ -44,6 +44,12 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> 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 }> {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> 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<PageDispatcher>(page) }));
|
||||
|
|
@ -219,10 +218,6 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
|
|||
this._dispatchEvent('frameAttached', { frame: FrameDispatcher.from(this._scope, frame) });
|
||||
}
|
||||
|
||||
_onFrameNavigated(frame: Frame) {
|
||||
this._dispatchEvent('frameNavigated', { frame: lookupDispatcher<FrameDispatcher>(frame), url: frame.url(), name: frame.name() });
|
||||
}
|
||||
|
||||
_onFrameDetached(frame: Frame) {
|
||||
this._dispatchEvent('frameDetached', { frame: lookupDispatcher<FrameDispatcher>(frame) });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export const ElectronEvents = {
|
|||
}
|
||||
};
|
||||
|
||||
interface ElectronPage extends Page {
|
||||
export interface ElectronPage extends Page {
|
||||
browserWindow: js.JSHandle<BrowserWindow>;
|
||||
_browserWindowId: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ describe('Auto waiting', () => {
|
|||
await page.setContent(`<a href="${server.EMPTY_PAGE}">empty.html</a>`);
|
||||
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');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ describe('Electron', function() {
|
|||
await page.goto('data:text/html,<title>Hello World 2</title>');
|
||||
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([
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -525,7 +525,7 @@ describe('text selector', () => {
|
|||
expect(await page.$$eval(`text=lowo`, els => els.map(e => e.outerHTML).join(''))).toBe('<div>helloworld</div><span>helloworld</span>');
|
||||
});
|
||||
|
||||
it.skip(CHANNEL)('create', async ({page}) => {
|
||||
it('create', async ({playwright, page}) => {
|
||||
await page.setContent(`<div>yo</div><div>"ya</div><div>ye ye</div>`);
|
||||
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"');
|
||||
|
|
|
|||
|
|
@ -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(`<div class='zombo'>anything</div>`);
|
||||
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');
|
||||
|
|
|
|||
Loading…
Reference in a new issue