diff --git a/docs/api.md b/docs/api.md index 7048bd901e..47bde516af 100644 --- a/docs/api.md +++ b/docs/api.md @@ -32,7 +32,7 @@ * [browser.browserContexts()](#browserbrowsercontexts) * [browser.chromium](#browserchromium) * [browser.close()](#browserclose) - * [browser.defaultContext()](#browserdefaultContext()) + * [browser.defaultContext()](#browserdefaultcontext) * [browser.disconnect()](#browserdisconnect) * [browser.isConnected()](#browserisconnected) * [browser.newContext(options)](#browsernewcontextoptions) diff --git a/src/chromium/Browser.ts b/src/chromium/Browser.ts index 9a3a612ef6..689ddd6f31 100644 --- a/src/chromium/Browser.ts +++ b/src/chromium/Browser.ts @@ -26,7 +26,6 @@ import { Target } from './Target'; import { Protocol } from './protocol'; import { Chromium } from './features/chromium'; import { FrameManager } from './FrameManager'; -import * as events from '../events'; import * as network from '../network'; import { Permissions } from './features/permissions'; import { Overrides } from './features/overrides'; diff --git a/src/firefox/Browser.ts b/src/firefox/Browser.ts index 42d6af0974..3e9743f2e2 100644 --- a/src/firefox/Browser.ts +++ b/src/firefox/Browser.ts @@ -22,7 +22,6 @@ import { Events } from './events'; import { Events as CommonEvents } from '../events'; import { Permissions } from './features/permissions'; import { Page } from '../page'; -import * as types from '../types'; import { FrameManager } from './FrameManager'; import { Firefox } from './features/firefox'; import * as network from '../network'; diff --git a/src/frames.ts b/src/frames.ts index a4c0b7185a..492f396b32 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -53,7 +53,7 @@ export type GotoResult = { export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'; const kLifecycleEvents: Set = new Set(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']); -export type WaitForOptions = types.TimeoutOptions & { waitFor?: boolean | 'visible' | 'hidden' | 'any' }; +export type WaitForOptions = types.TimeoutOptions & { waitFor?: types.Visibility | 'nowait' }; export class FrameManager { private _page: Page; @@ -364,8 +364,30 @@ export class Frame { return context.evaluate(pageFunction, ...args as any); } - async $(selector: string, options?: WaitForOptions): Promise | null> { - return this._optionallyWaitForSelector('main', selector, options, true /* returnNull */); + async $(selector: string): Promise | null> { + const utilityContext = await this._utilityContext(); + const mainContext = await this._mainContext(); + const handle = await utilityContext._$(selector); + if (handle && handle._context !== mainContext) { + const adopted = this._page._delegate.adoptElementHandle(handle, mainContext); + await handle.dispose(); + return adopted; + } + return handle; + } + + async waitForSelector(selector: string, options?: types.TimeoutOptions & { waitFor?: types.Visibility }): Promise | null> { + const { timeout = this._page._timeoutSettings.timeout(), waitFor = 'any' } = (options || {}); + if ((waitFor as any) === 'nowait') + throw new Error('waitForSelector does not support "nowait"'); + const handle = await this._waitForSelectorInUtilityContext(selector, waitFor as types.Visibility, timeout); + const mainContext = await this._mainContext(); + if (handle && handle._context !== mainContext) { + const adopted = this._page._delegate.adoptElementHandle(handle, mainContext); + await handle.dispose(); + return adopted; + } + return handle; } async $x(expression: string): Promise[]> { @@ -587,43 +609,43 @@ export class Frame { } async click(selector: string, options?: WaitForOptions & ClickOptions) { - const handle = await this._optionallyWaitForSelector('utility', selector, options); + const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options, 'visible'); await handle.click(options); await handle.dispose(); } async dblclick(selector: string, options?: WaitForOptions & MultiClickOptions) { - const handle = await this._optionallyWaitForSelector('utility', selector, options); + const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options, 'visible'); await handle.dblclick(options); await handle.dispose(); } async tripleclick(selector: string, options?: WaitForOptions & MultiClickOptions) { - const handle = await this._optionallyWaitForSelector('utility', selector, options); + const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options, 'visible'); await handle.tripleclick(options); await handle.dispose(); } async fill(selector: string, value: string, options?: WaitForOptions) { - const handle = await this._optionallyWaitForSelector('utility', selector, options); + const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options, 'visible'); await handle.fill(value); await handle.dispose(); } async focus(selector: string, options?: WaitForOptions) { - const handle = await this._optionallyWaitForSelector('utility', selector, options); + const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options, 'visible'); await handle.focus(); await handle.dispose(); } async hover(selector: string, options?: WaitForOptions & PointerActionOptions) { - const handle = await this._optionallyWaitForSelector('utility', selector, options); + const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options, 'visible'); await handle.hover(options); await handle.dispose(); } async select(selector: string, value: string | dom.ElementHandle | SelectOption | string[] | dom.ElementHandle[] | SelectOption[] | undefined, options?: WaitForOptions): Promise { - const handle = await this._optionallyWaitForSelector('utility', selector, options); + const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options, 'any'); const toDispose: Promise[] = []; const values = value === undefined ? [] : Array.isArray(value) ? value : [value]; const context = await this._utilityContext(); @@ -642,14 +664,14 @@ export class Frame { } async type(selector: string, text: string, options?: WaitForOptions & { delay?: number }) { - const handle = await this._optionallyWaitForSelector('utility', selector, options); + const handle = await this._optionallyWaitForSelectorInUtilityContext(selector, options, 'visible'); await handle.type(text, options); await handle.dispose(); } waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: any = {}, ...args: any[]): Promise { if (helper.isString(selectorOrFunctionOrTimeout)) - return this.$(selectorOrFunctionOrTimeout as string, { waitFor: true, ...options }) as any; + return this.waitForSelector(selectorOrFunctionOrTimeout as string, options) as any; if (helper.isNumber(selectorOrFunctionOrTimeout)) return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout as number)); if (typeof selectorOrFunctionOrTimeout === 'function') @@ -657,35 +679,36 @@ export class Frame { return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout))); } - private async _optionallyWaitForSelector(contextType: ContextType, selector: string, options: WaitForOptions = {}, returnNull?: boolean): Promise | null> { - const { timeout = this._page._timeoutSettings.timeout(), waitFor = undefined } = options; + private async _optionallyWaitForSelectorInUtilityContext(selector: string, options: WaitForOptions | undefined, defaultWaitFor: types.Visibility): Promise | null> { + const { timeout = this._page._timeoutSettings.timeout(), waitFor = defaultWaitFor } = (options || {}); let handle: dom.ElementHandle | null; - if (waitFor) { - let visibility: types.Visibility = 'any'; - if (waitFor === 'visible' || waitFor === 'hidden' || waitFor === 'any') - visibility = waitFor; - else if (waitFor === true) - visibility = 'any'; - else - throw new Error(`Unsupported waitFor option "${waitFor}"`); - const task = dom.waitForSelectorTask(selector, visibility, timeout); - const result = await this._scheduleRerunnableTask(task, contextType, timeout, `selector "${selectorToString(selector, visibility)}"`); - if (!result.asElement()) { - await result.dispose(); - if (returnNull) - return null; - throw new Error('No node found for selector: ' + selectorToString(selector, visibility)); - } - handle = result.asElement() as dom.ElementHandle; + if (waitFor !== 'nowait') { + handle = await this._waitForSelectorInUtilityContext(selector, waitFor, timeout); + if (!handle) + throw new Error('No node found for selector: ' + selectorToString(selector, waitFor)); } else { - const context = await this._context(contextType); + const context = await this._context('utility'); handle = await context._$(selector); - if (!returnNull) - assert(handle, 'No node found for selector: ' + selector); + assert(handle, 'No node found for selector: ' + selector); } return handle; } + private async _waitForSelectorInUtilityContext(selector: string, waitFor: types.Visibility, timeout: number): Promise | null> { + let visibility: types.Visibility = 'any'; + if (waitFor === 'visible' || waitFor === 'hidden' || waitFor === 'any') + visibility = waitFor; + else + throw new Error(`Unsupported waitFor option "${waitFor}"`); + const task = dom.waitForSelectorTask(selector, visibility, timeout); + const result = await this._scheduleRerunnableTask(task, 'utility', timeout, `selector "${selectorToString(selector, visibility)}"`); + if (!result.asElement()) { + await result.dispose(); + return null; + } + return result.asElement() as dom.ElementHandle; + } + waitForFunction(pageFunction: Function | string, options: types.WaitForFunctionOptions = {}, ...args: any[]): Promise { options = { timeout: this._page._timeoutSettings.timeout(), ...options }; const task = dom.waitForFunctionTask(pageFunction, options, ...args); diff --git a/src/page.ts b/src/page.ts index 054740d0d1..dbb9af1c60 100644 --- a/src/page.ts +++ b/src/page.ts @@ -162,8 +162,12 @@ export class Page extends EventEmitter { this._timeoutSettings.setDefaultTimeout(timeout); } - async $(selector: string, options?: frames.WaitForOptions): Promise | null> { - return this.mainFrame().$(selector, options); + async $(selector: string): Promise | null> { + return this.mainFrame().$(selector); + } + + async waitForSelector(selector: string, options?: types.TimeoutOptions & { waitFor?: types.Visibility }): Promise | null> { + return this.mainFrame().waitForSelector(selector, options); } async _createSelector(name: string, handle: dom.ElementHandle): Promise { diff --git a/src/webkit/Browser.ts b/src/webkit/Browser.ts index e5faef7f06..91a31656eb 100644 --- a/src/webkit/Browser.ts +++ b/src/webkit/Browser.ts @@ -19,7 +19,6 @@ import * as childProcess from 'child_process'; import { EventEmitter } from 'events'; import { helper, RegisteredListener, debugError } from '../helper'; import * as network from '../network'; -import * as types from '../types'; import { Connection, ConnectionEvents, TargetSession } from './Connection'; import { Page } from '../page'; import { Target } from './Target'; diff --git a/src/webkit/FrameManager.ts b/src/webkit/FrameManager.ts index 1cdb8399cf..dde87d88ee 100644 --- a/src/webkit/FrameManager.ts +++ b/src/webkit/FrameManager.ts @@ -281,8 +281,6 @@ export class FrameManager implements PageDelegate { await this._setEmulateMedia(this._session, mediaType, mediaColorScheme); } - - async setViewport(viewport: types.Viewport): Promise { if (viewport.isMobile || viewport.isLandscape || viewport.hasTouch) throw new Error('Not implemented'); diff --git a/test/chromium/connect.spec.js b/test/chromium/connect.spec.js index f904c82909..79c1e27544 100644 --- a/test/chromium/connect.spec.js +++ b/test/chromium/connect.spec.js @@ -120,7 +120,7 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p const browser = await playwright.launch(defaultBrowserOptions); const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.chromium.wsEndpoint()}); const page = await remote.newPage(); - const watchdog = page.$('div', { waitFor: true, timeout: 60000 }).catch(e => e); + const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e); remote.disconnect(); const error = await watchdog; expect(error.message).toContain('Protocol error'); diff --git a/test/chromium/headful.spec.js b/test/chromium/headful.spec.js index 5da9628887..71c0b3a725 100644 --- a/test/chromium/headful.spec.js +++ b/test/chromium/headful.spec.js @@ -99,7 +99,7 @@ module.exports.addTests = function({testRunner, expect, playwright, defaultBrows document.body.appendChild(frame); return new Promise(x => frame.onload = x); }); - await page.$('iframe[src="https://google.com/"]', { waitFor: true }); + await page.waitForSelector('iframe[src="https://google.com/"]'); const urls = page.frames().map(frame => frame.url()).sort(); expect(urls).toEqual([ server.EMPTY_PAGE, diff --git a/test/click.spec.js b/test/click.spec.js index add72cea4f..287b481291 100644 --- a/test/click.spec.js +++ b/test/click.spec.js @@ -128,7 +128,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME it('should waitFor visible when already visible', async({page, server}) => { await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button', { waitFor: 'visible' }); + await page.click('button'); expect(await page.evaluate(() => result)).toBe('Clicked'); }); it('should waitFor hidden when already hidden', async({page, server}) => { @@ -155,7 +155,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME let done = false; await page.goto(server.PREFIX + '/input/button.html'); await page.$eval('button', b => b.style.display = 'none'); - const clicked = page.click('button', { waitFor: 'visible' }).then(() => done = true); + const clicked = page.click('button').then(() => done = true); for (let i = 0; i < 5; i++) await page.evaluate('1'); // Do a round trip. expect(done).toBe(false); @@ -207,7 +207,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME it('should fail to click a missing button', async({page, server}) => { await page.goto(server.PREFIX + '/input/button.html'); let error = null; - await page.click('button.does-not-exist').catch(e => error = e); + await page.click('button.does-not-exist', { waitFor: 'nowait' }).catch(e => error = e); expect(error.message).toBe('No node found for selector: button.does-not-exist'); }); // @see https://github.com/GoogleChrome/puppeteer/issues/161 diff --git a/test/page.spec.js b/test/page.spec.js index 859cf9cefe..be4f477e49 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -600,7 +600,8 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF const result = await page.content(); expect(result).toBe(expectedOutput); }); - it('should not confuse with previous navigation', async({page, server}) => { + it.skip(FFOX || WEBKIT)('should not confuse with previous navigation', async({page, server}) => { + // TODO: ffox and webkit lack 'init' lifecycle event. const imgPath = '/img.png'; let imgResponse = null; server.setRoute(imgPath, (req, res) => imgResponse = res); @@ -1101,15 +1102,15 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF await page.fill('textarea', 123).catch(e => error = e); expect(error.message).toContain('Value must be string.'); }); - it('should respect selector visibilty', async({page, server}) => { + it('should wait for visible visibilty', async({page, server}) => { await page.goto(server.PREFIX + '/input/textarea.html'); - await page.fill('input', 'some value', { waitFor: 'visible' }); + await page.fill('input', 'some value'); expect(await page.evaluate(() => result)).toBe('some value'); await page.goto(server.PREFIX + '/input/textarea.html'); await page.$eval('input', i => i.style.display = 'none'); await Promise.all([ - page.fill('input', 'some value', { waitFor: 'visible' }), + page.fill('input', 'some value'), page.$eval('input', i => i.style.display = 'block'), ]); expect(await page.evaluate(() => result)).toBe('some value'); @@ -1128,12 +1129,12 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF it('should throw on hidden and invisible elements', async({page, server}) => { await page.goto(server.PREFIX + '/input/textarea.html'); await page.$eval('input', i => i.style.display = 'none'); - const invisibleError = await page.fill('input', 'some value').catch(e => e); + const invisibleError = await page.fill('input', 'some value', { waitFor: 'nowait' }).catch(e => e); expect(invisibleError.message).toBe('Element is not visible'); await page.goto(server.PREFIX + '/input/textarea.html'); await page.$eval('input', i => i.style.visibility = 'hidden'); - const hiddenError = await page.fill('input', 'some value').catch(e => e); + const hiddenError = await page.fill('input', 'some value', { waitFor: 'nowait' }).catch(e => e); expect(hiddenError.message).toBe('Element is hidden'); }); it('should be able to fill the body', async({page}) => { diff --git a/test/queryselector.spec.js b/test/queryselector.spec.js index 4e74c9a7f0..0b9e91bcc8 100644 --- a/test/queryselector.spec.js +++ b/test/queryselector.spec.js @@ -162,25 +162,6 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W const element = await page.$('non-existing-element'); expect(element).toBe(null); }); - it('should return null for non-existing element with waitFor:false', async({page, server}) => { - const element = await page.$('non-existing-element', { waitFor: false }); - expect(element).toBe(null); - }); - it('should query existing element with waitFor:false', async({page, server}) => { - await page.setContent('
test
'); - const element = await page.$('css=section', { waitFor: false }); - expect(element).toBeTruthy(); - }); - it('should throw for unknown waitFor option', async({page, server}) => { - await page.setContent('
test
'); - const error = await page.$('section', { waitFor: 'foo' }).catch(e => e); - expect(error.message).toContain('Unsupported waitFor option'); - }); - it('should throw for numeric waitFor option', async({page, server}) => { - await page.setContent('
test
'); - const error = await page.$('section', { waitFor: 123 }).catch(e => e); - expect(error.message).toContain('Unsupported waitFor option'); - }); it('should auto-detect xpath selector', async({page, server}) => { await page.setContent('
test
'); const element = await page.$('//html/body/section'); @@ -203,14 +184,14 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W }); it('should respect waitFor visibility', async({page, server}) => { await page.setContent('
43543
'); - expect(await page.$('css=section', { waitFor: 'visible'})).toBeTruthy(); - expect(await page.$('css=section', { waitFor: 'any'})).toBeTruthy(); - expect(await page.$('css=section')).toBeTruthy(); + expect(await page.waitForSelector('css=section', { waitFor: 'visible'})).toBeTruthy(); + expect(await page.waitForSelector('css=section', { waitFor: 'any'})).toBeTruthy(); + expect(await page.waitForSelector('css=section')).toBeTruthy(); await page.setContent(''); - expect(await page.$('css=section', { waitFor: 'hidden'})).toBeTruthy(); - expect(await page.$('css=section', { waitFor: 'any'})).toBeTruthy(); - expect(await page.$('css=section')).toBeTruthy(); + expect(await page.waitForSelector('css=section', { waitFor: 'hidden'})).toBeTruthy(); + expect(await page.waitForSelector('css=section', { waitFor: 'any'})).toBeTruthy(); + expect(await page.waitForSelector('css=section')).toBeTruthy(); }); }); diff --git a/test/waittask.spec.js b/test/waittask.spec.js index 64180130ae..ae019bdb73 100644 --- a/test/waittask.spec.js +++ b/test/waittask.spec.js @@ -205,21 +205,21 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO }); }); - describe('Frame.$ waitFor', function() { + 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.$('*', { waitFor: true }); + await frame.waitForSelector('*'); await frame.evaluate(addElement, 'div'); - await frame.$('div', { waitFor: true }); + await frame.waitForSelector('div'); }); it.skip(FFOX)('should work with removed MutationObserver', async({page, server}) => { await page.evaluate(() => delete window.MutationObserver); const [handle] = await Promise.all([ - page.$('.zombo', { waitFor: true }), + page.waitForSelector('.zombo'), page.setContent(`
anything
`), ]); expect(await page.evaluate(x => x.textContent, handle)).toBe('anything'); @@ -228,7 +228,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO it('should resolve promise when node is added', async({page, server}) => { await page.goto(server.EMPTY_PAGE); const frame = page.mainFrame(); - const watchdog = frame.$('div', { waitFor: true }); + const watchdog = frame.waitForSelector('div'); await frame.evaluate(addElement, 'br'); await frame.evaluate(addElement, 'div'); const eHandle = await watchdog; @@ -238,7 +238,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO it('should work when node is added through innerHTML', async({page, server}) => { await page.goto(server.EMPTY_PAGE); - const watchdog = page.$('h3 div', { waitFor: true }); + const watchdog = page.waitForSelector('h3 div'); await page.evaluate(addElement, 'span'); await page.evaluate(() => document.querySelector('span').innerHTML = '

'); await watchdog; @@ -248,7 +248,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO await page.goto(server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); const otherFrame = page.frames()[1]; - const watchdog = page.$('div', { waitFor: true }); + const watchdog = page.waitForSelector('div'); await otherFrame.evaluate(addElement, 'div'); await page.evaluate(addElement, 'div'); const eHandle = await watchdog; @@ -260,7 +260,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); const frame1 = page.frames()[1]; const frame2 = page.frames()[2]; - const waitForSelectorPromise = frame2.$('div', { waitFor: true }); + const waitForSelectorPromise = frame2.waitForSelector('div'); await frame1.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div'); const eHandle = await waitForSelectorPromise; @@ -271,7 +271,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); const frame = page.frames()[1]; let waitError = null; - const waitPromise = frame.$('.box', { waitFor: true }).catch(e => waitError = e); + const waitPromise = frame.waitForSelector('.box').catch(e => waitError = e); await utils.detachFrame(page, 'frame1'); await waitPromise; expect(waitError).toBeTruthy(); @@ -279,7 +279,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO }); it('should survive cross-process navigation', async({page, server}) => { let boxFound = false; - const waitForSelector = page.$('.box', { waitFor: true }).then(() => boxFound = true); + const waitForSelector = page.waitForSelector('.box').then(() => boxFound = true); await page.goto(server.EMPTY_PAGE); expect(boxFound).toBe(false); await page.reload(); @@ -290,7 +290,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO }); it('should wait for visible', async({page, server}) => { let divFound = false; - const waitForSelector = page.$('div', { waitFor: 'visible' }).then(() => divFound = true); + const waitForSelector = page.waitForSelector('div').then(() => divFound = true); await page.setContent(`
1
`); expect(divFound).toBe(false); await page.evaluate(() => document.querySelector('div').style.removeProperty('display')); @@ -301,7 +301,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO }); it('should wait for visible recursively', async({page, server}) => { let divVisible = false; - const waitForSelector = page.$('div#inner', { waitFor: 'visible' }).then(() => divVisible = true); + const waitForSelector = page.waitForSelector('div#inner', { waitFor: 'visible' }).then(() => divVisible = true); await page.setContent(`
hi
`); expect(divVisible).toBe(false); await page.evaluate(() => document.querySelector('div').style.removeProperty('display')); @@ -313,8 +313,8 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO it('hidden should wait for visibility: hidden', async({page, server}) => { let divHidden = false; await page.setContent(`
`); - const waitForSelector = page.$('div', { waitFor: 'hidden' }).then(() => divHidden = true); - await page.$('div', { waitFor: true }); // do a round trip + const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divHidden = true); + await page.waitForSelector('div'); // do a round trip expect(divHidden).toBe(false); await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden')); expect(await waitForSelector).toBe(true); @@ -323,8 +323,8 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO it('hidden should wait for display: none', async({page, server}) => { let divHidden = false; await page.setContent(`
`); - const waitForSelector = page.$('div', { waitFor: 'hidden' }).then(() => divHidden = true); - await page.$('div', { waitFor: true }); // do a round trip + const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divHidden = true); + await page.waitForSelector('div'); // do a round trip expect(divHidden).toBe(false); await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none')); expect(await waitForSelector).toBe(true); @@ -333,20 +333,20 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO it('hidden should wait for removal', async({page, server}) => { await page.setContent(`
`); let divRemoved = false; - const waitForSelector = page.$('div', { waitFor: 'hidden' }).then(() => divRemoved = true); - await page.$('div', { waitFor: true }); // do a round trip + const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divRemoved = true); + await page.waitForSelector('div'); // do a round trip expect(divRemoved).toBe(false); await page.evaluate(() => document.querySelector('div').remove()); expect(await waitForSelector).toBe(true); expect(divRemoved).toBe(true); }); it('should return null if waiting to hide non-existing element', async({page, server}) => { - const handle = await page.$('non-existing', { waitFor: 'hidden' }); + const handle = await page.waitForSelector('non-existing', { waitFor: 'hidden' }); expect(handle).toBe(null); }); it('should respect timeout', async({page, server}) => { let error = null; - await page.$('div', { waitFor: true, timeout: 10 }).catch(e => error = e); + await page.waitForSelector('div', { timeout: 10 }).catch(e => error = e); expect(error).toBeTruthy(); expect(error.message).toContain('waiting for selector "div" failed: timeout'); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); @@ -354,34 +354,62 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO it('should have an error message specifically for awaiting an element to be hidden', async({page, server}) => { await page.setContent(`
`); let error = null; - await page.$('div', { waitFor: 'hidden', timeout: 10 }).catch(e => error = e); + await page.waitForSelector('div', { waitFor: 'hidden', timeout: 10 }).catch(e => error = e); 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.$('.zombo', { waitFor: true }).then(() => divFound = true); + const waitForSelector = page.waitForSelector('.zombo').then(() => divFound = true); await page.setContent(`
`); expect(divFound).toBe(false); await page.evaluate(() => document.querySelector('div').className = 'zombo'); expect(await waitForSelector).toBe(true); }); it('should return the element handle', async({page, server}) => { - const waitForSelector = page.$('.zombo', { waitFor: true }); + const waitForSelector = page.waitForSelector('.zombo'); await page.setContent(`
anything
`); expect(await page.evaluate(x => x.textContent, await waitForSelector)).toBe('anything'); }); it('should have correct stack trace for timeout', async({page, server}) => { let error; - await page.$('.zombo', { waitFor: true, timeout: 10 }).catch(e => error = e); + await page.waitForSelector('.zombo', { timeout: 10 }).catch(e => error = e); expect(error.stack).toContain('waittask.spec.js'); }); - + it('should throw for waitFor nowait', async({page, server}) => { + let error; + try { + await page.waitForSelector('non-existing-element', { waitFor: 'nowait' }); + } catch (e) { + error = e; + } + expect(error.message).toBe('waitForSelector does not support "nowait"'); + }); + it('should throw for unknown waitFor option', async({page, server}) => { + await page.setContent('
test
'); + const error = await page.waitForSelector('section', { waitFor: 'foo' }).catch(e => e); + expect(error.message).toContain('Unsupported waitFor option'); + }); + it('should throw for numeric waitFor option', async({page, server}) => { + await page.setContent('
test
'); + const error = await page.waitForSelector('section', { waitFor: 123 }).catch(e => e); + expect(error.message).toContain('Unsupported waitFor option'); + }); + it('should throw for true waitFor option', async({page, server}) => { + await page.setContent('
test
'); + const error = await page.waitForSelector('section', { waitFor: true }).catch(e => e); + expect(error.message).toContain('Unsupported waitFor option'); + }); + it('should throw for false waitFor option', async({page, server}) => { + await page.setContent('
test
'); + const error = await page.waitForSelector('section', { waitFor: false }).catch(e => e); + expect(error.message).toContain('Unsupported waitFor option'); + }); it('should support >> selector syntax', async({page, server}) => { await page.goto(server.EMPTY_PAGE); const frame = page.mainFrame(); - const watchdog = frame.$('css=div >> css=span', { waitFor: true }); + const watchdog = frame.waitForSelector('css=div >> css=span'); await frame.evaluate(addElement, 'br'); await frame.evaluate(addElement, 'div'); await frame.evaluate(() => document.querySelector('div').appendChild(document.createElement('span'))); @@ -391,17 +419,17 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO }); }); - describe('Frame.$ waitFor xpath', function() { + describe('Frame.waitForSelector xpath', function() { const addElement = tag => document.body.appendChild(document.createElement(tag)); it('should support some fancy xpath', async({page, server}) => { await page.setContent(`

red herring

hello world

`); - const waitForXPath = page.$('//p[normalize-space(.)="hello world"]', { waitFor: true }); + const waitForXPath = page.waitForSelector('//p[normalize-space(.)="hello world"]'); expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('hello world '); }); it('should respect timeout', async({page}) => { let error = null; - await page.$('//div', { waitFor: true, timeout: 10 }).catch(e => error = e); + await page.waitForSelector('//div', { timeout: 10 }).catch(e => error = e); expect(error).toBeTruthy(); expect(error.message).toContain('waiting for selector "//div" failed: timeout'); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); @@ -411,7 +439,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); const frame1 = page.frames()[1]; const frame2 = page.frames()[2]; - const waitForXPathPromise = frame2.$('//div', { waitFor: true }); + const waitForXPathPromise = frame2.waitForSelector('//div'); await frame1.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div'); const eHandle = await waitForXPathPromise; @@ -421,20 +449,20 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); const frame = page.frames()[1]; let waitError = null; - const waitPromise = frame.$('//*[@class="box"]', { waitFor: true }).catch(e => waitError = e); + const waitPromise = frame.waitForSelector('//*[@class="box"]').catch(e => waitError = e); await utils.detachFrame(page, 'frame1'); await waitPromise; expect(waitError).toBeTruthy(); expect(waitError.message).toContain('waitForFunction failed: frame got detached.'); }); it('should return the element handle', async({page, server}) => { - const waitForXPath = page.$('//*[@class="zombo"]', { waitFor: true }); + const waitForXPath = page.waitForSelector('//*[@class="zombo"]'); await page.setContent(`
anything
`); expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('anything'); }); it('should allow you to select an element with single slash', async({page, server}) => { await page.setContent(`
some text
`); - const waitForXPath = page.$('//html/body/div', { waitFor: true }); + const waitForXPath = page.waitForSelector('//html/body/div'); expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('some text'); }); });