From a5b197819049711e85ec6d4735303534f533128a Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Tue, 20 Nov 2018 18:57:28 -0800 Subject: [PATCH] test: split out waittask tests (#3580) --- test/frame.spec.js | 343 ----------------------------------- test/page.spec.js | 46 ----- test/test.js | 1 + test/waittask.spec.js | 413 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 414 insertions(+), 389 deletions(-) create mode 100644 test/waittask.spec.js diff --git a/test/frame.spec.js b/test/frame.spec.js index 8d881fd38eb..e4b9ee9d676 100644 --- a/test/frame.spec.js +++ b/test/frame.spec.js @@ -15,7 +15,6 @@ */ const utils = require('./utils'); -const {TimeoutError} = utils.requireRoot('Errors'); module.exports.addTests = function({testRunner, expect}) { const {describe, xdescribe, fdescribe} = testRunner; @@ -157,348 +156,6 @@ module.exports.addTests = function({testRunner, expect}) { }); }); - describe('Frame.waitForFunction', function() { - it('should accept a string', async({page, server}) => { - const watchdog = page.waitForFunction('window.__FOO === 1'); - await page.evaluate(() => window.__FOO = 1); - await watchdog; - }); - it('should work when resolved right before execution context disposal', async({page, server}) => { - await page.evaluateOnNewDocument(() => window.__RELOADED = true); - await page.waitForFunction(() => { - if (!window.__RELOADED) - window.location.reload(); - return true; - }); - }); - it('should poll on interval', async({page, server}) => { - let success = false; - const startTime = Date.now(); - const polling = 100; - const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {polling}) - .then(() => success = true); - await page.evaluate(() => window.__FOO = 'hit'); - expect(success).toBe(false); - await page.evaluate(() => document.body.appendChild(document.createElement('div'))); - await watchdog; - expect(Date.now() - startTime).not.toBeLessThan(polling / 2); - }); - it('should poll on mutation', async({page, server}) => { - let success = false; - const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {polling: 'mutation'}) - .then(() => success = true); - await page.evaluate(() => window.__FOO = 'hit'); - expect(success).toBe(false); - await page.evaluate(() => document.body.appendChild(document.createElement('div'))); - await watchdog; - }); - it('should poll on raf', async({page, server}) => { - const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {polling: 'raf'}); - await page.evaluate(() => window.__FOO = 'hit'); - await watchdog; - }); - it('should work with strict CSP policy', async({page, server}) => { - server.setCSP('/empty.html', 'script-src ' + server.PREFIX); - await page.goto(server.EMPTY_PAGE); - const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {polling: 'raf'}); - await page.evaluate(() => window.__FOO = 'hit'); - await watchdog; - }); - it('should throw on bad polling value', async({page, server}) => { - let error = null; - try { - await page.waitForFunction(() => !!document.body, {polling: 'unknown'}); - } catch (e) { - error = e; - } - expect(error).toBeTruthy(); - expect(error.message).toContain('polling'); - }); - it('should throw negative polling interval', async({page, server}) => { - let error = null; - try { - await page.waitForFunction(() => !!document.body, {polling: -10}); - } catch (e) { - error = e; - } - expect(error).toBeTruthy(); - expect(error.message).toContain('Cannot poll with non-positive interval'); - }); - it('should return the success value as a JSHandle', async({page}) => { - expect(await (await page.waitForFunction(() => 5)).jsonValue()).toBe(5); - }); - it('should return the window as a success value', async({ page }) => { - expect(await page.waitForFunction(() => window)).toBeTruthy(); - }); - it('should accept ElementHandle arguments', async({page}) => { - await page.setContent('
'); - const div = await page.$('div'); - let resolved = false; - const waitForFunction = page.waitForFunction(element => !element.parentElement, {}, div).then(() => resolved = true); - expect(resolved).toBe(false); - await page.evaluate(element => element.remove(), div); - await waitForFunction; - }); - it('should respect timeout', async({page}) => { - let error = null; - await page.waitForFunction('false', {timeout: 10}).catch(e => error = e); - expect(error).toBeTruthy(); - expect(error.message).toContain('waiting for function failed: timeout'); - expect(error).toBeInstanceOf(TimeoutError); - }); - it('should disable timeout when its set to 0', async({page}) => { - const watchdog = page.waitForFunction(() => { - window.__counter = (window.__counter || 0) + 1; - return window.__injected; - }, {timeout: 0, polling: 10}); - await page.waitForFunction(() => window.__counter > 10); - await page.evaluate(() => window.__injected = true); - await watchdog; - }); - }); - - 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 resolve promise when node is added', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const frame = page.mainFrame(); - const watchdog = frame.waitForSelector('div'); - await frame.evaluate(addElement, 'br'); - await frame.evaluate(addElement, 'div'); - const eHandle = await watchdog; - 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 = '

'); - await watchdog; - }); - - it('Page.waitForSelector is shortcut for main frame', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - const otherFrame = page.frames()[1]; - const watchdog = page.waitForSelector('div'); - await otherFrame.evaluate(addElement, 'div'); - await page.evaluate(addElement, 'div'); - const eHandle = await watchdog; - expect(eHandle.executionContext().frame()).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); - const frame1 = page.frames()[1]; - const frame2 = page.frames()[2]; - const waitForSelectorPromise = frame2.waitForSelector('div'); - await frame1.evaluate(addElement, 'div'); - await frame2.evaluate(addElement, 'div'); - const eHandle = await waitForSelectorPromise; - expect(eHandle.executionContext().frame()).toBe(frame2); - }); - - it('should throw if evaluation failed', async({page, server}) => { - await page.evaluateOnNewDocument(function() { - document.querySelector = null; - }); - await page.goto(server.EMPTY_PAGE); - let error = null; - await page.waitForSelector('*').catch(e => error = e); - expect(error.message).toContain('document.querySelector is not a function'); - }); - it('should throw when frame is detached', async({page, server}) => { - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - const frame = page.frames()[1]; - let waitError = null; - const waitPromise = frame.waitForSelector('.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 survive cross-process navigation', async({page, server}) => { - let boxFound = false; - const waitForSelector = page.waitForSelector('.box').then(() => boxFound = true); - await page.goto(server.EMPTY_PAGE); - expect(boxFound).toBe(false); - await page.reload(); - expect(boxFound).toBe(false); - await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html'); - await waitForSelector; - expect(boxFound).toBe(true); - }); - it('should wait for visible', async({page, server}) => { - let divFound = false; - const waitForSelector = page.waitForSelector('div', {visible: true}).then(() => divFound = true); - await page.setContent(`
1
`); - expect(divFound).toBe(false); - await page.evaluate(() => document.querySelector('div').style.removeProperty('display')); - expect(divFound).toBe(false); - await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility')); - expect(await waitForSelector).toBe(true); - expect(divFound).toBe(true); - }); - it('should wait for visible recursively', async({page, server}) => { - let divVisible = false; - const waitForSelector = page.waitForSelector('div#inner', {visible: true}).then(() => divVisible = true); - await page.setContent(`
hi
`); - expect(divVisible).toBe(false); - await page.evaluate(() => document.querySelector('div').style.removeProperty('display')); - expect(divVisible).toBe(false); - await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility')); - expect(await waitForSelector).toBe(true); - expect(divVisible).toBe(true); - }); - it('hidden should wait for visibility: hidden', async({page, server}) => { - let divHidden = false; - await page.setContent(`
`); - const waitForSelector = page.waitForSelector('div', {hidden: true}).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); - expect(divHidden).toBe(true); - }); - it('hidden should wait for display: none', async({page, server}) => { - let divHidden = false; - await page.setContent(`
`); - const waitForSelector = page.waitForSelector('div', {hidden: true}).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); - expect(divHidden).toBe(true); - }); - it('hidden should wait for removal', async({page, server}) => { - await page.setContent(`
`); - let divRemoved = false; - const waitForSelector = page.waitForSelector('div', {hidden: true}).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 respect timeout', async({page, server}) => { - let error = null; - 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(TimeoutError); - }); - 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.waitForSelector('div', {hidden: true, timeout: 10}).catch(e => error = e); - expect(error).toBeTruthy(); - expect(error.message).toContain('waiting for selector "div" to be hidden failed: timeout'); - }); - - it('should respond to node attribute mutation', async({page, server}) => { - let divFound = false; - 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.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.waitForSelector('.zombo', {timeout: 10}).catch(e => error = e); - expect(error.stack).toContain('frame.spec.js'); - }); - }); - - describe('Frame.waitForXPath', 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.waitForXPath('//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.waitForXPath('//div', {timeout: 10}).catch(e => error = e); - expect(error).toBeTruthy(); - expect(error.message).toContain('waiting for XPath "//div" failed: timeout'); - expect(error).toBeInstanceOf(TimeoutError); - }); - 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); - const frame1 = page.frames()[1]; - const frame2 = page.frames()[2]; - const waitForXPathPromise = frame2.waitForXPath('//div'); - await frame1.evaluate(addElement, 'div'); - await frame2.evaluate(addElement, 'div'); - const eHandle = await waitForXPathPromise; - expect(eHandle.executionContext().frame()).toBe(frame2); - }); - it('should throw if evaluation failed', async({page, server}) => { - await page.evaluateOnNewDocument(function() { - document.evaluate = null; - }); - await page.goto(server.EMPTY_PAGE); - let error = null; - await page.waitForXPath('*').catch(e => error = e); - expect(error.message).toContain('document.evaluate is not a function'); - }); - it('should throw when frame is detached', async({page, server}) => { - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - const frame = page.frames()[1]; - let waitError = null; - const waitPromise = frame.waitForXPath('//*[@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('hidden should wait for display: none', async({page, server}) => { - let divHidden = false; - await page.setContent(`
`); - const waitForXPath = page.waitForXPath('//div', {hidden: true}).then(() => divHidden = true); - await page.waitForXPath('//div'); // do a round trip - expect(divHidden).toBe(false); - await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none')); - expect(await waitForXPath).toBe(true); - expect(divHidden).toBe(true); - }); - it('should return the element handle', async({page, server}) => { - const waitForXPath = page.waitForXPath('//*[@class="zombo"]'); - await page.setContent(`
anything
`); - expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('anything'); - }); - it('should allow you to select a text node', async({page, server}) => { - await page.setContent(`
some text
`); - const text = await page.waitForXPath('//div/text()'); - expect(await (await text.getProperty('nodeType')).jsonValue()).toBe(3 /* Node.TEXT_NODE */); - }); - it('should allow you to select an element with single slash', async({page, server}) => { - await page.setContent(`
some text
`); - const waitForXPath = page.waitForXPath('/html/body/div'); - expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('some text'); - }); - }); - describe('Frame Management', function() { it('should handle nested frames', async({page, server}) => { await page.goto(server.PREFIX + '/frames/nested-frames.html'); diff --git a/test/page.spec.js b/test/page.spec.js index 584af611749..a9a78f2d6e9 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -401,52 +401,6 @@ module.exports.addTests = function({testRunner, expect, headless}) { }); }); - 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(`
some text
`); - let error = null; - await page.waitFor('/html/body/div').catch(e => error = e); - expect(error).toBeTruthy(); - }); - it('should timeout', async({page, server}) => { - const startTime = Date.now(); - const timeout = 42; - await page.waitFor(timeout); - expect(Date.now() - startTime).not.toBeLessThan(timeout / 2); - }); - it('should wait for predicate', async({page, server}) => { - const watchdog = page.waitFor(() => window.innerWidth < 100); - page.setViewport({width: 10, height: 10}); - await watchdog; - }); - 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, {}, 1, 2); - }); - }); - describe('Page.Events.Console', function() { it('should work', async({page, server}) => { let message = null; diff --git a/test/test.js b/test/test.js index b4ecbcfe032..2e0d3128dfb 100644 --- a/test/test.js +++ b/test/test.js @@ -151,6 +151,7 @@ describe('Browser', function() { require('./coverage.spec.js').addTests({testRunner, expect}); require('./elementhandle.spec.js').addTests({testRunner, expect}); require('./queryselector.spec.js').addTests({testRunner, expect}); + require('./waittask.spec.js').addTests({testRunner, expect}); require('./frame.spec.js').addTests({testRunner, expect}); require('./input.spec.js').addTests({testRunner, expect}); require('./jshandle.spec.js').addTests({testRunner, expect}); diff --git a/test/waittask.spec.js b/test/waittask.spec.js new file mode 100644 index 00000000000..a8dcf91f523 --- /dev/null +++ b/test/waittask.spec.js @@ -0,0 +1,413 @@ +/** + * Copyright 2018 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const utils = require('./utils'); +const {TimeoutError} = utils.requireRoot('Errors'); + +module.exports.addTests = function({testRunner, expect, product}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; + + 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(`
some text
`); + let error = null; + await page.waitFor('/html/body/div').catch(e => error = e); + expect(error).toBeTruthy(); + }); + it('should timeout', async({page, server}) => { + const startTime = Date.now(); + const timeout = 42; + await page.waitFor(timeout); + expect(Date.now() - startTime).not.toBeLessThan(timeout / 2); + }); + it('should wait for predicate', async({page, server}) => { + const watchdog = page.waitFor(() => window.innerWidth < 100); + page.setViewport({width: 10, height: 10}); + await watchdog; + }); + 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, {}, 1, 2); + }); + }); + + describe('Frame.waitForFunction', function() { + it('should accept a string', async({page, server}) => { + const watchdog = page.waitForFunction('window.__FOO === 1'); + await page.evaluate(() => window.__FOO = 1); + await watchdog; + }); + it('should work when resolved right before execution context disposal', async({page, server}) => { + await page.evaluateOnNewDocument(() => window.__RELOADED = true); + await page.waitForFunction(() => { + if (!window.__RELOADED) + window.location.reload(); + return true; + }); + }); + it('should poll on interval', async({page, server}) => { + let success = false; + const startTime = Date.now(); + const polling = 100; + const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {polling}) + .then(() => success = true); + await page.evaluate(() => window.__FOO = 'hit'); + expect(success).toBe(false); + await page.evaluate(() => document.body.appendChild(document.createElement('div'))); + await watchdog; + expect(Date.now() - startTime).not.toBeLessThan(polling / 2); + }); + it('should poll on mutation', async({page, server}) => { + let success = false; + const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {polling: 'mutation'}) + .then(() => success = true); + await page.evaluate(() => window.__FOO = 'hit'); + expect(success).toBe(false); + await page.evaluate(() => document.body.appendChild(document.createElement('div'))); + await watchdog; + }); + it('should poll on raf', async({page, server}) => { + const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {polling: 'raf'}); + await page.evaluate(() => window.__FOO = 'hit'); + await watchdog; + }); + it('should work with strict CSP policy', async({page, server}) => { + server.setCSP('/empty.html', 'script-src ' + server.PREFIX); + await page.goto(server.EMPTY_PAGE); + const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {polling: 'raf'}); + await page.evaluate(() => window.__FOO = 'hit'); + await watchdog; + }); + it('should throw on bad polling value', async({page, server}) => { + let error = null; + try { + await page.waitForFunction(() => !!document.body, {polling: 'unknown'}); + } catch (e) { + error = e; + } + expect(error).toBeTruthy(); + expect(error.message).toContain('polling'); + }); + it('should throw negative polling interval', async({page, server}) => { + let error = null; + try { + await page.waitForFunction(() => !!document.body, {polling: -10}); + } catch (e) { + error = e; + } + expect(error).toBeTruthy(); + expect(error.message).toContain('Cannot poll with non-positive interval'); + }); + it('should return the success value as a JSHandle', async({page}) => { + expect(await (await page.waitForFunction(() => 5)).jsonValue()).toBe(5); + }); + it('should return the window as a success value', async({ page }) => { + expect(await page.waitForFunction(() => window)).toBeTruthy(); + }); + it('should accept ElementHandle arguments', async({page}) => { + await page.setContent('
'); + const div = await page.$('div'); + let resolved = false; + const waitForFunction = page.waitForFunction(element => !element.parentElement, {}, div).then(() => resolved = true); + expect(resolved).toBe(false); + await page.evaluate(element => element.remove(), div); + await waitForFunction; + }); + it('should respect timeout', async({page}) => { + let error = null; + await page.waitForFunction('false', {timeout: 10}).catch(e => error = e); + expect(error).toBeTruthy(); + expect(error.message).toContain('waiting for function failed: timeout'); + expect(error).toBeInstanceOf(TimeoutError); + }); + it('should disable timeout when its set to 0', async({page}) => { + const watchdog = page.waitForFunction(() => { + window.__counter = (window.__counter || 0) + 1; + return window.__injected; + }, {timeout: 0, polling: 10}); + await page.waitForFunction(() => window.__counter > 10); + await page.evaluate(() => window.__injected = true); + await watchdog; + }); + }); + + 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 resolve promise when node is added', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const frame = page.mainFrame(); + const watchdog = frame.waitForSelector('div'); + await frame.evaluate(addElement, 'br'); + await frame.evaluate(addElement, 'div'); + const eHandle = await watchdog; + 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 = '

'); + await watchdog; + }); + + it('Page.waitForSelector is shortcut for main frame', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + const otherFrame = page.frames()[1]; + const watchdog = page.waitForSelector('div'); + await otherFrame.evaluate(addElement, 'div'); + await page.evaluate(addElement, 'div'); + const eHandle = await watchdog; + expect(eHandle.executionContext().frame()).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); + const frame1 = page.frames()[1]; + const frame2 = page.frames()[2]; + const waitForSelectorPromise = frame2.waitForSelector('div'); + await frame1.evaluate(addElement, 'div'); + await frame2.evaluate(addElement, 'div'); + const eHandle = await waitForSelectorPromise; + expect(eHandle.executionContext().frame()).toBe(frame2); + }); + + it('should throw if evaluation failed', async({page, server}) => { + await page.evaluateOnNewDocument(function() { + document.querySelector = null; + }); + await page.goto(server.EMPTY_PAGE); + let error = null; + await page.waitForSelector('*').catch(e => error = e); + expect(error.message).toContain('document.querySelector is not a function'); + }); + it('should throw when frame is detached', async({page, server}) => { + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + const frame = page.frames()[1]; + let waitError = null; + const waitPromise = frame.waitForSelector('.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 survive cross-process navigation', async({page, server}) => { + let boxFound = false; + const waitForSelector = page.waitForSelector('.box').then(() => boxFound = true); + await page.goto(server.EMPTY_PAGE); + expect(boxFound).toBe(false); + await page.reload(); + expect(boxFound).toBe(false); + await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html'); + await waitForSelector; + expect(boxFound).toBe(true); + }); + it('should wait for visible', async({page, server}) => { + let divFound = false; + const waitForSelector = page.waitForSelector('div', {visible: true}).then(() => divFound = true); + await page.setContent(`
1
`); + expect(divFound).toBe(false); + await page.evaluate(() => document.querySelector('div').style.removeProperty('display')); + expect(divFound).toBe(false); + await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility')); + expect(await waitForSelector).toBe(true); + expect(divFound).toBe(true); + }); + it('should wait for visible recursively', async({page, server}) => { + let divVisible = false; + const waitForSelector = page.waitForSelector('div#inner', {visible: true}).then(() => divVisible = true); + await page.setContent(`
hi
`); + expect(divVisible).toBe(false); + await page.evaluate(() => document.querySelector('div').style.removeProperty('display')); + expect(divVisible).toBe(false); + await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility')); + expect(await waitForSelector).toBe(true); + expect(divVisible).toBe(true); + }); + it('hidden should wait for visibility: hidden', async({page, server}) => { + let divHidden = false; + await page.setContent(`
`); + const waitForSelector = page.waitForSelector('div', {hidden: true}).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); + expect(divHidden).toBe(true); + }); + it('hidden should wait for display: none', async({page, server}) => { + let divHidden = false; + await page.setContent(`
`); + const waitForSelector = page.waitForSelector('div', {hidden: true}).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); + expect(divHidden).toBe(true); + }); + it('hidden should wait for removal', async({page, server}) => { + await page.setContent(`
`); + let divRemoved = false; + const waitForSelector = page.waitForSelector('div', {hidden: true}).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 respect timeout', async({page, server}) => { + let error = null; + 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(TimeoutError); + }); + 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.waitForSelector('div', {hidden: true, timeout: 10}).catch(e => error = e); + expect(error).toBeTruthy(); + expect(error.message).toContain('waiting for selector "div" to be hidden failed: timeout'); + }); + + it('should respond to node attribute mutation', async({page, server}) => { + let divFound = false; + 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.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.waitForSelector('.zombo', {timeout: 10}).catch(e => error = e); + expect(error.stack).toContain('waittask.spec.js'); + }); + }); + + describe('Frame.waitForXPath', 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.waitForXPath('//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.waitForXPath('//div', {timeout: 10}).catch(e => error = e); + expect(error).toBeTruthy(); + expect(error.message).toContain('waiting for XPath "//div" failed: timeout'); + expect(error).toBeInstanceOf(TimeoutError); + }); + 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); + const frame1 = page.frames()[1]; + const frame2 = page.frames()[2]; + const waitForXPathPromise = frame2.waitForXPath('//div'); + await frame1.evaluate(addElement, 'div'); + await frame2.evaluate(addElement, 'div'); + const eHandle = await waitForXPathPromise; + expect(eHandle.executionContext().frame()).toBe(frame2); + }); + it('should throw if evaluation failed', async({page, server}) => { + await page.evaluateOnNewDocument(function() { + document.evaluate = null; + }); + await page.goto(server.EMPTY_PAGE); + let error = null; + await page.waitForXPath('*').catch(e => error = e); + expect(error.message).toContain('document.evaluate is not a function'); + }); + it('should throw when frame is detached', async({page, server}) => { + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + const frame = page.frames()[1]; + let waitError = null; + const waitPromise = frame.waitForXPath('//*[@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('hidden should wait for display: none', async({page, server}) => { + let divHidden = false; + await page.setContent(`
`); + const waitForXPath = page.waitForXPath('//div', {hidden: true}).then(() => divHidden = true); + await page.waitForXPath('//div'); // do a round trip + expect(divHidden).toBe(false); + await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none')); + expect(await waitForXPath).toBe(true); + expect(divHidden).toBe(true); + }); + it('should return the element handle', async({page, server}) => { + const waitForXPath = page.waitForXPath('//*[@class="zombo"]'); + await page.setContent(`
anything
`); + expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('anything'); + }); + it('should allow you to select a text node', async({page, server}) => { + await page.setContent(`
some text
`); + const text = await page.waitForXPath('//div/text()'); + expect(await (await text.getProperty('nodeType')).jsonValue()).toBe(3 /* Node.TEXT_NODE */); + }); + it('should allow you to select an element with single slash', async({page, server}) => { + await page.setContent(`
some text
`); + const waitForXPath = page.waitForXPath('/html/body/div'); + expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('some text'); + }); + }); + +};