diff --git a/docs/api.md b/docs/api.md index ec48bc5e3d..fb8e257c48 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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]> 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]> 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]> diff --git a/src/frames.ts b/src/frames.ts index 39959e2162..eb5db37217 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -620,6 +620,10 @@ export class Frame { return handle; } + async $wait(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise | 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 { const context = await this._utilityContext(); return context.evaluate(() => document.title); diff --git a/src/page.ts b/src/page.ts index b3c22d29cc..3e899647f5 100644 --- a/src/page.ts +++ b/src/page.ts @@ -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 | 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()]; } diff --git a/src/types.ts b/src/types.ts index 006d077c55..a4de23a5f1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -27,7 +27,6 @@ export type Evaluate = (pageFunction: PageFunction(pageFunction: PageFunction, ...args: Boxed) => Promise>; export type $Eval = (selector: string, pageFunction: PageFunctionOn, ...args: Boxed) => Promise; export type $$Eval = (selector: string, pageFunction: PageFunctionOn, ...args: Boxed) => Promise; -export type $Wait = (selector: string, pageFunction: PageFunctionOn, options?: WaitForFunctionOptions, ...args: Boxed) => Promise>; export type EvaluateOn = (pageFunction: PageFunctionOn, ...args: Boxed) => Promise; export type EvaluateHandleOn = (pageFunction: PageFunctionOn, ...args: Boxed) => Promise>; diff --git a/test/waittask.spec.js b/test/waittask.spec.js index 9bb1bead96..0f110840a7 100644 --- a/test/waittask.spec.js +++ b/test/waittask.spec.js @@ -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('
'); - 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('
'); - let done = null; - const resultPromise = page.$wait('span', e => e).then(r => done = r); - expect(done).toBe(null); - await page.setContent('
'); - expect(done).toBe(null); - await page.setContent('text'); - 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('
'); - let done = null; - const resultPromise = page.$wait('div', e => !e).then(r => done = r); - expect(done).toBe(null); - await page.setContent('
'); - 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 = '

'); 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('
test
'); + const handle = await page.$wait('section'); + expect(await handle.evaluate(e => e.textContent)).toBe('test'); + }); }); describe('Frame.waitForSelector xpath', function() {