api(waitFor): remove waitFor, use specialized wait functions (#1995)

This commit is contained in:
Pavel Feldman 2020-04-27 12:08:29 -07:00 committed by GitHub
parent f9f5fd03b0
commit 7f8aa703dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 51 additions and 138 deletions

View file

@ -710,7 +710,6 @@ page.removeListener('request', logRequest);
- [page.unroute(url[, handler])](#pageunrouteurl-handler) - [page.unroute(url[, handler])](#pageunrouteurl-handler)
- [page.url()](#pageurl) - [page.url()](#pageurl)
- [page.viewportSize()](#pageviewportsize) - [page.viewportSize()](#pageviewportsize)
- [page.waitFor(selectorOrFunctionOrTimeout[, options[, arg]])](#pagewaitforselectororfunctionortimeout-options-arg)
- [page.waitForEvent(event[, optionsOrPredicate])](#pagewaitforeventevent-optionsorpredicate) - [page.waitForEvent(event[, optionsOrPredicate])](#pagewaitforeventevent-optionsorpredicate)
- [page.waitForFunction(pageFunction[, arg, options])](#pagewaitforfunctionpagefunction-arg-options) - [page.waitForFunction(pageFunction[, arg, options])](#pagewaitforfunctionpagefunction-arg-options)
- [page.waitForLoadState([state[, options]])](#pagewaitforloadstatestate-options) - [page.waitForLoadState([state[, options]])](#pagewaitforloadstatestate-options)
@ -718,6 +717,7 @@ page.removeListener('request', logRequest);
- [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options) - [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options)
- [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options) - [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options)
- [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) - [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options)
- [page.waitForTimeout(timeout)](#pagewaitfortimeouttimeout)
- [page.workers()](#pageworkers) - [page.workers()](#pageworkers)
<!-- GEN:stop --> <!-- GEN:stop -->
@ -1688,41 +1688,6 @@ This is a shortcut for [page.mainFrame().url()](#frameurl)
- `width` <[number]> page width in pixels. - `width` <[number]> page width in pixels.
- `height` <[number]> page height in pixels. - `height` <[number]> page height in pixels.
#### page.waitFor(selectorOrFunctionOrTimeout[, options[, arg]])
- `selectorOrFunctionOrTimeout` <[string]|[number]|[function]> A [selector], predicate or timeout to wait for
- `options` <[Object]> Optional waiting parameters
- `waitFor` <"attached"|"detached"|"visible"|"hidden"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`attached`) or not present in dom (`detached`). Defaults to `attached`.
- `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 [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- `arg` <[Serializable]|[JSHandle]> Optional argument to pass to `pageFunction`
- returns: <[Promise]<?[JSHandle]>> Promise which resolves to a JSHandle of the success value
This method behaves differently with respect to the type of the first parameter:
- if `selectorOrFunctionOrTimeout` is a `string`, then the first argument is treated as a [selector] and the method is a shortcut for [page.waitForSelector](#pagewaitforselectorselector-options)
- if `selectorOrFunctionOrTimeout` is a `function`, then the first argument is treated as a predicate to wait for and the method is a shortcut for [page.waitForFunction()](#pagewaitforfunctionpagefunction-arg-options).
- if `selectorOrFunctionOrTimeout` is a `number`, then the first argument is treated as a timeout in milliseconds and the method returns a promise which resolves after the timeout
- otherwise, an exception is thrown
```js
// wait for selector
await page.waitFor('.foo');
// wait for 1 second
await page.waitFor(1000);
// wait for predicate
await page.waitFor(() => !!document.querySelector('.foo'));
```
To pass an argument from node.js to the predicate of `page.waitFor` function:
```js
const selector = '.foo';
await page.waitFor(selector => !!document.querySelector(selector), {}, selector);
```
Shortcut for [page.mainFrame().waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-arg).
#### page.waitForEvent(event[, optionsOrPredicate]) #### page.waitForEvent(event[, optionsOrPredicate])
- `event` <[string]> Event name, same one would pass into `page.on(event)`. - `event` <[string]> Event name, same one would pass into `page.on(event)`.
- `optionsOrPredicate` <[Function]|[Object]> Either a predicate that receives an event or an options object. - `optionsOrPredicate` <[Function]|[Object]> Either a predicate that receives an event or an options object.
@ -1871,7 +1836,22 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
await browser.close(); await browser.close();
})(); })();
``` ```
Shortcut for [page.mainFrame().waitForSelector(selector[, options])](#framewaitforselectororfunctionortimeout-options-arg). Shortcut for [page.mainFrame().waitForSelector(selector[, options])](#framewaitforselectorselector-options).
#### page.waitForTimeout(timeout)
- `timeout` <[number]> A timeout to wait for
- returns: <[Promise]>
Returns a promise that resolves after the timeout.
Note that `page.waitForTimeout()` should only be used for debugging. Tests using the timer in production are going to be flaky. Use signals such as network events, selectors becoming visible and others instead.
```js
// wait for 1 second
await page.waitForTimeout(1000);
```
Shortcut for [page.mainFrame().waitForTimeout(timeout)](#pagewaitfortimeouttimeout).
#### page.workers() #### page.workers()
- returns: <[Array]<[Worker]>> - returns: <[Array]<[Worker]>>
@ -1948,11 +1928,11 @@ An example of getting text from an iframe element:
- [frame.type(selector, text[, options])](#frametypeselector-text-options) - [frame.type(selector, text[, options])](#frametypeselector-text-options)
- [frame.uncheck(selector, [options])](#frameuncheckselector-options) - [frame.uncheck(selector, [options])](#frameuncheckselector-options)
- [frame.url()](#frameurl) - [frame.url()](#frameurl)
- [frame.waitFor(selectorOrFunctionOrTimeout[, options[, arg]])](#framewaitforselectororfunctionortimeout-options-arg)
- [frame.waitForFunction(pageFunction[, arg, options])](#framewaitforfunctionpagefunction-arg-options) - [frame.waitForFunction(pageFunction[, arg, options])](#framewaitforfunctionpagefunction-arg-options)
- [frame.waitForLoadState([state[, options]])](#framewaitforloadstatestate-options) - [frame.waitForLoadState([state[, options]])](#framewaitforloadstatestate-options)
- [frame.waitForNavigation([options])](#framewaitfornavigationoptions) - [frame.waitForNavigation([options])](#framewaitfornavigationoptions)
- [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options) - [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options)
- [frame.waitForTimeout(timeout)](#framewaitfortimeouttimeout)
<!-- GEN:stop --> <!-- GEN:stop -->
#### frame.$(selector) #### frame.$(selector)
@ -2385,39 +2365,6 @@ If there's no element matching `selector`, the method throws an error.
Returns frame's url. Returns frame's url.
#### frame.waitFor(selectorOrFunctionOrTimeout[, options[, arg]])
- `selectorOrFunctionOrTimeout` <[string]|[number]|[function]> A [selector], predicate or timeout to wait for
- `options` <[Object]> Optional waiting parameters
- `waitFor` <"attached"|"detached"|"visible"|"hidden"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`attached`) or not present in dom (`detached`). Defaults to `attached`.
- `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 [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- `arg` <[Serializable]|[JSHandle]> Optional argument to pass to `pageFunction`
- returns: <[Promise]<?[JSHandle]>> Promise which resolves to a JSHandle of the success value
This method behaves differently with respect to the type of the first parameter:
- if `selectorOrFunctionOrTimeout` is a `string`, then the first argument is treated as a [selector] and the method is a shortcut for [frame.waitForSelector](#framewaitforselectororfunctionortimeout-options-arg)
- if `selectorOrFunctionOrTimeout` is a `function`, then the first argument is treated as a predicate to wait for and the method is a shortcut for [frame.waitForFunction()](#framewaitforfunctionpagefunction-arg-options).
- if `selectorOrFunctionOrTimeout` is a `number`, then the first argument is treated as a timeout in milliseconds and the method returns a promise which resolves after the timeout
- otherwise, an exception is thrown
```js
// wait for selector
await frame.waitFor('.foo');
// wait for 1 second
await frame.waitFor(1000);
// wait for predicate
await frame.waitFor(() => !!document.querySelector('.foo'));
```
To pass an argument from node.js to the predicate of `frame.waitFor` function:
```js
const selector = '.foo';
await frame.waitFor(selector => !!document.querySelector(selector), {}, selector);
```
#### frame.waitForFunction(pageFunction[, arg, options]) #### frame.waitForFunction(pageFunction[, arg, options])
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context - `pageFunction` <[function]|[string]> Function to be evaluated in browser context
- `arg` <[Serializable]|[JSHandle]> Optional argument to pass to `pageFunction` - `arg` <[Serializable]|[JSHandle]> Optional argument to pass to `pageFunction`
@ -2514,6 +2461,14 @@ const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'.
})(); })();
``` ```
#### frame.waitForTimeout(timeout)
- `timeout` <[number]> A timeout to wait for
- returns: <[Promise]>
Returns a promise that resolves after the timeout.
Note that `frame.waitForTimeout()` should only be used for debugging. Tests using the timer in production are going to be flaky. Use signals such as network events, selectors becoming visible and others instead.
### class: ElementHandle ### class: ElementHandle
* extends: [JSHandle] * extends: [JSHandle]

View file

@ -5,7 +5,7 @@
- [Usage](#usage) - [Usage](#usage)
- [Writing your first script](#writing-your-first-script) - [Writing your first script](#writing-your-first-script)
- [Debugging scripts](#debugging-scripts) - [Debugging scripts](#debugging-scripts)
- [Deploying to CI](#deploying-to-ci) - [Continuous Integration](#continuous-integration)
<!-- GEN:stop --> <!-- GEN:stop -->
## Installation ## Installation

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{ {
"name": "playwright-core", "name": "playwright-core",
"version": "1.0.0-post", "version": "0.15.0-post",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View file

@ -27,7 +27,7 @@ import * as network from './network';
import { Page } from './page'; import { Page } from './page';
import { selectors } from './selectors'; import { selectors } from './selectors';
import * as types from './types'; import * as types from './types';
import { waitForTimeWasUsed } from './hints'; import { waitForTimeoutWasUsed } from './hints';
type ContextType = 'main' | 'utility'; type ContextType = 'main' | 'utility';
type ContextData = { type ContextData = {
@ -755,16 +755,9 @@ export class Frame {
(handle, deadline) => handle.uncheck(helper.optionsWithUpdatedTimeout(options, deadline))); (handle, deadline) => handle.uncheck(helper.optionsWithUpdatedTimeout(options, deadline)));
} }
async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: types.WaitForFunctionOptions & types.WaitForElementOptions = {}, arg?: any): Promise<js.JSHandle | null> { async waitForTimeout(timeout: number) {
if (helper.isString(selectorOrFunctionOrTimeout)) waitForTimeoutWasUsed(this._page);
return this.waitForSelector(selectorOrFunctionOrTimeout, options) as any; await new Promise(fulfill => setTimeout(fulfill, timeout));
if (helper.isNumber(selectorOrFunctionOrTimeout)) {
waitForTimeWasUsed(this._page);
return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout));
}
if (typeof selectorOrFunctionOrTimeout === 'function')
return this.waitForFunction(selectorOrFunctionOrTimeout as any, arg, options);
return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
} }
async waitForFunction<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg, options?: types.WaitForFunctionOptions): Promise<types.SmartHandle<R>>; async waitForFunction<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg, options?: types.WaitForFunctionOptions): Promise<types.SmartHandle<R>>;

View file

@ -22,12 +22,12 @@ const hintsLog: Log = {
severity: 'warning' severity: 'warning'
}; };
let waitForTimeWasUsedReported = false; let waitForTimeoutWasUsedReported = false;
export function waitForTimeWasUsed(page: Page) { export function waitForTimeoutWasUsed(page: Page) {
if (waitForTimeWasUsedReported) if (waitForTimeoutWasUsedReported)
return; return;
waitForTimeWasUsedReported = true; waitForTimeoutWasUsedReported = true;
page._log(hintsLog, `WARNING: page.waitFor(timeout) should only be used for debugging. page._log(hintsLog, `WARNING: page.waitForTimeout(timeout) should only be used for debugging.
It is likely that the tests using timer in production are going to be flaky. Tests using the timer in production are going to be flaky.
Use signals such as network events, selectors becoming visible, etc. instead.`); Use signals such as network events, selectors becoming visible, etc. instead.`);
} }

View file

@ -481,8 +481,8 @@ export class Page extends ExtendedEventEmitter implements InnerLogger {
return this.mainFrame().uncheck(selector, options); return this.mainFrame().uncheck(selector, options);
} }
async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options?: types.WaitForFunctionOptions & types.WaitForElementOptions, arg?: any): Promise<js.JSHandle | null> { async waitForTimeout(timeout: number) {
return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, arg); await this.mainFrame().waitForTimeout(timeout);
} }
async waitForFunction<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg, options?: types.WaitForFunctionOptions): Promise<types.SmartHandle<R>>; async waitForFunction<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg, options?: types.WaitForFunctionOptions): Promise<types.SmartHandle<R>>;

View file

@ -18,57 +18,13 @@
const utils = require('./utils'); const utils = require('./utils');
const {FFOX, CHROMIUM, WEBKIT} = utils.testOptions(browserType); const {FFOX, CHROMIUM, WEBKIT} = utils.testOptions(browserType);
describe('Page.waitFor', function() { describe('Page.waitForTimeout', function() {
it('should wait for selector', async({page, server}) => {
let found = false;
const waitFor = page.waitFor('div').then(() => found = true);
await page.goto(server.EMPTY_PAGE);
expect(found).toBe(false);
await page.goto(server.PREFIX + '/grid.html');
await waitFor;
expect(found).toBe(true);
});
it('should wait for an xpath', async({page, server}) => {
let found = false;
const waitFor = page.waitFor('//div').then(() => found = true);
await page.goto(server.EMPTY_PAGE);
expect(found).toBe(false);
await page.goto(server.PREFIX + '/grid.html');
await waitFor;
expect(found).toBe(true);
});
it('should not allow you to select an element with single slash xpath', async({page, server}) => {
await page.setContent(`<div>some text</div>`);
let error = null;
await page.waitFor('/html/body/div').catch(e => error = e);
expect(error).toBeTruthy();
});
it('should timeout', async({page, server}) => { it('should timeout', async({page, server}) => {
const startTime = Date.now(); const startTime = Date.now();
const timeout = 42; const timeout = 42;
await page.waitFor(timeout); await page.waitForTimeout(timeout);
expect(Date.now() - startTime).not.toBeLessThan(timeout / 2); expect(Date.now() - startTime).not.toBeLessThan(timeout / 2);
}); });
it('should work with multiline body', async({page, server}) => {
const result = await page.waitForFunction(`
(() => true)()
`);
expect(await result.jsonValue()).toBe(true);
});
it('should wait for predicate', async({page, server}) => {
await Promise.all([
page.waitFor(() => window.innerWidth < 130), // Windows doesn't like windows below 120px wide
page.setViewportSize({width: 10, height: 10}),
]);
});
it('should throw when unknown type', async({page, server}) => {
let error = null;
await page.waitFor({foo: 'bar'}).catch(e => error = e);
expect(error.message).toContain('Unsupported target type');
});
it('should wait for predicate with arguments', async({page, server}) => {
await page.waitFor(({arg1, arg2}) => arg1 + arg2 === 3, {}, { arg1: 1, arg2: 2});
});
}); });
describe('Frame.waitForFunction', function() { describe('Frame.waitForFunction', function() {
@ -198,6 +154,15 @@ describe('Frame.waitForFunction', function() {
await page.evaluate(() => window.__done = true); await page.evaluate(() => window.__done = true);
await watchdog; await watchdog;
}); });
it('should work with multiline body', async({page, server}) => {
const result = await page.waitForFunction(`
(() => true)()
`);
expect(await result.jsonValue()).toBe(true);
});
it('should wait for predicate with arguments', async({page, server}) => {
await page.waitForFunction(({arg1, arg2}) => arg1 + arg2 === 3, { arg1: 1, arg2: 2});
});
}); });
describe('Frame.waitForSelector', function() { describe('Frame.waitForSelector', function() {