Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 26 additions & 71 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -710,14 +710,14 @@ page.removeListener('request', logRequest);
- [page.unroute(url[, handler])](#pageunrouteurl-handler)
- [page.url()](#pageurl)
- [page.viewportSize()](#pageviewportsize)
- [page.waitFor(selectorOrFunctionOrTimeout[, options[, arg]])](#pagewaitforselectororfunctionortimeout-options-arg)
- [page.waitForEvent(event[, optionsOrPredicate])](#pagewaitforeventevent-optionsorpredicate)
- [page.waitForFunction(pageFunction[, arg, options])](#pagewaitforfunctionpagefunction-arg-options)
- [page.waitForLoadState([state[, options]])](#pagewaitforloadstatestate-options)
- [page.waitForNavigation([options])](#pagewaitfornavigationoptions)
- [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options)
- [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options)
- [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options)
- [page.waitForTimeout(timeout)](#pagewaitfortimeouttimeout)
- [page.workers()](#pageworkers)
<!-- GEN:stop -->

Expand Down Expand Up @@ -1688,41 +1688,6 @@ This is a shortcut for [page.mainFrame().url()](#frameurl)
- `width` <[number]> page width 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])
- `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.
Expand Down Expand Up @@ -1871,7 +1836,22 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
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()
- returns: <[Array]<[Worker]>>
Expand Down Expand Up @@ -1948,11 +1928,11 @@ An example of getting text from an iframe element:
- [frame.type(selector, text[, options])](#frametypeselector-text-options)
- [frame.uncheck(selector, [options])](#frameuncheckselector-options)
- [frame.url()](#frameurl)
- [frame.waitFor(selectorOrFunctionOrTimeout[, options[, arg]])](#framewaitforselectororfunctionortimeout-options-arg)
- [frame.waitForFunction(pageFunction[, arg, options])](#framewaitforfunctionpagefunction-arg-options)
- [frame.waitForLoadState([state[, options]])](#framewaitforloadstatestate-options)
- [frame.waitForNavigation([options])](#framewaitfornavigationoptions)
- [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options)
- [frame.waitForTimeout(timeout)](#framewaitfortimeouttimeout)
<!-- GEN:stop -->

#### frame.$(selector)
Expand Down Expand Up @@ -2385,39 +2365,6 @@ If there's no element matching `selector`, the method throws an error.

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])
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context
- `arg` <[Serializable]|[JSHandle]> Optional argument to pass to `pageFunction`
Expand Down Expand Up @@ -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
* extends: [JSHandle]

Expand Down
2 changes: 1 addition & 1 deletion docs/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
- [Usage](#usage)
- [Writing your first script](#writing-your-first-script)
- [Debugging scripts](#debugging-scripts)
- [Deploying to CI](#deploying-to-ci)
- [Continuous Integration](#continuous-integration)
<!-- GEN:stop -->

## Installation
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 4 additions & 11 deletions src/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import * as network from './network';
import { Page } from './page';
import { selectors } from './selectors';
import * as types from './types';
import { waitForTimeWasUsed } from './hints';
import { waitForTimeoutWasUsed } from './hints';

type ContextType = 'main' | 'utility';
type ContextData = {
Expand Down Expand Up @@ -755,16 +755,9 @@ export class Frame {
(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> {
if (helper.isString(selectorOrFunctionOrTimeout))
return this.waitForSelector(selectorOrFunctionOrTimeout, options) as any;
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 waitForTimeout(timeout: number) {
waitForTimeoutWasUsed(this._page);
await new Promise(fulfill => setTimeout(fulfill, timeout));
}

async waitForFunction<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg, options?: types.WaitForFunctionOptions): Promise<types.SmartHandle<R>>;
Expand Down
12 changes: 6 additions & 6 deletions src/hints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ const hintsLog: Log = {
severity: 'warning'
};

let waitForTimeWasUsedReported = false;
export function waitForTimeWasUsed(page: Page) {
if (waitForTimeWasUsedReported)
let waitForTimeoutWasUsedReported = false;
export function waitForTimeoutWasUsed(page: Page) {
if (waitForTimeoutWasUsedReported)
return;
waitForTimeWasUsedReported = true;
page._log(hintsLog, `WARNING: page.waitFor(timeout) should only be used for debugging.
It is likely that the tests using timer in production are going to be flaky.
waitForTimeoutWasUsedReported = true;
page._log(hintsLog, `WARNING: page.waitForTimeout(timeout) 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, etc. instead.`);
}
4 changes: 2 additions & 2 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,8 @@ export class Page extends ExtendedEventEmitter implements InnerLogger {
return this.mainFrame().uncheck(selector, options);
}

async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options?: types.WaitForFunctionOptions & types.WaitForElementOptions, arg?: any): Promise<js.JSHandle | null> {
return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, arg);
async waitForTimeout(timeout: number) {
await this.mainFrame().waitForTimeout(timeout);
}

async waitForFunction<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg, options?: types.WaitForFunctionOptions): Promise<types.SmartHandle<R>>;
Expand Down
57 changes: 11 additions & 46 deletions test/waittask.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,57 +18,13 @@
const utils = require('./utils');
const {FFOX, CHROMIUM, WEBKIT} = utils.testOptions(browserType);

describe('Page.waitFor', 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();
});
describe('Page.waitForTimeout', function() {
it('should timeout', async({page, server}) => {
const startTime = Date.now();
const timeout = 42;
await page.waitFor(timeout);
await page.waitForTimeout(timeout);
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() {
Expand Down Expand Up @@ -198,6 +154,15 @@ describe('Frame.waitForFunction', function() {
await page.evaluate(() => window.__done = true);
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() {
Expand Down