feat($wait): make $wait a shortcut for waitForSelector (#932)

This commit is contained in:
Pavel Feldman 2020-02-11 14:51:09 -08:00 committed by GitHub
parent 3a32b14f32
commit 53237009ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 46 additions and 78 deletions

View file

@ -452,7 +452,7 @@ page.removeListener('request', logRequest);
- [page.$$(selector)](#pageselector-1) - [page.$$(selector)](#pageselector-1)
- [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args) - [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args)
- [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args-1) - [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args-1)
- [page.$wait(selector, pageFunction[, options[, ...args]])](#pagewaitselector-pagefunction-options-args) - [page.$wait(selector[, options])](#pagewaitselector-options)
- [page.accessibility](#pageaccessibility) - [page.accessibility](#pageaccessibility)
- [page.addScriptTag(options)](#pageaddscripttagoptions) - [page.addScriptTag(options)](#pageaddscripttagoptions)
- [page.addStyleTag(options)](#pageaddstyletagoptions) - [page.addStyleTag(options)](#pageaddstyletagoptions)
@ -683,23 +683,24 @@ const html = await page.$eval('.main-container', e => e.outerHTML);
Shortcut for [page.mainFrame().$eval(selector, pageFunction)](#frameevalselector-pagefunction-args). Shortcut for [page.mainFrame().$eval(selector, pageFunction)](#frameevalselector-pagefunction-args).
#### page.$wait(selector, pageFunction[, options[, ...args]]) #### page.$wait(selector[, options])
- `selector` <[string]> A selector to query page for - `selector` <[string]> A selector of an element to wait for
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context - `options` <[Object]>
- `options` <[Object]> Optional waiting parameters - `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`.
- `polling` <[number]|"raf"|"mutation"> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values: - `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes. - returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector string is added to DOM. Resolves to `null` if waiting for `hidden: true` and selector is not found in DOM.
- `'mutation'` - to execute `pageFunction` on every DOM mutation.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[JSHandle]>> Promise which resolves to a JSHandle of the success value
This method runs `document.querySelector` within the page and passes it as the first argument to `pageFunction`. If there's no element matching `selector`, the method throws an error. Wait for the `selector` to appear in page. If at the moment of calling
the method the `selector` already exists, the method will return
immediately. If the selector doesn't appear after the `timeout` milliseconds of waiting, the function will throw.
If `pageFunction` returns a [Promise], then `page.$wait` would wait for the promise to resolve and return its value. The function This method works across navigations:
is being called on the element periodically until either timeout expires or the function returns the truthy value. ```js
const handle = await page.$wait(selector);
await handle.click();
```
Shortcut for [page.mainFrame().$wait(selector, pageFunction[, options[, ...args]])](#framewaitselector-pagefunction-options-args). This is a shortcut to [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options).
#### page.accessibility #### page.accessibility
- returns: <[Accessibility]> - returns: <[Accessibility]>
@ -1668,7 +1669,7 @@ An example of getting text from an iframe element:
- [frame.$$(selector)](#frameselector-1) - [frame.$$(selector)](#frameselector-1)
- [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args) - [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args)
- [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args-1) - [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args-1)
- [frame.$wait(selector, pageFunction[, options[, ...args]])](#framewaitselector-pagefunction-options-args) - [frame.$wait(selector[, options])](#framewaitselector-options)
- [frame.addScriptTag(options)](#frameaddscripttagoptions) - [frame.addScriptTag(options)](#frameaddscripttagoptions)
- [frame.addStyleTag(options)](#frameaddstyletagoptions) - [frame.addStyleTag(options)](#frameaddstyletagoptions)
- [frame.check(selector, [options])](#framecheckselector-options) - [frame.check(selector, [options])](#framecheckselector-options)
@ -1744,21 +1745,24 @@ const preloadHref = await frame.$eval('link[rel=preload]', el => el.href);
const html = await frame.$eval('.main-container', e => e.outerHTML); const html = await frame.$eval('.main-container', e => e.outerHTML);
``` ```
#### frame.$wait(selector, pageFunction[, options[, ...args]]) #### frame.$wait(selector[, options])
- `selector` <[string]> A selector to query page for - `selector` <[string]> A selector of an element to wait for
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context - `options` <[Object]>
- `options` <[Object]> Optional waiting parameters - `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`.
- `polling` <[number]|"raf"|"mutation"> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values: - `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes. - returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector string is added to DOM. Resolves to `null` if waiting for `hidden: true` and selector is not found in DOM.
- `'mutation'` - to execute `pageFunction` on every DOM mutation.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[JSHandle]>> Promise which resolves to a JSHandle of the success value
This method runs `document.querySelector` within the frame and passes it as the first argument to `pageFunction`. If there's no element matching `selector`, the method throws an error. Wait for the `selector` to appear in page. If at the moment of calling
the method the `selector` already exists, the method will return
immediately. If the selector doesn't appear after the `timeout` milliseconds of waiting, the function will throw.
If `pageFunction` returns a [Promise], then `page.$wait` would wait for the promise to resolve and return its value. The function This method works across navigations:
is being called on the element periodically until either timeout expires or the function returns the truthy value. ```js
const handle = await page.$wait(selector);
await handle.click();
```
This is a shortcut to [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options).
#### frame.addScriptTag(options) #### frame.addScriptTag(options)
- `options` <[Object]> - `options` <[Object]>

View file

@ -620,6 +620,10 @@ export class Frame {
return handle; return handle;
} }
async $wait(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
return this.waitForSelector(selector, options);
}
$eval: types.$Eval = async (selector, pageFunction, ...args) => { $eval: types.$Eval = async (selector, pageFunction, ...args) => {
const context = await this._mainContext(); const context = await this._mainContext();
const elementHandle = await context._$(selector); const elementHandle = await context._$(selector);
@ -938,12 +942,6 @@ export class Frame {
return this._scheduleRerunnableTask(task, 'main', options.timeout); return this._scheduleRerunnableTask(task, 'main', options.timeout);
} }
$wait: types.$Wait = async (selector, pageFunction, options, ...args) => {
options = { timeout: this._page._timeoutSettings.timeout(), ...(options || {}) };
const task = dom.waitForFunctionTask(selector, pageFunction, options, ...args);
return this._scheduleRerunnableTask(task, 'main', options.timeout) as any;
}
async title(): Promise<string> { async title(): Promise<string> {
const context = await this._utilityContext(); const context = await this._utilityContext();
return context.evaluate(() => document.title); return context.evaluate(() => document.title);

View file

@ -215,6 +215,10 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().waitForSelector(selector, options); return this.mainFrame().waitForSelector(selector, options);
} }
async $wait(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
return this.waitForSelector(selector, options);
}
evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => { evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => {
return this.mainFrame().evaluateHandle(pageFunction, ...args as any); return this.mainFrame().evaluateHandle(pageFunction, ...args as any);
} }
@ -529,10 +533,6 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().waitForFunction(pageFunction, options, ...args); return this.mainFrame().waitForFunction(pageFunction, options, ...args);
} }
$wait: types.$Wait = async (selector, pageFunction, options, ...args) => {
return this.mainFrame().$wait(selector, pageFunction, options, ...args as any);
}
workers(): Worker[] { workers(): Worker[] {
return [...this._workers.values()]; return [...this._workers.values()];
} }

View file

@ -27,7 +27,6 @@ export type Evaluate = <Args extends any[], R>(pageFunction: PageFunction<Args,
export type EvaluateHandle = <Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>; export type EvaluateHandle = <Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>;
export type $Eval = <Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<Element, Args, R>, ...args: Boxed<Args>) => Promise<R>; export type $Eval = <Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<Element, Args, R>, ...args: Boxed<Args>) => Promise<R>;
export type $$Eval = <Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<Element[], Args, R>, ...args: Boxed<Args>) => Promise<R>; export type $$Eval = <Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<Element[], Args, R>, ...args: Boxed<Args>) => Promise<R>;
export type $Wait = <Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<Element | undefined, Args, R>, options?: WaitForFunctionOptions, ...args: Boxed<Args>) => Promise<Handle<R>>;
export type EvaluateOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<R>; export type EvaluateOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<R>;
export type EvaluateHandleOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>; export type EvaluateHandleOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>;

View file

@ -208,39 +208,8 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
}); });
}); });
describe('Frame.$wait', function() {
it('should accept arguments', async({page, server}) => {
await page.setContent('<div></div>');
const result = await page.$wait('div', (e, foo, bar) => e.nodeName + foo + bar, {}, 'foo1', 'bar2');
expect(await result.jsonValue()).toBe('DIVfoo1bar2');
});
it('should query selector constantly', async({page, server}) => {
await page.setContent('<div></div>');
let done = null;
const resultPromise = page.$wait('span', e => e).then(r => done = r);
expect(done).toBe(null);
await page.setContent('<section></section>');
expect(done).toBe(null);
await page.setContent('<span>text</span>');
await resultPromise;
expect(done).not.toBe(null);
expect(await done.evaluate(e => e.textContent)).toBe('text');
});
it('should be able to wait for removal', async({page}) => {
await page.setContent('<div></div>');
let done = null;
const resultPromise = page.$wait('div', e => !e).then(r => done = r);
expect(done).toBe(null);
await page.setContent('<section></section>');
await resultPromise;
expect(done).not.toBe(null);
expect(await done.jsonValue()).toBe(true);
});
});
describe('Frame.waitForSelector', function() { describe('Frame.waitForSelector', function() {
const addElement = tag => document.body.appendChild(document.createElement(tag)); const addElement = tag => document.body.appendChild(document.createElement(tag));
it('should immediately resolve promise if node exists', async({page, server}) => { it('should immediately resolve promise if node exists', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame(); const frame = page.mainFrame();
@ -248,7 +217,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await frame.evaluate(addElement, 'div'); await frame.evaluate(addElement, 'div');
await frame.waitForSelector('div'); await frame.waitForSelector('div');
}); });
it('should work with removed MutationObserver', async({page, server}) => { it('should work with removed MutationObserver', async({page, server}) => {
await page.evaluate(() => delete window.MutationObserver); await page.evaluate(() => delete window.MutationObserver);
const [handle] = await Promise.all([ const [handle] = await Promise.all([
@ -257,7 +225,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
]); ]);
expect(await page.evaluate(x => x.textContent, handle)).toBe('anything'); expect(await page.evaluate(x => x.textContent, handle)).toBe('anything');
}); });
it('should resolve promise when node is added', async({page, server}) => { it('should resolve promise when node is added', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame(); const frame = page.mainFrame();
@ -268,7 +235,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue()); const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
expect(tagName).toBe('DIV'); expect(tagName).toBe('DIV');
}); });
it('should work when node is added through innerHTML', async({page, server}) => { it('should work when node is added through innerHTML', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const watchdog = page.waitForSelector('h3 div'); const watchdog = page.waitForSelector('h3 div');
@ -276,7 +242,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await page.evaluate(() => document.querySelector('span').innerHTML = '<h3><div></div></h3>'); await page.evaluate(() => document.querySelector('span').innerHTML = '<h3><div></div></h3>');
await watchdog; await watchdog;
}); });
it('Page.$ waitFor is shortcut for main frame', async({page, server}) => { it('Page.$ waitFor is shortcut for main frame', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
@ -287,7 +252,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
const eHandle = await watchdog; const eHandle = await watchdog;
expect(await eHandle.ownerFrame()).toBe(page.mainFrame()); expect(await eHandle.ownerFrame()).toBe(page.mainFrame());
}); });
it('should run in specified frame', async({page, server}) => { it('should run in specified frame', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
@ -299,7 +263,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
const eHandle = await waitForSelectorPromise; const eHandle = await waitForSelectorPromise;
expect(await eHandle.ownerFrame()).toBe(frame2); expect(await eHandle.ownerFrame()).toBe(frame2);
}); });
it('should throw when frame is detached', async({page, server}) => { it('should throw when frame is detached', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1]; const frame = page.frames()[1];
@ -391,7 +354,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "[hidden] div" failed: timeout'); expect(error.message).toContain('waiting for selector "[hidden] div" failed: timeout');
}); });
it('should respond to node attribute mutation', async({page, server}) => { it('should respond to node attribute mutation', async({page, server}) => {
let divFound = false; let divFound = false;
const waitForSelector = page.waitForSelector('.zombo').then(() => divFound = true); const waitForSelector = page.waitForSelector('.zombo').then(() => divFound = true);
@ -441,6 +403,11 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue()); const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
expect(tagName).toBe('SPAN'); expect(tagName).toBe('SPAN');
}); });
it('$wait alias should work', async({page, server}) => {
await page.setContent('<section>test</section>');
const handle = await page.$wait('section');
expect(await handle.evaluate(e => e.textContent)).toBe('test');
});
}); });
describe('Frame.waitForSelector xpath', function() { describe('Frame.waitForSelector xpath', function() {