Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Feb 11, 2020
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
62 changes: 33 additions & 29 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]>
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.

This method works across navigations:
```js
const handle = await page.$wait(selector);
await handle.click();
```

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 is a shortcut to [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options).

#### frame.addScriptTag(options)
- `options` <[Object]>
Expand Down
10 changes: 4 additions & 6 deletions src/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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()];
}
Expand Down
1 change: 0 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>>;

Expand Down
43 changes: 5 additions & 38 deletions test/waittask.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,47 +208,15 @@ 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();
await frame.waitForSelector('*');
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([
Expand 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();
Expand All @@ -268,15 +235,13 @@ 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');
await page.evaluate(addElement, 'span');
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);
Expand All @@ -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);
Expand All @@ -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];
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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() {
Expand Down