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.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args)
- [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.addScriptTag(options)](#pageaddscripttagoptions)
- [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).
#### page.$wait(selector, pageFunction[, options[, ...args]])
- `selector` <[string]> A selector to query page for
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context
- `options` <[Object]> Optional waiting parameters
- `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:
- `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes.
- `'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
#### page.$wait(selector[, options])
- `selector` <[string]> A selector of an element to wait for
- `options` <[Object]>
- `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`.
- `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.
- 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.
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
is being called on the element periodically until either timeout expires or the function returns the truthy value.
This method works across navigations:
```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
- returns: <[Accessibility]>
@ -1668,7 +1669,7 @@ An example of getting text from an iframe element:
- [frame.$$(selector)](#frameselector-1)
- [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args)
- [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.addStyleTag(options)](#frameaddstyletagoptions)
- [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);
```
#### frame.$wait(selector, pageFunction[, options[, ...args]])
- `selector` <[string]> A selector to query page for
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context
- `options` <[Object]> Optional waiting parameters
- `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:
- `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes.
- `'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
#### frame.$wait(selector[, options])
- `selector` <[string]> A selector of an element to wait for
- `options` <[Object]>
- `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`.
- `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.
- 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.
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
is being called on the element periodically until either timeout expires or the function returns the truthy value.
This method works across navigations:
```js
const handle = await page.$wait(selector);
await handle.click();
```
This is a shortcut to [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options).
#### frame.addScriptTag(options)
- `options` <[Object]>

View file

@ -620,6 +620,10 @@ export class Frame {
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) => {
const context = await this._mainContext();
const elementHandle = await context._$(selector);
@ -938,12 +942,6 @@ export class Frame {
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> {
const context = await this._utilityContext();
return context.evaluate(() => document.title);

View file

@ -215,6 +215,10 @@ export class Page extends platform.EventEmitter {
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) => {
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);
}
$wait: types.$Wait = async (selector, pageFunction, options, ...args) => {
return this.mainFrame().$wait(selector, pageFunction, options, ...args as any);
}
workers(): Worker[] {
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 $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 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() {
const addElement = tag => document.body.appendChild(document.createElement(tag));
it('should immediately resolve promise if node exists', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame();
@ -248,7 +217,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await frame.evaluate(addElement, 'div');
await frame.waitForSelector('div');
});
it('should work with removed MutationObserver', async({page, server}) => {
await page.evaluate(() => delete window.MutationObserver);
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');
});
it('should resolve promise when node is added', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
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());
expect(tagName).toBe('DIV');
});
it('should work when node is added through innerHTML', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
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 watchdog;
});
it('Page.$ waitFor is shortcut for main frame', async({page, server}) => {
await page.goto(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;
expect(await eHandle.ownerFrame()).toBe(page.mainFrame());
});
it('should run in specified frame', async({page, server}) => {
await utils.attachFrame(page, 'frame1', 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;
expect(await eHandle.ownerFrame()).toBe(frame2);
});
it('should throw when frame is detached', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
@ -391,7 +354,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "[hidden] div" failed: timeout');
});
it('should respond to node attribute mutation', async({page, server}) => {
let divFound = false;
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());
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() {