From 2e65b0afffe465e68e26f7e5775d546386245908 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 4 Aug 2020 15:09:24 -0700 Subject: [PATCH] test: remove describes (4) (#3286) --- test/jshandle-evaluate.spec.js | 34 + test/page-add-script-tag.spec.js | 112 +++ test/page-add-style-tag.spec.js | 85 ++ test/page-basic.spec.js | 248 +++++ test/page-evaluate.spec.js | 67 ++ test/page-event-console.spec.js | 139 +++ test/page-event-crash.spec.js | 72 ++ test/page-event-pageerror.spec.js | 75 ++ test/page-event-popup.spec.js | 12 + test/page-expose-function.spec.js | 174 ++++ test/page-fill.spec.js | 240 +++++ test/page-select-option.spec.js | 217 +++++ test/page-set-content.spec.js | 105 +++ test/page-wait-for-request.spec.js | 98 ++ test/page-wait-for-response.spec.js | 73 ++ test/page.jest.js | 1359 --------------------------- 16 files changed, 1751 insertions(+), 1359 deletions(-) create mode 100644 test/jshandle-evaluate.spec.js create mode 100644 test/page-add-script-tag.spec.js create mode 100644 test/page-add-style-tag.spec.js create mode 100644 test/page-basic.spec.js create mode 100644 test/page-event-console.spec.js create mode 100644 test/page-event-crash.spec.js create mode 100644 test/page-event-pageerror.spec.js create mode 100644 test/page-expose-function.spec.js create mode 100644 test/page-fill.spec.js create mode 100644 test/page-select-option.spec.js create mode 100644 test/page-set-content.spec.js create mode 100644 test/page-wait-for-request.spec.js create mode 100644 test/page-wait-for-response.spec.js delete mode 100644 test/page.jest.js diff --git a/test/jshandle-evaluate.spec.js b/test/jshandle-evaluate.spec.js new file mode 100644 index 0000000000..7c060ec78c --- /dev/null +++ b/test/jshandle-evaluate.spec.js @@ -0,0 +1,34 @@ +/** + * Copyright 2018 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * 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 {FFOX, CHROMIUM, WEBKIT} = testOptions; + +it('should work with function', async({page, server}) => { + const windowHandle = await page.evaluateHandle(() => { + window.foo = [1, 2]; + return window; + }); + expect(await windowHandle.evaluate(w => w.foo)).toEqual([1, 2]); +}); + +it('should work with expression', async({page, server}) => { + const windowHandle = await page.evaluateHandle(() => { + window.foo = [1, 2]; + return window; + }); + expect(await windowHandle.evaluate('window.foo')).toEqual([1, 2]); +}); diff --git a/test/page-add-script-tag.spec.js b/test/page-add-script-tag.spec.js new file mode 100644 index 0000000000..e19311d106 --- /dev/null +++ b/test/page-add-script-tag.spec.js @@ -0,0 +1,112 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * 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 path = require('path'); +const util = require('util'); +const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions; + +it('should throw an error if no options are provided', async({page, server}) => { + let error = null; + try { + await page.addScriptTag('/injectedfile.js'); + } catch (e) { + error = e; + } + expect(error.message).toContain('Provide an object with a `url`, `path` or `content` property'); +}); + +it('should work with a url', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const scriptHandle = await page.addScriptTag({ url: '/injectedfile.js' }); + expect(scriptHandle.asElement()).not.toBeNull(); + expect(await page.evaluate(() => __injected)).toBe(42); +}); + +it('should work with a url and type=module', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.addScriptTag({ url: '/es6/es6import.js', type: 'module' }); + expect(await page.evaluate(() => __es6injected)).toBe(42); +}); + +it('should work with a path and type=module', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.addScriptTag({ path: path.join(__dirname, 'assets/es6/es6pathimport.js'), type: 'module' }); + await page.waitForFunction('window.__es6injected'); + expect(await page.evaluate(() => __es6injected)).toBe(42); +}); + +it('should work with a content and type=module', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.addScriptTag({ content: `import num from '/es6/es6module.js';window.__es6injected = num;`, type: 'module' }); + await page.waitForFunction('window.__es6injected'); + expect(await page.evaluate(() => __es6injected)).toBe(42); +}); + +it('should throw an error if loading from url fail', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + let error = null; + try { + await page.addScriptTag({ url: '/nonexistfile.js' }); + } catch (e) { + error = e; + } + expect(error).not.toBe(null); +}); + +it('should work with a path', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const scriptHandle = await page.addScriptTag({ path: path.join(__dirname, 'assets/injectedfile.js') }); + expect(scriptHandle.asElement()).not.toBeNull(); + expect(await page.evaluate(() => __injected)).toBe(42); +}); + +it.skip(WEBKIT)('should include sourceURL when path is provided', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.addScriptTag({ path: path.join(__dirname, 'assets/injectedfile.js') }); + const result = await page.evaluate(() => __injectedError.stack); + expect(result).toContain(path.join('assets', 'injectedfile.js')); +}); + +it('should work with content', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const scriptHandle = await page.addScriptTag({ content: 'window.__injected = 35;' }); + expect(scriptHandle.asElement()).not.toBeNull(); + expect(await page.evaluate(() => __injected)).toBe(35); +}); + +it.fail(FFOX)('should throw when added with content to the CSP page', async({page, server}) => { + // Firefox fires onload for blocked script before it issues the CSP console error. + await page.goto(server.PREFIX + '/csp.html'); + let error = null; + await page.addScriptTag({ content: 'window.__injected = 35;' }).catch(e => error = e); + expect(error).toBeTruthy(); +}); + +it('should throw when added with URL to the CSP page', async({page, server}) => { + await page.goto(server.PREFIX + '/csp.html'); + let error = null; + await page.addScriptTag({ url: server.CROSS_PROCESS_PREFIX + '/injectedfile.js' }).catch(e => error = e); + expect(error).toBeTruthy(); +}); + +it('should throw a nice error when the request fails', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const url = server.PREFIX + '/this_does_not_exist.js'; + const error = await page.addScriptTag({url}).catch(e => e); + expect(error.message).toContain(url); +}); diff --git a/test/page-add-style-tag.spec.js b/test/page-add-style-tag.spec.js new file mode 100644 index 0000000000..df29e2e914 --- /dev/null +++ b/test/page-add-style-tag.spec.js @@ -0,0 +1,85 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * 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 path = require('path'); +const util = require('util'); +const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions; + +it('should throw an error if no options are provided', async({page, server}) => { + let error = null; + try { + await page.addStyleTag('/injectedstyle.css'); + } catch (e) { + error = e; + } + expect(error.message).toContain('Provide an object with a `url`, `path` or `content` property'); +}); + +it('should work with a url', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const styleHandle = await page.addStyleTag({ url: '/injectedstyle.css' }); + expect(styleHandle.asElement()).not.toBeNull(); + expect(await page.evaluate(`window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')`)).toBe('rgb(255, 0, 0)'); +}); + +it('should throw an error if loading from url fail', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + let error = null; + try { + await page.addStyleTag({ url: '/nonexistfile.js' }); + } catch (e) { + error = e; + } + expect(error).not.toBe(null); +}); + +it('should work with a path', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const styleHandle = await page.addStyleTag({ path: path.join(__dirname, 'assets/injectedstyle.css') }); + expect(styleHandle.asElement()).not.toBeNull(); + expect(await page.evaluate(`window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')`)).toBe('rgb(255, 0, 0)'); +}); + +it('should include sourceURL when path is provided', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.addStyleTag({ path: path.join(__dirname, 'assets/injectedstyle.css') }); + const styleHandle = await page.$('style'); + const styleContent = await page.evaluate(style => style.innerHTML, styleHandle); + expect(styleContent).toContain(path.join('assets', 'injectedstyle.css')); +}); + +it('should work with content', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const styleHandle = await page.addStyleTag({ content: 'body { background-color: green; }' }); + expect(styleHandle.asElement()).not.toBeNull(); + expect(await page.evaluate(`window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')`)).toBe('rgb(0, 128, 0)'); +}); + +it('should throw when added with content to the CSP page', async({page, server}) => { + await page.goto(server.PREFIX + '/csp.html'); + let error = null; + await page.addStyleTag({ content: 'body { background-color: green; }' }).catch(e => error = e); + expect(error).toBeTruthy(); +}); + +it('should throw when added with URL to the CSP page', async({page, server}) => { + await page.goto(server.PREFIX + '/csp.html'); + let error = null; + await page.addStyleTag({ url: server.CROSS_PROCESS_PREFIX + '/injectedstyle.css' }).catch(e => error = e); + expect(error).toBeTruthy(); +}); diff --git a/test/page-basic.spec.js b/test/page-basic.spec.js new file mode 100644 index 0000000000..801c9f93be --- /dev/null +++ b/test/page-basic.spec.js @@ -0,0 +1,248 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * 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 path = require('path'); +const util = require('util'); +const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions; + +it('should reject all promises when page is closed', async({context}) => { + const newPage = await context.newPage(); + let error = null; + await Promise.all([ + newPage.evaluate(() => new Promise(r => {})).catch(e => error = e), + newPage.close(), + ]); + expect(error.message).toContain('Protocol error'); +}); + +it('should not be visible in context.pages', async({context}) => { + const newPage = await context.newPage(); + expect(context.pages()).toContain(newPage); + await newPage.close(); + expect(context.pages()).not.toContain(newPage); +}); + +it('should run beforeunload if asked for', async({context, server}) => { + const newPage = await context.newPage(); + await newPage.goto(server.PREFIX + '/beforeunload.html'); + // We have to interact with a page so that 'beforeunload' handlers + // fire. + await newPage.click('body'); + const pageClosingPromise = newPage.close({ runBeforeUnload: true }); + const dialog = await newPage.waitForEvent('dialog'); + expect(dialog.type()).toBe('beforeunload'); + expect(dialog.defaultValue()).toBe(''); + if (CHROMIUM) + expect(dialog.message()).toBe(''); + else if (WEBKIT) + expect(dialog.message()).toBe('Leave?'); + else + expect(dialog.message()).toBe('This page is asking you to confirm that you want to leave - data you have entered may not be saved.'); + await dialog.accept(); + await pageClosingPromise; +}); + +it('should *not* run beforeunload by default', async({context, server}) => { + const newPage = await context.newPage(); + await newPage.goto(server.PREFIX + '/beforeunload.html'); + // We have to interact with a page so that 'beforeunload' handlers + // fire. + await newPage.click('body'); + await newPage.close(); +}); + +it('should set the page close state', async({context}) => { + const newPage = await context.newPage(); + expect(newPage.isClosed()).toBe(false); + await newPage.close(); + expect(newPage.isClosed()).toBe(true); +}); + +it('should terminate network waiters', async({context, server}) => { + const newPage = await context.newPage(); + const results = await Promise.all([ + newPage.waitForRequest(server.EMPTY_PAGE).catch(e => e), + newPage.waitForResponse(server.EMPTY_PAGE).catch(e => e), + newPage.close() + ]); + for (let i = 0; i < 2; i++) { + const message = results[i].message; + expect(message).toContain('Page closed'); + expect(message).not.toContain('Timeout'); + } +}); + +it('should be callable twice', async({context}) => { + const newPage = await context.newPage(); + await Promise.all([ + newPage.close(), + newPage.close(), + ]); + await newPage.close(); +}); + +it('should fire load when expected', async({page, server}) => { + await Promise.all([ + page.goto('about:blank'), + page.waitForEvent('load'), + ]); +}); + +it('async stacks should work', async({page, server}) => { + server.setRoute('/empty.html', (req, res) => { + req.socket.end(); + }); + let error = null; + await page.goto(server.EMPTY_PAGE).catch(e => error = e); + expect(error).not.toBe(null); + expect(error.stack).toContain(__filename); +}); + +it('should provide access to the opener page', async({page}) => { + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('about:blank')), + ]); + const opener = await popup.opener(); + expect(opener).toBe(page); +}); + +it('should return null if parent page has been closed', async({page}) => { + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('about:blank')), + ]); + await page.close(); + const opener = await popup.opener(); + expect(opener).toBe(null); +}); + +it('should fire domcontentloaded when expected', async({page, server}) => { + const navigatedPromise = page.goto('about:blank'); + await page.waitForEvent('domcontentloaded'); + await navigatedPromise; +}); + +it('should fail with error upon disconnect', async({page, server}) => { + let error; + const waitForPromise = page.waitForEvent('download').catch(e => error = e); + await page.close(); + await waitForPromise; + expect(error.message).toContain('Page closed'); +}); + +it('page.ur should work', async({page, server}) => { + expect(page.url()).toBe('about:blank'); + await page.goto(server.EMPTY_PAGE); + expect(page.url()).toBe(server.EMPTY_PAGE); +}); + +it('page.url should include hashes', async({page, server}) => { + await page.goto(server.EMPTY_PAGE + '#hash'); + expect(page.url()).toBe(server.EMPTY_PAGE + '#hash'); + await page.evaluate(() => { + window.location.hash = "dynamic"; + }); + expect(page.url()).toBe(server.EMPTY_PAGE + '#dynamic'); +}); + +it('page.title should return the page title', async({page, server}) => { + await page.goto(server.PREFIX + '/title.html'); + expect(await page.title()).toBe('Woof-Woof'); +}); + +it('page.close should work with window.close', async function({ page, context, server }) { + const newPagePromise = page.waitForEvent('popup'); + await page.evaluate(() => window['newPage'] = window.open('about:blank')); + const newPage = await newPagePromise; + const closedPromise = new Promise(x => newPage.on('close', x)); + await page.evaluate(() => window['newPage'].close()); + await closedPromise; +}); + +it('page.close should work with page.close', async function({ page, context, server }) { + const newPage = await context.newPage(); + const closedPromise = new Promise(x => newPage.on('close', x)); + await newPage.close(); + await closedPromise; +}); + +it('page.context should return the correct instance', async function({page, context}) { + expect(page.context()).toBe(context); +}); + +it('page.frame should respect name', async function({page, server}) { + await page.setContent(``); + expect(page.frame({ name: 'bogus' })).toBe(null); + const frame = page.frame({ name: 'target' }); + expect(frame).toBeTruthy(); + expect(frame === page.mainFrame().childFrames()[0]).toBeTruthy(); +}); + +it('page.frame should respect url', async function({page, server}) { + await page.setContent(``); + expect(page.frame({ url: /bogus/ })).toBe(null); + expect(page.frame({ url: /empty/ }).url()).toBe(server.EMPTY_PAGE); +}); + +it('should have sane user agent', async ({page}) => { + const userAgent = await page.evaluate(() => navigator.userAgent); + const [ + part1, + part2, + part3, + part4, + part5, + ] = userAgent.split(/[()]/).map(part => part.trim()); + // First part is always "Mozilla/5.0" + expect(part1).toBe('Mozilla/5.0'); + // Second part in parenthesis is platform - ignore it. + + // Third part for Firefox is the last one and encodes engine and browser versions. + if (FFOX) { + const [engine, browser] = part3.split(' '); + expect(engine.startsWith('Gecko')).toBe(true); + expect(browser.startsWith('Firefox')).toBe(true); + expect(part4).toBe(undefined); + expect(part5).toBe(undefined); + return; + } + // For both CHROMIUM and WEBKIT, third part is the AppleWebKit version. + expect(part3.startsWith('AppleWebKit/')).toBe(true); + expect(part4).toBe('KHTML, like Gecko'); + // 5th part encodes real browser name and engine version. + const [engine, browser] = part5.split(' '); + expect(browser.startsWith('Safari/')).toBe(true); + if (CHROMIUM) + expect(engine.includes('Chrome/')).toBe(true); + else + expect(engine.startsWith('Version/')).toBe(true); +}); + +it('page.press should work', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.press('textarea', 'a'); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('a'); +}); + +it('frame.press should work', async({page, server}) => { + await page.setContent(``); + const frame = page.frame('inner'); + await frame.press('textarea', 'a'); + expect(await frame.evaluate(() => document.querySelector('textarea').value)).toBe('a'); +}); diff --git a/test/page-evaluate.spec.js b/test/page-evaluate.spec.js index 17cd24813a..d23411e041 100644 --- a/test/page-evaluate.spec.js +++ b/test/page-evaluate.spec.js @@ -23,22 +23,27 @@ it('should work', async ({ page, server }) => { const result = await page.evaluate(() => 7 * 3); expect(result).toBe(21); }); + it('should transfer NaN', async ({ page, server }) => { const result = await page.evaluate(a => a, NaN); expect(Object.is(result, NaN)).toBe(true); }); + it('should transfer -0', async ({ page, server }) => { const result = await page.evaluate(a => a, -0); expect(Object.is(result, -0)).toBe(true); }); + it('should transfer Infinity', async ({ page, server }) => { const result = await page.evaluate(a => a, Infinity); expect(Object.is(result, Infinity)).toBe(true); }); + it('should transfer -Infinity', async ({ page, server }) => { const result = await page.evaluate(a => a, -Infinity); expect(Object.is(result, -Infinity)).toBe(true); }); + it('should roundtrip unserializable values', async ({ page }) => { const value = { infinity: Infinity, @@ -49,6 +54,7 @@ it('should roundtrip unserializable values', async ({ page }) => { const result = await page.evaluate(value => value, value); expect(result).toEqual(value); }); + it('should roundtrip promise to value', async ({ page }) => { { const result = await page.evaluate(value => Promise.resolve(value), null); @@ -67,6 +73,7 @@ it('should roundtrip promise to value', async ({ page }) => { expect(result === undefined).toBeTruthy(); } }); + it('should roundtrip promise to unserializable values', async ({ page }) => { const value = { infinity: Infinity, @@ -77,26 +84,32 @@ it('should roundtrip promise to unserializable values', async ({ page }) => { const result = await page.evaluate(value => Promise.resolve(value), value); expect(result).toEqual(value); }); + it('should transfer arrays', async ({ page, server }) => { const result = await page.evaluate(a => a, [1, 2, 3]); expect(result).toEqual([1, 2, 3]); }); + it('should transfer arrays as arrays, not objects', async ({ page, server }) => { const result = await page.evaluate(a => Array.isArray(a), [1, 2, 3]); expect(result).toBe(true); }); + it('should transfer maps as empty objects', async ({ page, server }) => { const result = await page.evaluate(a => a.x.constructor.name + ' ' + JSON.stringify(a.x), { x: new Map([[1, 2]]) }); expect(result).toBe('Object {}'); }); + it('should modify global environment', async ({ page }) => { await page.evaluate(() => window.globalVar = 123); expect(await page.evaluate('globalVar')).toBe(123); }); + it('should evaluate in the page context', async ({ page, server }) => { await page.goto(server.PREFIX + '/global-var.html'); expect(await page.evaluate('globalVar')).toBe(123); }); + it('should return undefined for objects with symbols', async ({ page, server }) => { expect(await page.evaluate(() => [Symbol('foo4')])).toEqual([undefined]); expect(await page.evaluate(() => { @@ -108,6 +121,7 @@ it('should return undefined for objects with symbols', async ({ page, server }) return { foo: [{ a: Symbol('foo4') }] }; })).toEqual({ foo: [{ a: undefined }] }); }); + it('should work with function shorthands', async ({ page, server }) => { const a = { sum([a, b]) { return a + b; }, @@ -116,10 +130,12 @@ it('should work with function shorthands', async ({ page, server }) => { expect(await page.evaluate(a.sum, [1, 2])).toBe(3); expect(await page.evaluate(a.mult, [2, 4])).toBe(8); }); + it('should work with unicode chars', async ({ page, server }) => { const result = await page.evaluate(a => a['中文字符'], { '中文字符': 42 }); expect(result).toBe(42); }); + it('should throw when evaluation triggers reload', async ({ page, server }) => { let error = null; await page.evaluate(() => { @@ -128,10 +144,12 @@ it('should throw when evaluation triggers reload', async ({ page, server }) => { }).catch(e => error = e); expect(error.message).toContain('navigation'); }); + it('should await promise', async ({ page, server }) => { const result = await page.evaluate(() => Promise.resolve(8 * 7)); expect(result).toBe(56); }); + it('should work right after framenavigated', async ({ page, server }) => { let frameEvaluation = null; page.on('framenavigated', async frame => { @@ -140,6 +158,7 @@ it('should work right after framenavigated', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); expect(await frameEvaluation).toBe(42); }); + it('should work right after a cross-origin navigation', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); let frameEvaluation = null; @@ -149,6 +168,7 @@ it('should work right after a cross-origin navigation', async ({ page, server }) await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); expect(await frameEvaluation).toBe(42); }); + it('should work from-inside an exposed function', async ({ page, server }) => { // Setup inpage callback, which calls Page.evaluate await page.exposeFunction('callController', async function (a, b) { @@ -159,46 +179,55 @@ it('should work from-inside an exposed function', async ({ page, server }) => { }); expect(result).toBe(27); }); + it('should reject promise with exception', async ({ page, server }) => { let error = null; await page.evaluate(() => not_existing_object.property).catch(e => error = e); expect(error).toBeTruthy(); expect(error.message).toContain('not_existing_object'); }); + it('should support thrown strings as error messages', async ({ page, server }) => { let error = null; await page.evaluate(() => { throw 'qwerty'; }).catch(e => error = e); expect(error).toBeTruthy(); expect(error.message).toContain('qwerty'); }); + it('should support thrown numbers as error messages', async ({ page, server }) => { let error = null; await page.evaluate(() => { throw 100500; }).catch(e => error = e); expect(error).toBeTruthy(); expect(error.message).toContain('100500'); }); + it('should return complex objects', async ({ page, server }) => { const object = { foo: 'bar!' }; const result = await page.evaluate(a => a, object); expect(result).not.toBe(object); expect(result).toEqual(object); }); + it('should return NaN', async ({ page, server }) => { const result = await page.evaluate(() => NaN); expect(Object.is(result, NaN)).toBe(true); }); + it('should return -0', async ({ page, server }) => { const result = await page.evaluate(() => -0); expect(Object.is(result, -0)).toBe(true); }); + it('should return Infinity', async ({ page, server }) => { const result = await page.evaluate(() => Infinity); expect(Object.is(result, Infinity)).toBe(true); }); + it('should return -Infinity', async ({ page, server }) => { const result = await page.evaluate(() => -Infinity); expect(Object.is(result, -Infinity)).toBe(true); }); + it('should work with overwritten Promise', async ({ page, server }) => { await page.evaluate(() => { const originalPromise = window.Promise; @@ -243,6 +272,7 @@ it('should work with overwritten Promise', async ({ page, server }) => { // Now, the new promise should be awaitable. expect(await page.evaluate(() => Promise.resolve(42))).toBe(42); }); + it('should throw when passed more than one parameter', async ({ page, server }) => { const expectThrow = async f => { let error; @@ -261,29 +291,37 @@ it('should throw when passed more than one parameter', async ({ page, server }) await expectThrow(() => frame.$$eval('sel', (a, b) => false, 1, 2)); await expectThrow(() => frame.evaluate((a, b) => false, 1, 2)); }); + it('should accept "undefined" as one of multiple parameters', async ({ page, server }) => { const result = await page.evaluate(({ a, b }) => Object.is(a, undefined) && Object.is(b, 'foo'), { a: undefined, b: 'foo' }); expect(result).toBe(true); }); + it('should properly serialize undefined arguments', async ({ page }) => { expect(await page.evaluate(x => ({ a: x }), undefined)).toEqual({}); }); + it('should properly serialize undefined fields', async ({ page }) => { expect(await page.evaluate(() => ({ a: undefined }))).toEqual({}); }); + it('should return undefined properties', async ({ page }) => { const value = await page.evaluate(() => ({ a: undefined })); expect('a' in value).toBe(true); }); + it('should properly serialize null arguments', async ({ page }) => { expect(await page.evaluate(x => x, null)).toEqual(null); }); + it('should properly serialize null fields', async ({ page }) => { expect(await page.evaluate(() => ({ a: null }))).toEqual({ a: null }); }); + it('should return undefined for non-serializable objects', async ({ page, server }) => { expect(await page.evaluate(() => window)).toBe(undefined); }); + it('should fail for circular object', async ({ page, server }) => { const result = await page.evaluate(() => { const a = {}; @@ -293,6 +331,7 @@ it('should fail for circular object', async ({ page, server }) => { }); expect(result).toBe(undefined); }); + it('should be able to throw a tricky error', async ({ page, server }) => { const windowHandle = await page.evaluateHandle(() => window); const errorText = await windowHandle.jsonValue().catch(e => e.message); @@ -301,24 +340,29 @@ it('should be able to throw a tricky error', async ({ page, server }) => { }, errorText).catch(e => e); expect(error.message).toContain(errorText); }); + it('should accept a string', async ({ page, server }) => { const result = await page.evaluate('1 + 2'); expect(result).toBe(3); }); + it('should accept a string with semi colons', async ({ page, server }) => { const result = await page.evaluate('1 + 5;'); expect(result).toBe(6); }); + it('should accept a string with comments', async ({ page, server }) => { const result = await page.evaluate('2 + 5;\n// do some math!'); expect(result).toBe(7); }); + it('should accept element handle as an argument', async ({ page, server }) => { await page.setContent('
42
'); const element = await page.$('section'); const text = await page.evaluate(e => e.textContent, element); expect(text).toBe('42'); }); + it('should throw if underlying element was disposed', async ({ page, server }) => { await page.setContent('
39
'); const element = await page.$('section'); @@ -328,6 +372,7 @@ it('should throw if underlying element was disposed', async ({ page, server }) = await page.evaluate(e => e.textContent, element).catch(e => error = e); expect(error.message).toContain('JSHandle is disposed'); }); + it('should simulate a user gesture', async ({ page, server }) => { const result = await page.evaluate(() => { document.body.appendChild(document.createTextNode('test')); @@ -336,6 +381,7 @@ it('should simulate a user gesture', async ({ page, server }) => { }); expect(result).toBe(true); }); + it('should throw a nice error after a navigation', async ({ page, server }) => { const errorPromise = page.evaluate(() => new Promise(f => window.__resolve = f)).catch(e => e); await Promise.all([ @@ -348,6 +394,7 @@ it('should throw a nice error after a navigation', async ({ page, server }) => { const error = await errorPromise; expect(error.message).toContain('navigation'); }); + it('should not throw an error when evaluation does a navigation', async ({ page, server }) => { await page.goto(server.PREFIX + '/one-style.html'); const result = await page.evaluate(() => { @@ -356,6 +403,7 @@ it('should not throw an error when evaluation does a navigation', async ({ page, }); expect(result).toEqual([42]); }); + it.fail(WEBKIT)('should not throw an error when evaluation does a synchronous navigation and returns an object', async ({ page, server }) => { // It is imporant to be on about:blank for sync reload. const result = await page.evaluate(() => { @@ -364,6 +412,7 @@ it.fail(WEBKIT)('should not throw an error when evaluation does a synchronous na }); expect(result).toEqual({ a: 42 }); }); + it('should not throw an error when evaluation does a synchronous navigation and returns undefined', async ({ page, server }) => { // It is imporant to be on about:blank for sync reload. const result = await page.evaluate(() => { @@ -372,11 +421,13 @@ it('should not throw an error when evaluation does a synchronous navigation and }); expect(result).toBe(undefined); }); + it.fail(USES_HOOKS)('should transfer 100Mb of data from page to node.js', async ({ page }) => { // This does not use hooks, but is slow in wire channel. const a = await page.evaluate(() => Array(100 * 1024 * 1024 + 1).join('a')); expect(a.length).toBe(100 * 1024 * 1024); }); + it('should throw error with detailed information on exception inside promise ', async ({ page, server }) => { let error = null; await page.evaluate(() => new Promise(() => { @@ -384,11 +435,13 @@ it('should throw error with detailed information on exception inside promise ', })).catch(e => error = e); expect(error.message).toContain('Error in promise'); }); + it('should work even when JSON is set to null', async ({ page }) => { await page.evaluate(() => { window.JSON.stringify = null; window.JSON = null; }); const result = await page.evaluate(() => ({ abc: 123 })); expect(result).toEqual({ abc: 123 }); }); + it.fail(FFOX)('should await promise from popup', async ({ page, server }) => { // Something is wrong about the way Firefox waits for the chained promise await page.goto(server.EMPTY_PAGE); @@ -398,17 +451,20 @@ it.fail(FFOX)('should await promise from popup', async ({ page, server }) => { }); expect(result).toBe(42); }); + it('should work with new Function() and CSP', async ({ page, server }) => { server.setCSP('/empty.html', 'script-src ' + server.PREFIX); await page.goto(server.PREFIX + '/empty.html'); expect(await page.evaluate(() => new Function('return true')())).toBe(true); }); + it('should work with non-strict expressions', async ({ page, server }) => { expect(await page.evaluate(() => { y = 3.14; return y; })).toBe(3.14); }); + it('should respect use strict expression', async ({ page, server }) => { const error = await page.evaluate(() => { "use strict"; @@ -417,18 +473,22 @@ it('should respect use strict expression', async ({ page, server }) => { }).catch(e => e); expect(error.message).toContain('variableY'); }); + it('should not leak utility script', async ({ page, server }) => { expect(await page.evaluate(() => this === window)).toBe(true); }); + it('should not leak handles', async ({ page, server }) => { const error = await page.evaluate(() => handles.length).catch(e => e); expect(error.message).toContain(' handles'); }); + it('should work with CSP', async ({ page, server }) => { server.setCSP('/empty.html', `script-src 'self'`); await page.goto(server.EMPTY_PAGE); expect(await page.evaluate(() => 2 + 2)).toBe(4); }); + it('should evaluate exception', async ({ page, server }) => { const error = await page.evaluate(() => { return (function functionOnStack() { @@ -438,32 +498,39 @@ it('should evaluate exception', async ({ page, server }) => { expect(error).toContain('Error: error message'); expect(error).toContain('functionOnStack'); }); + it('should evaluate exception', async ({ page, server }) => { const error = await page.evaluate(`new Error('error message')`); expect(error).toContain('Error: error message'); }); + it('should evaluate date', async ({ page }) => { const result = await page.evaluate(() => ({ date: new Date('2020-05-27T01:31:38.506Z') })); expect(result).toEqual({ date: new Date('2020-05-27T01:31:38.506Z') }); }); + it('should roundtrip date', async ({ page }) => { const date = new Date('2020-05-27T01:31:38.506Z'); const result = await page.evaluate(date => date, date); expect(result.toUTCString()).toEqual(date.toUTCString()); }); + it('should roundtrip regex', async ({ page }) => { const regex = /hello/im; const result = await page.evaluate(regex => regex, regex); expect(result.toString()).toEqual(regex.toString()); }); + it('should jsonValue() date', async ({ page }) => { const resultHandle = await page.evaluateHandle(() => ({ date: new Date('2020-05-27T01:31:38.506Z') })); expect(await resultHandle.jsonValue()).toEqual({ date: new Date('2020-05-27T01:31:38.506Z') }); }); + it('should not use toJSON when evaluating', async ({ page, server }) => { const result = await page.evaluate(() => ({ toJSON: () => 'string', data: 'data' })); expect(result).toEqual({ data: 'data', toJSON: {} }); }); + it('should not use toJSON in jsonValue', async ({ page, server }) => { const resultHandle = await page.evaluateHandle(() => ({ toJSON: () => 'string', data: 'data' })); expect(await resultHandle.jsonValue()).toEqual({ data: 'data', toJSON: {} }); diff --git a/test/page-event-console.spec.js b/test/page-event-console.spec.js new file mode 100644 index 0000000000..1e1fbdaf9a --- /dev/null +++ b/test/page-event-console.spec.js @@ -0,0 +1,139 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * 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 path = require('path'); +const util = require('util'); +const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions; + +it('should work', async({page, server}) => { + let message = null; + page.once('console', m => message = m); + await Promise.all([ + page.evaluate(() => console.log('hello', 5, {foo: 'bar'})), + page.waitForEvent('console') + ]); + expect(message.text()).toEqual('hello 5 JSHandle@object'); + expect(message.type()).toEqual('log'); + expect(await message.args()[0].jsonValue()).toEqual('hello'); + expect(await message.args()[1].jsonValue()).toEqual(5); + expect(await message.args()[2].jsonValue()).toEqual({foo: 'bar'}); +}); + +it('should emit same log twice', async({page, server}) => { + const messages = []; + page.on('console', m => messages.push(m.text())); + await page.evaluate(() => { for (let i = 0; i < 2; ++i ) console.log('hello'); } ); + expect(messages).toEqual(['hello', 'hello']); +}); + +it('should use text() for inspection', async({page}) => { + let text; + const inspect = value => { + text = util.inspect(value); + } + page.on('console', inspect); + await page.evaluate(() => console.log('Hello world')); + expect(text).toEqual('Hello world'); +}); + +it('should work for different console API calls', async({page, server}) => { + const messages = []; + page.on('console', msg => messages.push(msg)); + // All console events will be reported before `page.evaluate` is finished. + await page.evaluate(() => { + // A pair of time/timeEnd generates only one Console API call. + console.time('calling console.time'); + console.timeEnd('calling console.time'); + console.trace('calling console.trace'); + console.dir('calling console.dir'); + console.warn('calling console.warn'); + console.error('calling console.error'); + console.log(Promise.resolve('should not wait until resolved!')); + }); + expect(messages.map(msg => msg.type())).toEqual([ + 'timeEnd', 'trace', 'dir', 'warning', 'error', 'log' + ]); + expect(messages[0].text()).toContain('calling console.time'); + expect(messages.slice(1).map(msg => msg.text())).toEqual([ + 'calling console.trace', + 'calling console.dir', + 'calling console.warn', + 'calling console.error', + 'JSHandle@promise', + ]); +}); + +it('should not fail for window object', async({page, server}) => { + let message = null; + page.once('console', msg => message = msg); + await Promise.all([ + page.evaluate(() => console.error(window)), + page.waitForEvent('console') + ]); + expect(message.text()).toBe('JSHandle@object'); +}); + +it('should trigger correct Log', async({page, server}) => { + await page.goto('about:blank'); + const [message] = await Promise.all([ + page.waitForEvent('console'), + page.evaluate(async url => fetch(url).catch(e => {}), server.EMPTY_PAGE) + ]); + expect(message.text()).toContain('Access-Control-Allow-Origin'); + expect(message.type()).toEqual('error'); +}); + +it.only('should have location for console API calls', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [message] = await Promise.all([ + page.waitForEvent('console', m => m.text() === 'yellow' ), + page.goto(server.PREFIX + '/consolelog.html'), + ]); + expect(message.type()).toBe('log'); + const location = message.location(); + // Engines have different column notion. + delete location.columnNumber; + expect(location).toEqual({ + url: server.PREFIX + '/consolelog.html', + lineNumber: 7, + }); +}); + +it('should not throw when there are console messages in detached iframes', async({page, server}) => { + // @see https://github.com/GoogleChrome/puppeteer/issues/3865 + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(async() => { + // 1. Create a popup that Playwright is not connected to. + const win = window.open(''); + window._popup = win; + if (window.document.readyState !== 'complete') + await new Promise(f => window.addEventListener('load', f)); + // 2. In this popup, create an iframe that console.logs a message. + win.document.body.innerHTML = ``; + const frame = win.document.querySelector('iframe'); + if (!frame.contentDocument || frame.contentDocument.readyState !== 'complete') + await new Promise(f => frame.addEventListener('load', f)); + // 3. After that, remove the iframe. + frame.remove(); + }), + ]); + // 4. Connect to the popup and make sure it doesn't throw. + expect(await popup.evaluate('1 + 1')).toBe(2); +}); diff --git a/test/page-event-crash.spec.js b/test/page-event-crash.spec.js new file mode 100644 index 0000000000..0ce6699779 --- /dev/null +++ b/test/page-event-crash.spec.js @@ -0,0 +1,72 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * 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 path = require('path'); +const util = require('util'); +const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions; + +const CRASH_FAIL = (FFOX && WIN) || USES_HOOKS; +// Firefox Win: it just doesn't crash sometimes. +function crash(pageImpl) { + if (CHROMIUM) + pageImpl.goto('chrome://crash').catch(e => {}); + else if (WEBKIT) + pageImpl._delegate._session.send('Page.crash', {}).catch(e => {}); + else if (FFOX) + pageImpl._delegate._session.send('Page.crash', {}).catch(e => {}); +} + +it.fail(CRASH_FAIL)('should emit crash event when page crashes', async({page, toImpl}) => { + await page.setContent(`
This page should crash
`); + crash(toImpl(page)); + await new Promise(f => page.on('crash', f)); +}); + +it.fail(CRASH_FAIL)('should throw on any action after page crashes', async({page, toImpl}) => { + await page.setContent(`
This page should crash
`); + crash(toImpl(page)); + await page.waitForEvent('crash'); + const err = await page.evaluate(() => {}).then(() => null, e => e); + expect(err).toBeTruthy(); + expect(err.message).toContain('crash'); +}); + +it.fail(CRASH_FAIL)('should cancel waitForEvent when page crashes', async({page, toImpl}) => { + await page.setContent(`
This page should crash
`); + const promise = page.waitForEvent('response').catch(e => e); + crash(toImpl(page)); + const error = await promise; + expect(error.message).toContain('Page crashed'); +}); + +it.fail(CRASH_FAIL)('should cancel navigation when page crashes', async({page, toImpl, server}) => { + await page.setContent(`
This page should crash
`); + server.setRoute('/one-style.css', () => {}); + const promise = page.goto(server.PREFIX + '/one-style.html').catch(e => e); + await page.waitForNavigation({ waitUntil: 'domcontentloaded' }); + crash(toImpl(page)); + const error = await promise; + expect(error.message).toContain('Navigation failed because page crashed'); +}); + +it.fail(CRASH_FAIL)('should be able to close context when page crashes', async({page, toImpl}) => { + await page.setContent(`
This page should crash
`); + crash(toImpl(page)); + await page.waitForEvent('crash'); + await page.context().close(); +}); diff --git a/test/page-event-pageerror.spec.js b/test/page-event-pageerror.spec.js new file mode 100644 index 0000000000..b0694d1270 --- /dev/null +++ b/test/page-event-pageerror.spec.js @@ -0,0 +1,75 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * 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 path = require('path'); +const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions; + +it('should fire', async({page, server}) => { + const [error] = await Promise.all([ + page.waitForEvent('pageerror'), + page.goto(server.PREFIX + '/error.html'), + ]); + expect(error.name).toBe('Error'); + expect(error.message).toBe('Fancy error!'); + let stack = await page.evaluate(() => window.e.stack); + // Note that WebKit reports the stack of the 'throw' statement instead of the Error constructor call. + if (WEBKIT) + stack = stack.replace('14:25', '15:19'); + expect(error.stack).toBe(stack); +}); + +it.fail(WEBKIT)('should contain sourceURL', async({page, server}) => { + const [error] = await Promise.all([ + page.waitForEvent('pageerror'), + page.goto(server.PREFIX + '/error.html'), + ]); + expect(error.stack).toContain('myscript.js'); +}); + +it('should handle odd values', async ({page}) => { + const cases = [ + [null, 'null'], + [undefined, 'undefined'], + [0, '0'], + ['', ''], + ]; + for (const [value, message] of cases) { + const [error] = await Promise.all([ + page.waitForEvent('pageerror'), + page.evaluate(value => setTimeout(() => { throw value; }, 0), value), + ]); + expect(error.message).toBe(FFOX ? 'uncaught exception: ' + message : message); + } +}); + +it.fail(FFOX)('should handle object', async ({page}) => { + // Firefox just does not report this error. + const [error] = await Promise.all([ + page.waitForEvent('pageerror'), + page.evaluate(() => setTimeout(() => { throw {}; }, 0)), + ]); + expect(error.message).toBe(CHROMIUM ? 'Object' : '[object Object]'); +}); + +it.fail(FFOX)('should handle window', async ({page}) => { + // Firefox just does not report this error. + const [error] = await Promise.all([ + page.waitForEvent('pageerror'), + page.evaluate(() => setTimeout(() => { throw window; }, 0)), + ]); + expect(error.message).toBe(CHROMIUM ? 'Window' : '[object Window]'); +}); diff --git a/test/page-event-popup.spec.js b/test/page-event-popup.spec.js index b58377865c..e5de6d81d1 100644 --- a/test/page-event-popup.spec.js +++ b/test/page-event-popup.spec.js @@ -27,6 +27,7 @@ it('should work', async({browser}) => { expect(await popup.evaluate(() => !!window.opener)).toBe(true); await context.close(); }); + it('should work with window features', async({browser, server}) => { const context = await browser.newContext(); const page = await context.newPage(); @@ -39,6 +40,7 @@ it('should work with window features', async({browser, server}) => { expect(await popup.evaluate(() => !!window.opener)).toBe(true); await context.close(); }); + it('should emit for immediately closed popups', async({browser}) => { const context = await browser.newContext(); const page = await context.newPage(); @@ -52,6 +54,7 @@ it('should emit for immediately closed popups', async({browser}) => { expect(popup).toBeTruthy(); await context.close(); }); + it('should emit for immediately closed popups 2', async({browser, server}) => { const context = await browser.newContext(); const page = await context.newPage(); @@ -66,6 +69,7 @@ it('should emit for immediately closed popups 2', async({browser, server}) => { expect(popup).toBeTruthy(); await context.close(); }); + it('should be able to capture alert', async({browser}) => { const context = await browser.newContext(); const page = await context.newPage(); @@ -80,6 +84,7 @@ it('should be able to capture alert', async({browser}) => { await evaluatePromise; await context.close(); }); + it('should work with empty url', async({browser}) => { const context = await browser.newContext(); const page = await context.newPage(); @@ -91,6 +96,7 @@ it('should work with empty url', async({browser}) => { expect(await popup.evaluate(() => !!window.opener)).toBe(true); await context.close(); }); + it('should work with noopener and no url', async({browser}) => { const context = await browser.newContext(); const page = await context.newPage(); @@ -104,6 +110,7 @@ it('should work with noopener and no url', async({browser}) => { expect(await popup.evaluate(() => !!window.opener)).toBe(false); await context.close(); }); + it('should work with noopener and about:blank', async({browser}) => { const context = await browser.newContext(); const page = await context.newPage(); @@ -115,6 +122,7 @@ it('should work with noopener and about:blank', async({browser}) => { expect(await popup.evaluate(() => !!window.opener)).toBe(false); await context.close(); }); + it('should work with noopener and url', async({browser, server}) => { const context = await browser.newContext(); const page = await context.newPage(); @@ -127,6 +135,7 @@ it('should work with noopener and url', async({browser, server}) => { expect(await popup.evaluate(() => !!window.opener)).toBe(false); await context.close(); }); + it('should work with clicking target=_blank', async({browser, server}) => { const context = await browser.newContext(); const page = await context.newPage(); @@ -140,6 +149,7 @@ it('should work with clicking target=_blank', async({browser, server}) => { expect(await popup.evaluate(() => !!window.opener)).toBe(true); await context.close(); }); + it('should work with fake-clicking target=_blank and rel=noopener', async({browser, server}) => { const context = await browser.newContext(); const page = await context.newPage(); @@ -153,6 +163,7 @@ it('should work with fake-clicking target=_blank and rel=noopener', async({brows expect(await popup.evaluate(() => !!window.opener)).toBe(false); await context.close(); }); + it('should work with clicking target=_blank and rel=noopener', async({browser, server}) => { const context = await browser.newContext(); const page = await context.newPage(); @@ -166,6 +177,7 @@ it('should work with clicking target=_blank and rel=noopener', async({browser, s expect(await popup.evaluate(() => !!window.opener)).toBe(false); await context.close(); }); + it('should not treat navigations as new popups', async({browser, server}) => { const context = await browser.newContext(); const page = await context.newPage(); diff --git a/test/page-expose-function.spec.js b/test/page-expose-function.spec.js new file mode 100644 index 0000000000..258665c46d --- /dev/null +++ b/test/page-expose-function.spec.js @@ -0,0 +1,174 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * 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 path = require('path'); +const util = require('util'); +const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions; + +it('exposeBinding should work', async({browser}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + let bindingSource; + await page.exposeBinding('add', (source, a, b) => { + bindingSource = source; + return a + b; + }); + const result = await page.evaluate(async function() { + return add(5, 6); + }); + expect(bindingSource.context).toBe(context); + expect(bindingSource.page).toBe(page); + expect(bindingSource.frame).toBe(page.mainFrame()); + expect(result).toEqual(11); + await context.close(); +}); + +it('should work', async({page, server}) => { + await page.exposeFunction('compute', function(a, b) { + return a * b; + }); + const result = await page.evaluate(async function() { + return await compute(9, 4); + }); + expect(result).toBe(36); +}); + +it('should work with handles and complex objects', async({page, server}) => { + const fooHandle = await page.evaluateHandle(() => { + window.fooValue = { bar: 2 }; + return window.fooValue; + }); + await page.exposeFunction('handle', () => { + return [{ foo: fooHandle }]; + }); + const equals = await page.evaluate(async function() { + const value = await handle(); + const [{ foo }] = value; + return foo === window.fooValue; + }); + expect(equals).toBe(true); +}); + +it('should throw exception in page context', async({page, server}) => { + await page.exposeFunction('woof', function() { + throw new Error('WOOF WOOF'); + }); + const {message, stack} = await page.evaluate(async() => { + try { + await woof(); + } catch (e) { + return {message: e.message, stack: e.stack}; + } + }); + expect(message).toBe('WOOF WOOF'); + expect(stack).toContain(__filename); +}); + +it('should support throwing "null"', async({page, server}) => { + await page.exposeFunction('woof', function() { + throw null; + }); + const thrown = await page.evaluate(async() => { + try { + await woof(); + } catch (e) { + return e; + } + }); + expect(thrown).toBe(null); +}); + +it('should be callable from-inside addInitScript', async({page, server}) => { + let called = false; + await page.exposeFunction('woof', function() { + called = true; + }); + await page.addInitScript(() => woof()); + await page.reload(); + expect(called).toBe(true); +}); + +it('should survive navigation', async({page, server}) => { + await page.exposeFunction('compute', function(a, b) { + return a * b; + }); + + await page.goto(server.EMPTY_PAGE); + const result = await page.evaluate(async function() { + return await compute(9, 4); + }); + expect(result).toBe(36); +}); + +it('should await returned promise', async({page, server}) => { + await page.exposeFunction('compute', function(a, b) { + return Promise.resolve(a * b); + }); + + const result = await page.evaluate(async function() { + return await compute(3, 5); + }); + expect(result).toBe(15); +}); + +it('should work on frames', async({page, server}) => { + await page.exposeFunction('compute', function(a, b) { + return Promise.resolve(a * b); + }); + + await page.goto(server.PREFIX + '/frames/nested-frames.html'); + const frame = page.frames()[1]; + const result = await frame.evaluate(async function() { + return await compute(3, 5); + }); + expect(result).toBe(15); +}); + +it('should work on frames before navigation', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/nested-frames.html'); + await page.exposeFunction('compute', function(a, b) { + return Promise.resolve(a * b); + }); + + const frame = page.frames()[1]; + const result = await frame.evaluate(async function() { + return await compute(3, 5); + }); + expect(result).toBe(15); +}); + +it('should work after cross origin navigation', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.exposeFunction('compute', function(a, b) { + return a * b; + }); + + await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + const result = await page.evaluate(async function() { + return await compute(9, 4); + }); + expect(result).toBe(36); +}); + +it('should work with complex objects', async({page, server}) => { + await page.exposeFunction('complexObject', function(a, b) { + return {x: a.x + b.x}; + }); + const result = await page.evaluate(async() => complexObject({x: 5}, {x: 2})); + expect(result.x).toBe(7); +}); diff --git a/test/page-fill.spec.js b/test/page-fill.spec.js new file mode 100644 index 0000000000..5158bd4863 --- /dev/null +++ b/test/page-fill.spec.js @@ -0,0 +1,240 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * 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 path = require('path'); +const util = require('util'); +const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions; + +async function giveItAChanceToFill(page) { + for (let i = 0; i < 5; i++) + await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)))); +} + +it('should fill textarea', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.fill('textarea', 'some value'); + expect(await page.evaluate(() => result)).toBe('some value'); +}); + +it('should fill input', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.fill('input', 'some value'); + expect(await page.evaluate(() => result)).toBe('some value'); +}); + +it('should throw on unsupported inputs', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + for (const type of ['color', 'file']) { + await page.$eval('input', (input, type) => input.setAttribute('type', type), type); + let error = null; + await page.fill('input', '').catch(e => error = e); + expect(error.message).toContain(`input of type "${type}" cannot be filled`); + } +}); + +it('should fill different input types', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + for (const type of ['password', 'search', 'tel', 'text', 'url']) { + await page.$eval('input', (input, type) => input.setAttribute('type', type), type); + await page.fill('input', 'text ' + type); + expect(await page.evaluate(() => result)).toBe('text ' + type); + } +}); + +it('should fill date input after clicking', async({page, server}) => { + await page.setContent(''); + await page.click('input'); + await page.fill('input', '2020-03-02'); + expect(await page.$eval('input', input => input.value)).toBe('2020-03-02'); +}); + +it.skip(WEBKIT)('should throw on incorrect date', async({page, server}) => { + await page.setContent(''); + const error = await page.fill('input', '2020-13-05').catch(e => e); + expect(error.message).toContain('Malformed value'); +}); + +it('should fill time input', async({page, server}) => { + await page.setContent(''); + await page.fill('input', '13:15'); + expect(await page.$eval('input', input => input.value)).toBe('13:15'); +}); + +it.skip(WEBKIT)('should throw on incorrect time', async({page, server}) => { + await page.setContent(''); + const error = await page.fill('input', '25:05').catch(e => e); + expect(error.message).toContain('Malformed value'); +}); + +it('should fill datetime-local input', async({page, server}) => { + await page.setContent(''); + await page.fill('input', '2020-03-02T05:15'); + expect(await page.$eval('input', input => input.value)).toBe('2020-03-02T05:15'); +}); + +it.skip(WEBKIT || FFOX)('should throw on incorrect datetime-local', async({page, server}) => { + await page.setContent(''); + const error = await page.fill('input', 'abc').catch(e => e); + expect(error.message).toContain('Malformed value'); +}); + +it('should fill contenteditable', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.fill('div[contenteditable]', 'some value'); + expect(await page.$eval('div[contenteditable]', div => div.textContent)).toBe('some value'); +}); + +it('should fill elements with existing value and selection', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + + await page.$eval('input', input => input.value = 'value one'); + await page.fill('input', 'another value'); + expect(await page.evaluate(() => result)).toBe('another value'); + + await page.$eval('input', input => { + input.selectionStart = 1; + input.selectionEnd = 2; + }); + await page.fill('input', 'maybe this one'); + expect(await page.evaluate(() => result)).toBe('maybe this one'); + + await page.$eval('div[contenteditable]', div => { + div.innerHTML = 'some text some more text and even more text'; + const range = document.createRange(); + range.selectNodeContents(div.querySelector('span')); + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + }); + await page.fill('div[contenteditable]', 'replace with this'); + expect(await page.$eval('div[contenteditable]', div => div.textContent)).toBe('replace with this'); +}); + +it('should throw when element is not an ,