feat: add "commit" to possible waitUntil options (#9892)

This commit is contained in:
Yury Semikhatsky 2021-11-01 17:12:19 -07:00 committed by GitHub
parent ddda507ccd
commit 13cc266b37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 135 additions and 88 deletions

View File

@ -1,10 +1,11 @@
## navigation-wait-until
- `waitUntil` <[WaitUntilState]<"load"|"domcontentloaded"|"networkidle">>
- `waitUntil` <[WaitUntilState]<"load"|"domcontentloaded"|"networkidle"|"commit">>
When to consider operation succeeded, defaults to `load`. Events can be either:
* `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* `'load'` - consider operation to be finished when the `load` event is fired.
* `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
* `'commit'` - consider operation to be finished when network response is received and the document started loading.
## navigation-timeout
- `timeout` <[float]>

View File

@ -27,7 +27,7 @@ import { Page } from './page';
import { EventEmitter } from 'events';
import { Waiter } from './waiter';
import { Events } from './events';
import { LifecycleEvent, URLMatch, SelectOption, SelectOptionOptions, FilePayload, WaitForFunctionOptions, kLifecycleEvents, StrictOptions } from './types';
import { LifecycleEvent, URLMatch, SelectOption, SelectOptionOptions, FilePayload, WaitForFunctionOptions, StrictOptions, kLifecycleEvents } from './types';
import { urlMatches } from './clientHelper';
import * as api from '../../types/types';
import * as structs from '../../types/structs';
@ -157,7 +157,8 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
async waitForURL(url: URLMatch, options: { waitUntil?: LifecycleEvent, timeout?: number } = {}): Promise<void> {
if (urlMatches(this._page?.context()._options.baseURL, this.url(), url))
return await this.waitForLoadState(options?.waitUntil, options);
return await this.waitForLoadState(options.waitUntil, options);
await this.waitForNavigation({ url, ...options });
}
@ -476,6 +477,6 @@ export function verifyLoadState(name: string, waitUntil: LifecycleEvent): Lifecy
if (waitUntil as unknown === 'networkidle0')
waitUntil = 'networkidle';
if (!kLifecycleEvents.has(waitUntil))
throw new Error(`${name}: expected one of (load|domcontentloaded|networkidle)`);
throw new Error(`${name}: expected one of (load|domcontentloaded|networkidle|commit)`);
return waitUntil;
}

View File

@ -44,8 +44,8 @@ export type SetStorageState = {
origins?: channels.OriginStorage[]
};
export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle';
export const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domcontentloaded', 'networkidle']);
export type LifecycleEvent = channels.LifecycleEvent;
export const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domcontentloaded', 'networkidle', 'commit']);
export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'viewport' | 'noDefaultViewport' | 'extraHTTPHeaders' | 'storageState'> & {
viewport?: Size | null,

View File

@ -243,6 +243,7 @@ export type FetchResponse = {
headers: NameValue[],
};
export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle' | 'commit';
// ----------- Root -----------
export type RootInitializer = {};
export interface RootChannel extends Channel {
@ -1311,33 +1312,33 @@ export type PageExposeBindingOptions = {
export type PageExposeBindingResult = void;
export type PageGoBackParams = {
timeout?: number,
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle',
waitUntil?: LifecycleEvent,
};
export type PageGoBackOptions = {
timeout?: number,
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle',
waitUntil?: LifecycleEvent,
};
export type PageGoBackResult = {
response?: ResponseChannel,
};
export type PageGoForwardParams = {
timeout?: number,
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle',
waitUntil?: LifecycleEvent,
};
export type PageGoForwardOptions = {
timeout?: number,
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle',
waitUntil?: LifecycleEvent,
};
export type PageGoForwardResult = {
response?: ResponseChannel,
};
export type PageReloadParams = {
timeout?: number,
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle',
waitUntil?: LifecycleEvent,
};
export type PageReloadOptions = {
timeout?: number,
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle',
waitUntil?: LifecycleEvent,
};
export type PageReloadResult = {
response?: ResponseChannel,
@ -1604,7 +1605,7 @@ export type FrameInitializer = {
url: string,
name: string,
parentFrame?: FrameChannel,
loadStates: ('load' | 'domcontentloaded' | 'networkidle')[],
loadStates: LifecycleEvent[],
};
export interface FrameChannel extends Channel {
on(event: 'loadstate', callback: (params: FrameLoadstateEvent) => void): this;
@ -1653,8 +1654,8 @@ export interface FrameChannel extends Channel {
expect(params: FrameExpectParams, metadata?: Metadata): Promise<FrameExpectResult>;
}
export type FrameLoadstateEvent = {
add?: 'load' | 'domcontentloaded' | 'networkidle',
remove?: 'load' | 'domcontentloaded' | 'networkidle',
add?: LifecycleEvent,
remove?: LifecycleEvent,
};
export type FrameNavigatedEvent = {
url: string,
@ -1886,12 +1887,12 @@ export type FrameGetAttributeResult = {
export type FrameGotoParams = {
url: string,
timeout?: number,
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle',
waitUntil?: LifecycleEvent,
referer?: string,
};
export type FrameGotoOptions = {
timeout?: number,
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle',
waitUntil?: LifecycleEvent,
referer?: string,
};
export type FrameGotoResult = {
@ -2084,11 +2085,11 @@ export type FrameSelectOptionResult = {
export type FrameSetContentParams = {
html: string,
timeout?: number,
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle',
waitUntil?: LifecycleEvent,
};
export type FrameSetContentOptions = {
timeout?: number,
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle',
waitUntil?: LifecycleEvent,
};
export type FrameSetContentResult = void;
export type FrameSetInputFilesParams = {

View File

@ -301,6 +301,16 @@ FetchResponse:
type: array
items: NameValue
LifecycleEvent:
type: enum
literals:
- load
- domcontentloaded
- networkidle
- commit
LaunchOptions:
type: mixin
properties:
@ -956,12 +966,7 @@ Page:
goBack:
parameters:
timeout: number?
waitUntil:
type: enum?
literals:
- load
- domcontentloaded
- networkidle
waitUntil: LifecycleEvent?
returns:
response: Response?
tracing:
@ -970,12 +975,7 @@ Page:
goForward:
parameters:
timeout: number?
waitUntil:
type: enum?
literals:
- load
- domcontentloaded
- networkidle
waitUntil: LifecycleEvent?
returns:
response: Response?
tracing:
@ -984,12 +984,7 @@ Page:
reload:
parameters:
timeout: number?
waitUntil:
type: enum?
literals:
- load
- domcontentloaded
- networkidle
waitUntil: LifecycleEvent?
returns:
response: Response?
tracing:
@ -1280,12 +1275,7 @@ Frame:
parentFrame: Frame?
loadStates:
type: array
items:
type: enum
literals:
- load
- domcontentloaded
- networkidle
items: LifecycleEvent
commands:
@ -1490,12 +1480,7 @@ Frame:
parameters:
url: string
timeout: number?
waitUntil:
type: enum?
literals:
- load
- domcontentloaded
- networkidle
waitUntil: LifecycleEvent?
referer: string?
returns:
response: Response?
@ -1668,12 +1653,7 @@ Frame:
parameters:
html: string
timeout: number?
waitUntil:
type: enum?
literals:
- load
- domcontentloaded
- networkidle
waitUntil: LifecycleEvent?
tracing:
snapshot: true
@ -1819,18 +1799,8 @@ Frame:
loadstate:
parameters:
add:
type: enum?
literals:
- load
- domcontentloaded
- networkidle
remove:
type: enum?
literals:
- load
- domcontentloaded
- networkidle
add: LifecycleEvent?
remove: LifecycleEvent?
navigated:
parameters:

View File

@ -191,6 +191,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
statusText: tString,
headers: tArray(tType('NameValue')),
});
scheme.LifecycleEvent = tEnum(['load', 'domcontentloaded', 'networkidle', 'commit']);
scheme.RootInitializeParams = tObject({
sdkLanguage: tString,
});
@ -538,15 +539,15 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
});
scheme.PageGoBackParams = tObject({
timeout: tOptional(tNumber),
waitUntil: tOptional(tEnum(['load', 'domcontentloaded', 'networkidle'])),
waitUntil: tOptional(tType('LifecycleEvent')),
});
scheme.PageGoForwardParams = tObject({
timeout: tOptional(tNumber),
waitUntil: tOptional(tEnum(['load', 'domcontentloaded', 'networkidle'])),
waitUntil: tOptional(tType('LifecycleEvent')),
});
scheme.PageReloadParams = tObject({
timeout: tOptional(tNumber),
waitUntil: tOptional(tEnum(['load', 'domcontentloaded', 'networkidle'])),
waitUntil: tOptional(tType('LifecycleEvent')),
});
scheme.PageScreenshotParams = tObject({
timeout: tOptional(tNumber),
@ -753,7 +754,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
scheme.FrameGotoParams = tObject({
url: tString,
timeout: tOptional(tNumber),
waitUntil: tOptional(tEnum(['load', 'domcontentloaded', 'networkidle'])),
waitUntil: tOptional(tType('LifecycleEvent')),
referer: tOptional(tString),
});
scheme.FrameHoverParams = tObject({
@ -839,7 +840,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
scheme.FrameSetContentParams = tObject({
html: tString,
timeout: tOptional(tNumber),
waitUntil: tOptional(tEnum(['load', 'domcontentloaded', 'networkidle'])),
waitUntil: tOptional(tType('LifecycleEvent')),
});
scheme.FrameSetInputFilesParams = tObject({
selector: tString,

View File

@ -452,6 +452,9 @@ export class Frame extends SdkObject {
if (this._parentFrame)
this._parentFrame._childFrames.add(this);
this._firedLifecycleEvents.add('commit');
this._subtreeLifecycleEvents.add('commit');
}
_onLifecycleEvent(event: types.LifecycleEvent) {
@ -471,6 +474,7 @@ export class Frame extends SdkObject {
this._stopNetworkIdleTimer();
if (this._inflightRequests.size === 0)
this._startNetworkIdleTimer();
this._onLifecycleEvent('commit');
}
setPendingDocument(documentInfo: DocumentInfo | undefined) {
@ -1492,6 +1496,6 @@ function verifyLifecycle(name: string, waitUntil: types.LifecycleEvent): types.L
if (waitUntil as unknown === 'networkidle0')
waitUntil = 'networkidle';
if (!types.kLifecycleEvents.has(waitUntil))
throw new Error(`${name}: expected one of (load|domcontentloaded|networkidle)`);
throw new Error(`${name}: expected one of (load|domcontentloaded|networkidle|commit)`);
return waitUntil;
}

View File

@ -28,8 +28,8 @@ export type WaitForElementOptions = TimeoutOptions & StrictOptions & { state?: '
export type WaitForFunctionOptions = TimeoutOptions & { pollingInterval?: number };
export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle';
export const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domcontentloaded', 'networkidle']);
export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle' | 'commit';
export const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domcontentloaded', 'networkidle', 'commit']);
export type NavigateOptions = TimeoutOptions & {
waitUntil?: LifecycleEvent,

View File

@ -2146,8 +2146,9 @@ export interface Page {
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
* - `'commit'` - consider operation to be finished when network response is received and the document started loading.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
waitUntil?: "load"|"domcontentloaded"|"networkidle"|"commit";
}): Promise<null|Response>;
/**
@ -2173,8 +2174,9 @@ export interface Page {
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
* - `'commit'` - consider operation to be finished when network response is received and the document started loading.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
waitUntil?: "load"|"domcontentloaded"|"networkidle"|"commit";
}): Promise<null|Response>;
/**
@ -2224,8 +2226,9 @@ export interface Page {
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
* - `'commit'` - consider operation to be finished when network response is received and the document started loading.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
waitUntil?: "load"|"domcontentloaded"|"networkidle"|"commit";
}): Promise<null|Response>;
/**
@ -2750,8 +2753,9 @@ export interface Page {
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
* - `'commit'` - consider operation to be finished when network response is received and the document started loading.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
waitUntil?: "load"|"domcontentloaded"|"networkidle"|"commit";
}): Promise<null|Response>;
/**
@ -3000,8 +3004,9 @@ export interface Page {
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
* - `'commit'` - consider operation to be finished when network response is received and the document started loading.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
waitUntil?: "load"|"domcontentloaded"|"networkidle"|"commit";
}): Promise<void>;
/**
@ -3648,8 +3653,9 @@ export interface Page {
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
* - `'commit'` - consider operation to be finished when network response is received and the document started loading.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
waitUntil?: "load"|"domcontentloaded"|"networkidle"|"commit";
}): Promise<null|Response>;
/**
@ -3770,8 +3776,9 @@ export interface Page {
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
* - `'commit'` - consider operation to be finished when network response is received and the document started loading.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
waitUntil?: "load"|"domcontentloaded"|"networkidle"|"commit";
}): Promise<void>;
/**
@ -4925,8 +4932,9 @@ export interface Frame {
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
* - `'commit'` - consider operation to be finished when network response is received and the document started loading.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
waitUntil?: "load"|"domcontentloaded"|"networkidle"|"commit";
}): Promise<null|Response>;
/**
@ -5427,8 +5435,9 @@ export interface Frame {
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
* - `'commit'` - consider operation to be finished when network response is received and the document started loading.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
waitUntil?: "load"|"domcontentloaded"|"networkidle"|"commit";
}): Promise<void>;
/**
@ -5769,8 +5778,9 @@ export interface Frame {
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
* - `'commit'` - consider operation to be finished when network response is received and the document started loading.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
waitUntil?: "load"|"domcontentloaded"|"networkidle"|"commit";
}): Promise<null|Response>;
/**
@ -5810,8 +5820,9 @@ export interface Frame {
* - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* - `'load'` - consider operation to be finished when the `load` event is fired.
* - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms.
* - `'commit'` - consider operation to be finished when network response is received and the document started loading.
*/
waitUntil?: "load"|"domcontentloaded"|"networkidle";
waitUntil?: "load"|"domcontentloaded"|"networkidle"|"commit";
}): Promise<void>;}
/**

View File

@ -287,7 +287,7 @@ it('should throw if networkidle2 is passed as an option', async ({ page, server
let error = null;
// @ts-expect-error networkidle2 is not allowed
await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle2' }).catch(err => error = err);
expect(error.message).toContain(`waitUntil: expected one of (load|domcontentloaded|networkidle)`);
expect(error.message).toContain(`waitUntil: expected one of (load|domcontentloaded|networkidle|commit)`);
});
it('should fail when main resources failed to load', async ({ page, browserName, isWindows, mode }) => {
@ -628,3 +628,17 @@ it('should properly wait for load', async ({ page, server, browserName }) => {
'load'
]);
});
it('should return when navigation is committed if commit is specified', async ({ page, server }) => {
server.setRoute('/empty.html', (req, res) => {
res.writeHead(200, {
'content-type': 'text/html',
'content-length': '8192'
});
// Write enought bytes of the body to trigge response received event.
res.write('<title>' + 'A'.repeat(4100));
res.uncork();
});
const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'commit' });
expect(response.status()).toBe(200);
});

View File

@ -31,6 +31,12 @@ it('should work with domcontentloaded', async ({ page, server }) => {
expect(result).toBe(expectedOutput);
});
it('should work with commit', async ({ page }) => {
await page.setContent('<div>hello</div>', { waitUntil: 'commit' });
const result = await page.content();
expect(result).toBe(expectedOutput);
});
it('should work with doctype', async ({ page, server }) => {
const doctype = '<!DOCTYPE html>';
await page.setContent(`${doctype}<div>hello</div>`);

View File

@ -48,7 +48,7 @@ it('should throw for bad state', async ({ page, server }) => {
await page.goto(server.PREFIX + '/one-style.html');
// @ts-expect-error 'bad' is not a valid load state
const error = await page.waitForLoadState('bad').catch(e => e);
expect(error.message).toContain(`state: expected one of (load|domcontentloaded|networkidle)`);
expect(error.message).toContain(`state: expected one of (load|domcontentloaded|networkidle|commit)`);
});
it('should resolve immediately if load state matches', async ({ page, server }) => {

View File

@ -60,6 +60,21 @@ it('should work with both domcontentloaded and load', async ({ page, server }) =
await navigationPromise;
});
it('should work with commit', async ({ page, server }) => {
server.setRoute('/empty.html', (req, res) => {
res.writeHead(200, {
'content-type': 'text/html',
'content-length': '8192'
});
// Write enought bytes of the body to trigge response received event.
res.write('<title>' + 'A'.repeat(4100));
res.uncork();
});
page.goto(server.EMPTY_PAGE).catch(e => {});
await page.waitForNavigation({ waitUntil: 'commit' });
});
it('should work with clicking on anchor links', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<a href='#foobar'>foobar</a>`);

View File

@ -49,6 +49,25 @@ it('should work with both domcontentloaded and load', async ({ page, server }) =
await navigationPromise;
});
it('should work with commit', async ({ page, server }) => {
server.setRoute('/empty.html', (req, res) => {
res.writeHead(200, {
'content-type': 'text/html',
'content-length': '8192'
});
// Write enought bytes of the body to trigge response received event.
res.write('<title>' + 'A'.repeat(4100));
res.uncork();
});
page.goto(server.EMPTY_PAGE).catch(e => {});
await page.waitForURL('**/empty.html', { waitUntil: 'commit' });
});
it('should work with commit and about:blank', async ({ page, server }) => {
await page.waitForURL('about:blank', { waitUntil: 'commit' });
});
it('should work with clicking on anchor links', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<a href='#foobar'>foobar</a>`);

View File

@ -271,6 +271,10 @@ for (const [name, item] of Object.entries(protocol)) {
channels_ts.push(`export type ${name} = ${inner.ts};`);
channels_ts.push(``);
addScheme(name, inner.scheme);
} else if (item.type === 'enum') {
const ts = item.literals.map(literal => `'${literal}'`).join(' | ');
channels_ts.push(`export type ${name} = ${ts};`)
addScheme(name, `tEnum([${item.literals.map(literal => `'${literal}'`).join(', ')}])`);
}
}