diff --git a/jest.config.js b/jest.config.js index 03ab34ab5e..2ecc2fe52d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,7 +3,7 @@ module.exports = /** @type {import('@jest/types').Config.InitialOptions} */ ({ maxWorkers: Math.ceil(require('os').cpus().length / 2), rootDir: './test', testEnvironment: './jest', - testMatch: ['**/?(*.)jest.[jt]s'], + testMatch: ['**/?(*.)(jest|spec).[jt]s'], testRunner: 'jest-circus/runner', testTimeout: 10000, globalSetup: './jest/setup.js', diff --git a/test/__snapshots__/coverage.jest.js.snap b/test/__snapshots__/chromium-css-coverage.spec.js.snap similarity index 93% rename from test/__snapshots__/coverage.jest.js.snap rename to test/__snapshots__/chromium-css-coverage.spec.js.snap index 5fd3ae9f8b..ddf209d250 100644 --- a/test/__snapshots__/coverage.jest.js.snap +++ b/test/__snapshots__/chromium-css-coverage.spec.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CSSCoverage should work with complicated usecases 1`] = ` +exports[`should work with complicated usecases 1`] = ` "[ { \\"url\\": \\"http://localhost:/csscoverage/involved.html\\", diff --git a/test/autowaiting-basic.spec.js b/test/autowaiting-basic.spec.js new file mode 100644 index 0000000000..9e38cf1d8f --- /dev/null +++ b/test/autowaiting-basic.spec.js @@ -0,0 +1,211 @@ +/** + * 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, USES_HOOKS} = testOptions; + +it('should await navigation when clicking anchor', async({page, server}) => { + const messages = []; + server.setRoute('/empty.html', async (req, res) => { + messages.push('route'); + res.setHeader('Content-Type', 'text/html'); + res.end(``); + }); + + await page.setContent(`empty.html`); + + await Promise.all([ + page.click('a').then(() => messages.push('click')), + page.waitForEvent('framenavigated').then(() => messages.push('navigated')), + ]); + expect(messages.join('|')).toBe('route|navigated|click'); +}); + +it('should await cross-process navigation when clicking anchor', async({page, server}) => { + const messages = []; + server.setRoute('/empty.html', async (req, res) => { + messages.push('route'); + res.setHeader('Content-Type', 'text/html'); + res.end(``); + }); + + await page.setContent(`empty.html`); + + await Promise.all([ + page.click('a').then(() => messages.push('click')), + page.waitForEvent('framenavigated').then(() => messages.push('navigated')), + ]); + expect(messages.join('|')).toBe('route|navigated|click'); +}); + +it('should await form-get on click', async({page, server}) => { + const messages = []; + server.setRoute('/empty.html?foo=bar', async (req, res) => { + messages.push('route'); + res.setHeader('Content-Type', 'text/html'); + res.end(``); + }); + + await page.setContent(` +
+ + +
`); + + await Promise.all([ + page.click('input[type=submit]').then(() => messages.push('click')), + page.waitForEvent('framenavigated').then(() => messages.push('navigated')), + ]); + expect(messages.join('|')).toBe('route|navigated|click'); +}); + +it('should await form-post on click', async({page, server}) => { + const messages = []; + server.setRoute('/empty.html', async (req, res) => { + messages.push('route'); + res.setHeader('Content-Type', 'text/html'); + res.end(``); + }); + + await page.setContent(` +
+ + +
`); + + await Promise.all([ + page.click('input[type=submit]').then(() => messages.push('click')), + page.waitForEvent('framenavigated').then(() => messages.push('navigated')), + ]); + expect(messages.join('|')).toBe('route|navigated|click'); +}); + +it('should await navigation when assigning location', async({page, server}) => { + const messages = []; + server.setRoute('/empty.html', async (req, res) => { + messages.push('route'); + res.setHeader('Content-Type', 'text/html'); + res.end(``); + }); + await Promise.all([ + page.evaluate(`window.location.href = "${server.EMPTY_PAGE}"`).then(() => messages.push('evaluate')), + page.waitForEvent('framenavigated').then(() => messages.push('navigated')), + ]); + expect(messages.join('|')).toBe('route|navigated|evaluate'); +}); + +it('should await navigation when assigning location twice', async({page, server}) => { + const messages = []; + server.setRoute('/empty.html?cancel', async (req, res) => { res.end('done'); }); + server.setRoute('/empty.html?override', async (req, res) => { messages.push('routeoverride'); res.end('done'); }); + await page.evaluate(` + window.location.href = "${server.EMPTY_PAGE}?cancel"; + window.location.href = "${server.EMPTY_PAGE}?override"; + `); + messages.push('evaluate'); + expect(messages.join('|')).toBe('routeoverride|evaluate'); +}); + +it('should await navigation when evaluating reload', async({page, server}) => { + const messages = []; + await page.goto(server.EMPTY_PAGE); + server.setRoute('/empty.html', async (req, res) => { + messages.push('route'); + res.setHeader('Content-Type', 'text/html'); + res.end(``); + }); + + await Promise.all([ + page.evaluate(`window.location.reload()`).then(() => messages.push('evaluate')), + page.waitForEvent('framenavigated').then(() => messages.push('navigated')), + ]); + expect(messages.join('|')).toBe('route|navigated|evaluate'); +}); + +it('should await navigating specified target', async({page, server}) => { + const messages = []; + server.setRoute('/empty.html', async (req, res) => { + messages.push('route'); + res.setHeader('Content-Type', 'text/html'); + res.end(``); + }); + + await page.setContent(` + empty.html + + `); + const frame = page.frame({ name: 'target' }); + await Promise.all([ + page.click('a').then(() => messages.push('click')), + page.waitForEvent('framenavigated').then(() => messages.push('navigated')), + ]); + expect(frame.url()).toBe(server.EMPTY_PAGE); + expect(messages.join('|')).toBe('route|navigated|click'); +}); + +it('should work with noWaitAfter: true', async({page, server}) => { + server.setRoute('/empty.html', async () => {}); + await page.setContent(`empty.html`); + await page.click('a', { noWaitAfter: true }); +}); + +it('should work with dblclick noWaitAfter: true', async({page, server}) => { + server.setRoute('/empty.html', async () => {}); + await page.setContent(`empty.html`); + await page.dblclick('a', { noWaitAfter: true }); +}); + +it('should work with waitForLoadState(load)', async({page, server}) => { + const messages = []; + server.setRoute('/empty.html', async (req, res) => { + messages.push('route'); + res.setHeader('Content-Type', 'text/html'); + res.end(``); + }); + + await page.setContent(`empty.html`); + await Promise.all([ + page.click('a').then(() => page.waitForLoadState('load')).then(() => messages.push('clickload')), + page.waitForEvent('framenavigated').then(() => page.waitForLoadState('domcontentloaded')).then(() => messages.push('domcontentloaded')), + ]); + expect(messages.join('|')).toBe('route|domcontentloaded|clickload'); +}); + +it('should work with goto following click', async({page, server}) => { + server.setRoute('/login.html', async (req, res) => { + res.setHeader('Content-Type', 'text/html'); + res.end(`You are logged in`); + }); + + await page.setContent(` +
+ + +
`); + + await page.fill('input[type=text]', 'admin'); + await page.click('input[type=submit]'); + await page.goto(server.EMPTY_PAGE); +}); + +it.skip(USES_HOOKS)('should report navigation in the log when clicking anchor', async({page, server}) => { + await page.setContent(`click me`); + const __testHookAfterPointerAction = () => new Promise(f => setTimeout(f, 6000)); + const error = await page.click('a', { timeout: 5000, __testHookAfterPointerAction }).catch(e => e); + expect(error.message).toContain('page.click: Timeout 5000ms exceeded.'); + expect(error.message).toContain('waiting for scheduled navigations to finish'); + expect(error.message).toContain(`navigated to "${server.PREFIX + '/frames/one-frame.html'}"`); +}); diff --git a/test/autowaiting-no-hang.spec.js b/test/autowaiting-no-hang.spec.js new file mode 100644 index 0000000000..d604ee8ee9 --- /dev/null +++ b/test/autowaiting-no-hang.spec.js @@ -0,0 +1,67 @@ +/** + * 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, USES_HOOKS} = testOptions; + +it('clicking on links which do not commit navigation', async({page, server, httpsServer}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(`foobar`); + await page.click('a'); +}); + +it('calling window.stop async', async({page, server, httpsServer}) => { + server.setRoute('/empty.html', async (req, res) => {}); + await page.evaluate((url) => { + window.location.href = url; + setTimeout(() => window.stop(), 100); + }, server.EMPTY_PAGE); +}); + +it('calling window.stop sync', async({page, server, httpsServer}) => { + await page.evaluate((url) => { + window.location.href = url; + window.stop(); + }, server.EMPTY_PAGE); +}); + +it('assigning location to about:blank', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.evaluate(`window.location.href = "about:blank";`); +}); + +it('assigning location to about:blank after non-about:blank', async({page, server}) => { + server.setRoute('/empty.html', async (req, res) => {}); + await page.evaluate(` + window.location.href = "${server.EMPTY_PAGE}"; + window.location.href = "about:blank";`); +}); + +it('calling window.open and window.close', async function({page, server}) { + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => { + const popup = window.open(window.location.href); + popup.close(); + }); +}); + +it('opening a popup', async function({page, server}) { + await page.goto(server.EMPTY_PAGE); + await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window._popup = window.open(window.location.href)), + ]); +}); diff --git a/test/autowaiting.jest.js b/test/autowaiting.jest.js deleted file mode 100644 index 58a3a4a080..0000000000 --- a/test/autowaiting.jest.js +++ /dev/null @@ -1,247 +0,0 @@ -/** - * 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, USES_HOOKS} = testOptions; - -describe('Auto waiting', () => { - it('should await navigation when clicking anchor', async({page, server}) => { - const messages = []; - server.setRoute('/empty.html', async (req, res) => { - messages.push('route'); - res.setHeader('Content-Type', 'text/html'); - res.end(``); - }); - - await page.setContent(`empty.html`); - - await Promise.all([ - page.click('a').then(() => messages.push('click')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|click'); - }); - it('should await cross-process navigation when clicking anchor', async({page, server}) => { - const messages = []; - server.setRoute('/empty.html', async (req, res) => { - messages.push('route'); - res.setHeader('Content-Type', 'text/html'); - res.end(``); - }); - - await page.setContent(`empty.html`); - - await Promise.all([ - page.click('a').then(() => messages.push('click')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|click'); - }); - it('should await form-get on click', async({page, server}) => { - const messages = []; - server.setRoute('/empty.html?foo=bar', async (req, res) => { - messages.push('route'); - res.setHeader('Content-Type', 'text/html'); - res.end(``); - }); - - await page.setContent(` -
- - -
`); - - await Promise.all([ - page.click('input[type=submit]').then(() => messages.push('click')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|click'); - }); - it('should await form-post on click', async({page, server}) => { - const messages = []; - server.setRoute('/empty.html', async (req, res) => { - messages.push('route'); - res.setHeader('Content-Type', 'text/html'); - res.end(``); - }); - - await page.setContent(` -
- - -
`); - - await Promise.all([ - page.click('input[type=submit]').then(() => messages.push('click')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|click'); - }); - it('should await navigation when assigning location', async({page, server}) => { - const messages = []; - server.setRoute('/empty.html', async (req, res) => { - messages.push('route'); - res.setHeader('Content-Type', 'text/html'); - res.end(``); - }); - await Promise.all([ - page.evaluate(`window.location.href = "${server.EMPTY_PAGE}"`).then(() => messages.push('evaluate')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|evaluate'); - }); - it('should await navigation when assigning location twice', async({page, server}) => { - const messages = []; - server.setRoute('/empty.html?cancel', async (req, res) => { res.end('done'); }); - server.setRoute('/empty.html?override', async (req, res) => { messages.push('routeoverride'); res.end('done'); }); - await page.evaluate(` - window.location.href = "${server.EMPTY_PAGE}?cancel"; - window.location.href = "${server.EMPTY_PAGE}?override"; - `); - messages.push('evaluate'); - expect(messages.join('|')).toBe('routeoverride|evaluate'); - }); - it('should await navigation when evaluating reload', async({page, server}) => { - const messages = []; - await page.goto(server.EMPTY_PAGE); - server.setRoute('/empty.html', async (req, res) => { - messages.push('route'); - res.setHeader('Content-Type', 'text/html'); - res.end(``); - }); - - await Promise.all([ - page.evaluate(`window.location.reload()`).then(() => messages.push('evaluate')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|evaluate'); - }); - it('should await navigating specified target', async({page, server}) => { - const messages = []; - server.setRoute('/empty.html', async (req, res) => { - messages.push('route'); - res.setHeader('Content-Type', 'text/html'); - res.end(``); - }); - - await page.setContent(` - empty.html - - `); - const frame = page.frame({ name: 'target' }); - await Promise.all([ - page.click('a').then(() => messages.push('click')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(frame.url()).toBe(server.EMPTY_PAGE); - expect(messages.join('|')).toBe('route|navigated|click'); - }); - it('should work with noWaitAfter: true', async({page, server}) => { - server.setRoute('/empty.html', async () => {}); - await page.setContent(`empty.html`); - await page.click('a', { noWaitAfter: true }); - }); - it('should work with dblclick noWaitAfter: true', async({page, server}) => { - server.setRoute('/empty.html', async () => {}); - await page.setContent(`empty.html`); - await page.dblclick('a', { noWaitAfter: true }); - }); - it('should work with waitForLoadState(load)', async({page, server}) => { - const messages = []; - server.setRoute('/empty.html', async (req, res) => { - messages.push('route'); - res.setHeader('Content-Type', 'text/html'); - res.end(``); - }); - - await page.setContent(`empty.html`); - await Promise.all([ - page.click('a').then(() => page.waitForLoadState('load')).then(() => messages.push('clickload')), - page.waitForEvent('framenavigated').then(() => page.waitForLoadState('domcontentloaded')).then(() => messages.push('domcontentloaded')), - ]); - expect(messages.join('|')).toBe('route|domcontentloaded|clickload'); - }); - it('should work with goto following click', async({page, server}) => { - server.setRoute('/login.html', async (req, res) => { - res.setHeader('Content-Type', 'text/html'); - res.end(`You are logged in`); - }); - - await page.setContent(` -
- - -
`); - - await page.fill('input[type=text]', 'admin'); - await page.click('input[type=submit]'); - await page.goto(server.EMPTY_PAGE); - }); - it.skip(USES_HOOKS)('should report navigation in the log when clicking anchor', async({page, server}) => { - await page.setContent(`click me`); - const __testHookAfterPointerAction = () => new Promise(f => setTimeout(f, 6000)); - const error = await page.click('a', { timeout: 5000, __testHookAfterPointerAction }).catch(e => e); - expect(error.message).toContain('page.click: Timeout 5000ms exceeded.'); - expect(error.message).toContain('waiting for scheduled navigations to finish'); - expect(error.message).toContain(`navigated to "${server.PREFIX + '/frames/one-frame.html'}"`); - }); -}); - -describe('Auto waiting should not hang when', () => { - it('clicking on links which do not commit navigation', async({page, server, httpsServer}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent(`foobar`); - await page.click('a'); - }); - it('calling window.stop async', async({page, server, httpsServer}) => { - server.setRoute('/empty.html', async (req, res) => {}); - await page.evaluate((url) => { - window.location.href = url; - setTimeout(() => window.stop(), 100); - }, server.EMPTY_PAGE); - }); - it('calling window.stop sync', async({page, server, httpsServer}) => { - await page.evaluate((url) => { - window.location.href = url; - window.stop(); - }, server.EMPTY_PAGE); - }); - it('assigning location to about:blank', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.evaluate(`window.location.href = "about:blank";`); - }); - it('assigning location to about:blank after non-about:blank', async({page, server}) => { - server.setRoute('/empty.html', async (req, res) => {}); - await page.evaluate(` - window.location.href = "${server.EMPTY_PAGE}"; - window.location.href = "about:blank";`); - }); - it('calling window.open and window.close', async function({page, server}) { - await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => { - const popup = window.open(window.location.href); - popup.close(); - }); - }); - it('opening a popup', async function({page, server}) { - await page.goto(server.EMPTY_PAGE); - await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window._popup = window.open(window.location.href)), - ]); - }); -}); - diff --git a/test/browser.jest.js b/test/browser.jest.js deleted file mode 100644 index 53853f9fc8..0000000000 --- a/test/browser.jest.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2020 Microsoft Corporation. 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 {FFOX, CHROMIUM, WEBKIT} = testOptions; - -describe('Browser.newPage', function() { - it('should create new page', async function({browser}) { - const page1 = await browser.newPage(); - expect(browser.contexts().length).toBe(1); - - const page2 = await browser.newPage(); - expect(browser.contexts().length).toBe(2); - - await page1.close(); - expect(browser.contexts().length).toBe(1); - - await page2.close(); - expect(browser.contexts().length).toBe(0); - }); - it('should throw upon second create new page', async function({browser}) { - const page = await browser.newPage(); - let error; - await page.context().newPage().catch(e => error = e); - await page.close(); - expect(error.message).toContain('Please use browser.newContext()'); - }); -}); - -describe('Browser.version', function() { - it('should work', async function({browser}) { - const version = browser.version(); - if (CHROMIUM) - expect(version.match(/^\d+\.\d+\.\d+\.\d+$/)).toBeTruthy(); - else - expect(version.match(/^\d+\.\d+$/)).toBeTruthy(); - }); -}); diff --git a/test/browser.spec.js b/test/browser.spec.js new file mode 100644 index 0000000000..4f2406caa9 --- /dev/null +++ b/test/browser.spec.js @@ -0,0 +1,47 @@ +/** + * Copyright 2020 Microsoft Corporation. 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 {FFOX, CHROMIUM, WEBKIT} = testOptions; + +it('should create new page', async function({browser}) { + const page1 = await browser.newPage(); + expect(browser.contexts().length).toBe(1); + + const page2 = await browser.newPage(); + expect(browser.contexts().length).toBe(2); + + await page1.close(); + expect(browser.contexts().length).toBe(1); + + await page2.close(); + expect(browser.contexts().length).toBe(0); +}); + +it('should throw upon second create new page', async function({browser}) { + const page = await browser.newPage(); + let error; + await page.context().newPage().catch(e => error = e); + await page.close(); + expect(error.message).toContain('Please use browser.newContext()'); +}); + +it('version should work', async function({browser}) { + const version = browser.version(); + if (CHROMIUM) + expect(version.match(/^\d+\.\d+\.\d+\.\d+$/)).toBeTruthy(); + else + expect(version.match(/^\d+\.\d+$/)).toBeTruthy(); +}); diff --git a/test/browsercontext-add-cookies.spec.js b/test/browsercontext-add-cookies.spec.js new file mode 100644 index 0000000000..8ecc8c8295 --- /dev/null +++ b/test/browsercontext-add-cookies.spec.js @@ -0,0 +1,329 @@ +/** + * 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, WIN} = testOptions; + +it('should work', async({context, page, server}) => { + await page.goto(server.EMPTY_PAGE); + await context.addCookies([{ + url: server.EMPTY_PAGE, + name: 'password', + value: '123456' + }]); + expect(await page.evaluate(() => document.cookie)).toEqual('password=123456'); +}); +it('should roundtrip cookie', async({context, page, server}) => { + await page.goto(server.EMPTY_PAGE); + // @see https://en.wikipedia.org/wiki/Year_2038_problem + const date = +(new Date('1/1/2038')); + const documentCookie = await page.evaluate(timestamp => { + const date = new Date(timestamp); + document.cookie = `username=John Doe;expires=${date.toUTCString()}`; + return document.cookie; + }, date); + expect(documentCookie).toBe('username=John Doe'); + const cookies = await context.cookies(); + await context.clearCookies(); + expect(await context.cookies()).toEqual([]); + await context.addCookies(cookies); + expect(await context.cookies()).toEqual(cookies); +}); +it('should send cookie header', async({server, context}) => { + let cookie = ''; + server.setRoute('/empty.html', (req, res) => { + cookie = req.headers.cookie; + res.end(); + }); + await context.addCookies([{url: server.EMPTY_PAGE, name: 'cookie', value: 'value'}]); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + expect(cookie).toBe('cookie=value'); +}); +it('should isolate cookies in browser contexts', async({context, server, browser}) => { + const anotherContext = await browser.newContext(); + await context.addCookies([{url: server.EMPTY_PAGE, name: 'isolatecookie', value: 'page1value'}]); + await anotherContext.addCookies([{url: server.EMPTY_PAGE, name: 'isolatecookie', value: 'page2value'}]); + + const cookies1 = await context.cookies(); + const cookies2 = await anotherContext.cookies(); + expect(cookies1.length).toBe(1); + expect(cookies2.length).toBe(1); + expect(cookies1[0].name).toBe('isolatecookie'); + expect(cookies1[0].value).toBe('page1value'); + expect(cookies2[0].name).toBe('isolatecookie'); + expect(cookies2[0].value).toBe('page2value'); + await anotherContext.close(); +}); +it('should isolate session cookies', async({context, server, browser}) => { + server.setRoute('/setcookie.html', (req, res) => { + res.setHeader('Set-Cookie', 'session=value'); + res.end(); + }); + { + const page = await context.newPage(); + await page.goto(server.PREFIX + '/setcookie.html'); + } + { + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const cookies = await context.cookies(); + expect(cookies.length).toBe(1); + expect(cookies.map(c => c.value).join(',')).toBe('value'); + } + { + const context2 = await browser.newContext(); + const page = await context2.newPage(); + await page.goto(server.EMPTY_PAGE); + const cookies = await context2.cookies(); + expect(cookies[0] && cookies[0].name).toBe(undefined); + await context2.close(); + } +}); +it('should isolate persistent cookies', async({context, server, browser}) => { + server.setRoute('/setcookie.html', (req, res) => { + res.setHeader('Set-Cookie', 'persistent=persistent-value; max-age=3600'); + res.end(); + }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/setcookie.html'); + + const context1 = context; + const context2 = await browser.newContext(); + const [page1, page2] = await Promise.all([context1.newPage(), context2.newPage()]); + await Promise.all([page1.goto(server.EMPTY_PAGE), page2.goto(server.EMPTY_PAGE)]); + const [cookies1, cookies2] = await Promise.all([context1.cookies(), context2.cookies()]); + expect(cookies1.length).toBe(1); + expect(cookies1[0].name).toBe('persistent'); + expect(cookies1[0].value).toBe('persistent-value'); + expect(cookies2.length).toBe(0); + await context2.close(); +}); +it('should isolate send cookie header', async({server, context, browser}) => { + let cookie = []; + server.setRoute('/empty.html', (req, res) => { + cookie = req.headers.cookie || ''; + res.end(); + }); + await context.addCookies([{url: server.EMPTY_PAGE, name: 'sendcookie', value: 'value'}]); + { + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + expect(cookie).toBe('sendcookie=value'); + } + { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + expect(cookie).toBe(''); + await context.close(); + } +}); +it.slow()('should isolate cookies between launches', async({browserType, server, defaultBrowserOptions}) => { + const browser1 = await browserType.launch(defaultBrowserOptions); + const context1 = await browser1.newContext(); + await context1.addCookies([{url: server.EMPTY_PAGE, name: 'cookie-in-context-1', value: 'value', expires: Date.now() / 1000 + 10000}]); + await browser1.close(); + + const browser2 = await browserType.launch(defaultBrowserOptions); + const context2 = await browser2.newContext(); + const cookies = await context2.cookies(); + expect(cookies.length).toBe(0); + await browser2.close(); +}); +it('should set multiple cookies', async({context, page, server}) => { + await page.goto(server.EMPTY_PAGE); + await context.addCookies([{ + url: server.EMPTY_PAGE, + name: 'multiple-1', + value: '123456' + }, { + url: server.EMPTY_PAGE, + name: 'multiple-2', + value: 'bar' + }]); + expect(await page.evaluate(() => { + const cookies = document.cookie.split(';'); + return cookies.map(cookie => cookie.trim()).sort(); + })).toEqual([ + 'multiple-1=123456', + 'multiple-2=bar', + ]); +}); +it('should have |expires| set to |-1| for session cookies', async({context, server}) => { + await context.addCookies([{ + url: server.EMPTY_PAGE, + name: 'expires', + value: '123456' + }]); + const cookies = await context.cookies(); + expect(cookies[0].expires).toBe(-1); +}); +it('should set cookie with reasonable defaults', async({context, server}) => { + await context.addCookies([{ + url: server.EMPTY_PAGE, + name: 'defaults', + value: '123456' + }]); + const cookies = await context.cookies(); + expect(cookies.sort((a, b) => a.name.localeCompare(b.name))).toEqual([{ + name: 'defaults', + value: '123456', + domain: 'localhost', + path: '/', + expires: -1, + httpOnly: false, + secure: false, + sameSite: 'None', + }]); +}); +it('should set a cookie with a path', async({context, page, server}) => { + await page.goto(server.PREFIX + '/grid.html'); + await context.addCookies([{ + domain: 'localhost', + path: '/grid.html', + name: 'gridcookie', + value: 'GRID', + }]); + expect(await context.cookies()).toEqual([{ + name: 'gridcookie', + value: 'GRID', + domain: 'localhost', + path: '/grid.html', + expires: -1, + httpOnly: false, + secure: false, + sameSite: 'None', + }]); + expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID'); + await page.goto(server.EMPTY_PAGE); + expect(await page.evaluate('document.cookie')).toBe(''); + await page.goto(server.PREFIX + '/grid.html'); + expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID'); +}); +it('should not set a cookie with blank page URL', async function({context, server}) { + let error = null; + try { + await context.addCookies([ + {url: server.EMPTY_PAGE, name: 'example-cookie', value: 'best'}, + {url: 'about:blank', name: 'example-cookie-blank', value: 'best'} + ]); + } catch (e) { + error = e; + } + expect(error.message).toContain( + `Blank page can not have cookie "example-cookie-blank"` + ); +}); +it('should not set a cookie on a data URL page', async function({context}) { + let error = null; + try { + await context.addCookies([{url: 'data:,Hello%2C%20World!', name: 'example-cookie', value: 'best'}]); + } catch (e) { + error = e; + } + expect(error.message).toContain('Data URL page can not have cookie "example-cookie"'); +}); +it('should default to setting secure cookie for HTTPS websites', async({context, page, server}) => { + await page.goto(server.EMPTY_PAGE); + const SECURE_URL = 'https://example.com'; + await context.addCookies([{ + url: SECURE_URL, + name: 'foo', + value: 'bar', + }]); + const [cookie] = await context.cookies(SECURE_URL); + expect(cookie.secure).toBe(true); +}); +it('should be able to set unsecure cookie for HTTP website', async({context, page, server}) => { + await page.goto(server.EMPTY_PAGE); + const HTTP_URL = 'http://example.com'; + await context.addCookies([{ + url: HTTP_URL, + name: 'foo', + value: 'bar', + }]); + const [cookie] = await context.cookies(HTTP_URL); + expect(cookie.secure).toBe(false); +}); +it('should set a cookie on a different domain', async({context, page, server}) => { + await page.goto(server.EMPTY_PAGE); + await context.addCookies([{ + url: 'https://www.example.com', + name: 'example-cookie', + value: 'best', + }]); + expect(await page.evaluate('document.cookie')).toBe(''); + expect(await context.cookies('https://www.example.com')).toEqual([{ + name: 'example-cookie', + value: 'best', + domain: 'www.example.com', + path: '/', + expires: -1, + httpOnly: false, + secure: true, + sameSite: 'None', + }]); +}); +it('should set cookies for a frame', async({context, page, server}) => { + await page.goto(server.EMPTY_PAGE); + await context.addCookies([ + {url: server.PREFIX, name: 'frame-cookie', value: 'value'} + ]); + await page.evaluate(src => { + let fulfill; + const promise = new Promise(x => fulfill = x); + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + iframe.onload = fulfill; + iframe.src = src; + return promise; + }, server.PREFIX + '/grid.html'); + + expect(await page.frames()[1].evaluate('document.cookie')).toBe('frame-cookie=value'); +}); +it('should(not) block third party cookies', async({context, page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.evaluate(src => { + let fulfill; + const promise = new Promise(x => fulfill = x); + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + iframe.onload = fulfill; + iframe.src = src; + return promise; + }, server.CROSS_PROCESS_PREFIX + '/grid.html'); + await page.frames()[1].evaluate(`document.cookie = 'username=John Doe'`); + await page.waitForTimeout(2000); + const allowsThirdParty = CHROMIUM || FFOX; + const cookies = await context.cookies(server.CROSS_PROCESS_PREFIX + '/grid.html'); + if (allowsThirdParty) { + expect(cookies).toEqual([ + { + "domain": "127.0.0.1", + "expires": -1, + "httpOnly": false, + "name": "username", + "path": "/", + "sameSite": "None", + "secure": false, + "value": "John Doe" + } + ]); + } else { + expect(cookies).toEqual([]); + } +}); diff --git a/test/browsercontext-basic.spec.js b/test/browsercontext-basic.spec.js new file mode 100644 index 0000000000..9592db628b --- /dev/null +++ b/test/browsercontext-basic.spec.js @@ -0,0 +1,232 @@ +/** + * 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 utils = require('./utils'); +const {FFOX, CHROMIUM, WEBKIT, MAC, CHANNEL, HEADLESS} = testOptions; +const {devices} = require('..'); + +it('should create new context', async function({browser}) { + expect(browser.contexts().length).toBe(0); + const context = await browser.newContext(); + expect(browser.contexts().length).toBe(1); + expect(browser.contexts().indexOf(context) !== -1).toBe(true); + await context.close(); + expect(browser.contexts().length).toBe(0); +}); + +it('window.open should use parent tab context', async function({browser, server}) { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window.open(url), server.EMPTY_PAGE) + ]); + expect(popup.context()).toBe(context); + await context.close(); +}); + +it('should isolate localStorage and cookies', async function({browser, server}) { + // Create two incognito contexts. + const context1 = await browser.newContext(); + const context2 = await browser.newContext(); + expect(context1.pages().length).toBe(0); + expect(context2.pages().length).toBe(0); + + // Create a page in first incognito context. + const page1 = await context1.newPage(); + await page1.goto(server.EMPTY_PAGE); + await page1.evaluate(() => { + localStorage.setItem('name', 'page1'); + document.cookie = 'name=page1'; + }); + + expect(context1.pages().length).toBe(1); + expect(context2.pages().length).toBe(0); + + // Create a page in second incognito context. + const page2 = await context2.newPage(); + await page2.goto(server.EMPTY_PAGE); + await page2.evaluate(() => { + localStorage.setItem('name', 'page2'); + document.cookie = 'name=page2'; + }); + + expect(context1.pages().length).toBe(1); + expect(context2.pages().length).toBe(1); + expect(context1.pages()[0]).toBe(page1); + expect(context2.pages()[0]).toBe(page2); + + // Make sure pages don't share localstorage or cookies. + expect(await page1.evaluate(() => localStorage.getItem('name'))).toBe('page1'); + expect(await page1.evaluate(() => document.cookie)).toBe('name=page1'); + expect(await page2.evaluate(() => localStorage.getItem('name'))).toBe('page2'); + expect(await page2.evaluate(() => document.cookie)).toBe('name=page2'); + + // Cleanup contexts. + await Promise.all([ + context1.close(), + context2.close() + ]); + expect(browser.contexts().length).toBe(0); +}); + +it('should propagate default viewport to the page', async({ browser }) => { + const context = await browser.newContext({ viewport: { width: 456, height: 789 } }); + const page = await context.newPage(); + await utils.verifyViewport(page, 456, 789); + await context.close(); +}); + +it('should make a copy of default viewport', async({ browser }) => { + const viewport = { width: 456, height: 789 }; + const context = await browser.newContext({ viewport }); + viewport.width = 567; + const page = await context.newPage(); + await utils.verifyViewport(page, 456, 789); + await context.close(); +}); + +it('should respect deviceScaleFactor', async({ browser }) => { + const context = await browser.newContext({ deviceScaleFactor: 3 }); + const page = await context.newPage(); + expect(await page.evaluate('window.devicePixelRatio')).toBe(3); + await context.close(); +}); + +it('should not allow deviceScaleFactor with null viewport', async({ browser }) => { + const error = await browser.newContext({ viewport: null, deviceScaleFactor: 1 }).catch(e => e); + expect(error.message).toContain('"deviceScaleFactor" option is not supported with null "viewport"'); +}); + +it('should not allow isMobile with null viewport', async({ browser }) => { + const error = await browser.newContext({ viewport: null, isMobile: true }).catch(e => e); + expect(error.message).toContain('"isMobile" option is not supported with null "viewport"'); +}); + +it('close() should work for empty context', async({ browser }) => { + const context = await browser.newContext(); + await context.close(); +}); + +it('close() should abort waitForEvent', async({ browser }) => { + const context = await browser.newContext(); + const promise = context.waitForEvent('page').catch(e => e); + await context.close(); + let error = await promise; + expect(error.message).toContain('Context closed'); +}); + +it('close() should be callable twice', async({browser}) => { + const context = await browser.newContext(); + await Promise.all([ + context.close(), + context.close(), + ]); + await context.close(); +}); + +it('should not report frameless pages on error', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + server.setRoute('/empty.html', (req, res) => { + res.end(`Click me`); + }); + let popup; + context.on('page', p => popup = p); + await page.goto(server.EMPTY_PAGE); + await page.click('"Click me"'); + await context.close(); + if (popup) { + // This races on Firefox :/ + expect(popup.isClosed()).toBeTruthy(); + expect(popup.mainFrame()).toBeTruthy(); + } +}); + +it('should return all of the pages', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const second = await context.newPage(); + const allPages = context.pages(); + expect(allPages.length).toBe(2); + expect(allPages).toContain(page); + expect(allPages).toContain(second); + await context.close(); +}); + +it('should close all belonging pages once closing context', async function({browser}) { + const context = await browser.newContext(); + await context.newPage(); + expect(context.pages().length).toBe(1); + + await context.close(); + expect(context.pages().length).toBe(0); +}); + +it('should disable javascript', async({browser}) => { + { + const context = await browser.newContext({ javaScriptEnabled: false }); + const page = await context.newPage(); + await page.goto('data:text/html, '); + let error = null; + await page.evaluate('something').catch(e => error = e); + if (WEBKIT) + expect(error.message).toContain('Can\'t find variable: something'); + else + expect(error.message).toContain('something is not defined'); + await context.close(); + } + + { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto('data:text/html, '); + expect(await page.evaluate('something')).toBe('forbidden'); + await context.close(); + } +}); + +it('should be able to navigate after disabling javascript', async({browser, server}) => { + const context = await browser.newContext({ javaScriptEnabled: false }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await context.close(); +}); + +it('should work with offline option', async({browser, server}) => { + const context = await browser.newContext({offline: true}); + const page = await context.newPage(); + let error = null; + await page.goto(server.EMPTY_PAGE).catch(e => error = e); + expect(error).toBeTruthy(); + await context.setOffline(false); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(200); + await context.close(); +}); + +it('should emulate navigator.onLine', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); + await context.setOffline(true); + expect(await page.evaluate(() => window.navigator.onLine)).toBe(false); + await context.setOffline(false); + expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); + await context.close(); +}); diff --git a/test/browsercontext-clearcookies.spec.js b/test/browsercontext-clearcookies.spec.js new file mode 100644 index 0000000000..1476542481 --- /dev/null +++ b/test/browsercontext-clearcookies.spec.js @@ -0,0 +1,50 @@ +/** + * 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, WIN} = testOptions; + +it('should clear cookies', async({context, page, server}) => { + await page.goto(server.EMPTY_PAGE); + await context.addCookies([{ + url: server.EMPTY_PAGE, + name: 'cookie1', + value: '1' + }]); + expect(await page.evaluate('document.cookie')).toBe('cookie1=1'); + await context.clearCookies(); + expect(await context.cookies()).toEqual([]); + await page.reload(); + expect(await page.evaluate('document.cookie')).toBe(''); +}); + +it('should isolate cookies when clearing', async({context, server, browser}) => { + const anotherContext = await browser.newContext(); + await context.addCookies([{url: server.EMPTY_PAGE, name: 'page1cookie', value: 'page1value'}]); + await anotherContext.addCookies([{url: server.EMPTY_PAGE, name: 'page2cookie', value: 'page2value'}]); + + expect((await context.cookies()).length).toBe(1); + expect((await anotherContext.cookies()).length).toBe(1); + + await context.clearCookies(); + expect((await context.cookies()).length).toBe(0); + expect((await anotherContext.cookies()).length).toBe(1); + + await anotherContext.clearCookies(); + expect((await context.cookies()).length).toBe(0); + expect((await anotherContext.cookies()).length).toBe(0); + await anotherContext.close(); +}); diff --git a/test/browsercontext-cookies.spec.js b/test/browsercontext-cookies.spec.js new file mode 100644 index 0000000000..4a57510ba0 --- /dev/null +++ b/test/browsercontext-cookies.spec.js @@ -0,0 +1,167 @@ +/** + * 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, WIN} = testOptions; + +it('should return no cookies in pristine browser context', async({context, page, server}) => { + expect(await context.cookies()).toEqual([]); +}); + +it('should get a cookie', async({context, page, server}) => { + await page.goto(server.EMPTY_PAGE); + const documentCookie = await page.evaluate(() => { + document.cookie = 'username=John Doe'; + return document.cookie; + }); + expect(documentCookie).toBe('username=John Doe'); + expect(await context.cookies()).toEqual([{ + name: 'username', + value: 'John Doe', + domain: 'localhost', + path: '/', + expires: -1, + httpOnly: false, + secure: false, + sameSite: 'None', + }]); +}); + +it('should get a non-session cookie', async({context, page, server}) => { + await page.goto(server.EMPTY_PAGE); + // @see https://en.wikipedia.org/wiki/Year_2038_problem + const date = +(new Date('1/1/2038')); + const documentCookie = await page.evaluate(timestamp => { + const date = new Date(timestamp); + document.cookie = `username=John Doe;expires=${date.toUTCString()}`; + return document.cookie; + }, date); + expect(documentCookie).toBe('username=John Doe'); + expect(await context.cookies()).toEqual([{ + name: 'username', + value: 'John Doe', + domain: 'localhost', + path: '/', + expires: date / 1000, + httpOnly: false, + secure: false, + sameSite: 'None', + }]); +}); + +it('should properly report httpOnly cookie', async({context, page, server}) => { + server.setRoute('/empty.html', (req, res) => { + res.setHeader('Set-Cookie', 'name=value;HttpOnly; Path=/'); + res.end(); + }); + await page.goto(server.EMPTY_PAGE); + const cookies = await context.cookies(); + expect(cookies.length).toBe(1); + expect(cookies[0].httpOnly).toBe(true); +}); + +it.fail(WEBKIT && WIN)('should properly report "Strict" sameSite cookie', async({context, page, server}) => { + server.setRoute('/empty.html', (req, res) => { + res.setHeader('Set-Cookie', 'name=value;SameSite=Strict'); + res.end(); + }); + await page.goto(server.EMPTY_PAGE); + const cookies = await context.cookies(); + expect(cookies.length).toBe(1); + expect(cookies[0].sameSite).toBe('Strict'); +}); + +it.fail(WEBKIT && WIN)('should properly report "Lax" sameSite cookie', async({context, page, server}) => { + server.setRoute('/empty.html', (req, res) => { + res.setHeader('Set-Cookie', 'name=value;SameSite=Lax'); + res.end(); + }); + await page.goto(server.EMPTY_PAGE); + const cookies = await context.cookies(); + expect(cookies.length).toBe(1); + expect(cookies[0].sameSite).toBe('Lax'); +}); + +it('should get multiple cookies', async({context, page, server}) => { + await page.goto(server.EMPTY_PAGE); + const documentCookie = await page.evaluate(() => { + document.cookie = 'username=John Doe'; + document.cookie = 'password=1234'; + return document.cookie.split('; ').sort().join('; '); + }); + const cookies = await context.cookies(); + cookies.sort((a, b) => a.name.localeCompare(b.name)); + expect(documentCookie).toBe('password=1234; username=John Doe'); + expect(cookies).toEqual([ + { + name: 'password', + value: '1234', + domain: 'localhost', + path: '/', + expires: -1, + httpOnly: false, + secure: false, + sameSite: 'None', + }, + { + name: 'username', + value: 'John Doe', + domain: 'localhost', + path: '/', + expires: -1, + httpOnly: false, + secure: false, + sameSite: 'None', + }, + ]); +}); + +it('should get cookies from multiple urls', async({context}) => { + await context.addCookies([{ + url: 'https://foo.com', + name: 'doggo', + value: 'woofs', + }, { + url: 'https://bar.com', + name: 'catto', + value: 'purrs', + }, { + url: 'https://baz.com', + name: 'birdo', + value: 'tweets', + }]); + const cookies = await context.cookies(['https://foo.com', 'https://baz.com']); + cookies.sort((a, b) => a.name.localeCompare(b.name)); + expect(cookies).toEqual([{ + name: 'birdo', + value: 'tweets', + domain: 'baz.com', + path: '/', + expires: -1, + httpOnly: false, + secure: true, + sameSite: 'None', + }, { + name: 'doggo', + value: 'woofs', + domain: 'foo.com', + path: '/', + expires: -1, + httpOnly: false, + secure: true, + sameSite: 'None', + }]); +}); diff --git a/test/browsercontext-credentials.spec.js b/test/browsercontext-credentials.spec.js new file mode 100644 index 0000000000..90f11bee19 --- /dev/null +++ b/test/browsercontext-credentials.spec.js @@ -0,0 +1,76 @@ +/** + * 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 utils = require('./utils'); +const {FFOX, CHROMIUM, WEBKIT, MAC, CHANNEL, HEADLESS} = testOptions; +const {devices} = require('..'); + +it.fail(CHROMIUM && !HEADLESS)('should fail without credentials', async({browser, server}) => { + server.setAuth('/empty.html', 'user', 'pass'); + const context = await browser.newContext(); + const page = await context.newPage(); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(401); + await context.close(); +}); + +it.fail(CHROMIUM && !HEADLESS)('should work with setHTTPCredentials', async({browser, server}) => { + server.setAuth('/empty.html', 'user', 'pass'); + const context = await browser.newContext(); + const page = await context.newPage(); + let response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(401); + await context.setHTTPCredentials({ username: 'user', password: 'pass' }); + response = await page.reload(); + expect(response.status()).toBe(200); + await context.close(); +}); + +it('should work with correct credentials', async({browser, server}) => { + server.setAuth('/empty.html', 'user', 'pass'); + const context = await browser.newContext({ + httpCredentials: { username: 'user', password: 'pass' } + }); + const page = await context.newPage(); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(200); + await context.close(); +}); + +it.fail(CHROMIUM && !HEADLESS)('should fail with wrong credentials', async({browser, server}) => { + server.setAuth('/empty.html', 'user', 'pass'); + const context = await browser.newContext({ + httpCredentials: { username: 'foo', password: 'bar' } + }); + const page = await context.newPage(); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(401); + await context.close(); +}); + +it('should return resource body', async({browser, server}) => { + server.setAuth('/playground.html', 'user', 'pass'); + const context = await browser.newContext({ + httpCredentials: { username: 'user', password: 'pass' } + }); + const page = await context.newPage(); + const response = await page.goto(server.PREFIX + '/playground.html'); + expect(response.status()).toBe(200); + expect(await page.title()).toBe("Playground"); + expect((await response.body()).toString()).toContain("Playground"); + await context.close(); +}); diff --git a/test/browsercontext-csp.spec.js b/test/browsercontext-csp.spec.js new file mode 100644 index 0000000000..86351244c2 --- /dev/null +++ b/test/browsercontext-csp.spec.js @@ -0,0 +1,103 @@ +/** + * 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 utils = require('./utils'); +const {FFOX, CHROMIUM, WEBKIT, MAC, CHANNEL, HEADLESS} = testOptions; +const {devices} = require('..'); + +it('should bypass CSP meta tag', async({browser, server}) => { + // Make sure CSP prohibits addScriptTag. + { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/csp.html'); + await page.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); + expect(await page.evaluate(() => window.__injected)).toBe(undefined); + await context.close(); + } + + // By-pass CSP and try one more time. + { + const context = await browser.newContext({ bypassCSP: true }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/csp.html'); + await page.addScriptTag({content: 'window.__injected = 42;'}); + expect(await page.evaluate(() => window.__injected)).toBe(42); + await context.close(); + } +}); + +it('should bypass CSP header', async({browser, server}) => { + // Make sure CSP prohibits addScriptTag. + server.setCSP('/empty.html', 'default-src "self"'); + + { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); + expect(await page.evaluate(() => window.__injected)).toBe(undefined); + await context.close(); + } + + // By-pass CSP and try one more time. + { + const context = await browser.newContext({ bypassCSP: true }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.addScriptTag({content: 'window.__injected = 42;'}); + expect(await page.evaluate(() => window.__injected)).toBe(42); + await context.close(); + } +}); + +it('should bypass after cross-process navigation', async({browser, server}) => { + const context = await browser.newContext({ bypassCSP: true }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/csp.html'); + await page.addScriptTag({content: 'window.__injected = 42;'}); + expect(await page.evaluate(() => window.__injected)).toBe(42); + + await page.goto(server.CROSS_PROCESS_PREFIX + '/csp.html'); + await page.addScriptTag({content: 'window.__injected = 42;'}); + expect(await page.evaluate(() => window.__injected)).toBe(42); + await context.close(); +}); + +it('should bypass CSP in iframes as well', async({browser, server}) => { + // Make sure CSP prohibits addScriptTag in an iframe. + { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); + await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); + expect(await frame.evaluate(() => window.__injected)).toBe(undefined); + await context.close(); + } + + // By-pass CSP and try one more time. + { + const context = await browser.newContext({ bypassCSP: true }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); + await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); + expect(await frame.evaluate(() => window.__injected)).toBe(42); + await context.close(); + } +}); diff --git a/test/browsercontext-device.spec.js b/test/browsercontext-device.spec.js new file mode 100644 index 0000000000..7eadc0dd93 --- /dev/null +++ b/test/browsercontext-device.spec.js @@ -0,0 +1,58 @@ +/** + * 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 utils = require('./utils'); +const {CHROMIUM, FFOX, MAC, HEADLESS} = testOptions; + +it.skip(FFOX)('should work', async({playwright, browser, server}) => { + const iPhone = playwright.devices['iPhone 6']; + const context = await browser.newContext({ ...iPhone }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/mobile.html'); + expect(await page.evaluate(() => window.innerWidth)).toBe(375); + expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone'); + await context.close(); +}); + +it.skip(FFOX)('should support clicking', async({playwright, browser, server}) => { + const iPhone = playwright.devices['iPhone 6']; + const context = await browser.newContext({ ...iPhone }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(button => button.style.marginTop = '200px', button); + await button.click(); + expect(await page.evaluate(() => result)).toBe('Clicked'); + await context.close(); +}); + +it.skip(FFOX)('should scroll to click', async({browser, server}) => { + const context = await browser.newContext({ + viewport: { + width: 400, + height: 400, + }, + deviceScaleFactor: 1, + isMobile: true + }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/input/scrollable.html'); + const element = await page.$('#button-91'); + await element.click(); + expect(await element.textContent()).toBe('clicked'); + await context.close(); +}); diff --git a/test/browsercontext-expose-function.spec.js b/test/browsercontext-expose-function.spec.js new file mode 100644 index 0000000000..6812efed66 --- /dev/null +++ b/test/browsercontext-expose-function.spec.js @@ -0,0 +1,85 @@ +/** + * 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 utils = require('./utils'); +const {FFOX, CHROMIUM, WEBKIT, MAC, CHANNEL, HEADLESS} = testOptions; +const {devices} = require('..'); + +it('expose binding should work', async({browser}) => { + const context = await browser.newContext(); + let bindingSource; + await context.exposeBinding('add', (source, a, b) => { + bindingSource = source; + return a + b; + }); + const page = await context.newPage(); + 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({browser, server}) => { + const context = await browser.newContext(); + await context.exposeFunction('add', (a, b) => a + b); + const page = await context.newPage(); + await page.exposeFunction('mul', (a, b) => a * b); + await context.exposeFunction('sub', (a, b) => a - b); + await context.exposeBinding('addHandle', async ({ frame }, a, b) => { + const handle = await frame.evaluateHandle(([a, b]) => a + b, [a, b]); + return handle; + }); + const result = await page.evaluate(async function() { + return { mul: await mul(9, 4), add: await add(9, 4), sub: await sub(9, 4), addHandle: await addHandle(5, 6) }; + }); + expect(result).toEqual({ mul: 36, add: 13, sub: 5, addHandle: 11 }); + await context.close(); +}); + +it('should throw for duplicate registrations', async({browser, server}) => { + const context = await browser.newContext(); + await context.exposeFunction('foo', () => {}); + await context.exposeFunction('bar', () => {}); + let error = await context.exposeFunction('foo', () => {}).catch(e => e); + expect(error.message).toContain('Function "foo" has been already registered'); + const page = await context.newPage(); + error = await page.exposeFunction('foo', () => {}).catch(e => e); + expect(error.message).toContain('Function "foo" has been already registered in the browser context'); + await page.exposeFunction('baz', () => {}); + error = await context.exposeFunction('baz', () => {}).catch(e => e); + expect(error.message).toContain('Function "baz" has been already registered in one of the pages'); + await context.close(); +}); + +it('should be callable from-inside addInitScript', async({browser, server}) => { + const context = await browser.newContext(); + let args = []; + await context.exposeFunction('woof', function(arg) { + args.push(arg); + }); + await context.addInitScript(() => woof('context')); + const page = await context.newPage(); + await page.addInitScript(() => woof('page')); + args = []; + await page.reload(); + expect(args).toEqual(['context', 'page']); + await context.close(); +}); diff --git a/test/browsercontext-locale.spec.js b/test/browsercontext-locale.spec.js new file mode 100644 index 0000000000..09b14df969 --- /dev/null +++ b/test/browsercontext-locale.spec.js @@ -0,0 +1,140 @@ +/** + * 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 utils = require('./utils'); +const {CHROMIUM, FFOX, MAC, HEADLESS} = testOptions; + +it('should affect accept-language header', async({browser, server}) => { + const context = await browser.newContext({ locale: 'fr-CH' }); + const page = await context.newPage(); + const [request] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.goto(server.EMPTY_PAGE), + ]); + expect(request.headers['accept-language'].substr(0, 5)).toBe('fr-CH'); + await context.close(); +}); + +it('should affect navigator.language', async({browser, server}) => { + const context = await browser.newContext({ locale: 'fr-CH' }); + const page = await context.newPage(); + expect(await page.evaluate(() => navigator.language)).toBe('fr-CH'); + await context.close(); +}); + +it('should format number', async({browser, server}) => { + { + const context = await browser.newContext({ locale: 'en-US' }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + expect(await page.evaluate(() => (1000000.50).toLocaleString())).toBe('1,000,000.5'); + await context.close(); + } + { + const context = await browser.newContext({ locale: 'fr-CH' }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + expect(await page.evaluate(() => (1000000.50).toLocaleString().replace(/\s/g, ' '))).toBe('1 000 000,5'); + await context.close(); + } +}); + +it('should format date', async({browser, server}) => { + { + const context = await browser.newContext({ locale: 'en-US', timezoneId: 'America/Los_Angeles' }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const formatted = 'Sat Nov 19 2016 10:12:34 GMT-0800 (Pacific Standard Time)'; + expect(await page.evaluate(() => new Date(1479579154987).toString())).toBe(formatted); + await context.close(); + } + { + const context = await browser.newContext({ locale: 'de-DE', timezoneId: 'Europe/Berlin' }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + expect(await page.evaluate(() => new Date(1479579154987).toString())).toBe( + 'Sat Nov 19 2016 19:12:34 GMT+0100 (Mitteleuropäische Normalzeit)'); + await context.close(); + } +}); + +it('should format number in popups', async({browser, server}) => { + const context = await browser.newContext({ locale: 'fr-CH' }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/formatted-number.html'), + ]); + await popup.waitForLoadState('domcontentloaded'); + const result = await popup.evaluate(() => window.result); + expect(result).toBe('1 000 000,5'); + await context.close(); +}); + +it('should affect navigator.language in popups', async({browser, server}) => { + const context = await browser.newContext({ locale: 'fr-CH' }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/formatted-number.html'), + ]); + await popup.waitForLoadState('domcontentloaded'); + const result = await popup.evaluate(() => window.initialNavigatorLanguage); + expect(result).toBe('fr-CH'); + await context.close(); +}); + +it('should work for multiple pages sharing same process', async({browser, server}) => { + const context = await browser.newContext({ locale: 'ru-RU' }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + let [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), + ]); + [popup] = await Promise.all([ + popup.waitForEvent('popup'), + popup.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), + ]); + await context.close(); +}); + +it('should be isolated between contexts', async({browser, server}) => { + const context1 = await browser.newContext({ locale: 'en-US' }); + const promises = []; + // By default firefox limits number of child web processes to 8. + for (let i = 0; i< 8; i++) + promises.push(context1.newPage()); + await Promise.all(promises); + + const context2 = await browser.newContext({ locale: 'ru-RU' }); + const page2 = await context2.newPage(); + + const localeNumber = () => (1000000.50).toLocaleString(); + const numbers = await Promise.all(context1.pages().map(page => page.evaluate(localeNumber))); + + numbers.forEach(value => expect(value).toBe('1,000,000.5')); + expect(await page2.evaluate(localeNumber)).toBe('1 000 000,5'); + + await Promise.all([ + context1.close(), + context2.close() + ]); +}); diff --git a/test/browsercontext-page-event.spec.js b/test/browsercontext-page-event.spec.js new file mode 100644 index 0000000000..958de2ad84 --- /dev/null +++ b/test/browsercontext-page-event.spec.js @@ -0,0 +1,187 @@ +/** + * 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 utils = require('./utils'); +const {FFOX, CHROMIUM, WEBKIT, MAC, CHANNEL, HEADLESS} = testOptions; +const {devices} = require('..'); + +it('should have url', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [otherPage] = await Promise.all([ + context.waitForEvent('page'), + page.evaluate(url => window.open(url), server.EMPTY_PAGE) + ]); + expect(otherPage.url()).toBe(server.EMPTY_PAGE); + await context.close(); +}); + +it('should have url after domcontentloaded', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [otherPage] = await Promise.all([ + context.waitForEvent('page'), + page.evaluate(url => window.open(url), server.EMPTY_PAGE) + ]); + await otherPage.waitForLoadState('domcontentloaded'); + expect(otherPage.url()).toBe(server.EMPTY_PAGE); + await context.close(); +}); + +it('should have about:blank url with domcontentloaded', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [otherPage] = await Promise.all([ + context.waitForEvent('page'), + page.evaluate(url => window.open(url), 'about:blank') + ]); + await otherPage.waitForLoadState('domcontentloaded'); + expect(otherPage.url()).toBe('about:blank'); + await context.close(); +}); + +it('should have about:blank for empty url with domcontentloaded', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [otherPage] = await Promise.all([ + context.waitForEvent('page'), + page.evaluate(() => window.open()) + ]); + await otherPage.waitForLoadState('domcontentloaded'); + expect(otherPage.url()).toBe('about:blank'); + await context.close(); +}); + +it('should report when a new page is created and closed', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [otherPage] = await Promise.all([ + context.waitForEvent('page'), + page.evaluate(url => window.open(url), server.CROSS_PROCESS_PREFIX + '/empty.html'), + ]); + // The url is about:blank in FF when 'page' event is fired. + expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX); + expect(await otherPage.evaluate(() => ['Hello', 'world'].join(' '))).toBe('Hello world'); + expect(await otherPage.$('body')).toBeTruthy(); + + let allPages = context.pages(); + expect(allPages).toContain(page); + expect(allPages).toContain(otherPage); + + let closeEventReceived; + otherPage.once('close', () => closeEventReceived = true); + await otherPage.close(); + expect(closeEventReceived).toBeTruthy(); + + allPages = context.pages(); + expect(allPages).toContain(page); + expect(allPages).not.toContain(otherPage); + await context.close(); +}); + +it('should report initialized pages', async({browser, server}) => { + const context = await browser.newContext(); + const pagePromise = context.waitForEvent('page'); + context.newPage(); + const newPage = await pagePromise; + expect(newPage.url()).toBe('about:blank'); + + const popupPromise = context.waitForEvent('page'); + const evaluatePromise = newPage.evaluate(() => window.open('about:blank')); + const popup = await popupPromise; + expect(popup.url()).toBe('about:blank'); + await evaluatePromise; + await context.close(); +}); + +it('should not crash while redirecting of original request was missed', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + let serverResponse = null; + server.setRoute('/one-style.css', (req, res) => serverResponse = res); + // Open a new page. Use window.open to connect to the page later. + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + page.evaluate(url => window.open(url), server.PREFIX + '/one-style.html'), + server.waitForRequest('/one-style.css') + ]); + // Issue a redirect. + serverResponse.writeHead(302, { location: '/injectedstyle.css' }); + serverResponse.end(); + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toBe(server.PREFIX + '/one-style.html'); + // Cleanup. + await context.close(); +}); + +it('should have an opener', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + context.waitForEvent('page'), + page.goto(server.PREFIX + '/popup/window-open.html') + ]); + expect(popup.url()).toBe(server.PREFIX + '/popup/popup.html'); + expect(await popup.opener()).toBe(page); + expect(await page.opener()).toBe(null); + await context.close(); +}); + +it('should fire page lifecycle events', async function({browser, server}) { + const context = await browser.newContext(); + const events = []; + context.on('page', async page => { + events.push('CREATED: ' + page.url()); + page.on('close', () => events.push('DESTROYED: ' + page.url())); + }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.close(); + expect(events).toEqual([ + 'CREATED: about:blank', + `DESTROYED: ${server.EMPTY_PAGE}` + ]); + await context.close(); +}); +it.fail(WEBKIT)('should work with Shift-clicking', async({browser, server}) => { + // WebKit: Shift+Click does not open a new window. + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + context.waitForEvent('page'), + page.click('a', { modifiers: ['Shift'] }), + ]); + expect(await popup.opener()).toBe(null); + await context.close(); +}); +it.fail(WEBKIT || FFOX)('should work with Ctrl-clicking', async({browser, server}) => { + // Firefox: reports an opener in this case. + // WebKit: Ctrl+Click does not open a new tab. + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + context.waitForEvent('page'), + page.click('a', { modifiers: [ MAC ? 'Meta' : 'Control'] }), + ]); + expect(await popup.opener()).toBe(null); + await context.close(); +}); diff --git a/test/browsercontext-route.spec.js b/test/browsercontext-route.spec.js new file mode 100644 index 0000000000..d81f30d943 --- /dev/null +++ b/test/browsercontext-route.spec.js @@ -0,0 +1,111 @@ +/** + * 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 utils = require('./utils'); +const {FFOX, CHROMIUM, WEBKIT, MAC, CHANNEL, HEADLESS} = testOptions; +const {devices} = require('..'); + +it('should intercept', async({browser, server}) => { + const context = await browser.newContext(); + let intercepted = false; + await context.route('**/empty.html', route => { + intercepted = true; + const request = route.request(); + expect(request.url()).toContain('empty.html'); + expect(request.headers()['user-agent']).toBeTruthy(); + expect(request.method()).toBe('GET'); + expect(request.postData()).toBe(null); + expect(request.isNavigationRequest()).toBe(true); + expect(request.resourceType()).toBe('document'); + expect(request.frame() === page.mainFrame()).toBe(true); + expect(request.frame().url()).toBe('about:blank'); + route.continue(); + }); + const page = await context.newPage(); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + expect(intercepted).toBe(true); + await context.close(); +}); + +it('should unroute', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + + let intercepted = []; + const handler1 = route => { + intercepted.push(1); + route.continue(); + }; + await context.route('**/empty.html', handler1); + await context.route('**/empty.html', route => { + intercepted.push(2); + route.continue(); + }); + await context.route('**/empty.html', route => { + intercepted.push(3); + route.continue(); + }); + await context.route('**/*', route => { + intercepted.push(4); + route.continue(); + }); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([1]); + + intercepted = []; + await context.unroute('**/empty.html', handler1); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([2]); + + intercepted = []; + await context.unroute('**/empty.html'); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([4]); + + await context.close(); +}); + +it('should yield to page.route', async({browser, server}) => { + const context = await browser.newContext(); + await context.route('**/empty.html', route => { + route.fulfill({ status: 200, body: 'context' }); + }); + const page = await context.newPage(); + await page.route('**/empty.html', route => { + route.fulfill({ status: 200, body: 'page' }); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + expect(await response.text()).toBe('page'); + await context.close(); +}); + +it('should fall back to context.route', async({browser, server}) => { + const context = await browser.newContext(); + await context.route('**/empty.html', route => { + route.fulfill({ status: 200, body: 'context' }); + }); + const page = await context.newPage(); + await page.route('**/non-empty.html', route => { + route.fulfill({ status: 200, body: 'page' }); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + expect(await response.text()).toBe('context'); + await context.close(); +}); diff --git a/test/browsercontext-timezone-id.spec.js b/test/browsercontext-timezone-id.spec.js new file mode 100644 index 0000000000..f0a9785fa2 --- /dev/null +++ b/test/browsercontext-timezone-id.spec.js @@ -0,0 +1,72 @@ +/** + * 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 utils = require('./utils'); +const {CHROMIUM, FFOX, MAC, HEADLESS} = testOptions; + +it('should work', async ({ browser }) => { + const func = () => new Date(1479579154987).toString(); + { + const context = await browser.newContext({ timezoneId: 'America/Jamaica' }); + const page = await context.newPage(); + expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)'); + await context.close(); + } + { + const context = await browser.newContext({ timezoneId: 'Pacific/Honolulu' }); + const page = await context.newPage(); + expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 08:12:34 GMT-1000 (Hawaii-Aleutian Standard Time)'); + await context.close(); + } + { + const context = await browser.newContext({ timezoneId: 'America/Buenos_Aires' }); + const page = await context.newPage(); + expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 15:12:34 GMT-0300 (Argentina Standard Time)'); + await context.close(); + } + { + const context = await browser.newContext({ timezoneId: 'Europe/Berlin' }); + const page = await context.newPage(); + expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 19:12:34 GMT+0100 (Central European Standard Time)'); + await context.close(); + } +}); + +it('should throw for invalid timezone IDs when creating pages', async({browser}) => { + for (const timezoneId of ['Foo/Bar', 'Baz/Qux']) { + let error = null; + const context = await browser.newContext({ timezoneId }); + const page = await context.newPage().catch(e => error = e); + expect(error.message).toContain(`Invalid timezone ID: ${timezoneId}`); + await context.close(); + } +}); + +it('should work for multiple pages sharing same process', async({browser, server}) => { + const context = await browser.newContext({ timezoneId: 'Europe/Moscow' }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + let [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), + ]); + [popup] = await Promise.all([ + popup.waitForEvent('popup'), + popup.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), + ]); + await context.close(); +}); diff --git a/test/browsercontext-user-agent.spec.js b/test/browsercontext-user-agent.spec.js new file mode 100644 index 0000000000..291b794dc9 --- /dev/null +++ b/test/browsercontext-user-agent.spec.js @@ -0,0 +1,88 @@ +/** + * 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 utils = require('./utils'); +const {FFOX, CHROMIUM, WEBKIT, MAC, CHANNEL, HEADLESS} = testOptions; +const {devices} = require('..'); + +it('should work', async({browser, server}) => { + { + const context = await browser.newContext(); + const page = await context.newPage(); + expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla'); + await context.close(); + } + { + const context = await browser.newContext({ userAgent: 'foobar' }); + const page = await context.newPage(); + const [request] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.goto(server.EMPTY_PAGE), + ]); + expect(request.headers['user-agent']).toBe('foobar'); + await context.close(); + } +}); + +it('should work for subframes', async({browser, server}) => { + { + const context = await browser.newContext(); + const page = await context.newPage(); + expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla'); + await context.close(); + } + { + const context = await browser.newContext({ userAgent: 'foobar' }); + const page = await context.newPage(); + const [request] = await Promise.all([ + server.waitForRequest('/empty.html'), + utils.attachFrame(page, 'frame1', server.EMPTY_PAGE), + ]); + expect(request.headers['user-agent']).toBe('foobar'); + await context.close(); + } +}); + +it('should emulate device user-agent', async({browser, server}) => { + { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/mobile.html'); + expect(await page.evaluate(() => navigator.userAgent)).not.toContain('iPhone'); + await context.close(); + } + { + const context = await browser.newContext({ userAgent: devices['iPhone 6'].userAgent }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/mobile.html'); + expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone'); + await context.close(); + } +}); + +it('should make a copy of default options', async({browser, server}) => { + const options = { userAgent: 'foobar' }; + const context = await browser.newContext(options); + options.userAgent = 'wrong'; + const page = await context.newPage(); + const [request] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.goto(server.EMPTY_PAGE), + ]); + expect(request.headers['user-agent']).toBe('foobar'); + await context.close(); +}); diff --git a/test/browsercontext-viewport-mobile.spec.js b/test/browsercontext-viewport-mobile.spec.js new file mode 100644 index 0000000000..504808c520 --- /dev/null +++ b/test/browsercontext-viewport-mobile.spec.js @@ -0,0 +1,130 @@ +/** + * 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 utils = require('./utils'); +const {CHROMIUM, FFOX, MAC, HEADLESS} = testOptions; + +it.skip(FFOX)('should support mobile emulation', async({playwright, browser, server}) => { + const iPhone = playwright.devices['iPhone 6']; + const context = await browser.newContext({ ...iPhone }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/mobile.html'); + expect(await page.evaluate(() => window.innerWidth)).toBe(375); + await page.setViewportSize({width: 400, height: 300}); + expect(await page.evaluate(() => window.innerWidth)).toBe(400); + await context.close(); +}); + +it.skip(FFOX)('should support touch emulation', async({playwright, browser, server}) => { + const iPhone = playwright.devices['iPhone 6']; + const context = await browser.newContext({ ...iPhone }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/mobile.html'); + expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(true); + expect(await page.evaluate(dispatchTouch)).toBe('Received touch'); + await context.close(); + + function dispatchTouch() { + let fulfill; + const promise = new Promise(x => fulfill = x); + window.ontouchstart = function(e) { + fulfill('Received touch'); + }; + window.dispatchEvent(new Event('touchstart')); + + fulfill('Did not receive touch'); + + return promise; + } +}); + +it.skip(FFOX)('should be detectable by Modernizr', async({playwright, browser, server}) => { + const iPhone = playwright.devices['iPhone 6']; + const context = await browser.newContext({ ...iPhone }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/detect-touch.html'); + expect(await page.evaluate(() => document.body.textContent.trim())).toBe('YES'); + await context.close(); +}); + +it.skip(FFOX)('should detect touch when applying viewport with touches', async({browser, server}) => { + const context = await browser.newContext({ viewport: { width: 800, height: 600 }, hasTouch: true }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.addScriptTag({url: server.PREFIX + '/modernizr.js'}); + expect(await page.evaluate(() => Modernizr.touchevents)).toBe(true); + await context.close(); +}); + +it.skip(FFOX)('should support landscape emulation', async({playwright, browser, server}) => { + const iPhone = playwright.devices['iPhone 6']; + const iPhoneLandscape = playwright.devices['iPhone 6 landscape']; + const context1 = await browser.newContext({ ...iPhone }); + const page1 = await context1.newPage(); + await page1.goto(server.PREFIX + '/mobile.html'); + expect(await page1.evaluate(() => matchMedia('(orientation: landscape)').matches)).toBe(false); + const context2 = await browser.newContext({ ...iPhoneLandscape }); + const page2 = await context2.newPage(); + expect(await page2.evaluate(() => matchMedia('(orientation: landscape)').matches)).toBe(true); + await context1.close(); + await context2.close(); +}); + +it.skip(FFOX)('should support window.orientation emulation', async({browser, server}) => { + const context = await browser.newContext({ viewport: { width: 300, height: 400 }, isMobile: true }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/mobile.html'); + expect(await page.evaluate(() => window.orientation)).toBe(0); + await page.setViewportSize({width: 400, height: 300}); + expect(await page.evaluate(() => window.orientation)).toBe(90); + await context.close(); +}); + +it.skip(FFOX)('should fire orientationchange event', async({browser, server}) => { + const context = await browser.newContext({ viewport: { width: 300, height: 400 }, isMobile: true }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/mobile.html'); + await page.evaluate(() => { + window.counter = 0; + window.addEventListener('orientationchange', () => console.log(++window.counter)); + }); + + const event1 = page.waitForEvent('console'); + await page.setViewportSize({width: 400, height: 300}); + expect((await event1).text()).toBe('1'); + + const event2 = page.waitForEvent('console'); + await page.setViewportSize({width: 300, height: 400}); + expect((await event2).text()).toBe('2'); + await context.close(); +}); + +it.skip(FFOX)('default mobile viewports to 980 width', async({browser, server}) => { + const context = await browser.newContext({ viewport: {width: 320, height: 480 }, isMobile: true }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/empty.html'); + expect(await page.evaluate(() => window.innerWidth)).toBe(980); + await context.close(); +}); + +it.skip(FFOX)('respect meta viewport tag', async({browser, server}) => { + const context = await browser.newContext({ viewport: {width: 320, height: 480 }, isMobile: true }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/mobile.html'); + expect(await page.evaluate(() => window.innerWidth)).toBe(320); + await context.close(); +}); diff --git a/test/browsercontext-viewport.spec.js b/test/browsercontext-viewport.spec.js new file mode 100644 index 0000000000..f499a31bf0 --- /dev/null +++ b/test/browsercontext-viewport.spec.js @@ -0,0 +1,106 @@ +/** + * 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 utils = require('./utils'); +const {CHROMIUM, FFOX, MAC, HEADLESS} = testOptions; + +it('should get the proper default viewport size', async({page, server}) => { + await utils.verifyViewport(page, 1280, 720); +}); + +it('should set the proper viewport size', async({page, server}) => { + await utils.verifyViewport(page, 1280, 720); + await page.setViewportSize({width: 123, height: 456}); + await utils.verifyViewport(page, 123, 456); +}); + +it('should return correct outerWidth and outerHeight', async({page}) => { + const size = await page.evaluate(() => { + return { + innerWidth: window.innerWidth, + innerHeight: window.innerHeight, + outerWidth: window.outerWidth, + outerHeight: window.outerHeight, + }; + }); + expect(size.innerWidth).toBe(1280); + expect(size.innerHeight).toBe(720); + expect(size.outerWidth >= size.innerWidth).toBeTruthy(); + expect(size.outerHeight >= size.innerHeight).toBeTruthy(); +}); + +it('should emulate device width', async({page, server}) => { + expect(page.viewportSize()).toEqual({width: 1280, height: 720}); + await page.setViewportSize({width: 200, height: 200}); + expect(await page.evaluate(() => window.screen.width)).toBe(200); + expect(await page.evaluate(() => matchMedia('(min-device-width: 100px)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(min-device-width: 300px)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(max-device-width: 100px)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(max-device-width: 300px)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(device-width: 500px)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(device-width: 200px)').matches)).toBe(true); + await page.setViewportSize({width: 500, height: 500}); + expect(await page.evaluate(() => window.screen.width)).toBe(500); + expect(await page.evaluate(() => matchMedia('(min-device-width: 400px)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(min-device-width: 600px)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(max-device-width: 400px)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(max-device-width: 600px)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(device-width: 200px)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(device-width: 500px)').matches)).toBe(true); +}); + +it('should emulate device height', async({page, server}) => { + expect(page.viewportSize()).toEqual({width: 1280, height: 720}); + await page.setViewportSize({width: 200, height: 200}); + expect(await page.evaluate(() => window.screen.height)).toBe(200); + expect(await page.evaluate(() => matchMedia('(min-device-height: 100px)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(min-device-height: 300px)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(max-device-height: 100px)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(max-device-height: 300px)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(device-height: 500px)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(device-height: 200px)').matches)).toBe(true); + await page.setViewportSize({width: 500, height: 500}); + expect(await page.evaluate(() => window.screen.height)).toBe(500); + expect(await page.evaluate(() => matchMedia('(min-device-height: 400px)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(min-device-height: 600px)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(max-device-height: 400px)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(max-device-height: 600px)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(device-height: 200px)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(device-height: 500px)').matches)).toBe(true); +}); + +it('should not have touch by default', async({page, server}) => { + await page.goto(server.PREFIX + '/mobile.html'); + expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false); + await page.goto(server.PREFIX + '/detect-touch.html'); + expect(await page.evaluate(() => document.body.textContent.trim())).toBe('NO'); +}); + +it('should support touch with null viewport', async({browser, server}) => { + const context = await browser.newContext({ viewport: null, hasTouch: true }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/mobile.html'); + expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(true); + await context.close(); +}); + +it('should report null viewportSize when given null viewport', async({browser, server}) => { + const context = await browser.newContext({ viewport: null }); + const page = await context.newPage(); + expect(page.viewportSize()).toBe(null); + await context.close(); +}); diff --git a/test/browsercontext.jest.js b/test/browsercontext.jest.js deleted file mode 100644 index 73939805ab..0000000000 --- a/test/browsercontext.jest.js +++ /dev/null @@ -1,749 +0,0 @@ -/** - * 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 utils = require('./utils'); -const {FFOX, CHROMIUM, WEBKIT, MAC, CHANNEL, HEADLESS} = testOptions; -const {devices} = require('..'); - -describe('BrowserContext', function() { - it('should create new context', async function({browser}) { - expect(browser.contexts().length).toBe(0); - const context = await browser.newContext(); - expect(browser.contexts().length).toBe(1); - expect(browser.contexts().indexOf(context) !== -1).toBe(true); - await context.close(); - expect(browser.contexts().length).toBe(0); - }); - it('window.open should use parent tab context', async function({browser, server}) { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window.open(url), server.EMPTY_PAGE) - ]); - expect(popup.context()).toBe(context); - await context.close(); - }); - it('should isolate localStorage and cookies', async function({browser, server}) { - // Create two incognito contexts. - const context1 = await browser.newContext(); - const context2 = await browser.newContext(); - expect(context1.pages().length).toBe(0); - expect(context2.pages().length).toBe(0); - - // Create a page in first incognito context. - const page1 = await context1.newPage(); - await page1.goto(server.EMPTY_PAGE); - await page1.evaluate(() => { - localStorage.setItem('name', 'page1'); - document.cookie = 'name=page1'; - }); - - expect(context1.pages().length).toBe(1); - expect(context2.pages().length).toBe(0); - - // Create a page in second incognito context. - const page2 = await context2.newPage(); - await page2.goto(server.EMPTY_PAGE); - await page2.evaluate(() => { - localStorage.setItem('name', 'page2'); - document.cookie = 'name=page2'; - }); - - expect(context1.pages().length).toBe(1); - expect(context2.pages().length).toBe(1); - expect(context1.pages()[0]).toBe(page1); - expect(context2.pages()[0]).toBe(page2); - - // Make sure pages don't share localstorage or cookies. - expect(await page1.evaluate(() => localStorage.getItem('name'))).toBe('page1'); - expect(await page1.evaluate(() => document.cookie)).toBe('name=page1'); - expect(await page2.evaluate(() => localStorage.getItem('name'))).toBe('page2'); - expect(await page2.evaluate(() => document.cookie)).toBe('name=page2'); - - // Cleanup contexts. - await Promise.all([ - context1.close(), - context2.close() - ]); - expect(browser.contexts().length).toBe(0); - }); - it('should propagate default viewport to the page', async({ browser }) => { - const context = await browser.newContext({ viewport: { width: 456, height: 789 } }); - const page = await context.newPage(); - await utils.verifyViewport(page, 456, 789); - await context.close(); - }); - it('should make a copy of default viewport', async({ browser }) => { - const viewport = { width: 456, height: 789 }; - const context = await browser.newContext({ viewport }); - viewport.width = 567; - const page = await context.newPage(); - await utils.verifyViewport(page, 456, 789); - await context.close(); - }); - it('should respect deviceScaleFactor', async({ browser }) => { - const context = await browser.newContext({ deviceScaleFactor: 3 }); - const page = await context.newPage(); - expect(await page.evaluate('window.devicePixelRatio')).toBe(3); - await context.close(); - }); - it('should not allow deviceScaleFactor with null viewport', async({ browser }) => { - const error = await browser.newContext({ viewport: null, deviceScaleFactor: 1 }).catch(e => e); - expect(error.message).toContain('"deviceScaleFactor" option is not supported with null "viewport"'); - }); - it('should not allow isMobile with null viewport', async({ browser }) => { - const error = await browser.newContext({ viewport: null, isMobile: true }).catch(e => e); - expect(error.message).toContain('"isMobile" option is not supported with null "viewport"'); - }); - it('close() should work for empty context', async({ browser }) => { - const context = await browser.newContext(); - await context.close(); - }); - it('close() should abort waitForEvent', async({ browser }) => { - const context = await browser.newContext(); - const promise = context.waitForEvent('page').catch(e => e); - await context.close(); - let error = await promise; - expect(error.message).toContain('Context closed'); - }); - it('close() should be callable twice', async({browser}) => { - const context = await browser.newContext(); - await Promise.all([ - context.close(), - context.close(), - ]); - await context.close(); - }); - it('should not report frameless pages on error', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - server.setRoute('/empty.html', (req, res) => { - res.end(`Click me`); - }); - let popup; - context.on('page', p => popup = p); - await page.goto(server.EMPTY_PAGE); - await page.click('"Click me"'); - await context.close(); - if (popup) { - // This races on Firefox :/ - expect(popup.isClosed()).toBeTruthy(); - expect(popup.mainFrame()).toBeTruthy(); - } - }); -}); - -describe('BrowserContext({userAgent})', function() { - it('should work', async({browser, server}) => { - { - const context = await browser.newContext(); - const page = await context.newPage(); - expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla'); - await context.close(); - } - { - const context = await browser.newContext({ userAgent: 'foobar' }); - const page = await context.newPage(); - const [request] = await Promise.all([ - server.waitForRequest('/empty.html'), - page.goto(server.EMPTY_PAGE), - ]); - expect(request.headers['user-agent']).toBe('foobar'); - await context.close(); - } - }); - it('should work for subframes', async({browser, server}) => { - { - const context = await browser.newContext(); - const page = await context.newPage(); - expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla'); - await context.close(); - } - { - const context = await browser.newContext({ userAgent: 'foobar' }); - const page = await context.newPage(); - const [request] = await Promise.all([ - server.waitForRequest('/empty.html'), - utils.attachFrame(page, 'frame1', server.EMPTY_PAGE), - ]); - expect(request.headers['user-agent']).toBe('foobar'); - await context.close(); - } - }); - it('should emulate device user-agent', async({browser, server}) => { - { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => navigator.userAgent)).not.toContain('iPhone'); - await context.close(); - } - { - const context = await browser.newContext({ userAgent: devices['iPhone 6'].userAgent }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone'); - await context.close(); - } - }); - it('should make a copy of default options', async({browser, server}) => { - const options = { userAgent: 'foobar' }; - const context = await browser.newContext(options); - options.userAgent = 'wrong'; - const page = await context.newPage(); - const [request] = await Promise.all([ - server.waitForRequest('/empty.html'), - page.goto(server.EMPTY_PAGE), - ]); - expect(request.headers['user-agent']).toBe('foobar'); - await context.close(); - }); -}); - -describe('BrowserContext({bypassCSP})', function() { - it('should bypass CSP meta tag', async({browser, server}) => { - // Make sure CSP prohibits addScriptTag. - { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/csp.html'); - await page.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); - expect(await page.evaluate(() => window.__injected)).toBe(undefined); - await context.close(); - } - - // By-pass CSP and try one more time. - { - const context = await browser.newContext({ bypassCSP: true }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/csp.html'); - await page.addScriptTag({content: 'window.__injected = 42;'}); - expect(await page.evaluate(() => window.__injected)).toBe(42); - await context.close(); - } - }); - - it('should bypass CSP header', async({browser, server}) => { - // Make sure CSP prohibits addScriptTag. - server.setCSP('/empty.html', 'default-src "self"'); - - { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); - expect(await page.evaluate(() => window.__injected)).toBe(undefined); - await context.close(); - } - - // By-pass CSP and try one more time. - { - const context = await browser.newContext({ bypassCSP: true }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({content: 'window.__injected = 42;'}); - expect(await page.evaluate(() => window.__injected)).toBe(42); - await context.close(); - } - }); - - it('should bypass after cross-process navigation', async({browser, server}) => { - const context = await browser.newContext({ bypassCSP: true }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/csp.html'); - await page.addScriptTag({content: 'window.__injected = 42;'}); - expect(await page.evaluate(() => window.__injected)).toBe(42); - - await page.goto(server.CROSS_PROCESS_PREFIX + '/csp.html'); - await page.addScriptTag({content: 'window.__injected = 42;'}); - expect(await page.evaluate(() => window.__injected)).toBe(42); - await context.close(); - }); - it('should bypass CSP in iframes as well', async({browser, server}) => { - // Make sure CSP prohibits addScriptTag in an iframe. - { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); - await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); - expect(await frame.evaluate(() => window.__injected)).toBe(undefined); - await context.close(); - } - - // By-pass CSP and try one more time. - { - const context = await browser.newContext({ bypassCSP: true }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); - await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); - expect(await frame.evaluate(() => window.__injected)).toBe(42); - await context.close(); - } - }); -}); - -describe('BrowserContext({javaScriptEnabled})', function() { - it('should work', async({browser}) => { - { - const context = await browser.newContext({ javaScriptEnabled: false }); - const page = await context.newPage(); - await page.goto('data:text/html, '); - let error = null; - await page.evaluate('something').catch(e => error = e); - if (WEBKIT) - expect(error.message).toContain('Can\'t find variable: something'); - else - expect(error.message).toContain('something is not defined'); - await context.close(); - } - - { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto('data:text/html, '); - expect(await page.evaluate('something')).toBe('forbidden'); - await context.close(); - } - }); - it('should be able to navigate after disabling javascript', async({browser, server}) => { - const context = await browser.newContext({ javaScriptEnabled: false }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await context.close(); - }); -}); - -describe('BrowserContext.pages()', function() { - it('should return all of the pages', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const second = await context.newPage(); - const allPages = context.pages(); - expect(allPages.length).toBe(2); - expect(allPages).toContain(page); - expect(allPages).toContain(second); - await context.close(); - }); - it('should close all belonging pages once closing context', async function({browser}) { - const context = await browser.newContext(); - await context.newPage(); - expect(context.pages().length).toBe(1); - - await context.close(); - expect(context.pages().length).toBe(0); - }); -}); - -describe('BrowserContext.exposeBinding', () => { - it('should work', async({browser}) => { - const context = await browser.newContext(); - let bindingSource; - await context.exposeBinding('add', (source, a, b) => { - bindingSource = source; - return a + b; - }); - const page = await context.newPage(); - 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(); - }); -}); - -describe('BrowserContext.exposeFunction', () => { - it('should work', async({browser, server}) => { - const context = await browser.newContext(); - await context.exposeFunction('add', (a, b) => a + b); - const page = await context.newPage(); - await page.exposeFunction('mul', (a, b) => a * b); - await context.exposeFunction('sub', (a, b) => a - b); - await context.exposeBinding('addHandle', async ({ frame }, a, b) => { - const handle = await frame.evaluateHandle(([a, b]) => a + b, [a, b]); - return handle; - }); - const result = await page.evaluate(async function() { - return { mul: await mul(9, 4), add: await add(9, 4), sub: await sub(9, 4), addHandle: await addHandle(5, 6) }; - }); - expect(result).toEqual({ mul: 36, add: 13, sub: 5, addHandle: 11 }); - await context.close(); - }); - it('should throw for duplicate registrations', async({browser, server}) => { - const context = await browser.newContext(); - await context.exposeFunction('foo', () => {}); - await context.exposeFunction('bar', () => {}); - let error = await context.exposeFunction('foo', () => {}).catch(e => e); - expect(error.message).toContain('Function "foo" has been already registered'); - const page = await context.newPage(); - error = await page.exposeFunction('foo', () => {}).catch(e => e); - expect(error.message).toContain('Function "foo" has been already registered in the browser context'); - await page.exposeFunction('baz', () => {}); - error = await context.exposeFunction('baz', () => {}).catch(e => e); - expect(error.message).toContain('Function "baz" has been already registered in one of the pages'); - await context.close(); - }); - it('should be callable from-inside addInitScript', async({browser, server}) => { - const context = await browser.newContext(); - let args = []; - await context.exposeFunction('woof', function(arg) { - args.push(arg); - }); - await context.addInitScript(() => woof('context')); - const page = await context.newPage(); - await page.addInitScript(() => woof('page')); - args = []; - await page.reload(); - expect(args).toEqual(['context', 'page']); - await context.close(); - }); -}); - -describe('BrowserContext.route', () => { - it('should intercept', async({browser, server}) => { - const context = await browser.newContext(); - let intercepted = false; - await context.route('**/empty.html', route => { - intercepted = true; - const request = route.request(); - expect(request.url()).toContain('empty.html'); - expect(request.headers()['user-agent']).toBeTruthy(); - expect(request.method()).toBe('GET'); - expect(request.postData()).toBe(null); - expect(request.isNavigationRequest()).toBe(true); - expect(request.resourceType()).toBe('document'); - expect(request.frame() === page.mainFrame()).toBe(true); - expect(request.frame().url()).toBe('about:blank'); - route.continue(); - }); - const page = await context.newPage(); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - expect(intercepted).toBe(true); - await context.close(); - }); - it('should unroute', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - - let intercepted = []; - const handler1 = route => { - intercepted.push(1); - route.continue(); - }; - await context.route('**/empty.html', handler1); - await context.route('**/empty.html', route => { - intercepted.push(2); - route.continue(); - }); - await context.route('**/empty.html', route => { - intercepted.push(3); - route.continue(); - }); - await context.route('**/*', route => { - intercepted.push(4); - route.continue(); - }); - await page.goto(server.EMPTY_PAGE); - expect(intercepted).toEqual([1]); - - intercepted = []; - await context.unroute('**/empty.html', handler1); - await page.goto(server.EMPTY_PAGE); - expect(intercepted).toEqual([2]); - - intercepted = []; - await context.unroute('**/empty.html'); - await page.goto(server.EMPTY_PAGE); - expect(intercepted).toEqual([4]); - - await context.close(); - }); - it('should yield to page.route', async({browser, server}) => { - const context = await browser.newContext(); - await context.route('**/empty.html', route => { - route.fulfill({ status: 200, body: 'context' }); - }); - const page = await context.newPage(); - await page.route('**/empty.html', route => { - route.fulfill({ status: 200, body: 'page' }); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - expect(await response.text()).toBe('page'); - await context.close(); - }); - it('should fall back to context.route', async({browser, server}) => { - const context = await browser.newContext(); - await context.route('**/empty.html', route => { - route.fulfill({ status: 200, body: 'context' }); - }); - const page = await context.newPage(); - await page.route('**/non-empty.html', route => { - route.fulfill({ status: 200, body: 'page' }); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - expect(await response.text()).toBe('context'); - await context.close(); - }); -}); - -describe('BrowserContext({httpCredentials})', function() { - it.fail(CHROMIUM && !HEADLESS)('should fail without credentials', async({browser, server}) => { - server.setAuth('/empty.html', 'user', 'pass'); - const context = await browser.newContext(); - const page = await context.newPage(); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(401); - await context.close(); - }); - it.fail(CHROMIUM && !HEADLESS)('should work with setHTTPCredentials', async({browser, server}) => { - server.setAuth('/empty.html', 'user', 'pass'); - const context = await browser.newContext(); - const page = await context.newPage(); - let response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(401); - await context.setHTTPCredentials({ username: 'user', password: 'pass' }); - response = await page.reload(); - expect(response.status()).toBe(200); - await context.close(); - }); - it('should work with correct credentials', async({browser, server}) => { - server.setAuth('/empty.html', 'user', 'pass'); - const context = await browser.newContext({ - httpCredentials: { username: 'user', password: 'pass' } - }); - const page = await context.newPage(); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(200); - await context.close(); - }); - it.fail(CHROMIUM && !HEADLESS)('should fail with wrong credentials', async({browser, server}) => { - server.setAuth('/empty.html', 'user', 'pass'); - const context = await browser.newContext({ - httpCredentials: { username: 'foo', password: 'bar' } - }); - const page = await context.newPage(); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(401); - await context.close(); - }); - it('should return resource body', async({browser, server}) => { - server.setAuth('/playground.html', 'user', 'pass'); - const context = await browser.newContext({ - httpCredentials: { username: 'user', password: 'pass' } - }); - const page = await context.newPage(); - const response = await page.goto(server.PREFIX + '/playground.html'); - expect(response.status()).toBe(200); - expect(await page.title()).toBe("Playground"); - expect((await response.body()).toString()).toContain("Playground"); - await context.close(); - }); -}); - -describe('BrowserContext.setOffline', function() { - it('should work with initial option', async({browser, server}) => { - const context = await browser.newContext({offline: true}); - const page = await context.newPage(); - let error = null; - await page.goto(server.EMPTY_PAGE).catch(e => error = e); - expect(error).toBeTruthy(); - await context.setOffline(false); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(200); - await context.close(); - }); - it('should emulate navigator.onLine', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); - await context.setOffline(true); - expect(await page.evaluate(() => window.navigator.onLine)).toBe(false); - await context.setOffline(false); - expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); - await context.close(); - }); -}); - -describe('Events.BrowserContext.Page', function() { - it('should have url', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [otherPage] = await Promise.all([ - context.waitForEvent('page'), - page.evaluate(url => window.open(url), server.EMPTY_PAGE) - ]); - expect(otherPage.url()).toBe(server.EMPTY_PAGE); - await context.close(); - }); - it('should have url after domcontentloaded', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [otherPage] = await Promise.all([ - context.waitForEvent('page'), - page.evaluate(url => window.open(url), server.EMPTY_PAGE) - ]); - await otherPage.waitForLoadState('domcontentloaded'); - expect(otherPage.url()).toBe(server.EMPTY_PAGE); - await context.close(); - }); - it('should have about:blank url with domcontentloaded', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [otherPage] = await Promise.all([ - context.waitForEvent('page'), - page.evaluate(url => window.open(url), 'about:blank') - ]); - await otherPage.waitForLoadState('domcontentloaded'); - expect(otherPage.url()).toBe('about:blank'); - await context.close(); - }); - it('should have about:blank for empty url with domcontentloaded', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [otherPage] = await Promise.all([ - context.waitForEvent('page'), - page.evaluate(() => window.open()) - ]); - await otherPage.waitForLoadState('domcontentloaded'); - expect(otherPage.url()).toBe('about:blank'); - await context.close(); - }); - it('should report when a new page is created and closed', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [otherPage] = await Promise.all([ - context.waitForEvent('page'), - page.evaluate(url => window.open(url), server.CROSS_PROCESS_PREFIX + '/empty.html'), - ]); - // The url is about:blank in FF when 'page' event is fired. - expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX); - expect(await otherPage.evaluate(() => ['Hello', 'world'].join(' '))).toBe('Hello world'); - expect(await otherPage.$('body')).toBeTruthy(); - - let allPages = context.pages(); - expect(allPages).toContain(page); - expect(allPages).toContain(otherPage); - - let closeEventReceived; - otherPage.once('close', () => closeEventReceived = true); - await otherPage.close(); - expect(closeEventReceived).toBeTruthy(); - - allPages = context.pages(); - expect(allPages).toContain(page); - expect(allPages).not.toContain(otherPage); - await context.close(); - }); - it('should report initialized pages', async({browser, server}) => { - const context = await browser.newContext(); - const pagePromise = context.waitForEvent('page'); - context.newPage(); - const newPage = await pagePromise; - expect(newPage.url()).toBe('about:blank'); - - const popupPromise = context.waitForEvent('page'); - const evaluatePromise = newPage.evaluate(() => window.open('about:blank')); - const popup = await popupPromise; - expect(popup.url()).toBe('about:blank'); - await evaluatePromise; - await context.close(); - }); - it('should not crash while redirecting of original request was missed', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - let serverResponse = null; - server.setRoute('/one-style.css', (req, res) => serverResponse = res); - // Open a new page. Use window.open to connect to the page later. - const [newPage] = await Promise.all([ - context.waitForEvent('page'), - page.evaluate(url => window.open(url), server.PREFIX + '/one-style.html'), - server.waitForRequest('/one-style.css') - ]); - // Issue a redirect. - serverResponse.writeHead(302, { location: '/injectedstyle.css' }); - serverResponse.end(); - await newPage.waitForLoadState('domcontentloaded'); - expect(newPage.url()).toBe(server.PREFIX + '/one-style.html'); - // Cleanup. - await context.close(); - }); - it('should have an opener', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - context.waitForEvent('page'), - page.goto(server.PREFIX + '/popup/window-open.html') - ]); - expect(popup.url()).toBe(server.PREFIX + '/popup/popup.html'); - expect(await popup.opener()).toBe(page); - expect(await page.opener()).toBe(null); - await context.close(); - }); - it('should fire page lifecycle events', async function({browser, server}) { - const context = await browser.newContext(); - const events = []; - context.on('page', async page => { - events.push('CREATED: ' + page.url()); - page.on('close', () => events.push('DESTROYED: ' + page.url())); - }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.close(); - expect(events).toEqual([ - 'CREATED: about:blank', - `DESTROYED: ${server.EMPTY_PAGE}` - ]); - await context.close(); - }); - it.fail(WEBKIT)('should work with Shift-clicking', async({browser, server}) => { - // WebKit: Shift+Click does not open a new window. - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - context.waitForEvent('page'), - page.click('a', { modifiers: ['Shift'] }), - ]); - expect(await popup.opener()).toBe(null); - await context.close(); - }); - it.fail(WEBKIT || FFOX)('should work with Ctrl-clicking', async({browser, server}) => { - // Firefox: reports an opener in this case. - // WebKit: Ctrl+Click does not open a new tab. - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - context.waitForEvent('page'), - page.click('a', { modifiers: [ MAC ? 'Meta' : 'Control'] }), - ]); - expect(await popup.opener()).toBe(null); - await context.close(); - }); -}); diff --git a/test/capabilities.jest.js b/test/capabilities.jest.js deleted file mode 100644 index 238b4387cb..0000000000 --- a/test/capabilities.jest.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright Microsoft Corporation. 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 path = require('path'); - -const {FFOX, CHROMIUM, WEBKIT, WIN, LINUX, ASSETS_DIR} = testOptions; - -describe('Capabilities', function() { - it.fail(WEBKIT && WIN)('Web Assembly should work', async function({page, server}) { - await page.goto(server.PREFIX + '/wasm/table2.html'); - expect(await page.evaluate(() => loadTable())).toBe('42, 83'); - }); - - it('WebSocket should work', async({page, server}) => { - const value = await page.evaluate((port) => { - let cb; - const result = new Promise(f => cb = f); - const ws = new WebSocket('ws://localhost:' + port + '/ws'); - ws.addEventListener('message', data => { ws.close(); cb(data.data); }); - ws.addEventListener('error', error => cb('Error')); - return result; - }, server.PORT); - expect(value).toBe('incoming'); - }); - - it('should respect CSP', async({page, server}) => { - server.setRoute('/empty.html', async (req, res) => { - res.setHeader('Content-Security-Policy', `script-src 'unsafe-inline';`); - res.end(` - `); - }); - - await page.goto(server.EMPTY_PAGE); - expect(await page.evaluate(() => window.testStatus)).toBe('SUCCESS'); - }); - it.fail(WEBKIT && WIN)('should play video', async({page}) => { - // TODO: the test passes on Windows locally but fails on GitHub Action bot, - // apparently due to a Media Pack issue in the Windows Server. - // - // Our test server doesn't support range requests required to play on Mac, - // so we load the page using a file url. - const url = WIN - ? 'file:///' + path.join(ASSETS_DIR, 'video.html').replace(/\\/g, '/') - : 'file://' + path.join(ASSETS_DIR, 'video.html'); - await page.goto(url); - await page.$eval('video', v => v.play()); - await page.$eval('video', v => v.pause()); - }); -}); diff --git a/test/capabilities.spec.js b/test/capabilities.spec.js new file mode 100644 index 0000000000..26e9322fe2 --- /dev/null +++ b/test/capabilities.spec.js @@ -0,0 +1,64 @@ +/** + * Copyright Microsoft Corporation. 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 path = require('path'); + +const {FFOX, CHROMIUM, WEBKIT, WIN, LINUX, ASSETS_DIR} = testOptions; + +it.fail(WEBKIT && WIN)('Web Assembly should work', async function({page, server}) { + await page.goto(server.PREFIX + '/wasm/table2.html'); + expect(await page.evaluate(() => loadTable())).toBe('42, 83'); +}); + +it('WebSocket should work', async({page, server}) => { + const value = await page.evaluate((port) => { + let cb; + const result = new Promise(f => cb = f); + const ws = new WebSocket('ws://localhost:' + port + '/ws'); + ws.addEventListener('message', data => { ws.close(); cb(data.data); }); + ws.addEventListener('error', error => cb('Error')); + return result; + }, server.PORT); + expect(value).toBe('incoming'); +}); + +it('should respect CSP', async({page, server}) => { + server.setRoute('/empty.html', async (req, res) => { + res.setHeader('Content-Security-Policy', `script-src 'unsafe-inline';`); + res.end(` + `); + }); + + await page.goto(server.EMPTY_PAGE); + expect(await page.evaluate(() => window.testStatus)).toBe('SUCCESS'); +}); + +it.fail(WEBKIT && WIN)('should play video', async({page}) => { + // TODO: the test passes on Windows locally but fails on GitHub Action bot, + // apparently due to a Media Pack issue in the Windows Server. + // + // Our test server doesn't support range requests required to play on Mac, + // so we load the page using a file url. + const url = WIN + ? 'file:///' + path.join(ASSETS_DIR, 'video.html').replace(/\\/g, '/') + : 'file://' + path.join(ASSETS_DIR, 'video.html'); + await page.goto(url); + await page.$eval('video', v => v.play()); + await page.$eval('video', v => v.pause()); +}); diff --git a/test/channels.jest.js b/test/channels.jest.js deleted file mode 100644 index 1be794311b..0000000000 --- a/test/channels.jest.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * 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 { FFOX, CHROMIUM, WEBKIT, WIN, CHANNEL } = testOptions; - -describe.skip(!CHANNEL)('Channels', function() { - it('should work', async({browser}) => { - expect(!!browser._connection).toBeTruthy(); - }); - - it('should scope context handles', async({browserType, browser, server}) => { - const GOLDEN_PRECONDITION = { - _guid: '', - objects: [ - { _guid: 'BrowserType', objects: [ - { _guid: 'Browser', objects: [] } - ] }, - { _guid: 'BrowserType', objects: [] }, - { _guid: 'BrowserType', objects: [] }, - { _guid: 'Playwright', objects: [] }, - { _guid: 'Selectors', objects: [] }, - { _guid: 'Electron', objects: [] }, - ] - }; - await expectScopeState(browser, GOLDEN_PRECONDITION); - - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await expectScopeState(browser, { - _guid: '', - objects: [ - { _guid: 'BrowserType', objects: [] }, - { _guid: 'BrowserType', objects: [] }, - { _guid: 'BrowserType', objects: [ - { _guid: 'Browser', objects: [ - { _guid: 'BrowserContext', objects: [ - { _guid: 'Frame', objects: [] }, - { _guid: 'Page', objects: [] }, - { _guid: 'Request', objects: [] }, - { _guid: 'Response', objects: [] }, - ]}, - ] }, - ] }, - { _guid: 'Playwright', objects: [] }, - { _guid: 'Selectors', objects: [] }, - { _guid: 'Electron', objects: [] }, - ] - }); - - await context.close(); - await expectScopeState(browser, GOLDEN_PRECONDITION); - }); - - it.skip(!CHROMIUM)('should scope CDPSession handles', async({browserType, browser, server}) => { - const GOLDEN_PRECONDITION = { - _guid: '', - objects: [ - { _guid: 'BrowserType', objects: [ - { _guid: 'Browser', objects: [] } - ] }, - { _guid: 'BrowserType', objects: [] }, - { _guid: 'BrowserType', objects: [] }, - { _guid: 'Playwright', objects: [] }, - { _guid: 'Selectors', objects: [] }, - { _guid: 'Electron', objects: [] }, - ] - }; - await expectScopeState(browserType, GOLDEN_PRECONDITION); - - const session = await browser.newBrowserCDPSession(); - await expectScopeState(browserType, { - _guid: '', - objects: [ - { _guid: 'BrowserType', objects: [ - { _guid: 'Browser', objects: [ - { _guid: 'CDPSession', objects: [] }, - ] }, - ] }, - { _guid: 'BrowserType', objects: [] }, - { _guid: 'BrowserType', objects: [] }, - { _guid: 'Playwright', objects: [] }, - { _guid: 'Selectors', objects: [] }, - { _guid: 'Electron', objects: [] }, - ] - }); - - await session.detach(); - await expectScopeState(browserType, GOLDEN_PRECONDITION); - }); - - it('should scope browser handles', async({browserType, defaultBrowserOptions}) => { - const GOLDEN_PRECONDITION = { - _guid: '', - objects: [ - { _guid: 'BrowserType', objects: [ - { _guid: 'Browser', objects: [] } - ] }, - { _guid: 'BrowserType', objects: [] }, - { _guid: 'BrowserType', objects: [] }, - { _guid: 'Playwright', objects: [] }, - { _guid: 'Selectors', objects: [] }, - { _guid: 'Electron', objects: [] }, - ] - }; - await expectScopeState(browserType, GOLDEN_PRECONDITION); - - const browser = await browserType.launch(defaultBrowserOptions); - await browser.newContext(); - await expectScopeState(browserType, { - _guid: '', - objects: [ - { _guid: 'BrowserType', objects: [ - { _guid: 'Browser', objects: [ - { _guid: 'BrowserContext', objects: [] } - ] }, - { _guid: 'Browser', objects: [] }, - ] }, - { _guid: 'BrowserType', objects: [] }, - { _guid: 'BrowserType', objects: [] }, - { _guid: 'Playwright', objects: [] }, - { _guid: 'Selectors', objects: [] }, - { _guid: 'Electron', objects: [] }, - ] - }); - - await browser.close(); - await expectScopeState(browserType, GOLDEN_PRECONDITION); - }); -}); - -async function expectScopeState(object, golden) { - golden = trimGuids(golden); - const remoteState = trimGuids(await object._channel.debugScopeState()); - const localState = trimGuids(object._connection._debugScopeState()); - expect(localState).toEqual(golden); - expect(remoteState).toEqual(golden); -} - -function compareObjects(a, b) { - if (a._guid !== b._guid) - return a._guid.localeCompare(b._guid); - return a.objects.length - b.objects.length; -} - -function trimGuids(object) { - if (Array.isArray(object)) - return object.map(trimGuids).sort(compareObjects); - if (typeof object === 'object') { - const result = {}; - for (const key in object) - result[key] = trimGuids(object[key]); - return result; - } - if (typeof object === 'string') - return object ? object.match(/[^@]+/)[0] : ''; - return object; -} diff --git a/test/channels.spec.js b/test/channels.spec.js new file mode 100644 index 0000000000..9da7afd6d4 --- /dev/null +++ b/test/channels.spec.js @@ -0,0 +1,170 @@ +/** + * 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 { FFOX, CHROMIUM, WEBKIT, WIN, CHANNEL } = testOptions; + +it.skip(!CHANNEL)('should work', async({browser}) => { + expect(!!browser._connection).toBeTruthy(); +}); + +it.skip(!CHANNEL)('should scope context handles', async({browserType, browser, server}) => { + const GOLDEN_PRECONDITION = { + _guid: '', + objects: [ + { _guid: 'BrowserType', objects: [ + { _guid: 'Browser', objects: [] } + ] }, + { _guid: 'BrowserType', objects: [] }, + { _guid: 'BrowserType', objects: [] }, + { _guid: 'Playwright', objects: [] }, + { _guid: 'Selectors', objects: [] }, + { _guid: 'Electron', objects: [] }, + ] + }; + await expectScopeState(browser, GOLDEN_PRECONDITION); + + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await expectScopeState(browser, { + _guid: '', + objects: [ + { _guid: 'BrowserType', objects: [] }, + { _guid: 'BrowserType', objects: [] }, + { _guid: 'BrowserType', objects: [ + { _guid: 'Browser', objects: [ + { _guid: 'BrowserContext', objects: [ + { _guid: 'Frame', objects: [] }, + { _guid: 'Page', objects: [] }, + { _guid: 'Request', objects: [] }, + { _guid: 'Response', objects: [] }, + ]}, + ] }, + ] }, + { _guid: 'Playwright', objects: [] }, + { _guid: 'Selectors', objects: [] }, + { _guid: 'Electron', objects: [] }, + ] + }); + + await context.close(); + await expectScopeState(browser, GOLDEN_PRECONDITION); +}); + +it.skip(!CHANNEL || !CHROMIUM)('should scope CDPSession handles', async({browserType, browser, server}) => { + const GOLDEN_PRECONDITION = { + _guid: '', + objects: [ + { _guid: 'BrowserType', objects: [ + { _guid: 'Browser', objects: [] } + ] }, + { _guid: 'BrowserType', objects: [] }, + { _guid: 'BrowserType', objects: [] }, + { _guid: 'Playwright', objects: [] }, + { _guid: 'Selectors', objects: [] }, + { _guid: 'Electron', objects: [] }, + ] + }; + await expectScopeState(browserType, GOLDEN_PRECONDITION); + + const session = await browser.newBrowserCDPSession(); + await expectScopeState(browserType, { + _guid: '', + objects: [ + { _guid: 'BrowserType', objects: [ + { _guid: 'Browser', objects: [ + { _guid: 'CDPSession', objects: [] }, + ] }, + ] }, + { _guid: 'BrowserType', objects: [] }, + { _guid: 'BrowserType', objects: [] }, + { _guid: 'Playwright', objects: [] }, + { _guid: 'Selectors', objects: [] }, + { _guid: 'Electron', objects: [] }, + ] + }); + + await session.detach(); + await expectScopeState(browserType, GOLDEN_PRECONDITION); +}); + +it.skip(!CHANNEL)('should scope browser handles', async({browserType, defaultBrowserOptions}) => { + const GOLDEN_PRECONDITION = { + _guid: '', + objects: [ + { _guid: 'BrowserType', objects: [ + { _guid: 'Browser', objects: [] } + ] }, + { _guid: 'BrowserType', objects: [] }, + { _guid: 'BrowserType', objects: [] }, + { _guid: 'Playwright', objects: [] }, + { _guid: 'Selectors', objects: [] }, + { _guid: 'Electron', objects: [] }, + ] + }; + await expectScopeState(browserType, GOLDEN_PRECONDITION); + + const browser = await browserType.launch(defaultBrowserOptions); + await browser.newContext(); + await expectScopeState(browserType, { + _guid: '', + objects: [ + { _guid: 'BrowserType', objects: [ + { _guid: 'Browser', objects: [ + { _guid: 'BrowserContext', objects: [] } + ] }, + { _guid: 'Browser', objects: [] }, + ] }, + { _guid: 'BrowserType', objects: [] }, + { _guid: 'BrowserType', objects: [] }, + { _guid: 'Playwright', objects: [] }, + { _guid: 'Selectors', objects: [] }, + { _guid: 'Electron', objects: [] }, + ] + }); + + await browser.close(); + await expectScopeState(browserType, GOLDEN_PRECONDITION); +}); + +async function expectScopeState(object, golden) { + golden = trimGuids(golden); + const remoteState = trimGuids(await object._channel.debugScopeState()); + const localState = trimGuids(object._connection._debugScopeState()); + expect(localState).toEqual(golden); + expect(remoteState).toEqual(golden); +} + +function compareObjects(a, b) { + if (a._guid !== b._guid) + return a._guid.localeCompare(b._guid); + return a.objects.length - b.objects.length; +} + +function trimGuids(object) { + if (Array.isArray(object)) + return object.map(trimGuids).sort(compareObjects); + if (typeof object === 'object') { + const result = {}; + for (const key in object) + result[key] = trimGuids(object[key]); + return result; + } + if (typeof object === 'string') + return object ? object.match(/[^@]+/)[0] : ''; + return object; +} diff --git a/test/check.jest.js b/test/check.jest.js deleted file mode 100644 index e83626a423..0000000000 --- a/test/check.jest.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * 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. - */ - -describe('Page.check', function() { - it('should check the box', async({page}) => { - await page.setContent(``); - await page.check('input'); - expect(await page.evaluate(() => checkbox.checked)).toBe(true); - }); - it('should not check the checked box', async({page}) => { - await page.setContent(``); - await page.check('input'); - expect(await page.evaluate(() => checkbox.checked)).toBe(true); - }); - it('should uncheck the box', async({page}) => { - await page.setContent(``); - await page.uncheck('input'); - expect(await page.evaluate(() => checkbox.checked)).toBe(false); - }); - it('should not uncheck the unchecked box', async({page}) => { - await page.setContent(``); - await page.uncheck('input'); - expect(await page.evaluate(() => checkbox.checked)).toBe(false); - }); - it('should check the box by label', async({page}) => { - await page.setContent(``); - await page.check('label'); - expect(await page.evaluate(() => checkbox.checked)).toBe(true); - }); - it('should check the box outside label', async({page}) => { - await page.setContent(`
`); - await page.check('label'); - expect(await page.evaluate(() => checkbox.checked)).toBe(true); - }); - it('should check the box inside label w/o id', async({page}) => { - await page.setContent(``); - await page.check('label'); - expect(await page.evaluate(() => checkbox.checked)).toBe(true); - }); - it('should check radio', async({page}) => { - await page.setContent(` - one - two - three`); - await page.check('#two'); - expect(await page.evaluate(() => two.checked)).toBe(true); - }); - it('should check the box by aria role', async({page}) => { - await page.setContent(` - `); - await page.check('div'); - expect(await page.evaluate(() => checkbox.getAttribute('aria-checked'))).toBe('true'); - }); -}); diff --git a/test/check.spec.js b/test/check.spec.js new file mode 100644 index 0000000000..65cab858f7 --- /dev/null +++ b/test/check.spec.js @@ -0,0 +1,76 @@ +/** + * 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. + */ + +it('should check the box', async({page}) => { + await page.setContent(``); + await page.check('input'); + expect(await page.evaluate(() => checkbox.checked)).toBe(true); +}); + +it('should not check the checked box', async({page}) => { + await page.setContent(``); + await page.check('input'); + expect(await page.evaluate(() => checkbox.checked)).toBe(true); +}); + +it('should uncheck the box', async({page}) => { + await page.setContent(``); + await page.uncheck('input'); + expect(await page.evaluate(() => checkbox.checked)).toBe(false); +}); + +it('should not uncheck the unchecked box', async({page}) => { + await page.setContent(``); + await page.uncheck('input'); + expect(await page.evaluate(() => checkbox.checked)).toBe(false); +}); + +it('should check the box by label', async({page}) => { + await page.setContent(``); + await page.check('label'); + expect(await page.evaluate(() => checkbox.checked)).toBe(true); +}); + +it('should check the box outside label', async({page}) => { + await page.setContent(`
`); + await page.check('label'); + expect(await page.evaluate(() => checkbox.checked)).toBe(true); +}); + +it('should check the box inside label w/o id', async({page}) => { + await page.setContent(``); + await page.check('label'); + expect(await page.evaluate(() => checkbox.checked)).toBe(true); +}); + +it('should check radio', async({page}) => { + await page.setContent(` + one + two + three`); + await page.check('#two'); + expect(await page.evaluate(() => two.checked)).toBe(true); +}); + +it('should check the box by aria role', async({page}) => { + await page.setContent(` + `); + await page.check('div'); + expect(await page.evaluate(() => checkbox.getAttribute('aria-checked'))).toBe('true'); +}); diff --git a/test/chromium-css-coverage.spec.js b/test/chromium-css-coverage.spec.js new file mode 100644 index 0000000000..42677d0649 --- /dev/null +++ b/test/chromium-css-coverage.spec.js @@ -0,0 +1,117 @@ +/** + * 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 {FFOX, CHROMIUM, WEBKIT} = testOptions; + +it.skip(!CHROMIUM)('should work', async function({browserType, page, server}) { + await page.coverage.startCSSCoverage(); + await page.goto(server.PREFIX + '/csscoverage/simple.html'); + const coverage = await page.coverage.stopCSSCoverage(); + expect(coverage.length).toBe(1); + expect(coverage[0].url).toContain('/csscoverage/simple.html'); + expect(coverage[0].ranges).toEqual([ + {start: 1, end: 22} + ]); + const range = coverage[0].ranges[0]; + expect(coverage[0].text.substring(range.start, range.end)).toBe('div { color: green; }'); +}); + +it.skip(!CHROMIUM)('should report sourceURLs', async function({page, server}) { + await page.coverage.startCSSCoverage(); + await page.goto(server.PREFIX + '/csscoverage/sourceurl.html'); + const coverage = await page.coverage.stopCSSCoverage(); + expect(coverage.length).toBe(1); + expect(coverage[0].url).toBe('nicename.css'); +}); + +it.skip(!CHROMIUM)('should report multiple stylesheets', async function({page, server}) { + await page.coverage.startCSSCoverage(); + await page.goto(server.PREFIX + '/csscoverage/multiple.html'); + const coverage = await page.coverage.stopCSSCoverage(); + expect(coverage.length).toBe(2); + coverage.sort((a, b) => a.url.localeCompare(b.url)); + expect(coverage[0].url).toContain('/csscoverage/stylesheet1.css'); + expect(coverage[1].url).toContain('/csscoverage/stylesheet2.css'); +}); + +it.skip(!CHROMIUM)('should report stylesheets that have no coverage', async function({page, server}) { + await page.coverage.startCSSCoverage(); + await page.goto(server.PREFIX + '/csscoverage/unused.html'); + const coverage = await page.coverage.stopCSSCoverage(); + expect(coverage.length).toBe(1); + expect(coverage[0].url).toBe('unused.css'); + expect(coverage[0].ranges.length).toBe(0); +}); + +it.skip(!CHROMIUM)('should work with media queries', async function({page, server}) { + await page.coverage.startCSSCoverage(); + await page.goto(server.PREFIX + '/csscoverage/media.html'); + const coverage = await page.coverage.stopCSSCoverage(); + expect(coverage.length).toBe(1); + expect(coverage[0].url).toContain('/csscoverage/media.html'); + expect(coverage[0].ranges).toEqual([ + {start: 17, end: 38} + ]); +}); + +it.skip(!CHROMIUM)('should work with complicated usecases', async function({page, server}) { + await page.coverage.startCSSCoverage(); + await page.goto(server.PREFIX + '/csscoverage/involved.html'); + const coverage = await page.coverage.stopCSSCoverage(); + expect(JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':/')).toMatchSnapshot(); +}); + +it.skip(!CHROMIUM)('should ignore injected stylesheets', async function({page, server}) { + await page.coverage.startCSSCoverage(); + await page.addStyleTag({content: 'body { margin: 10px;}'}); + // trigger style recalc + const margin = await page.evaluate(() => window.getComputedStyle(document.body).margin); + expect(margin).toBe('10px'); + const coverage = await page.coverage.stopCSSCoverage(); + expect(coverage.length).toBe(0); +}); + +it.skip(!CHROMIUM)('should report stylesheets across navigations', async function({page, server}) { + await page.coverage.startCSSCoverage({resetOnNavigation: false}); + await page.goto(server.PREFIX + '/csscoverage/multiple.html'); + await page.goto(server.EMPTY_PAGE); + const coverage = await page.coverage.stopCSSCoverage(); + expect(coverage.length).toBe(2); +}); + +it.skip(!CHROMIUM)('should NOT report scripts across navigations', async function({page, server}) { + await page.coverage.startCSSCoverage(); // Enabled by default. + await page.goto(server.PREFIX + '/csscoverage/multiple.html'); + await page.goto(server.EMPTY_PAGE); + const coverage = await page.coverage.stopCSSCoverage(); + expect(coverage.length).toBe(0); +}); + +it.skip(!CHROMIUM)('should work with a recently loaded stylesheet', async function({page, server}) { + await page.coverage.startCSSCoverage(); + await page.evaluate(async url => { + document.body.textContent = 'hello, world'; + + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = url; + document.head.appendChild(link); + await new Promise(x => link.onload = x); + await new Promise(f => requestAnimationFrame(f)); + }, server.PREFIX + '/csscoverage/stylesheet1.css'); + const coverage = await page.coverage.stopCSSCoverage(); + expect(coverage.length).toBe(1); +}); diff --git a/test/chromium-js-coverage.spec.js b/test/chromium-js-coverage.spec.js new file mode 100644 index 0000000000..3d34b9383f --- /dev/null +++ b/test/chromium-js-coverage.spec.js @@ -0,0 +1,97 @@ +/** + * 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 {FFOX, CHROMIUM, WEBKIT} = testOptions; + +it.skip(CHROMIUM)('should be missing', async function({page, server}) { + expect(page.coverage).toBe(null); +}); + +it.skip(!CHROMIUM)('should work', async function({browserType, page, server}) { + await page.coverage.startJSCoverage(); + await page.goto(server.PREFIX + '/jscoverage/simple.html', { waitUntil: 'load' }); + const coverage = await page.coverage.stopJSCoverage(); + expect(coverage.length).toBe(1); + expect(coverage[0].url).toContain('/jscoverage/simple.html'); + expect(coverage[0].functions.find(f => f.functionName === 'foo').ranges[0].count).toEqual(1); +}); + +it.skip(!CHROMIUM)('should report sourceURLs', async function({page, server}) { + await page.coverage.startJSCoverage(); + await page.goto(server.PREFIX + '/jscoverage/sourceurl.html'); + const coverage = await page.coverage.stopJSCoverage(); + expect(coverage.length).toBe(1); + expect(coverage[0].url).toBe('nicename.js'); +}); + +it.skip(!CHROMIUM)('should ignore eval() scripts by default', async function({page, server}) { + await page.coverage.startJSCoverage(); + await page.goto(server.PREFIX + '/jscoverage/eval.html'); + const coverage = await page.coverage.stopJSCoverage(); + expect(coverage.length).toBe(1); +}); + +it.skip(!CHROMIUM)('shouldn\'t ignore eval() scripts if reportAnonymousScripts is true', async function({page, server}) { + await page.coverage.startJSCoverage({reportAnonymousScripts: true}); + await page.goto(server.PREFIX + '/jscoverage/eval.html'); + const coverage = await page.coverage.stopJSCoverage(); + expect(coverage.find(entry => entry.url.startsWith('debugger://'))).not.toBe(null); + expect(coverage.length).toBe(2); +}); + +it.skip(!CHROMIUM)('should ignore playwright internal scripts if reportAnonymousScripts is true', async function({page, server}) { + await page.coverage.startJSCoverage({reportAnonymousScripts: true}); + await page.goto(server.EMPTY_PAGE); + await page.evaluate('console.log("foo")'); + await page.evaluate(() => console.log('bar')); + const coverage = await page.coverage.stopJSCoverage(); + expect(coverage.length).toBe(0); +}); + +it.skip(!CHROMIUM)('should report multiple scripts', async function({page, server}) { + await page.coverage.startJSCoverage(); + await page.goto(server.PREFIX + '/jscoverage/multiple.html'); + const coverage = await page.coverage.stopJSCoverage(); + expect(coverage.length).toBe(2); + coverage.sort((a, b) => a.url.localeCompare(b.url)); + expect(coverage[0].url).toContain('/jscoverage/script1.js'); + expect(coverage[1].url).toContain('/jscoverage/script2.js'); +}); + +it.skip(!CHROMIUM)('should report scripts across navigations when disabled', async function({page, server}) { + await page.coverage.startJSCoverage({resetOnNavigation: false}); + await page.goto(server.PREFIX + '/jscoverage/multiple.html'); + await page.goto(server.EMPTY_PAGE); + const coverage = await page.coverage.stopJSCoverage(); + expect(coverage.length).toBe(2); +}); + +it.skip(!CHROMIUM)('should NOT report scripts across navigations when enabled', async function({page, server}) { + await page.coverage.startJSCoverage(); // Enabled by default. + await page.goto(server.PREFIX + '/jscoverage/multiple.html'); + await page.goto(server.EMPTY_PAGE); + const coverage = await page.coverage.stopJSCoverage(); + expect(coverage.length).toBe(0); +}); + +it.skip(!CHROMIUM)('should not hang when there is a debugger statement', async function({page, server}) { + await page.coverage.startJSCoverage(); + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => { + debugger; // eslint-disable-line no-debugger + }); + await page.coverage.stopJSCoverage(); +}); diff --git a/test/click-timeout-1.jest.js b/test/click-timeout-1.jest.js deleted file mode 100644 index 24f04d4442..0000000000 --- a/test/click-timeout-1.jest.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * 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 {USES_HOOKS} = testOptions; - -describe('Page.click', function() { - it.skip(USES_HOOKS)('should avoid side effects after timeout', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const error = await page.click('button', { timeout: 2000, __testHookBeforePointerAction: () => new Promise(f => setTimeout(f, 2500))}).catch(e => e); - await page.waitForTimeout(5000); // Give it some time to click after the test hook is done waiting. - expect(await page.evaluate(() => result)).toBe('Was not clicked'); - expect(error.message).toContain('page.click: Timeout 2000ms exceeded.'); - }); - it('should timeout waiting for button to be enabled', async({page, server}) => { - await page.setContent(''); - const error = await page.click('text=Click target', { timeout: 3000 }).catch(e => e); - expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); - expect(error.message).toContain('page.click: Timeout 3000ms exceeded.'); - expect(error.message).toContain('element is disabled - waiting'); - }); -}); diff --git a/test/click-timeout-1.spec.js b/test/click-timeout-1.spec.js new file mode 100644 index 0000000000..00e1321cd9 --- /dev/null +++ b/test/click-timeout-1.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 {USES_HOOKS} = testOptions; + +it.skip(USES_HOOKS)('should avoid side effects after timeout', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const error = await page.click('button', { timeout: 2000, __testHookBeforePointerAction: () => new Promise(f => setTimeout(f, 2500))}).catch(e => e); + await page.waitForTimeout(5000); // Give it some time to click after the test hook is done waiting. + expect(await page.evaluate(() => result)).toBe('Was not clicked'); + expect(error.message).toContain('page.click: Timeout 2000ms exceeded.'); +}); + +it('should timeout waiting for button to be enabled', async({page, server}) => { + await page.setContent(''); + const error = await page.click('text=Click target', { timeout: 3000 }).catch(e => e); + expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); + expect(error.message).toContain('page.click: Timeout 3000ms exceeded.'); + expect(error.message).toContain('element is disabled - waiting'); +}); diff --git a/test/click-timeout-2.jest.js b/test/click-timeout-2.jest.js deleted file mode 100644 index cd643abfa8..0000000000 --- a/test/click-timeout-2.jest.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * 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 {USES_HOOKS} = testOptions; - -describe('Page.click', function() { - it('should timeout waiting for display:none to be gone', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', b => b.style.display = 'none'); - const error = await page.click('button', { timeout: 5000 }).catch(e => e); - expect(error.message).toContain('page.click: Timeout 5000ms exceeded.'); - expect(error.message).toContain('waiting for element to be visible, enabled and not moving'); - expect(error.message).toContain('element is not visible - waiting'); - }); - it('should timeout waiting for visbility:hidden to be gone', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', b => b.style.visibility = 'hidden'); - const error = await page.click('button', { timeout: 5000 }).catch(e => e); - expect(error.message).toContain('page.click: Timeout 5000ms exceeded.'); - expect(error.message).toContain('waiting for element to be visible, enabled and not moving'); - expect(error.message).toContain('element is not visible - waiting'); - }); -}); diff --git a/test/click-timeout-2.spec.js b/test/click-timeout-2.spec.js new file mode 100644 index 0000000000..c0cc6d2194 --- /dev/null +++ b/test/click-timeout-2.spec.js @@ -0,0 +1,36 @@ +/** + * 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 {USES_HOOKS} = testOptions; + +it('should timeout waiting for display:none to be gone', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', b => b.style.display = 'none'); + const error = await page.click('button', { timeout: 5000 }).catch(e => e); + expect(error.message).toContain('page.click: Timeout 5000ms exceeded.'); + expect(error.message).toContain('waiting for element to be visible, enabled and not moving'); + expect(error.message).toContain('element is not visible - waiting'); +}); + +it('should timeout waiting for visbility:hidden to be gone', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', b => b.style.visibility = 'hidden'); + const error = await page.click('button', { timeout: 5000 }).catch(e => e); + expect(error.message).toContain('page.click: Timeout 5000ms exceeded.'); + expect(error.message).toContain('waiting for element to be visible, enabled and not moving'); + expect(error.message).toContain('element is not visible - waiting'); +}); diff --git a/test/click-timeout-3.jest.js b/test/click-timeout-3.jest.js deleted file mode 100644 index f77e3ead3f..0000000000 --- a/test/click-timeout-3.jest.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * 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 {USES_HOOKS} = testOptions; - -describe('Page.click', function() { - it.skip(USES_HOOKS)('should fail when element jumps during hit testing', async({page, server}) => { - await page.setContent(''); - let clicked = false; - const handle = await page.$('button'); - const __testHookBeforeHitTarget = () => page.evaluate(() => { - const margin = parseInt(document.querySelector('button').style.marginLeft || 0) + 100; - document.querySelector('button').style.marginLeft = margin + 'px'; - }); - const promise = handle.click({ timeout: 5000, __testHookBeforeHitTarget }).then(() => clicked = true).catch(e => e); - const error = await promise; - expect(clicked).toBe(false); - expect(await page.evaluate(() => window.clicked)).toBe(undefined); - expect(error.message).toContain('elementHandle.click: Timeout 5000ms exceeded.'); - expect(error.message).toContain('element does not receive pointer events'); - expect(error.message).toContain('retrying click action'); - }); - it('should timeout waiting for hit target', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await page.evaluate(() => { - document.body.style.position = 'relative'; - const blocker = document.createElement('div'); - blocker.style.position = 'absolute'; - blocker.style.width = '400px'; - blocker.style.height = '20px'; - blocker.style.left = '0'; - blocker.style.top = '0'; - document.body.appendChild(blocker); - }); - const error = await button.click({ timeout: 5000 }).catch(e => e); - expect(error.message).toContain('elementHandle.click: Timeout 5000ms exceeded.'); - expect(error.message).toContain('element does not receive pointer events'); - expect(error.message).toContain('retrying click action'); - }); -}); diff --git a/test/click-timeout-3.spec.js b/test/click-timeout-3.spec.js new file mode 100644 index 0000000000..1fa192a576 --- /dev/null +++ b/test/click-timeout-3.spec.js @@ -0,0 +1,54 @@ +/** + * 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 {USES_HOOKS} = testOptions; + +it.skip(USES_HOOKS)('should fail when element jumps during hit testing', async({page, server}) => { + await page.setContent(''); + let clicked = false; + const handle = await page.$('button'); + const __testHookBeforeHitTarget = () => page.evaluate(() => { + const margin = parseInt(document.querySelector('button').style.marginLeft || 0) + 100; + document.querySelector('button').style.marginLeft = margin + 'px'; + }); + const promise = handle.click({ timeout: 5000, __testHookBeforeHitTarget }).then(() => clicked = true).catch(e => e); + const error = await promise; + expect(clicked).toBe(false); + expect(await page.evaluate(() => window.clicked)).toBe(undefined); + expect(error.message).toContain('elementHandle.click: Timeout 5000ms exceeded.'); + expect(error.message).toContain('element does not receive pointer events'); + expect(error.message).toContain('retrying click action'); +}); + +it('should timeout waiting for hit target', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(() => { + document.body.style.position = 'relative'; + const blocker = document.createElement('div'); + blocker.style.position = 'absolute'; + blocker.style.width = '400px'; + blocker.style.height = '20px'; + blocker.style.left = '0'; + blocker.style.top = '0'; + document.body.appendChild(blocker); + }); + const error = await button.click({ timeout: 5000 }).catch(e => e); + expect(error.message).toContain('elementHandle.click: Timeout 5000ms exceeded.'); + expect(error.message).toContain('element does not receive pointer events'); + expect(error.message).toContain('retrying click action'); +}); diff --git a/test/click-timeout-4.jest.js b/test/click-timeout-4.jest.js deleted file mode 100644 index 825cbef068..0000000000 --- a/test/click-timeout-4.jest.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 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 {USES_HOOKS} = testOptions; - -describe('Page.click', function() { - it('should timeout waiting for stable position', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await button.evaluate(button => { - button.style.transition = 'margin 5s linear 0s'; - button.style.marginLeft = '200px'; - }); - const error = await button.click({ timeout: 5000 }).catch(e => e); - expect(error.message).toContain('elementHandle.click: Timeout 5000ms exceeded.'); - expect(error.message).toContain('waiting for element to be visible, enabled and not moving'); - expect(error.message).toContain('element is moving - waiting'); - }); -}); diff --git a/test/click-timeout-4.spec.js b/test/click-timeout-4.spec.js new file mode 100644 index 0000000000..7b83fa3fcb --- /dev/null +++ b/test/click-timeout-4.spec.js @@ -0,0 +1,29 @@ +/** + * 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. + */ + +it('should timeout waiting for stable position', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await button.evaluate(button => { + button.style.transition = 'margin 5s linear 0s'; + button.style.marginLeft = '200px'; + }); + const error = await button.click({ timeout: 5000 }).catch(e => e); + expect(error.message).toContain('elementHandle.click: Timeout 5000ms exceeded.'); + expect(error.message).toContain('waiting for element to be visible, enabled and not moving'); + expect(error.message).toContain('element is moving - waiting'); +}); diff --git a/test/click-timeout-5.jest.js b/test/click-timeout-5.jest.js deleted file mode 100644 index bb523306bd..0000000000 --- a/test/click-timeout-5.jest.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * 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 {USES_HOOKS} = testOptions; - -describe('Page.click', function() { - it.fail(true)('should report that selector does not match anymore', async ({page, server}) => { - await page.goto(server.PREFIX + '/react.html'); - await page.evaluate(() => { - renderComponent(e('div', {}, [e(MyButton, { name: 'button1' }), e(MyButton, { name: 'button2' })] )); - }); - const __testHookAfterStable = () => page.evaluate(() => { - window.counter = (window.counter || 0) + 1; - if (window.counter === 1) - renderComponent(e('div', {}, [e(MyButton, { name: 'button2' }), e(MyButton, { name: 'button1' })] )); - else - renderComponent(e('div', {}, [])); - }); - const error = await page.dblclick('text=button1', { __testHookAfterStable, timeout: 3000 }).catch(e => e); - expect(await page.evaluate(() => window.button1)).toBe(undefined); - expect(await page.evaluate(() => window.button2)).toBe(undefined); - expect(error.message).toContain('page.dblclick: Timeout 3000ms exceeded.'); - expect(error.message).toContain('element does not match the selector anymore'); - }); - it.fail(true)('should not retarget the handle when element is recycled', async ({page, server}) => { - await page.goto(server.PREFIX + '/react.html'); - await page.evaluate(() => { - renderComponent(e('div', {}, [e(MyButton, { name: 'button1' }), e(MyButton, { name: 'button2', disabled: true })] )); - }); - const __testHookBeforeStable = () => page.evaluate(() => { - window.counter = (window.counter || 0) + 1; - if (window.counter === 1) - renderComponent(e('div', {}, [e(MyButton, { name: 'button2', disabled: true }), e(MyButton, { name: 'button1' })] )); - }); - const handle = await page.$('text=button1'); - const error = await handle.click({ __testHookBeforeStable, timeout: 3000 }).catch(e => e); - expect(await page.evaluate(() => window.button1)).toBe(undefined); - expect(await page.evaluate(() => window.button2)).toBe(undefined); - expect(error.message).toContain('elementHandle.click: Timeout 3000ms exceeded.'); - expect(error.message).toContain('element is disabled - waiting'); - }); - it('should timeout when click opens alert', async({page, server}) => { - const dialogPromise = page.waitForEvent('dialog'); - await page.setContent(`
Click me
`); - const error = await page.click('div', { timeout: 3000 }).catch(e => e); - expect(error.message).toContain('page.click: Timeout 3000ms exceeded.'); - const dialog = await dialogPromise; - await dialog.dismiss(); - }); -}); diff --git a/test/click-timeout-5.spec.js b/test/click-timeout-5.spec.js new file mode 100644 index 0000000000..25e8b3ac09 --- /dev/null +++ b/test/click-timeout-5.spec.js @@ -0,0 +1,62 @@ +/** + * 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. + */ + +it.fail(true)('should report that selector does not match anymore', async ({page, server}) => { + await page.goto(server.PREFIX + '/react.html'); + await page.evaluate(() => { + renderComponent(e('div', {}, [e(MyButton, { name: 'button1' }), e(MyButton, { name: 'button2' })] )); + }); + const __testHookAfterStable = () => page.evaluate(() => { + window.counter = (window.counter || 0) + 1; + if (window.counter === 1) + renderComponent(e('div', {}, [e(MyButton, { name: 'button2' }), e(MyButton, { name: 'button1' })] )); + else + renderComponent(e('div', {}, [])); + }); + const error = await page.dblclick('text=button1', { __testHookAfterStable, timeout: 3000 }).catch(e => e); + expect(await page.evaluate(() => window.button1)).toBe(undefined); + expect(await page.evaluate(() => window.button2)).toBe(undefined); + expect(error.message).toContain('page.dblclick: Timeout 3000ms exceeded.'); + expect(error.message).toContain('element does not match the selector anymore'); +}); + +it.fail(true)('should not retarget the handle when element is recycled', async ({page, server}) => { + await page.goto(server.PREFIX + '/react.html'); + await page.evaluate(() => { + renderComponent(e('div', {}, [e(MyButton, { name: 'button1' }), e(MyButton, { name: 'button2', disabled: true })] )); + }); + const __testHookBeforeStable = () => page.evaluate(() => { + window.counter = (window.counter || 0) + 1; + if (window.counter === 1) + renderComponent(e('div', {}, [e(MyButton, { name: 'button2', disabled: true }), e(MyButton, { name: 'button1' })] )); + }); + const handle = await page.$('text=button1'); + const error = await handle.click({ __testHookBeforeStable, timeout: 3000 }).catch(e => e); + expect(await page.evaluate(() => window.button1)).toBe(undefined); + expect(await page.evaluate(() => window.button2)).toBe(undefined); + expect(error.message).toContain('elementHandle.click: Timeout 3000ms exceeded.'); + expect(error.message).toContain('element is disabled - waiting'); +}); + +it('should timeout when click opens alert', async({page, server}) => { + const dialogPromise = page.waitForEvent('dialog'); + await page.setContent(`
Click me
`); + const error = await page.click('div', { timeout: 3000 }).catch(e => e); + expect(error.message).toContain('page.click: Timeout 3000ms exceeded.'); + const dialog = await dialogPromise; + await dialog.dismiss(); +}); diff --git a/test/click.jest.js b/test/click.jest.js deleted file mode 100644 index 976a10191d..0000000000 --- a/test/click.jest.js +++ /dev/null @@ -1,728 +0,0 @@ -/** - * 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 utils = require('./utils'); -const {FFOX, CHROMIUM, WEBKIT, HEADLESS, USES_HOOKS} = testOptions; - -async function giveItAChanceToClick(page) { - for (let i = 0; i < 5; i++) - await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)))); -} - -describe('Page.click', function() { - it('should click the button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should click svg', async({page, server}) => { - await page.setContent(` - - - - `); - await page.click('circle'); - expect(await page.evaluate(() => window.__CLICKED)).toBe(42); - }); - it('should click the button if window.Node is removed', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.evaluate(() => delete window.Node); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/4281 - it('should click on a span with an inline element inside', async({page, server}) => { - await page.setContent(` - - - `); - await page.click('span'); - expect(await page.evaluate(() => window.CLICKED)).toBe(42); - }); - it('should not throw UnhandledPromiseRejection when page closes', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await Promise.all([ - page.close(), - page.mouse.click(1, 2), - ]).catch(e => {}); - await context.close(); - }); - it('should click the button after navigation ', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button'); - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should click the button after a cross origin navigation ', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button'); - await page.goto(server.CROSS_PROCESS_PREFIX + '/input/button.html'); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should click with disabled javascript', async({browser, server}) => { - const context = await browser.newContext({ javaScriptEnabled: false }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/wrappedlink.html'); - await Promise.all([ - page.click('a'), - page.waitForNavigation() - ]); - expect(page.url()).toBe(server.PREFIX + '/wrappedlink.html#clicked'); - await context.close(); - }); - it('should click when one of inline box children is outside of viewport', async({page, server}) => { - await page.setContent(` - - woofdoggo - `); - await page.click('span'); - expect(await page.evaluate(() => window.CLICKED)).toBe(42); - }); - it('should select the text by triple clicking', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - const text = 'This is the text that we are going to try to select. Let\'s see how it goes.'; - await page.fill('textarea', text); - await page.click('textarea', { clickCount: 3 }); - expect(await page.evaluate(() => { - const textarea = document.querySelector('textarea'); - return textarea.value.substring(textarea.selectionStart, textarea.selectionEnd); - })).toBe(text); - }); - it('should click offscreen buttons', async({page, server}) => { - await page.goto(server.PREFIX + '/offscreenbuttons.html'); - const messages = []; - page.on('console', msg => messages.push(msg.text())); - for (let i = 0; i < 11; ++i) { - // We might've scrolled to click a button - reset to (0, 0). - await page.evaluate(() => window.scrollTo(0, 0)); - await page.click(`#btn${i}`); - } - expect(messages).toEqual([ - 'button #0 clicked', - 'button #1 clicked', - 'button #2 clicked', - 'button #3 clicked', - 'button #4 clicked', - 'button #5 clicked', - 'button #6 clicked', - 'button #7 clicked', - 'button #8 clicked', - 'button #9 clicked', - 'button #10 clicked' - ]); - }); - - it('should waitFor visible when already visible', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should not wait with force', async({page, server}) => { - let error = null; - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', b => b.style.display = 'none'); - await page.click('button', { force: true }).catch(e => error = e); - expect(error.message).toContain('Element is not visible'); - expect(await page.evaluate(() => result)).toBe('Was not clicked'); - }); - it('should waitFor display:none to be gone', async({page, server}) => { - let done = false; - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', b => b.style.display = 'none'); - const clicked = page.click('button', { timeout: 0 }).then(() => done = true); - await giveItAChanceToClick(page); - expect(await page.evaluate(() => result)).toBe('Was not clicked'); - expect(done).toBe(false); - await page.$eval('button', b => b.style.display = 'block'); - await clicked; - expect(done).toBe(true); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should waitFor visibility:hidden to be gone', async({page, server}) => { - let done = false; - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', b => b.style.visibility = 'hidden'); - const clicked = page.click('button', { timeout: 0 }).then(() => done = true); - await giveItAChanceToClick(page); - expect(await page.evaluate(() => result)).toBe('Was not clicked'); - expect(done).toBe(false); - await page.$eval('button', b => b.style.visibility = 'visible'); - await clicked; - expect(done).toBe(true); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should waitFor visible when parent is hidden', async({page, server}) => { - let done = false; - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', b => b.parentElement.style.display = 'none'); - const clicked = page.click('button', { timeout: 0 }).then(() => done = true); - await giveItAChanceToClick(page); - expect(done).toBe(false); - await page.$eval('button', b => b.parentElement.style.display = 'block'); - await clicked; - expect(done).toBe(true); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - - it('should click wrapped links', async({page, server}) => { - await page.goto(server.PREFIX + '/wrappedlink.html'); - await page.click('a'); - expect(await page.evaluate(() => window.__clicked)).toBe(true); - }); - - it('should click on checkbox input and toggle', async({page, server}) => { - await page.goto(server.PREFIX + '/input/checkbox.html'); - expect(await page.evaluate(() => result.check)).toBe(null); - await page.click('input#agree'); - expect(await page.evaluate(() => result.check)).toBe(true); - expect(await page.evaluate(() => result.events)).toEqual([ - 'mouseover', - 'mouseenter', - 'mousemove', - 'mousedown', - 'mouseup', - 'click', - 'input', - 'change', - ]); - await page.click('input#agree'); - expect(await page.evaluate(() => result.check)).toBe(false); - }); - - it('should click on checkbox label and toggle', async({page, server}) => { - await page.goto(server.PREFIX + '/input/checkbox.html'); - expect(await page.evaluate(() => result.check)).toBe(null); - await page.click('label[for="agree"]'); - expect(await page.evaluate(() => result.check)).toBe(true); - expect(await page.evaluate(() => result.events)).toEqual([ - 'click', - 'input', - 'change', - ]); - await page.click('label[for="agree"]'); - expect(await page.evaluate(() => result.check)).toBe(false); - }); - it('should not hang with touch-enabled viewports', async({browser, playwright}) => { - // @see https://github.com/GoogleChrome/puppeteer/issues/161 - const { viewport, hasTouch } = playwright.devices['iPhone 6']; - const context = await browser.newContext({ viewport, hasTouch }); - const page = await context.newPage(); - await page.mouse.down(); - await page.mouse.move(100, 10); - await page.mouse.up(); - await context.close(); - }); - it('should scroll and click the button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/scrollable.html'); - await page.click('#button-5'); - expect(await page.evaluate(() => document.querySelector('#button-5').textContent)).toBe('clicked'); - await page.click('#button-80'); - expect(await page.evaluate(() => document.querySelector('#button-80').textContent)).toBe('clicked'); - }); - it('should double click the button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.evaluate(() => { - window.double = false; - const button = document.querySelector('button'); - button.addEventListener('dblclick', event => { - window.double = true; - }); - }); - await page.dblclick('button'); - expect(await page.evaluate('double')).toBe(true); - expect(await page.evaluate('result')).toBe('Clicked'); - }); - it('should click a partially obscured button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.evaluate(() => { - const button = document.querySelector('button'); - button.textContent = 'Some really long text that will go offscreen'; - button.style.position = 'absolute'; - button.style.left = '368px'; - }); - await page.click('button'); - expect(await page.evaluate(() => window.result)).toBe('Clicked'); - }); - it('should click a rotated button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/rotatedButton.html'); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should fire contextmenu event on right click', async({page, server}) => { - await page.goto(server.PREFIX + '/input/scrollable.html'); - await page.click('#button-8', {button: 'right'}); - expect(await page.evaluate(() => document.querySelector('#button-8').textContent)).toBe('context menu'); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/206 - it('should click links which cause navigation', async({page, server}) => { - await page.setContent(`empty.html`); - // This await should not hang. - await page.click('a'); - }); - it('should click the button inside an iframe', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent('
spacer
'); - await utils.attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); - const frame = page.frames()[1]; - const button = await frame.$('button'); - await button.click(); - expect(await frame.evaluate(() => window.result)).toBe('Clicked'); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/4110 - // @see https://bugs.chromium.org/p/chromium/issues/detail?id=986390 - // @see https://chromium-review.googlesource.com/c/chromium/src/+/1742784 - it.fail(CHROMIUM || WEBKIT)('should click the button with fixed position inside an iframe', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setViewportSize({width: 500, height: 500}); - await page.setContent('
spacer
'); - await utils.attachFrame(page, 'button-test', server.CROSS_PROCESS_PREFIX + '/input/button.html'); - const frame = page.frames()[1]; - await frame.$eval('button', button => button.style.setProperty('position', 'fixed')); - await frame.click('button'); - expect(await frame.evaluate(() => window.result)).toBe('Clicked'); - }); - it('should click the button with deviceScaleFactor set', async({browser, server}) => { - const context = await browser.newContext({ viewport: { width: 400, height: 400 }, deviceScaleFactor: 5 }); - const page = await context.newPage(); - expect(await page.evaluate(() => window.devicePixelRatio)).toBe(5); - await page.setContent('
spacer
'); - await utils.attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); - const frame = page.frames()[1]; - const button = await frame.$('button'); - await button.click(); - expect(await frame.evaluate(() => window.result)).toBe('Clicked'); - await context.close(); - }); - it('should click the button with px border with offset', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', button => button.style.borderWidth = '8px'); - await page.click('button', { position: { x: 20, y: 10 } }); - expect(await page.evaluate(() => result)).toBe('Clicked'); - // Safari reports border-relative offsetX/offsetY. - expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 20 + 8 : 20); - expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 10 + 8 : 10); - }); - it('should click the button with em border with offset', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', button => button.style.borderWidth = '2em'); - await page.$eval('button', button => button.style.fontSize = '12px'); - await page.click('button', { position: { x: 20, y: 10 } }); - expect(await page.evaluate(() => result)).toBe('Clicked'); - // Safari reports border-relative offsetX/offsetY. - expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 12 * 2 + 20 : 20); - expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 12 * 2 + 10 : 10); - }); - it('should click a very large button with offset', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', button => button.style.borderWidth = '8px'); - await page.$eval('button', button => button.style.height = button.style.width = '2000px'); - await page.click('button', { position: { x: 1900, y: 1910 } }); - expect(await page.evaluate(() => window.result)).toBe('Clicked'); - // Safari reports border-relative offsetX/offsetY. - expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 1900 + 8 : 1900); - expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 1910 + 8 : 1910); - }); - it('should click a button in scrolling container with offset', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', button => { - const container = document.createElement('div'); - container.style.overflow = 'auto'; - container.style.width = '200px'; - container.style.height = '200px'; - button.parentElement.insertBefore(container, button); - container.appendChild(button); - button.style.height = '2000px'; - button.style.width = '2000px'; - button.style.borderWidth = '8px'; - }); - await page.click('button', { position: { x: 1900, y: 1910 } }); - expect(await page.evaluate(() => window.result)).toBe('Clicked'); - // Safari reports border-relative offsetX/offsetY. - expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 1900 + 8 : 1900); - expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 1910 + 8 : 1910); - }); - it.skip(FFOX)('should click the button with offset with page scale', async({browser, server}) => { - const context = await browser.newContext({ viewport: { width: 400, height: 400 }, isMobile: true }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', button => { - button.style.borderWidth = '8px'; - document.body.style.margin = '0'; - }); - await page.click('button', { position: { x: 20, y: 10 } }); - expect(await page.evaluate(() => result)).toBe('Clicked'); - const round = x => Math.round(x + 0.01); - let expected = { x: 28, y: 18 }; // 20;10 + 8px of border in each direction - if (WEBKIT) { - // WebKit rounds up during css -> dip -> css conversion. - expected = { x: 29, y: 19 }; - } else if (CHROMIUM && HEADLESS) { - // Headless Chromium rounds down during css -> dip -> css conversion. - expected = { x: 27, y: 18 }; - } - expect(round(await page.evaluate(() => pageX))).toBe(expected.x); - expect(round(await page.evaluate(() => pageY))).toBe(expected.y); - await context.close(); - }); - - it('should wait for stable position', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', button => { - button.style.transition = 'margin 500ms linear 0s'; - button.style.marginLeft = '200px'; - button.style.borderWidth = '0'; - button.style.width = '200px'; - button.style.height = '20px'; - // Set display to "block" - otherwise Firefox layouts with non-even - // values on Linux. - button.style.display = 'block'; - document.body.style.margin = '0'; - }); - await page.click('button'); - expect(await page.evaluate(() => window.result)).toBe('Clicked'); - expect(await page.evaluate(() => pageX)).toBe(300); - expect(await page.evaluate(() => pageY)).toBe(10); - }); - it('should wait for becoming hit target', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.$eval('button', button => { - button.style.borderWidth = '0'; - button.style.width = '200px'; - button.style.height = '20px'; - document.body.style.margin = '0'; - document.body.style.position = 'relative'; - const flyOver = document.createElement('div'); - flyOver.className = 'flyover'; - flyOver.style.position = 'absolute'; - flyOver.style.width = '400px'; - flyOver.style.height = '20px'; - flyOver.style.left = '-200px'; - flyOver.style.top = '0'; - flyOver.style.background = 'red'; - document.body.appendChild(flyOver); - }); - let clicked = false; - const clickPromise = page.click('button').then(() => clicked = true); - expect(clicked).toBe(false); - - await page.$eval('.flyover', flyOver => flyOver.style.left = '0'); - await giveItAChanceToClick(page); - expect(clicked).toBe(false); - - await page.$eval('.flyover', flyOver => flyOver.style.left = '200px'); - await clickPromise; - expect(clicked).toBe(true); - expect(await page.evaluate(() => window.result)).toBe('Clicked'); - }); - it('should fail when obscured and not waiting for hit target', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await page.evaluate(() => { - document.body.style.position = 'relative'; - const blocker = document.createElement('div'); - blocker.style.position = 'absolute'; - blocker.style.width = '400px'; - blocker.style.height = '20px'; - blocker.style.left = '0'; - blocker.style.top = '0'; - document.body.appendChild(blocker); - }); - await button.click({ force: true }); - expect(await page.evaluate(() => window.result)).toBe('Was not clicked'); - }); - - it('should wait for button to be enabled', async({page, server}) => { - await page.setContent(''); - let done = false; - const clickPromise = page.click('text=Click target').then(() => done = true); - await giveItAChanceToClick(page); - expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); - expect(done).toBe(false); - await page.evaluate(() => document.querySelector('button').removeAttribute('disabled')); - await clickPromise; - expect(await page.evaluate(() => window.__CLICKED)).toBe(true); - }); - it('should wait for input to be enabled', async({page, server}) => { - await page.setContent(''); - let done = false; - const clickPromise = page.click('input').then(() => done = true); - await giveItAChanceToClick(page); - expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); - expect(done).toBe(false); - await page.evaluate(() => document.querySelector('input').removeAttribute('disabled')); - await clickPromise; - expect(await page.evaluate(() => window.__CLICKED)).toBe(true); - }); - it('should wait for select to be enabled', async({page, server}) => { - await page.setContent(''); - let done = false; - const clickPromise = page.click('select').then(() => done = true); - await giveItAChanceToClick(page); - expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); - expect(done).toBe(false); - await page.evaluate(() => document.querySelector('select').removeAttribute('disabled')); - await clickPromise; - expect(await page.evaluate(() => window.__CLICKED)).toBe(true); - }); - it('should click disabled div', async({page, server}) => { - await page.setContent('
Click target
'); - await page.click('text=Click target'); - expect(await page.evaluate(() => window.__CLICKED)).toBe(true); - }); - - it('should climb dom for inner label with pointer-events:none', async({page, server}) => { - await page.setContent(''); - await page.click('text=Click target'); - expect(await page.evaluate(() => window.__CLICKED)).toBe(true); - }); - it('should climb up to [role=button]', async({page, server}) => { - await page.setContent('
Click target
'); - await page.click('text=Click target'); - expect(await page.evaluate(() => window.__CLICKED)).toBe(true); - }); - it('should wait for BUTTON to be clickable when it has pointer-events:none', async({page, server}) => { - await page.setContent(''); - let done = false; - const clickPromise = page.click('text=Click target').then(() => done = true); - await giveItAChanceToClick(page); - expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); - expect(done).toBe(false); - await page.evaluate(() => document.querySelector('button').style.removeProperty('pointer-events')); - await clickPromise; - expect(await page.evaluate(() => window.__CLICKED)).toBe(true); - }); - it('should wait for LABEL to be clickable when it has pointer-events:none', async({page, server}) => { - await page.setContent(''); - const clickPromise = page.click('text=Click target'); - // Do a few roundtrips to the page. - for (let i = 0; i < 5; ++i) - expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); - // remove `pointer-events: none` css from button. - await page.evaluate(() => document.querySelector('label').style.removeProperty('pointer-events')); - await clickPromise; - expect(await page.evaluate(() => window.__CLICKED)).toBe(true); - }); - it('should update modifiers correctly', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button', { modifiers: ['Shift'] }); - expect(await page.evaluate(() => shiftKey)).toBe(true); - await page.click('button', { modifiers: [] }); - expect(await page.evaluate(() => shiftKey)).toBe(false); - - await page.keyboard.down('Shift'); - await page.click('button', { modifiers: [] }); - expect(await page.evaluate(() => shiftKey)).toBe(false); - await page.click('button'); - expect(await page.evaluate(() => shiftKey)).toBe(true); - await page.keyboard.up('Shift'); - await page.click('button'); - expect(await page.evaluate(() => shiftKey)).toBe(false); - }); - it('should click an offscreen element when scroll-behavior is smooth', async({page}) => { - await page.setContent(` -
- -
- `); - await page.click('button'); - expect(await page.evaluate('window.clicked')).toBe(true); - }); - it('should report nice error when element is detached and force-clicked', async({page, server}) => { - await page.goto(server.PREFIX + '/input/animating-button.html'); - await page.evaluate(() => addButton()); - const handle = await page.$('button'); - await page.evaluate(() => stopButton(true)); - const promise = handle.click({ force: true }).catch(e => e); - const error = await promise; - expect(await page.evaluate(() => window.clicked)).toBe(undefined); - expect(error.message).toContain('Element is not attached to the DOM'); - }); - it('should fail when element detaches after animation', async({page, server}) => { - await page.goto(server.PREFIX + '/input/animating-button.html'); - await page.evaluate(() => addButton()); - const handle = await page.$('button'); - const promise = handle.click().catch(e => e); - await page.evaluate(() => stopButton(true)); - const error = await promise; - expect(await page.evaluate(() => window.clicked)).toBe(undefined); - expect(error.message).toContain('Element is not attached to the DOM'); - }); - it('should retry when element detaches after animation', async({page, server}) => { - await page.goto(server.PREFIX + '/input/animating-button.html'); - await page.evaluate(() => addButton()); - let clicked = false; - const promise = page.click('button').then(() => clicked = true); - expect(clicked).toBe(false); - expect(await page.evaluate(() => window.clicked)).toBe(undefined); - await page.evaluate(() => stopButton(true)); - await page.evaluate(() => addButton()); - expect(clicked).toBe(false); - expect(await page.evaluate(() => window.clicked)).toBe(undefined); - await page.evaluate(() => stopButton(true)); - await page.evaluate(() => addButton()); - expect(clicked).toBe(false); - expect(await page.evaluate(() => window.clicked)).toBe(undefined); - await page.evaluate(() => stopButton(false)); - await promise; - expect(clicked).toBe(true); - expect(await page.evaluate(() => window.clicked)).toBe(true); - }); - it('should retry when element is animating from outside the viewport', async({page, server}) => { - await page.setContent(` -
- -
- `); - const handle = await page.$('button'); - const promise = handle.click(); - await handle.evaluate(button => button.className = 'animated'); - await promise; - expect(await page.evaluate(() => window.clicked)).toBe(true); - }); - it('should fail when element is animating from outside the viewport with force', async({page, server}) => { - await page.setContent(` -
- -
- `); - const handle = await page.$('button'); - const promise = handle.click({ force: true }).catch(e => e); - await handle.evaluate(button => button.className = 'animated'); - const error = await promise; - expect(await page.evaluate(() => window.clicked)).toBe(undefined); - expect(error.message).toContain('Element is outside of the viewport'); - }); - it('should dispatch microtasks in order', async({page, server}) => { - await page.setContent(` - - - `); - await page.click('button'); - expect(await page.evaluate(() => window.result)).toBe(1); - }); - it.fail(true)('should retarget when element is recycled during hit testing', async ({page, server}) => { - await page.goto(server.PREFIX + '/react.html'); - await page.evaluate(() => { - renderComponent(e('div', {}, [e(MyButton, { name: 'button1' }), e(MyButton, { name: 'button2' })] )); - }); - const __testHookAfterStable = () => page.evaluate(() => { - window.counter = (window.counter || 0) + 1; - if (window.counter === 1) - renderComponent(e('div', {}, [e(MyButton, { name: 'button2' }), e(MyButton, { name: 'button1' })] )); - }); - await page.click('text=button1', { __testHookAfterStable }); - expect(await page.evaluate(() => window.button1)).toBe(true); - expect(await page.evaluate(() => window.button2)).toBe(undefined); - }); - it.fail(true)('should retarget when element is recycled before enabled check', async ({page, server}) => { - await page.goto(server.PREFIX + '/react.html'); - await page.evaluate(() => { - renderComponent(e('div', {}, [e(MyButton, { name: 'button1' }), e(MyButton, { name: 'button2', disabled: true })] )); - }); - const __testHookBeforeStable = () => page.evaluate(() => { - window.counter = (window.counter || 0) + 1; - if (window.counter === 1) - renderComponent(e('div', {}, [e(MyButton, { name: 'button2', disabled: true }), e(MyButton, { name: 'button1' })] )); - }); - await page.click('text=button1', { __testHookBeforeStable }); - expect(await page.evaluate(() => window.button1)).toBe(true); - expect(await page.evaluate(() => window.button2)).toBe(undefined); - }); - it('should not retarget when element changes on hover', async ({page, server}) => { - await page.goto(server.PREFIX + '/react.html'); - await page.evaluate(() => { - renderComponent(e('div', {}, [e(MyButton, { name: 'button1', renameOnHover: true }), e(MyButton, { name: 'button2' })] )); - }); - await page.click('text=button1'); - expect(await page.evaluate(() => window.button1)).toBe(true); - expect(await page.evaluate(() => window.button2)).toBe(undefined); - }); - it('should not retarget when element is recycled on hover', async ({page, server}) => { - await page.goto(server.PREFIX + '/react.html'); - await page.evaluate(() => { - function shuffle() { - renderComponent(e('div', {}, [e(MyButton, { name: 'button2' }), e(MyButton, { name: 'button1' })] )); - } - renderComponent(e('div', {}, [e(MyButton, { name: 'button1', onHover: shuffle }), e(MyButton, { name: 'button2' })] )); - }); - await page.click('text=button1'); - expect(await page.evaluate(() => window.button1)).toBe(undefined); - expect(await page.evaluate(() => window.button2)).toBe(true); - }); - it('should click the button when window.innerWidth is corrupted', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.evaluate(() => window.innerWidth = 0); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); -}); diff --git a/test/click.spec.js b/test/click.spec.js new file mode 100644 index 0000000000..3478434640 --- /dev/null +++ b/test/click.spec.js @@ -0,0 +1,776 @@ +/** + * 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 utils = require('./utils'); +const {FFOX, CHROMIUM, WEBKIT, HEADLESS, USES_HOOKS} = testOptions; + +async function giveItAChanceToClick(page) { + for (let i = 0; i < 5; i++) + await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)))); +} + +it('should click the button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); + +it('should click svg', async({page, server}) => { + await page.setContent(` + + + + `); + await page.click('circle'); + expect(await page.evaluate(() => window.__CLICKED)).toBe(42); +}); + +it('should click the button if window.Node is removed', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.evaluate(() => delete window.Node); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); + +// @see https://github.com/GoogleChrome/puppeteer/issues/4281 +it('should click on a span with an inline element inside', async({page, server}) => { + await page.setContent(` + + + `); + await page.click('span'); + expect(await page.evaluate(() => window.CLICKED)).toBe(42); +}); + +it('should not throw UnhandledPromiseRejection when page closes', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await Promise.all([ + page.close(), + page.mouse.click(1, 2), + ]).catch(e => {}); + await context.close(); +}); + +it('should click the button after navigation ', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); + +it('should click the button after a cross origin navigation ', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + await page.goto(server.CROSS_PROCESS_PREFIX + '/input/button.html'); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); + +it('should click with disabled javascript', async({browser, server}) => { + const context = await browser.newContext({ javaScriptEnabled: false }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/wrappedlink.html'); + await Promise.all([ + page.click('a'), + page.waitForNavigation() + ]); + expect(page.url()).toBe(server.PREFIX + '/wrappedlink.html#clicked'); + await context.close(); +}); + +it('should click when one of inline box children is outside of viewport', async({page, server}) => { + await page.setContent(` + + woofdoggo + `); + await page.click('span'); + expect(await page.evaluate(() => window.CLICKED)).toBe(42); +}); + +it('should select the text by triple clicking', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + const text = 'This is the text that we are going to try to select. Let\'s see how it goes.'; + await page.fill('textarea', text); + await page.click('textarea', { clickCount: 3 }); + expect(await page.evaluate(() => { + const textarea = document.querySelector('textarea'); + return textarea.value.substring(textarea.selectionStart, textarea.selectionEnd); + })).toBe(text); +}); + +it('should click offscreen buttons', async({page, server}) => { + await page.goto(server.PREFIX + '/offscreenbuttons.html'); + const messages = []; + page.on('console', msg => messages.push(msg.text())); + for (let i = 0; i < 11; ++i) { + // We might've scrolled to click a button - reset to (0, 0). + await page.evaluate(() => window.scrollTo(0, 0)); + await page.click(`#btn${i}`); + } + expect(messages).toEqual([ + 'button #0 clicked', + 'button #1 clicked', + 'button #2 clicked', + 'button #3 clicked', + 'button #4 clicked', + 'button #5 clicked', + 'button #6 clicked', + 'button #7 clicked', + 'button #8 clicked', + 'button #9 clicked', + 'button #10 clicked' + ]); +}); + +it('should waitFor visible when already visible', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); + +it('should not wait with force', async({page, server}) => { + let error = null; + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', b => b.style.display = 'none'); + await page.click('button', { force: true }).catch(e => error = e); + expect(error.message).toContain('Element is not visible'); + expect(await page.evaluate(() => result)).toBe('Was not clicked'); +}); + +it('should waitFor display:none to be gone', async({page, server}) => { + let done = false; + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', b => b.style.display = 'none'); + const clicked = page.click('button', { timeout: 0 }).then(() => done = true); + await giveItAChanceToClick(page); + expect(await page.evaluate(() => result)).toBe('Was not clicked'); + expect(done).toBe(false); + await page.$eval('button', b => b.style.display = 'block'); + await clicked; + expect(done).toBe(true); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); + +it('should waitFor visibility:hidden to be gone', async({page, server}) => { + let done = false; + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', b => b.style.visibility = 'hidden'); + const clicked = page.click('button', { timeout: 0 }).then(() => done = true); + await giveItAChanceToClick(page); + expect(await page.evaluate(() => result)).toBe('Was not clicked'); + expect(done).toBe(false); + await page.$eval('button', b => b.style.visibility = 'visible'); + await clicked; + expect(done).toBe(true); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); + +it('should waitFor visible when parent is hidden', async({page, server}) => { + let done = false; + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', b => b.parentElement.style.display = 'none'); + const clicked = page.click('button', { timeout: 0 }).then(() => done = true); + await giveItAChanceToClick(page); + expect(done).toBe(false); + await page.$eval('button', b => b.parentElement.style.display = 'block'); + await clicked; + expect(done).toBe(true); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); + +it('should click wrapped links', async({page, server}) => { + await page.goto(server.PREFIX + '/wrappedlink.html'); + await page.click('a'); + expect(await page.evaluate(() => window.__clicked)).toBe(true); +}); + +it('should click on checkbox input and toggle', async({page, server}) => { + await page.goto(server.PREFIX + '/input/checkbox.html'); + expect(await page.evaluate(() => result.check)).toBe(null); + await page.click('input#agree'); + expect(await page.evaluate(() => result.check)).toBe(true); + expect(await page.evaluate(() => result.events)).toEqual([ + 'mouseover', + 'mouseenter', + 'mousemove', + 'mousedown', + 'mouseup', + 'click', + 'input', + 'change', + ]); + await page.click('input#agree'); + expect(await page.evaluate(() => result.check)).toBe(false); +}); + +it('should click on checkbox label and toggle', async({page, server}) => { + await page.goto(server.PREFIX + '/input/checkbox.html'); + expect(await page.evaluate(() => result.check)).toBe(null); + await page.click('label[for="agree"]'); + expect(await page.evaluate(() => result.check)).toBe(true); + expect(await page.evaluate(() => result.events)).toEqual([ + 'click', + 'input', + 'change', + ]); + await page.click('label[for="agree"]'); + expect(await page.evaluate(() => result.check)).toBe(false); +}); + +it('should not hang with touch-enabled viewports', async({browser, playwright}) => { + // @see https://github.com/GoogleChrome/puppeteer/issues/161 + const { viewport, hasTouch } = playwright.devices['iPhone 6']; + const context = await browser.newContext({ viewport, hasTouch }); + const page = await context.newPage(); + await page.mouse.down(); + await page.mouse.move(100, 10); + await page.mouse.up(); + await context.close(); +}); + +it('should scroll and click the button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.click('#button-5'); + expect(await page.evaluate(() => document.querySelector('#button-5').textContent)).toBe('clicked'); + await page.click('#button-80'); + expect(await page.evaluate(() => document.querySelector('#button-80').textContent)).toBe('clicked'); +}); + +it('should double click the button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.evaluate(() => { + window.double = false; + const button = document.querySelector('button'); + button.addEventListener('dblclick', event => { + window.double = true; + }); + }); + await page.dblclick('button'); + expect(await page.evaluate('double')).toBe(true); + expect(await page.evaluate('result')).toBe('Clicked'); +}); + +it('should click a partially obscured button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.evaluate(() => { + const button = document.querySelector('button'); + button.textContent = 'Some really long text that will go offscreen'; + button.style.position = 'absolute'; + button.style.left = '368px'; + }); + await page.click('button'); + expect(await page.evaluate(() => window.result)).toBe('Clicked'); +}); + +it('should click a rotated button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/rotatedButton.html'); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); + +it('should fire contextmenu event on right click', async({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.click('#button-8', {button: 'right'}); + expect(await page.evaluate(() => document.querySelector('#button-8').textContent)).toBe('context menu'); +}); + +it('should click links which cause navigation', async({page, server}) => { + // @see https://github.com/GoogleChrome/puppeteer/issues/206 + await page.setContent(`empty.html`); + // This await should not hang. + await page.click('a'); +}); + +it('should click the button inside an iframe', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent('
spacer
'); + await utils.attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); + const frame = page.frames()[1]; + const button = await frame.$('button'); + await button.click(); + expect(await frame.evaluate(() => window.result)).toBe('Clicked'); +}); + +it.fail(CHROMIUM || WEBKIT)('should click the button with fixed position inside an iframe', async({page, server}) => { + // @see https://github.com/GoogleChrome/puppeteer/issues/4110 + // @see https://bugs.chromium.org/p/chromium/issues/detail?id=986390 + // @see https://chromium-review.googlesource.com/c/chromium/src/+/1742784 + await page.goto(server.EMPTY_PAGE); + await page.setViewportSize({width: 500, height: 500}); + await page.setContent('
spacer
'); + await utils.attachFrame(page, 'button-test', server.CROSS_PROCESS_PREFIX + '/input/button.html'); + const frame = page.frames()[1]; + await frame.$eval('button', button => button.style.setProperty('position', 'fixed')); + await frame.click('button'); + expect(await frame.evaluate(() => window.result)).toBe('Clicked'); +}); + +it('should click the button with deviceScaleFactor set', async({browser, server}) => { + const context = await browser.newContext({ viewport: { width: 400, height: 400 }, deviceScaleFactor: 5 }); + const page = await context.newPage(); + expect(await page.evaluate(() => window.devicePixelRatio)).toBe(5); + await page.setContent('
spacer
'); + await utils.attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); + const frame = page.frames()[1]; + const button = await frame.$('button'); + await button.click(); + expect(await frame.evaluate(() => window.result)).toBe('Clicked'); + await context.close(); +}); + +it('should click the button with px border with offset', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => button.style.borderWidth = '8px'); + await page.click('button', { position: { x: 20, y: 10 } }); + expect(await page.evaluate(() => result)).toBe('Clicked'); + // Safari reports border-relative offsetX/offsetY. + expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 20 + 8 : 20); + expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 10 + 8 : 10); +}); + +it('should click the button with em border with offset', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => button.style.borderWidth = '2em'); + await page.$eval('button', button => button.style.fontSize = '12px'); + await page.click('button', { position: { x: 20, y: 10 } }); + expect(await page.evaluate(() => result)).toBe('Clicked'); + // Safari reports border-relative offsetX/offsetY. + expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 12 * 2 + 20 : 20); + expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 12 * 2 + 10 : 10); +}); + +it('should click a very large button with offset', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => button.style.borderWidth = '8px'); + await page.$eval('button', button => button.style.height = button.style.width = '2000px'); + await page.click('button', { position: { x: 1900, y: 1910 } }); + expect(await page.evaluate(() => window.result)).toBe('Clicked'); + // Safari reports border-relative offsetX/offsetY. + expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 1900 + 8 : 1900); + expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 1910 + 8 : 1910); +}); + +it('should click a button in scrolling container with offset', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => { + const container = document.createElement('div'); + container.style.overflow = 'auto'; + container.style.width = '200px'; + container.style.height = '200px'; + button.parentElement.insertBefore(container, button); + container.appendChild(button); + button.style.height = '2000px'; + button.style.width = '2000px'; + button.style.borderWidth = '8px'; + }); + await page.click('button', { position: { x: 1900, y: 1910 } }); + expect(await page.evaluate(() => window.result)).toBe('Clicked'); + // Safari reports border-relative offsetX/offsetY. + expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 1900 + 8 : 1900); + expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 1910 + 8 : 1910); +}); + +it.skip(FFOX)('should click the button with offset with page scale', async({browser, server}) => { + const context = await browser.newContext({ viewport: { width: 400, height: 400 }, isMobile: true }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => { + button.style.borderWidth = '8px'; + document.body.style.margin = '0'; + }); + await page.click('button', { position: { x: 20, y: 10 } }); + expect(await page.evaluate(() => result)).toBe('Clicked'); + const round = x => Math.round(x + 0.01); + let expected = { x: 28, y: 18 }; // 20;10 + 8px of border in each direction + if (WEBKIT) { + // WebKit rounds up during css -> dip -> css conversion. + expected = { x: 29, y: 19 }; + } else if (CHROMIUM && HEADLESS) { + // Headless Chromium rounds down during css -> dip -> css conversion. + expected = { x: 27, y: 18 }; + } + expect(round(await page.evaluate(() => pageX))).toBe(expected.x); + expect(round(await page.evaluate(() => pageY))).toBe(expected.y); + await context.close(); +}); + +it('should wait for stable position', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => { + button.style.transition = 'margin 500ms linear 0s'; + button.style.marginLeft = '200px'; + button.style.borderWidth = '0'; + button.style.width = '200px'; + button.style.height = '20px'; + // Set display to "block" - otherwise Firefox layouts with non-even + // values on Linux. + button.style.display = 'block'; + document.body.style.margin = '0'; + }); + await page.click('button'); + expect(await page.evaluate(() => window.result)).toBe('Clicked'); + expect(await page.evaluate(() => pageX)).toBe(300); + expect(await page.evaluate(() => pageY)).toBe(10); +}); + +it('should wait for becoming hit target', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => { + button.style.borderWidth = '0'; + button.style.width = '200px'; + button.style.height = '20px'; + document.body.style.margin = '0'; + document.body.style.position = 'relative'; + const flyOver = document.createElement('div'); + flyOver.className = 'flyover'; + flyOver.style.position = 'absolute'; + flyOver.style.width = '400px'; + flyOver.style.height = '20px'; + flyOver.style.left = '-200px'; + flyOver.style.top = '0'; + flyOver.style.background = 'red'; + document.body.appendChild(flyOver); + }); + let clicked = false; + const clickPromise = page.click('button').then(() => clicked = true); + expect(clicked).toBe(false); + + await page.$eval('.flyover', flyOver => flyOver.style.left = '0'); + await giveItAChanceToClick(page); + expect(clicked).toBe(false); + + await page.$eval('.flyover', flyOver => flyOver.style.left = '200px'); + await clickPromise; + expect(clicked).toBe(true); + expect(await page.evaluate(() => window.result)).toBe('Clicked'); +}); + +it('should fail when obscured and not waiting for hit target', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(() => { + document.body.style.position = 'relative'; + const blocker = document.createElement('div'); + blocker.style.position = 'absolute'; + blocker.style.width = '400px'; + blocker.style.height = '20px'; + blocker.style.left = '0'; + blocker.style.top = '0'; + document.body.appendChild(blocker); + }); + await button.click({ force: true }); + expect(await page.evaluate(() => window.result)).toBe('Was not clicked'); +}); + +it('should wait for button to be enabled', async({page, server}) => { + await page.setContent(''); + let done = false; + const clickPromise = page.click('text=Click target').then(() => done = true); + await giveItAChanceToClick(page); + expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); + expect(done).toBe(false); + await page.evaluate(() => document.querySelector('button').removeAttribute('disabled')); + await clickPromise; + expect(await page.evaluate(() => window.__CLICKED)).toBe(true); +}); + +it('should wait for input to be enabled', async({page, server}) => { + await page.setContent(''); + let done = false; + const clickPromise = page.click('input').then(() => done = true); + await giveItAChanceToClick(page); + expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); + expect(done).toBe(false); + await page.evaluate(() => document.querySelector('input').removeAttribute('disabled')); + await clickPromise; + expect(await page.evaluate(() => window.__CLICKED)).toBe(true); +}); + +it('should wait for select to be enabled', async({page, server}) => { + await page.setContent(''); + let done = false; + const clickPromise = page.click('select').then(() => done = true); + await giveItAChanceToClick(page); + expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); + expect(done).toBe(false); + await page.evaluate(() => document.querySelector('select').removeAttribute('disabled')); + await clickPromise; + expect(await page.evaluate(() => window.__CLICKED)).toBe(true); +}); + +it('should click disabled div', async({page, server}) => { + await page.setContent('
Click target
'); + await page.click('text=Click target'); + expect(await page.evaluate(() => window.__CLICKED)).toBe(true); +}); + +it('should climb dom for inner label with pointer-events:none', async({page, server}) => { + await page.setContent(''); + await page.click('text=Click target'); + expect(await page.evaluate(() => window.__CLICKED)).toBe(true); +}); + +it('should climb up to [role=button]', async({page, server}) => { + await page.setContent('
Click target
'); + await page.click('text=Click target'); + expect(await page.evaluate(() => window.__CLICKED)).toBe(true); +}); + +it('should wait for BUTTON to be clickable when it has pointer-events:none', async({page, server}) => { + await page.setContent(''); + let done = false; + const clickPromise = page.click('text=Click target').then(() => done = true); + await giveItAChanceToClick(page); + expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); + expect(done).toBe(false); + await page.evaluate(() => document.querySelector('button').style.removeProperty('pointer-events')); + await clickPromise; + expect(await page.evaluate(() => window.__CLICKED)).toBe(true); +}); + +it('should wait for LABEL to be clickable when it has pointer-events:none', async({page, server}) => { + await page.setContent(''); + const clickPromise = page.click('text=Click target'); + // Do a few roundtrips to the page. + for (let i = 0; i < 5; ++i) + expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); + // remove `pointer-events: none` css from button. + await page.evaluate(() => document.querySelector('label').style.removeProperty('pointer-events')); + await clickPromise; + expect(await page.evaluate(() => window.__CLICKED)).toBe(true); +}); + +it('should update modifiers correctly', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button', { modifiers: ['Shift'] }); + expect(await page.evaluate(() => shiftKey)).toBe(true); + await page.click('button', { modifiers: [] }); + expect(await page.evaluate(() => shiftKey)).toBe(false); + + await page.keyboard.down('Shift'); + await page.click('button', { modifiers: [] }); + expect(await page.evaluate(() => shiftKey)).toBe(false); + await page.click('button'); + expect(await page.evaluate(() => shiftKey)).toBe(true); + await page.keyboard.up('Shift'); + await page.click('button'); + expect(await page.evaluate(() => shiftKey)).toBe(false); +}); + +it('should click an offscreen element when scroll-behavior is smooth', async({page}) => { + await page.setContent(` +
+ +
+ `); + await page.click('button'); + expect(await page.evaluate('window.clicked')).toBe(true); +}); + +it('should report nice error when element is detached and force-clicked', async({page, server}) => { + await page.goto(server.PREFIX + '/input/animating-button.html'); + await page.evaluate(() => addButton()); + const handle = await page.$('button'); + await page.evaluate(() => stopButton(true)); + const promise = handle.click({ force: true }).catch(e => e); + const error = await promise; + expect(await page.evaluate(() => window.clicked)).toBe(undefined); + expect(error.message).toContain('Element is not attached to the DOM'); +}); + +it('should fail when element detaches after animation', async({page, server}) => { + await page.goto(server.PREFIX + '/input/animating-button.html'); + await page.evaluate(() => addButton()); + const handle = await page.$('button'); + const promise = handle.click().catch(e => e); + await page.evaluate(() => stopButton(true)); + const error = await promise; + expect(await page.evaluate(() => window.clicked)).toBe(undefined); + expect(error.message).toContain('Element is not attached to the DOM'); +}); + +it('should retry when element detaches after animation', async({page, server}) => { + await page.goto(server.PREFIX + '/input/animating-button.html'); + await page.evaluate(() => addButton()); + let clicked = false; + const promise = page.click('button').then(() => clicked = true); + expect(clicked).toBe(false); + expect(await page.evaluate(() => window.clicked)).toBe(undefined); + await page.evaluate(() => stopButton(true)); + await page.evaluate(() => addButton()); + expect(clicked).toBe(false); + expect(await page.evaluate(() => window.clicked)).toBe(undefined); + await page.evaluate(() => stopButton(true)); + await page.evaluate(() => addButton()); + expect(clicked).toBe(false); + expect(await page.evaluate(() => window.clicked)).toBe(undefined); + await page.evaluate(() => stopButton(false)); + await promise; + expect(clicked).toBe(true); + expect(await page.evaluate(() => window.clicked)).toBe(true); +}); + +it('should retry when element is animating from outside the viewport', async({page, server}) => { + await page.setContent(` +
+ +
+ `); + const handle = await page.$('button'); + const promise = handle.click(); + await handle.evaluate(button => button.className = 'animated'); + await promise; + expect(await page.evaluate(() => window.clicked)).toBe(true); +}); + +it('should fail when element is animating from outside the viewport with force', async({page, server}) => { + await page.setContent(` +
+ +
+ `); + const handle = await page.$('button'); + const promise = handle.click({ force: true }).catch(e => e); + await handle.evaluate(button => button.className = 'animated'); + const error = await promise; + expect(await page.evaluate(() => window.clicked)).toBe(undefined); + expect(error.message).toContain('Element is outside of the viewport'); +}); + +it('should dispatch microtasks in order', async({page, server}) => { + await page.setContent(` + + + `); + await page.click('button'); + expect(await page.evaluate(() => window.result)).toBe(1); +}); + +it.fail(true)('should retarget when element is recycled during hit testing', async ({page, server}) => { + await page.goto(server.PREFIX + '/react.html'); + await page.evaluate(() => { + renderComponent(e('div', {}, [e(MyButton, { name: 'button1' }), e(MyButton, { name: 'button2' })] )); + }); + const __testHookAfterStable = () => page.evaluate(() => { + window.counter = (window.counter || 0) + 1; + if (window.counter === 1) + renderComponent(e('div', {}, [e(MyButton, { name: 'button2' }), e(MyButton, { name: 'button1' })] )); + }); + await page.click('text=button1', { __testHookAfterStable }); + expect(await page.evaluate(() => window.button1)).toBe(true); + expect(await page.evaluate(() => window.button2)).toBe(undefined); +}); + +it.fail(true)('should retarget when element is recycled before enabled check', async ({page, server}) => { + await page.goto(server.PREFIX + '/react.html'); + await page.evaluate(() => { + renderComponent(e('div', {}, [e(MyButton, { name: 'button1' }), e(MyButton, { name: 'button2', disabled: true })] )); + }); + const __testHookBeforeStable = () => page.evaluate(() => { + window.counter = (window.counter || 0) + 1; + if (window.counter === 1) + renderComponent(e('div', {}, [e(MyButton, { name: 'button2', disabled: true }), e(MyButton, { name: 'button1' })] )); + }); + await page.click('text=button1', { __testHookBeforeStable }); + expect(await page.evaluate(() => window.button1)).toBe(true); + expect(await page.evaluate(() => window.button2)).toBe(undefined); +}); + +it('should not retarget when element changes on hover', async ({page, server}) => { + await page.goto(server.PREFIX + '/react.html'); + await page.evaluate(() => { + renderComponent(e('div', {}, [e(MyButton, { name: 'button1', renameOnHover: true }), e(MyButton, { name: 'button2' })] )); + }); + await page.click('text=button1'); + expect(await page.evaluate(() => window.button1)).toBe(true); + expect(await page.evaluate(() => window.button2)).toBe(undefined); +}); + +it('should not retarget when element is recycled on hover', async ({page, server}) => { + await page.goto(server.PREFIX + '/react.html'); + await page.evaluate(() => { + function shuffle() { + renderComponent(e('div', {}, [e(MyButton, { name: 'button2' }), e(MyButton, { name: 'button1' })] )); + } + renderComponent(e('div', {}, [e(MyButton, { name: 'button1', onHover: shuffle }), e(MyButton, { name: 'button2' })] )); + }); + await page.click('text=button1'); + expect(await page.evaluate(() => window.button1)).toBe(undefined); + expect(await page.evaluate(() => window.button2)).toBe(true); +}); + +it('should click the button when window.innerWidth is corrupted', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.evaluate(() => window.innerWidth = 0); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); diff --git a/test/cookies.jest.js b/test/cookies.jest.js deleted file mode 100644 index aaa8e90aef..0000000000 --- a/test/cookies.jest.js +++ /dev/null @@ -1,508 +0,0 @@ -/** - * 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, WIN} = testOptions; - -describe('BrowserContext.cookies', function() { - it('should return no cookies in pristine browser context', async({context, page, server}) => { - expect(await context.cookies()).toEqual([]); - }); - it('should get a cookie', async({context, page, server}) => { - await page.goto(server.EMPTY_PAGE); - const documentCookie = await page.evaluate(() => { - document.cookie = 'username=John Doe'; - return document.cookie; - }); - expect(documentCookie).toBe('username=John Doe'); - expect(await context.cookies()).toEqual([{ - name: 'username', - value: 'John Doe', - domain: 'localhost', - path: '/', - expires: -1, - httpOnly: false, - secure: false, - sameSite: 'None', - }]); - }); - it('should get a non-session cookie', async({context, page, server}) => { - await page.goto(server.EMPTY_PAGE); - // @see https://en.wikipedia.org/wiki/Year_2038_problem - const date = +(new Date('1/1/2038')); - const documentCookie = await page.evaluate(timestamp => { - const date = new Date(timestamp); - document.cookie = `username=John Doe;expires=${date.toUTCString()}`; - return document.cookie; - }, date); - expect(documentCookie).toBe('username=John Doe'); - expect(await context.cookies()).toEqual([{ - name: 'username', - value: 'John Doe', - domain: 'localhost', - path: '/', - expires: date / 1000, - httpOnly: false, - secure: false, - sameSite: 'None', - }]); - }); - it('should properly report httpOnly cookie', async({context, page, server}) => { - server.setRoute('/empty.html', (req, res) => { - res.setHeader('Set-Cookie', 'name=value;HttpOnly; Path=/'); - res.end(); - }); - await page.goto(server.EMPTY_PAGE); - const cookies = await context.cookies(); - expect(cookies.length).toBe(1); - expect(cookies[0].httpOnly).toBe(true); - }); - it.fail(WEBKIT && WIN)('should properly report "Strict" sameSite cookie', async({context, page, server}) => { - server.setRoute('/empty.html', (req, res) => { - res.setHeader('Set-Cookie', 'name=value;SameSite=Strict'); - res.end(); - }); - await page.goto(server.EMPTY_PAGE); - const cookies = await context.cookies(); - expect(cookies.length).toBe(1); - expect(cookies[0].sameSite).toBe('Strict'); - }); - it.fail(WEBKIT && WIN)('should properly report "Lax" sameSite cookie', async({context, page, server}) => { - server.setRoute('/empty.html', (req, res) => { - res.setHeader('Set-Cookie', 'name=value;SameSite=Lax'); - res.end(); - }); - await page.goto(server.EMPTY_PAGE); - const cookies = await context.cookies(); - expect(cookies.length).toBe(1); - expect(cookies[0].sameSite).toBe('Lax'); - }); - it('should get multiple cookies', async({context, page, server}) => { - await page.goto(server.EMPTY_PAGE); - const documentCookie = await page.evaluate(() => { - document.cookie = 'username=John Doe'; - document.cookie = 'password=1234'; - return document.cookie.split('; ').sort().join('; '); - }); - const cookies = await context.cookies(); - cookies.sort((a, b) => a.name.localeCompare(b.name)); - expect(documentCookie).toBe('password=1234; username=John Doe'); - expect(cookies).toEqual([ - { - name: 'password', - value: '1234', - domain: 'localhost', - path: '/', - expires: -1, - httpOnly: false, - secure: false, - sameSite: 'None', - }, - { - name: 'username', - value: 'John Doe', - domain: 'localhost', - path: '/', - expires: -1, - httpOnly: false, - secure: false, - sameSite: 'None', - }, - ]); - }); - it('should get cookies from multiple urls', async({context}) => { - await context.addCookies([{ - url: 'https://foo.com', - name: 'doggo', - value: 'woofs', - }, { - url: 'https://bar.com', - name: 'catto', - value: 'purrs', - }, { - url: 'https://baz.com', - name: 'birdo', - value: 'tweets', - }]); - const cookies = await context.cookies(['https://foo.com', 'https://baz.com']); - cookies.sort((a, b) => a.name.localeCompare(b.name)); - expect(cookies).toEqual([{ - name: 'birdo', - value: 'tweets', - domain: 'baz.com', - path: '/', - expires: -1, - httpOnly: false, - secure: true, - sameSite: 'None', - }, { - name: 'doggo', - value: 'woofs', - domain: 'foo.com', - path: '/', - expires: -1, - httpOnly: false, - secure: true, - sameSite: 'None', - }]); - }); -}); - -describe('BrowserContext.addCookies', function() { - it('should work', async({context, page, server}) => { - await page.goto(server.EMPTY_PAGE); - await context.addCookies([{ - url: server.EMPTY_PAGE, - name: 'password', - value: '123456' - }]); - expect(await page.evaluate(() => document.cookie)).toEqual('password=123456'); - }); - it('should roundtrip cookie', async({context, page, server}) => { - await page.goto(server.EMPTY_PAGE); - // @see https://en.wikipedia.org/wiki/Year_2038_problem - const date = +(new Date('1/1/2038')); - const documentCookie = await page.evaluate(timestamp => { - const date = new Date(timestamp); - document.cookie = `username=John Doe;expires=${date.toUTCString()}`; - return document.cookie; - }, date); - expect(documentCookie).toBe('username=John Doe'); - const cookies = await context.cookies(); - await context.clearCookies(); - expect(await context.cookies()).toEqual([]); - await context.addCookies(cookies); - expect(await context.cookies()).toEqual(cookies); - }); - it('should send cookie header', async({server, context}) => { - let cookie = ''; - server.setRoute('/empty.html', (req, res) => { - cookie = req.headers.cookie; - res.end(); - }); - await context.addCookies([{url: server.EMPTY_PAGE, name: 'cookie', value: 'value'}]); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - expect(cookie).toBe('cookie=value'); - }); - it('should isolate cookies in browser contexts', async({context, server, browser}) => { - const anotherContext = await browser.newContext(); - await context.addCookies([{url: server.EMPTY_PAGE, name: 'isolatecookie', value: 'page1value'}]); - await anotherContext.addCookies([{url: server.EMPTY_PAGE, name: 'isolatecookie', value: 'page2value'}]); - - const cookies1 = await context.cookies(); - const cookies2 = await anotherContext.cookies(); - expect(cookies1.length).toBe(1); - expect(cookies2.length).toBe(1); - expect(cookies1[0].name).toBe('isolatecookie'); - expect(cookies1[0].value).toBe('page1value'); - expect(cookies2[0].name).toBe('isolatecookie'); - expect(cookies2[0].value).toBe('page2value'); - await anotherContext.close(); - }); - it('should isolate session cookies', async({context, server, browser}) => { - server.setRoute('/setcookie.html', (req, res) => { - res.setHeader('Set-Cookie', 'session=value'); - res.end(); - }); - { - const page = await context.newPage(); - await page.goto(server.PREFIX + '/setcookie.html'); - } - { - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const cookies = await context.cookies(); - expect(cookies.length).toBe(1); - expect(cookies.map(c => c.value).join(',')).toBe('value'); - } - { - const context2 = await browser.newContext(); - const page = await context2.newPage(); - await page.goto(server.EMPTY_PAGE); - const cookies = await context2.cookies(); - expect(cookies[0] && cookies[0].name).toBe(undefined); - await context2.close(); - } - }); - it('should isolate persistent cookies', async({context, server, browser}) => { - server.setRoute('/setcookie.html', (req, res) => { - res.setHeader('Set-Cookie', 'persistent=persistent-value; max-age=3600'); - res.end(); - }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/setcookie.html'); - - const context1 = context; - const context2 = await browser.newContext(); - const [page1, page2] = await Promise.all([context1.newPage(), context2.newPage()]); - await Promise.all([page1.goto(server.EMPTY_PAGE), page2.goto(server.EMPTY_PAGE)]); - const [cookies1, cookies2] = await Promise.all([context1.cookies(), context2.cookies()]); - expect(cookies1.length).toBe(1); - expect(cookies1[0].name).toBe('persistent'); - expect(cookies1[0].value).toBe('persistent-value'); - expect(cookies2.length).toBe(0); - await context2.close(); - }); - it('should isolate send cookie header', async({server, context, browser}) => { - let cookie = []; - server.setRoute('/empty.html', (req, res) => { - cookie = req.headers.cookie || ''; - res.end(); - }); - await context.addCookies([{url: server.EMPTY_PAGE, name: 'sendcookie', value: 'value'}]); - { - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - expect(cookie).toBe('sendcookie=value'); - } - { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - expect(cookie).toBe(''); - await context.close(); - } - }); - it.slow()('should isolate cookies between launches', async({browserType, server, defaultBrowserOptions}) => { - const browser1 = await browserType.launch(defaultBrowserOptions); - const context1 = await browser1.newContext(); - await context1.addCookies([{url: server.EMPTY_PAGE, name: 'cookie-in-context-1', value: 'value', expires: Date.now() / 1000 + 10000}]); - await browser1.close(); - - const browser2 = await browserType.launch(defaultBrowserOptions); - const context2 = await browser2.newContext(); - const cookies = await context2.cookies(); - expect(cookies.length).toBe(0); - await browser2.close(); - }); - it('should set multiple cookies', async({context, page, server}) => { - await page.goto(server.EMPTY_PAGE); - await context.addCookies([{ - url: server.EMPTY_PAGE, - name: 'multiple-1', - value: '123456' - }, { - url: server.EMPTY_PAGE, - name: 'multiple-2', - value: 'bar' - }]); - expect(await page.evaluate(() => { - const cookies = document.cookie.split(';'); - return cookies.map(cookie => cookie.trim()).sort(); - })).toEqual([ - 'multiple-1=123456', - 'multiple-2=bar', - ]); - }); - it('should have |expires| set to |-1| for session cookies', async({context, server}) => { - await context.addCookies([{ - url: server.EMPTY_PAGE, - name: 'expires', - value: '123456' - }]); - const cookies = await context.cookies(); - expect(cookies[0].expires).toBe(-1); - }); - it('should set cookie with reasonable defaults', async({context, server}) => { - await context.addCookies([{ - url: server.EMPTY_PAGE, - name: 'defaults', - value: '123456' - }]); - const cookies = await context.cookies(); - expect(cookies.sort((a, b) => a.name.localeCompare(b.name))).toEqual([{ - name: 'defaults', - value: '123456', - domain: 'localhost', - path: '/', - expires: -1, - httpOnly: false, - secure: false, - sameSite: 'None', - }]); - }); - it('should set a cookie with a path', async({context, page, server}) => { - await page.goto(server.PREFIX + '/grid.html'); - await context.addCookies([{ - domain: 'localhost', - path: '/grid.html', - name: 'gridcookie', - value: 'GRID', - }]); - expect(await context.cookies()).toEqual([{ - name: 'gridcookie', - value: 'GRID', - domain: 'localhost', - path: '/grid.html', - expires: -1, - httpOnly: false, - secure: false, - sameSite: 'None', - }]); - expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID'); - await page.goto(server.EMPTY_PAGE); - expect(await page.evaluate('document.cookie')).toBe(''); - await page.goto(server.PREFIX + '/grid.html'); - expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID'); - }); - it('should not set a cookie with blank page URL', async function({context, server}) { - let error = null; - try { - await context.addCookies([ - {url: server.EMPTY_PAGE, name: 'example-cookie', value: 'best'}, - {url: 'about:blank', name: 'example-cookie-blank', value: 'best'} - ]); - } catch (e) { - error = e; - } - expect(error.message).toContain( - `Blank page can not have cookie "example-cookie-blank"` - ); - }); - it('should not set a cookie on a data URL page', async function({context}) { - let error = null; - try { - await context.addCookies([{url: 'data:,Hello%2C%20World!', name: 'example-cookie', value: 'best'}]); - } catch (e) { - error = e; - } - expect(error.message).toContain('Data URL page can not have cookie "example-cookie"'); - }); - it('should default to setting secure cookie for HTTPS websites', async({context, page, server}) => { - await page.goto(server.EMPTY_PAGE); - const SECURE_URL = 'https://example.com'; - await context.addCookies([{ - url: SECURE_URL, - name: 'foo', - value: 'bar', - }]); - const [cookie] = await context.cookies(SECURE_URL); - expect(cookie.secure).toBe(true); - }); - it('should be able to set unsecure cookie for HTTP website', async({context, page, server}) => { - await page.goto(server.EMPTY_PAGE); - const HTTP_URL = 'http://example.com'; - await context.addCookies([{ - url: HTTP_URL, - name: 'foo', - value: 'bar', - }]); - const [cookie] = await context.cookies(HTTP_URL); - expect(cookie.secure).toBe(false); - }); - it('should set a cookie on a different domain', async({context, page, server}) => { - await page.goto(server.EMPTY_PAGE); - await context.addCookies([{ - url: 'https://www.example.com', - name: 'example-cookie', - value: 'best', - }]); - expect(await page.evaluate('document.cookie')).toBe(''); - expect(await context.cookies('https://www.example.com')).toEqual([{ - name: 'example-cookie', - value: 'best', - domain: 'www.example.com', - path: '/', - expires: -1, - httpOnly: false, - secure: true, - sameSite: 'None', - }]); - }); - it('should set cookies for a frame', async({context, page, server}) => { - await page.goto(server.EMPTY_PAGE); - await context.addCookies([ - {url: server.PREFIX, name: 'frame-cookie', value: 'value'} - ]); - await page.evaluate(src => { - let fulfill; - const promise = new Promise(x => fulfill = x); - const iframe = document.createElement('iframe'); - document.body.appendChild(iframe); - iframe.onload = fulfill; - iframe.src = src; - return promise; - }, server.PREFIX + '/grid.html'); - - expect(await page.frames()[1].evaluate('document.cookie')).toBe('frame-cookie=value'); - }); - it('should(not) block third party cookies', async({context, page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.evaluate(src => { - let fulfill; - const promise = new Promise(x => fulfill = x); - const iframe = document.createElement('iframe'); - document.body.appendChild(iframe); - iframe.onload = fulfill; - iframe.src = src; - return promise; - }, server.CROSS_PROCESS_PREFIX + '/grid.html'); - await page.frames()[1].evaluate(`document.cookie = 'username=John Doe'`); - await page.waitForTimeout(2000); - const allowsThirdParty = CHROMIUM || FFOX; - const cookies = await context.cookies(server.CROSS_PROCESS_PREFIX + '/grid.html'); - if (allowsThirdParty) { - expect(cookies).toEqual([ - { - "domain": "127.0.0.1", - "expires": -1, - "httpOnly": false, - "name": "username", - "path": "/", - "sameSite": "None", - "secure": false, - "value": "John Doe" - } - ]); - } else { - expect(cookies).toEqual([]); - } - }); -}); - -describe('BrowserContext.clearCookies', function() { - it('should clear cookies', async({context, page, server}) => { - await page.goto(server.EMPTY_PAGE); - await context.addCookies([{ - url: server.EMPTY_PAGE, - name: 'cookie1', - value: '1' - }]); - expect(await page.evaluate('document.cookie')).toBe('cookie1=1'); - await context.clearCookies(); - expect(await context.cookies()).toEqual([]); - await page.reload(); - expect(await page.evaluate('document.cookie')).toBe(''); - }); - it('should isolate cookies when clearing', async({context, server, browser}) => { - const anotherContext = await browser.newContext(); - await context.addCookies([{url: server.EMPTY_PAGE, name: 'page1cookie', value: 'page1value'}]); - await anotherContext.addCookies([{url: server.EMPTY_PAGE, name: 'page2cookie', value: 'page2value'}]); - - expect((await context.cookies()).length).toBe(1); - expect((await anotherContext.cookies()).length).toBe(1); - - await context.clearCookies(); - expect((await context.cookies()).length).toBe(0); - expect((await anotherContext.cookies()).length).toBe(1); - - await anotherContext.clearCookies(); - expect((await context.cookies()).length).toBe(0); - expect((await anotherContext.cookies()).length).toBe(0); - await anotherContext.close(); - }); -}); diff --git a/test/coverage.jest.js b/test/coverage.jest.js deleted file mode 100644 index e4b51d74a2..0000000000 --- a/test/coverage.jest.js +++ /dev/null @@ -1,190 +0,0 @@ -/** - * 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 {FFOX, CHROMIUM, WEBKIT} = testOptions; - -describe.skip(CHROMIUM)('Page.coverage missing', function() { - it('should work', async function({page, server}) { - expect(page.coverage).toBe(null); - }); -}); - -describe.skip(!CHROMIUM)('JSCoverage', function() { - it('should work', async function({browserType, page, server}) { - await page.coverage.startJSCoverage(); - await page.goto(server.PREFIX + '/jscoverage/simple.html', { waitUntil: 'load' }); - const coverage = await page.coverage.stopJSCoverage(); - expect(coverage.length).toBe(1); - expect(coverage[0].url).toContain('/jscoverage/simple.html'); - expect(coverage[0].functions.find(f => f.functionName === 'foo').ranges[0].count).toEqual(1); - }); - it('should report sourceURLs', async function({page, server}) { - await page.coverage.startJSCoverage(); - await page.goto(server.PREFIX + '/jscoverage/sourceurl.html'); - const coverage = await page.coverage.stopJSCoverage(); - expect(coverage.length).toBe(1); - expect(coverage[0].url).toBe('nicename.js'); - }); - it('should ignore eval() scripts by default', async function({page, server}) { - await page.coverage.startJSCoverage(); - await page.goto(server.PREFIX + '/jscoverage/eval.html'); - const coverage = await page.coverage.stopJSCoverage(); - expect(coverage.length).toBe(1); - }); - it('shouldn\'t ignore eval() scripts if reportAnonymousScripts is true', async function({page, server}) { - await page.coverage.startJSCoverage({reportAnonymousScripts: true}); - await page.goto(server.PREFIX + '/jscoverage/eval.html'); - const coverage = await page.coverage.stopJSCoverage(); - expect(coverage.find(entry => entry.url.startsWith('debugger://'))).not.toBe(null); - expect(coverage.length).toBe(2); - }); - it('should ignore playwright internal scripts if reportAnonymousScripts is true', async function({page, server}) { - await page.coverage.startJSCoverage({reportAnonymousScripts: true}); - await page.goto(server.EMPTY_PAGE); - await page.evaluate('console.log("foo")'); - await page.evaluate(() => console.log('bar')); - const coverage = await page.coverage.stopJSCoverage(); - expect(coverage.length).toBe(0); - }); - it('should report multiple scripts', async function({page, server}) { - await page.coverage.startJSCoverage(); - await page.goto(server.PREFIX + '/jscoverage/multiple.html'); - const coverage = await page.coverage.stopJSCoverage(); - expect(coverage.length).toBe(2); - coverage.sort((a, b) => a.url.localeCompare(b.url)); - expect(coverage[0].url).toContain('/jscoverage/script1.js'); - expect(coverage[1].url).toContain('/jscoverage/script2.js'); - }); - describe('resetOnNavigation', function() { - it('should report scripts across navigations when disabled', async function({page, server}) { - await page.coverage.startJSCoverage({resetOnNavigation: false}); - await page.goto(server.PREFIX + '/jscoverage/multiple.html'); - await page.goto(server.EMPTY_PAGE); - const coverage = await page.coverage.stopJSCoverage(); - expect(coverage.length).toBe(2); - }); - it('should NOT report scripts across navigations when enabled', async function({page, server}) { - await page.coverage.startJSCoverage(); // Enabled by default. - await page.goto(server.PREFIX + '/jscoverage/multiple.html'); - await page.goto(server.EMPTY_PAGE); - const coverage = await page.coverage.stopJSCoverage(); - expect(coverage.length).toBe(0); - }); - }); - it('should not hang when there is a debugger statement', async function({page, server}) { - await page.coverage.startJSCoverage(); - await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => { - debugger; // eslint-disable-line no-debugger - }); - await page.coverage.stopJSCoverage(); - }); -}); - -describe.skip(!CHROMIUM)('CSSCoverage', function() { - it('should work', async function({browserType, page, server}) { - await page.coverage.startCSSCoverage(); - await page.goto(server.PREFIX + '/csscoverage/simple.html'); - const coverage = await page.coverage.stopCSSCoverage(); - expect(coverage.length).toBe(1); - expect(coverage[0].url).toContain('/csscoverage/simple.html'); - expect(coverage[0].ranges).toEqual([ - {start: 1, end: 22} - ]); - const range = coverage[0].ranges[0]; - expect(coverage[0].text.substring(range.start, range.end)).toBe('div { color: green; }'); - }); - it('should report sourceURLs', async function({page, server}) { - await page.coverage.startCSSCoverage(); - await page.goto(server.PREFIX + '/csscoverage/sourceurl.html'); - const coverage = await page.coverage.stopCSSCoverage(); - expect(coverage.length).toBe(1); - expect(coverage[0].url).toBe('nicename.css'); - }); - it('should report multiple stylesheets', async function({page, server}) { - await page.coverage.startCSSCoverage(); - await page.goto(server.PREFIX + '/csscoverage/multiple.html'); - const coverage = await page.coverage.stopCSSCoverage(); - expect(coverage.length).toBe(2); - coverage.sort((a, b) => a.url.localeCompare(b.url)); - expect(coverage[0].url).toContain('/csscoverage/stylesheet1.css'); - expect(coverage[1].url).toContain('/csscoverage/stylesheet2.css'); - }); - it('should report stylesheets that have no coverage', async function({page, server}) { - await page.coverage.startCSSCoverage(); - await page.goto(server.PREFIX + '/csscoverage/unused.html'); - const coverage = await page.coverage.stopCSSCoverage(); - expect(coverage.length).toBe(1); - expect(coverage[0].url).toBe('unused.css'); - expect(coverage[0].ranges.length).toBe(0); - }); - it('should work with media queries', async function({page, server}) { - await page.coverage.startCSSCoverage(); - await page.goto(server.PREFIX + '/csscoverage/media.html'); - const coverage = await page.coverage.stopCSSCoverage(); - expect(coverage.length).toBe(1); - expect(coverage[0].url).toContain('/csscoverage/media.html'); - expect(coverage[0].ranges).toEqual([ - {start: 17, end: 38} - ]); - }); - it('should work with complicated usecases', async function({page, server}) { - await page.coverage.startCSSCoverage(); - await page.goto(server.PREFIX + '/csscoverage/involved.html'); - const coverage = await page.coverage.stopCSSCoverage(); - expect(JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':/')).toMatchSnapshot(); - }); - it('should ignore injected stylesheets', async function({page, server}) { - await page.coverage.startCSSCoverage(); - await page.addStyleTag({content: 'body { margin: 10px;}'}); - // trigger style recalc - const margin = await page.evaluate(() => window.getComputedStyle(document.body).margin); - expect(margin).toBe('10px'); - const coverage = await page.coverage.stopCSSCoverage(); - expect(coverage.length).toBe(0); - }); - describe('resetOnNavigation', function() { - it('should report stylesheets across navigations', async function({page, server}) { - await page.coverage.startCSSCoverage({resetOnNavigation: false}); - await page.goto(server.PREFIX + '/csscoverage/multiple.html'); - await page.goto(server.EMPTY_PAGE); - const coverage = await page.coverage.stopCSSCoverage(); - expect(coverage.length).toBe(2); - }); - it('should NOT report scripts across navigations', async function({page, server}) { - await page.coverage.startCSSCoverage(); // Enabled by default. - await page.goto(server.PREFIX + '/csscoverage/multiple.html'); - await page.goto(server.EMPTY_PAGE); - const coverage = await page.coverage.stopCSSCoverage(); - expect(coverage.length).toBe(0); - }); - }); - it('should work with a recently loaded stylesheet', async function({page, server}) { - await page.coverage.startCSSCoverage(); - await page.evaluate(async url => { - document.body.textContent = 'hello, world'; - - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = url; - document.head.appendChild(link); - await new Promise(x => link.onload = x); - await new Promise(f => requestAnimationFrame(f)); - }, server.PREFIX + '/csscoverage/stylesheet1.css'); - const coverage = await page.coverage.stopCSSCoverage(); - expect(coverage.length).toBe(1); - }); -}); diff --git a/test/defaultbrowsercontext.jest.js b/test/defaultbrowsercontext.jest.js deleted file mode 100644 index 03c21166f3..0000000000 --- a/test/defaultbrowsercontext.jest.js +++ /dev/null @@ -1,376 +0,0 @@ -/** - * 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 fs = require('fs'); -const path = require('path'); -const utils = require('./utils'); -const os = require('os'); - -const {mkdtempAsync, makeUserDataDir, removeUserDataDir} = utils; -const {FFOX, MAC, CHROMIUM, WEBKIT, WIN, USES_HOOKS} = testOptions; - -registerFixture('userDataDir', async ({}, test) => { - const userDataDir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_profile-')); - try { - await test(userDataDir); - } finally { - removeFolderAsync(userDataDir).catch(e => {}); - } -}); - -registerFixture('launchPersistent', async ({userDataDir, defaultBrowserOptions, browserType}, test) => { - let context; - async function launchPersistent(options) { - if (context) - throw new Error('can only launch one persitent context'); - context = await browserType.launchPersistentContext(userDataDir, {...defaultBrowserOptions, ...options}); - const page = context.pages()[0]; - return {context, page}; - } - try { - await test(launchPersistent); - } finally { - if (context) - await context.close(); - } -}); - -describe('launchPersistentContext()', function() { - it('context.cookies() should work', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent(); - await page.goto(server.EMPTY_PAGE); - const documentCookie = await page.evaluate(() => { - document.cookie = 'username=John Doe'; - return document.cookie; - }); - expect(documentCookie).toBe('username=John Doe'); - expect(await page.context().cookies()).toEqual([{ - name: 'username', - value: 'John Doe', - domain: 'localhost', - path: '/', - expires: -1, - httpOnly: false, - secure: false, - sameSite: 'None', - }]); - }); - it('context.addCookies() should work', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent(); - await page.goto(server.EMPTY_PAGE); - await page.context().addCookies([{ - url: server.EMPTY_PAGE, - name: 'username', - value: 'John Doe' - }]); - expect(await page.evaluate(() => document.cookie)).toBe('username=John Doe'); - expect(await page.context().cookies()).toEqual([{ - name: 'username', - value: 'John Doe', - domain: 'localhost', - path: '/', - expires: -1, - httpOnly: false, - secure: false, - sameSite: 'None', - }]); - }); - it('context.clearCookies() should work', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent(); - await page.goto(server.EMPTY_PAGE); - await page.context().addCookies([{ - url: server.EMPTY_PAGE, - name: 'cookie1', - value: '1' - }, { - url: server.EMPTY_PAGE, - name: 'cookie2', - value: '2' - }]); - expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie2=2'); - await page.context().clearCookies(); - await page.reload(); - expect(await page.context().cookies([])).toEqual([]); - expect(await page.evaluate('document.cookie')).toBe(''); - }); - it('should(not) block third party cookies', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent(); - await page.goto(server.EMPTY_PAGE); - await page.evaluate(src => { - let fulfill; - const promise = new Promise(x => fulfill = x); - const iframe = document.createElement('iframe'); - document.body.appendChild(iframe); - iframe.onload = fulfill; - iframe.src = src; - return promise; - }, server.CROSS_PROCESS_PREFIX + '/grid.html'); - const documentCookie = await page.frames()[1].evaluate(() => { - document.cookie = 'username=John Doe'; - return document.cookie; - }); - await page.waitForTimeout(2000); - const allowsThirdParty = CHROMIUM || FFOX; - expect(documentCookie).toBe(allowsThirdParty ? 'username=John Doe' : ''); - const cookies = await context.cookies(server.CROSS_PROCESS_PREFIX + '/grid.html'); - if (allowsThirdParty) { - expect(cookies).toEqual([ - { - "domain": "127.0.0.1", - "expires": -1, - "httpOnly": false, - "name": "username", - "path": "/", - "sameSite": "None", - "secure": false, - "value": "John Doe" - } - ]); - } else { - expect(cookies).toEqual([]); - } - }); - it('should support viewport option', async ({launchPersistent}) => { - const {page, context} = await launchPersistent({viewport: { width: 456, height: 789 }}); - await utils.verifyViewport(page, 456, 789); - const page2 = await context.newPage(); - await utils.verifyViewport(page2, 456, 789); - }); - it('should support deviceScaleFactor option', async ({launchPersistent}) => { - const {page, context} = await launchPersistent({deviceScaleFactor: 3}); - expect(await page.evaluate('window.devicePixelRatio')).toBe(3); - }); - it('should support userAgent option', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent({userAgent: 'foobar'}); - expect(await page.evaluate(() => navigator.userAgent)).toBe('foobar'); - const [request] = await Promise.all([ - server.waitForRequest('/empty.html'), - page.goto(server.EMPTY_PAGE), - ]); - expect(request.headers['user-agent']).toBe('foobar'); - }); - it('should support bypassCSP option', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent({bypassCSP: true}); - await page.goto(server.PREFIX + '/csp.html'); - await page.addScriptTag({content: 'window.__injected = 42;'}); - expect(await page.evaluate(() => window.__injected)).toBe(42); - }); - it('should support javascriptEnabled option', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent({javaScriptEnabled: false}); - await page.goto('data:text/html, '); - let error = null; - await page.evaluate('something').catch(e => error = e); - if (WEBKIT) - expect(error.message).toContain('Can\'t find variable: something'); - else - expect(error.message).toContain('something is not defined'); - }); - it('should support httpCredentials option', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent({httpCredentials: { username: 'user', password: 'pass' }}); - server.setAuth('/playground.html', 'user', 'pass'); - const response = await page.goto(server.PREFIX + '/playground.html'); - expect(response.status()).toBe(200); - }); - it('should support offline option', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent({offline: true}); - const error = await page.goto(server.EMPTY_PAGE).catch(e => e); - expect(error).toBeTruthy(); - }); - it.skip(true)('should support acceptDownloads option', async ({server, launchPersistent}) => { - // TODO: unskip once we support downloads in persistent context. - const {page, context} = await launchPersistent({acceptDownloads: true}); - server.setRoute('/download', (req, res) => { - res.setHeader('Content-Type', 'application/octet-stream'); - res.setHeader('Content-Disposition', 'attachment'); - res.end(`Hello world`); - }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path = await download.path(); - expect(fs.existsSync(path)).toBeTruthy(); - expect(fs.readFileSync(path).toString()).toBe('Hello world'); - }); - it('should support hasTouch option', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent({hasTouch: true}); - await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(true); - }); - it.skip(FFOX)('should work in persistent context', async ({server, launchPersistent}) => { - // Firefox does not support mobile. - const {page, context} = await launchPersistent({viewport: {width: 320, height: 480}, isMobile: true}); - await page.goto(server.PREFIX + '/empty.html'); - expect(await page.evaluate(() => window.innerWidth)).toBe(980); - }); - it('should support colorScheme option', async ({launchPersistent}) => { - const {page, context} = await launchPersistent({colorScheme: 'dark'}); - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); - }); - it('should support timezoneId option', async ({launchPersistent}) => { - const {page, context} = await launchPersistent({timezoneId: 'America/Jamaica'}); - expect(await page.evaluate(() => new Date(1479579154987).toString())).toBe('Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)'); - }); - it('should support locale option', async ({launchPersistent}) => { - const {page, context} = await launchPersistent({locale: 'fr-CH'}); - expect(await page.evaluate(() => navigator.language)).toBe('fr-CH'); - }); - it('should support geolocation and permissions options', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent({geolocation: {longitude: 10, latitude: 10}, permissions: ['geolocation']}); - await page.goto(server.EMPTY_PAGE); - const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => { - resolve({latitude: position.coords.latitude, longitude: position.coords.longitude}); - }))); - expect(geolocation).toEqual({latitude: 10, longitude: 10}); - }); - it('should support ignoreHTTPSErrors option', async ({httpsServer, launchPersistent}) => { - const {page, context} = await launchPersistent({ignoreHTTPSErrors: true}); - let error = null; - const response = await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); - expect(error).toBe(null); - expect(response.ok()).toBe(true); - }); - it('should support extraHTTPHeaders option', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent({extraHTTPHeaders: { foo: 'bar' }}); - const [request] = await Promise.all([ - server.waitForRequest('/empty.html'), - page.goto(server.EMPTY_PAGE), - ]); - expect(request.headers['foo']).toBe('bar'); - }); - it('should accept userDataDir', async ({launchPersistent, userDataDir}) => { - const {page, context} = await launchPersistent(); - // Note: we need an open page to make sure its functional. - expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); - await context.close(); - expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); - // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await removeUserDataDir(userDataDir); - }); - it.slow()('should restore state from userDataDir', async({browserType, defaultBrowserOptions, server, launchPersistent}) => { - const userDataDir = await makeUserDataDir(); - const browserContext = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions); - const page = await browserContext.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => localStorage.hey = 'hello'); - await browserContext.close(); - - const browserContext2 = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions); - const page2 = await browserContext2.newPage(); - await page2.goto(server.EMPTY_PAGE); - expect(await page2.evaluate(() => localStorage.hey)).toBe('hello'); - await browserContext2.close(); - - const userDataDir2 = await makeUserDataDir(); - const browserContext3 = await browserType.launchPersistentContext(userDataDir2, defaultBrowserOptions); - const page3 = await browserContext3.newPage(); - await page3.goto(server.EMPTY_PAGE); - expect(await page3.evaluate(() => localStorage.hey)).not.toBe('hello'); - await browserContext3.close(); - - // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await removeUserDataDir(userDataDir); - await removeUserDataDir(userDataDir2); - }); - it.fail(CHROMIUM && (WIN || MAC)).slow()('should restore cookies from userDataDir', async({browserType, defaultBrowserOptions, server, launchPersistent}) => { - const userDataDir = await makeUserDataDir(); - const browserContext = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions); - const page = await browserContext.newPage(); - await page.goto(server.EMPTY_PAGE); - const documentCookie = await page.evaluate(() => { - document.cookie = 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'; - return document.cookie; - }); - expect(documentCookie).toBe('doSomethingOnlyOnce=true'); - await browserContext.close(); - - const browserContext2 = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions); - const page2 = await browserContext2.newPage(); - await page2.goto(server.EMPTY_PAGE); - expect(await page2.evaluate(() => document.cookie)).toBe('doSomethingOnlyOnce=true'); - await browserContext2.close(); - - const userDataDir2 = await makeUserDataDir(); - const browserContext3 = await browserType.launchPersistentContext(userDataDir2, defaultBrowserOptions); - const page3 = await browserContext3.newPage(); - await page3.goto(server.EMPTY_PAGE); - expect(await page3.evaluate(() => document.cookie)).not.toBe('doSomethingOnlyOnce=true'); - await browserContext3.close(); - - // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await removeUserDataDir(userDataDir); - await removeUserDataDir(userDataDir2); - }); - it('should have default URL when launching browser', async ({launchPersistent}) => { - const {page, context} = await launchPersistent(); - const urls = context.pages().map(page => page.url()); - expect(urls).toEqual(['about:blank']); - }); - it.skip(FFOX)('should throw if page argument is passed', async ({browserType, defaultBrowserOptions, server, userDataDir}) => { - const options = {...defaultBrowserOptions, args: [server.EMPTY_PAGE] }; - const error = await browserType.launchPersistentContext(userDataDir, options).catch(e => e); - expect(error.message).toContain('can not specify page'); - }); - it.skip(USES_HOOKS)('should have passed URL when launching with ignoreDefaultArgs: true', async ({browserType, defaultBrowserOptions, server, userDataDir, toImpl}) => { - const args = toImpl(browserType)._defaultArgs(defaultBrowserOptions, 'persistent', userDataDir, 0).filter(a => a !== 'about:blank'); - const options = { - ...defaultBrowserOptions, - args: [...args, server.EMPTY_PAGE], - ignoreDefaultArgs: true, - }; - const browserContext = await browserType.launchPersistentContext(userDataDir, options); - if (!browserContext.pages().length) - await browserContext.waitForEvent('page'); - await browserContext.pages()[0].waitForLoadState(); - const gotUrls = browserContext.pages().map(page => page.url()); - expect(gotUrls).toEqual([server.EMPTY_PAGE]); - await browserContext.close(); - }); - it.skip(USES_HOOKS)('should handle timeout', async({browserType, defaultBrowserOptions, userDataDir}) => { - const options = { ...defaultBrowserOptions, timeout: 5000, __testHookBeforeCreateBrowser: () => new Promise(f => setTimeout(f, 6000)) }; - const error = await browserType.launchPersistentContext(userDataDir, options).catch(e => e); - expect(error.message).toContain(`browserType.launchPersistentContext: Timeout 5000ms exceeded.`); - }); - it.skip(USES_HOOKS)('should handle exception', async({browserType, defaultBrowserOptions, userDataDir}) => { - const e = new Error('Dummy'); - const options = { ...defaultBrowserOptions, __testHookBeforeCreateBrowser: () => { throw e; } }; - const error = await browserType.launchPersistentContext(userDataDir, options).catch(e => e); - expect(error.message).toContain('Dummy'); - }); - it('should fire close event for a persistent context', async({launchPersistent}) => { - const {page, context} = await launchPersistent(); - let closed = false; - context.on('close', () => closed = true); - await context.close(); - expect(closed).toBe(true); - }); - it.skip(!CHROMIUM)('coverage should work', async ({server, launchPersistent}) => { - const {page, context} = await launchPersistent(); - await page.coverage.startJSCoverage(); - await page.goto(server.PREFIX + '/jscoverage/simple.html', { waitUntil: 'load' }); - const coverage = await page.coverage.stopJSCoverage(); - expect(coverage.length).toBe(1); - expect(coverage[0].url).toContain('/jscoverage/simple.html'); - expect(coverage[0].functions.find(f => f.functionName === 'foo').ranges[0].count).toEqual(1); - }); - it.skip(CHROMIUM)('coverage should be missing', async ({launchPersistent}) => { - const {page, context} = await launchPersistent(); - expect(page.coverage).toBe(null); - }); -}); diff --git a/test/defaultbrowsercontext.spec.js b/test/defaultbrowsercontext.spec.js new file mode 100644 index 0000000000..13d9961835 --- /dev/null +++ b/test/defaultbrowsercontext.spec.js @@ -0,0 +1,404 @@ +/** + * 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 fs = require('fs'); +const path = require('path'); +const utils = require('./utils'); +const os = require('os'); + +const {mkdtempAsync, makeUserDataDir, removeUserDataDir} = utils; +const {FFOX, MAC, CHROMIUM, WEBKIT, WIN, USES_HOOKS} = testOptions; + +registerFixture('userDataDir', async ({}, test) => { + const userDataDir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_profile-')); + try { + await test(userDataDir); + } finally { + removeFolderAsync(userDataDir).catch(e => {}); + } +}); + +registerFixture('launchPersistent', async ({userDataDir, defaultBrowserOptions, browserType}, test) => { + let context; + async function launchPersistent(options) { + if (context) + throw new Error('can only launch one persitent context'); + context = await browserType.launchPersistentContext(userDataDir, {...defaultBrowserOptions, ...options}); + const page = context.pages()[0]; + return {context, page}; + } + try { + await test(launchPersistent); + } finally { + if (context) + await context.close(); + } +}); + +it('context.cookies() should work', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent(); + await page.goto(server.EMPTY_PAGE); + const documentCookie = await page.evaluate(() => { + document.cookie = 'username=John Doe'; + return document.cookie; + }); + expect(documentCookie).toBe('username=John Doe'); + expect(await page.context().cookies()).toEqual([{ + name: 'username', + value: 'John Doe', + domain: 'localhost', + path: '/', + expires: -1, + httpOnly: false, + secure: false, + sameSite: 'None', + }]); +}); + +it('context.addCookies() should work', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent(); + await page.goto(server.EMPTY_PAGE); + await page.context().addCookies([{ + url: server.EMPTY_PAGE, + name: 'username', + value: 'John Doe' + }]); + expect(await page.evaluate(() => document.cookie)).toBe('username=John Doe'); + expect(await page.context().cookies()).toEqual([{ + name: 'username', + value: 'John Doe', + domain: 'localhost', + path: '/', + expires: -1, + httpOnly: false, + secure: false, + sameSite: 'None', + }]); +}); + +it('context.clearCookies() should work', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent(); + await page.goto(server.EMPTY_PAGE); + await page.context().addCookies([{ + url: server.EMPTY_PAGE, + name: 'cookie1', + value: '1' + }, { + url: server.EMPTY_PAGE, + name: 'cookie2', + value: '2' + }]); + expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie2=2'); + await page.context().clearCookies(); + await page.reload(); + expect(await page.context().cookies([])).toEqual([]); + expect(await page.evaluate('document.cookie')).toBe(''); +}); + +it('should(not) block third party cookies', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent(); + await page.goto(server.EMPTY_PAGE); + await page.evaluate(src => { + let fulfill; + const promise = new Promise(x => fulfill = x); + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + iframe.onload = fulfill; + iframe.src = src; + return promise; + }, server.CROSS_PROCESS_PREFIX + '/grid.html'); + const documentCookie = await page.frames()[1].evaluate(() => { + document.cookie = 'username=John Doe'; + return document.cookie; + }); + await page.waitForTimeout(2000); + const allowsThirdParty = CHROMIUM || FFOX; + expect(documentCookie).toBe(allowsThirdParty ? 'username=John Doe' : ''); + const cookies = await context.cookies(server.CROSS_PROCESS_PREFIX + '/grid.html'); + if (allowsThirdParty) { + expect(cookies).toEqual([ + { + "domain": "127.0.0.1", + "expires": -1, + "httpOnly": false, + "name": "username", + "path": "/", + "sameSite": "None", + "secure": false, + "value": "John Doe" + } + ]); + } else { + expect(cookies).toEqual([]); + } +}); + +it('should support viewport option', async ({launchPersistent}) => { + const {page, context} = await launchPersistent({viewport: { width: 456, height: 789 }}); + await utils.verifyViewport(page, 456, 789); + const page2 = await context.newPage(); + await utils.verifyViewport(page2, 456, 789); +}); + +it('should support deviceScaleFactor option', async ({launchPersistent}) => { + const {page, context} = await launchPersistent({deviceScaleFactor: 3}); + expect(await page.evaluate('window.devicePixelRatio')).toBe(3); +}); + +it('should support userAgent option', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent({userAgent: 'foobar'}); + expect(await page.evaluate(() => navigator.userAgent)).toBe('foobar'); + const [request] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.goto(server.EMPTY_PAGE), + ]); + expect(request.headers['user-agent']).toBe('foobar'); +}); + +it('should support bypassCSP option', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent({bypassCSP: true}); + await page.goto(server.PREFIX + '/csp.html'); + await page.addScriptTag({content: 'window.__injected = 42;'}); + expect(await page.evaluate(() => window.__injected)).toBe(42); +}); + +it('should support javascriptEnabled option', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent({javaScriptEnabled: false}); + await page.goto('data:text/html, '); + let error = null; + await page.evaluate('something').catch(e => error = e); + if (WEBKIT) + expect(error.message).toContain('Can\'t find variable: something'); + else + expect(error.message).toContain('something is not defined'); +}); + +it('should support httpCredentials option', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent({httpCredentials: { username: 'user', password: 'pass' }}); + server.setAuth('/playground.html', 'user', 'pass'); + const response = await page.goto(server.PREFIX + '/playground.html'); + expect(response.status()).toBe(200); +}); + +it('should support offline option', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent({offline: true}); + const error = await page.goto(server.EMPTY_PAGE).catch(e => e); + expect(error).toBeTruthy(); +}); + +it.skip(true)('should support acceptDownloads option', async ({server, launchPersistent}) => { + // TODO: unskip once we support downloads in persistent context. + const {page, context} = await launchPersistent({acceptDownloads: true}); + server.setRoute('/download', (req, res) => { + res.setHeader('Content-Type', 'application/octet-stream'); + res.setHeader('Content-Disposition', 'attachment'); + res.end(`Hello world`); + }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path = await download.path(); + expect(fs.existsSync(path)).toBeTruthy(); + expect(fs.readFileSync(path).toString()).toBe('Hello world'); +}); + +it('should support hasTouch option', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent({hasTouch: true}); + await page.goto(server.PREFIX + '/mobile.html'); + expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(true); +}); + +it.skip(FFOX)('should work in persistent context', async ({server, launchPersistent}) => { + // Firefox does not support mobile. + const {page, context} = await launchPersistent({viewport: {width: 320, height: 480}, isMobile: true}); + await page.goto(server.PREFIX + '/empty.html'); + expect(await page.evaluate(() => window.innerWidth)).toBe(980); +}); + +it('should support colorScheme option', async ({launchPersistent}) => { + const {page, context} = await launchPersistent({colorScheme: 'dark'}); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); +}); + +it('should support timezoneId option', async ({launchPersistent}) => { + const {page, context} = await launchPersistent({timezoneId: 'America/Jamaica'}); + expect(await page.evaluate(() => new Date(1479579154987).toString())).toBe('Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)'); +}); + +it('should support locale option', async ({launchPersistent}) => { + const {page, context} = await launchPersistent({locale: 'fr-CH'}); + expect(await page.evaluate(() => navigator.language)).toBe('fr-CH'); +}); + +it('should support geolocation and permissions options', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent({geolocation: {longitude: 10, latitude: 10}, permissions: ['geolocation']}); + await page.goto(server.EMPTY_PAGE); + const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => { + resolve({latitude: position.coords.latitude, longitude: position.coords.longitude}); + }))); + expect(geolocation).toEqual({latitude: 10, longitude: 10}); +}); + +it('should support ignoreHTTPSErrors option', async ({httpsServer, launchPersistent}) => { + const {page, context} = await launchPersistent({ignoreHTTPSErrors: true}); + let error = null; + const response = await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); + expect(error).toBe(null); + expect(response.ok()).toBe(true); +}); + +it('should support extraHTTPHeaders option', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent({extraHTTPHeaders: { foo: 'bar' }}); + const [request] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.goto(server.EMPTY_PAGE), + ]); + expect(request.headers['foo']).toBe('bar'); +}); + +it('should accept userDataDir', async ({launchPersistent, userDataDir}) => { + const {page, context} = await launchPersistent(); + // Note: we need an open page to make sure its functional. + expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); + await context.close(); + expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); + // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 + await removeUserDataDir(userDataDir); +}); + +it.slow()('should restore state from userDataDir', async({browserType, defaultBrowserOptions, server, launchPersistent}) => { + const userDataDir = await makeUserDataDir(); + const browserContext = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions); + const page = await browserContext.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => localStorage.hey = 'hello'); + await browserContext.close(); + + const browserContext2 = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions); + const page2 = await browserContext2.newPage(); + await page2.goto(server.EMPTY_PAGE); + expect(await page2.evaluate(() => localStorage.hey)).toBe('hello'); + await browserContext2.close(); + + const userDataDir2 = await makeUserDataDir(); + const browserContext3 = await browserType.launchPersistentContext(userDataDir2, defaultBrowserOptions); + const page3 = await browserContext3.newPage(); + await page3.goto(server.EMPTY_PAGE); + expect(await page3.evaluate(() => localStorage.hey)).not.toBe('hello'); + await browserContext3.close(); + + // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 + await removeUserDataDir(userDataDir); + await removeUserDataDir(userDataDir2); +}); + +it.fail(CHROMIUM && (WIN || MAC)).slow()('should restore cookies from userDataDir', async({browserType, defaultBrowserOptions, server, launchPersistent}) => { + const userDataDir = await makeUserDataDir(); + const browserContext = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions); + const page = await browserContext.newPage(); + await page.goto(server.EMPTY_PAGE); + const documentCookie = await page.evaluate(() => { + document.cookie = 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'; + return document.cookie; + }); + expect(documentCookie).toBe('doSomethingOnlyOnce=true'); + await browserContext.close(); + + const browserContext2 = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions); + const page2 = await browserContext2.newPage(); + await page2.goto(server.EMPTY_PAGE); + expect(await page2.evaluate(() => document.cookie)).toBe('doSomethingOnlyOnce=true'); + await browserContext2.close(); + + const userDataDir2 = await makeUserDataDir(); + const browserContext3 = await browserType.launchPersistentContext(userDataDir2, defaultBrowserOptions); + const page3 = await browserContext3.newPage(); + await page3.goto(server.EMPTY_PAGE); + expect(await page3.evaluate(() => document.cookie)).not.toBe('doSomethingOnlyOnce=true'); + await browserContext3.close(); + + // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 + await removeUserDataDir(userDataDir); + await removeUserDataDir(userDataDir2); +}); + +it('should have default URL when launching browser', async ({launchPersistent}) => { + const {page, context} = await launchPersistent(); + const urls = context.pages().map(page => page.url()); + expect(urls).toEqual(['about:blank']); +}); + +it.skip(FFOX)('should throw if page argument is passed', async ({browserType, defaultBrowserOptions, server, userDataDir}) => { + const options = {...defaultBrowserOptions, args: [server.EMPTY_PAGE] }; + const error = await browserType.launchPersistentContext(userDataDir, options).catch(e => e); + expect(error.message).toContain('can not specify page'); +}); + +it.skip(USES_HOOKS)('should have passed URL when launching with ignoreDefaultArgs: true', async ({browserType, defaultBrowserOptions, server, userDataDir, toImpl}) => { + const args = toImpl(browserType)._defaultArgs(defaultBrowserOptions, 'persistent', userDataDir, 0).filter(a => a !== 'about:blank'); + const options = { + ...defaultBrowserOptions, + args: [...args, server.EMPTY_PAGE], + ignoreDefaultArgs: true, + }; + const browserContext = await browserType.launchPersistentContext(userDataDir, options); + if (!browserContext.pages().length) + await browserContext.waitForEvent('page'); + await browserContext.pages()[0].waitForLoadState(); + const gotUrls = browserContext.pages().map(page => page.url()); + expect(gotUrls).toEqual([server.EMPTY_PAGE]); + await browserContext.close(); +}); + +it.skip(USES_HOOKS)('should handle timeout', async({browserType, defaultBrowserOptions, userDataDir}) => { + const options = { ...defaultBrowserOptions, timeout: 5000, __testHookBeforeCreateBrowser: () => new Promise(f => setTimeout(f, 6000)) }; + const error = await browserType.launchPersistentContext(userDataDir, options).catch(e => e); + expect(error.message).toContain(`browserType.launchPersistentContext: Timeout 5000ms exceeded.`); +}); + +it.skip(USES_HOOKS)('should handle exception', async({browserType, defaultBrowserOptions, userDataDir}) => { + const e = new Error('Dummy'); + const options = { ...defaultBrowserOptions, __testHookBeforeCreateBrowser: () => { throw e; } }; + const error = await browserType.launchPersistentContext(userDataDir, options).catch(e => e); + expect(error.message).toContain('Dummy'); +}); + +it('should fire close event for a persistent context', async({launchPersistent}) => { + const {page, context} = await launchPersistent(); + let closed = false; + context.on('close', () => closed = true); + await context.close(); + expect(closed).toBe(true); +}); + +it.skip(!CHROMIUM)('coverage should work', async ({server, launchPersistent}) => { + const {page, context} = await launchPersistent(); + await page.coverage.startJSCoverage(); + await page.goto(server.PREFIX + '/jscoverage/simple.html', { waitUntil: 'load' }); + const coverage = await page.coverage.stopJSCoverage(); + expect(coverage.length).toBe(1); + expect(coverage[0].url).toContain('/jscoverage/simple.html'); + expect(coverage[0].functions.find(f => f.functionName === 'foo').ranges[0].count).toEqual(1); +}); + +it.skip(CHROMIUM)('coverage should be missing', async ({launchPersistent}) => { + const {page, context} = await launchPersistent(); + expect(page.coverage).toBe(null); +}); diff --git a/test/dialog.jest.js b/test/dialog.jest.js deleted file mode 100644 index e75f39926e..0000000000 --- a/test/dialog.jest.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * 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, CHANNEL} = testOptions; - -describe('Page.Events.Dialog', function() { - it('should fire', async({page, server}) => { - page.on('dialog', dialog => { - expect(dialog.type()).toBe('alert'); - expect(dialog.defaultValue()).toBe(''); - expect(dialog.message()).toBe('yo'); - dialog.accept(); - }); - await page.evaluate(() => alert('yo')); - }); - it('should allow accepting prompts', async({page, server}) => { - page.on('dialog', dialog => { - expect(dialog.type()).toBe('prompt'); - expect(dialog.defaultValue()).toBe('yes.'); - expect(dialog.message()).toBe('question?'); - dialog.accept('answer!'); - }); - const result = await page.evaluate(() => prompt('question?', 'yes.')); - expect(result).toBe('answer!'); - }); - it('should dismiss the prompt', async({page, server}) => { - page.on('dialog', dialog => { - dialog.dismiss(); - }); - const result = await page.evaluate(() => prompt('question?')); - expect(result).toBe(null); - }); - it('should accept the confirm prompt', async({page, server}) => { - page.on('dialog', dialog => { - dialog.accept(); - }); - const result = await page.evaluate(() => confirm('boolean?')); - expect(result).toBe(true); - }); - it('should dismiss the confirm prompt', async({page, server}) => { - page.on('dialog', dialog => { - dialog.dismiss(); - }); - const result = await page.evaluate(() => confirm('boolean?')); - expect(result).toBe(false); - }); - it.fail(CHANNEL)('should log prompt actions', async({browser}) => { - const messages = []; - const context = await browser.newContext({ - logger: { - isEnabled: () => true, - log: (name, severity, message) => messages.push(message), - } - }); - const page = await context.newPage(); - const promise = page.evaluate(() => confirm('01234567890123456789012345678901234567890123456789012345678901234567890123456789')); - const dialog = await page.waitForEvent('dialog'); - expect(messages.join()).toContain('confirm "0123456789012345678901234567890123456789012345678…" was shown'); - await dialog.accept('123'); - await promise; - expect(messages.join()).toContain('confirm "0123456789012345678901234567890123456789012345678…" was accepted'); - await context.close(); - }); - it.fail(WEBKIT)('should be able to close context with open alert', async({browser}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const alertPromise = page.waitForEvent('dialog'); - await page.evaluate(() => { - setTimeout(() => alert('hello'), 0); - }); - await alertPromise; - await context.close(); - }); -}); diff --git a/test/dialog.spec.js b/test/dialog.spec.js new file mode 100644 index 0000000000..7fcc498feb --- /dev/null +++ b/test/dialog.spec.js @@ -0,0 +1,92 @@ +/** + * 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, CHANNEL} = testOptions; + +it('should fire', async({page, server}) => { + page.on('dialog', dialog => { + expect(dialog.type()).toBe('alert'); + expect(dialog.defaultValue()).toBe(''); + expect(dialog.message()).toBe('yo'); + dialog.accept(); + }); + await page.evaluate(() => alert('yo')); +}); + +it('should allow accepting prompts', async({page, server}) => { + page.on('dialog', dialog => { + expect(dialog.type()).toBe('prompt'); + expect(dialog.defaultValue()).toBe('yes.'); + expect(dialog.message()).toBe('question?'); + dialog.accept('answer!'); + }); + const result = await page.evaluate(() => prompt('question?', 'yes.')); + expect(result).toBe('answer!'); +}); + +it('should dismiss the prompt', async({page, server}) => { + page.on('dialog', dialog => { + dialog.dismiss(); + }); + const result = await page.evaluate(() => prompt('question?')); + expect(result).toBe(null); +}); + +it('should accept the confirm prompt', async({page, server}) => { + page.on('dialog', dialog => { + dialog.accept(); + }); + const result = await page.evaluate(() => confirm('boolean?')); + expect(result).toBe(true); +}); + +it('should dismiss the confirm prompt', async({page, server}) => { + page.on('dialog', dialog => { + dialog.dismiss(); + }); + const result = await page.evaluate(() => confirm('boolean?')); + expect(result).toBe(false); +}); + +it.fail(CHANNEL)('should log prompt actions', async({browser}) => { + const messages = []; + const context = await browser.newContext({ + logger: { + isEnabled: () => true, + log: (name, severity, message) => messages.push(message), + } + }); + const page = await context.newPage(); + const promise = page.evaluate(() => confirm('01234567890123456789012345678901234567890123456789012345678901234567890123456789')); + const dialog = await page.waitForEvent('dialog'); + expect(messages.join()).toContain('confirm "0123456789012345678901234567890123456789012345678…" was shown'); + await dialog.accept('123'); + await promise; + expect(messages.join()).toContain('confirm "0123456789012345678901234567890123456789012345678…" was accepted'); + await context.close(); +}); + +it.fail(WEBKIT)('should be able to close context with open alert', async({browser}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const alertPromise = page.waitForEvent('dialog'); + await page.evaluate(() => { + setTimeout(() => alert('hello'), 0); + }); + await alertPromise; + await context.close(); +}); diff --git a/test/dispatchevent.jest.js b/test/dispatchevent.jest.js deleted file mode 100644 index 5bc5d419ba..0000000000 --- a/test/dispatchevent.jest.js +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Copyright Microsoft Corporation. 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 {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS} = testOptions; - -describe('Page.dispatchEvent(click)', function() { - it('should dispatch click event', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.dispatchEvent('button', 'click'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should dispatch click event properties', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.dispatchEvent('button', 'click'); - expect(await page.evaluate(() => bubbles)).toBeTruthy(); - expect(await page.evaluate(() => cancelable)).toBeTruthy(); - expect(await page.evaluate(() => composed)).toBeTruthy(); - }); - it('should dispatch click svg', async({page, server}) => { - await page.setContent(` - - - - `); - await page.dispatchEvent('circle', 'click'); - expect(await page.evaluate(() => window.__CLICKED)).toBe(42); - }); - it('should dispatch click on a span with an inline element inside', async({page, server}) => { - await page.setContent(` - - - `); - await page.dispatchEvent('span', 'click'); - expect(await page.evaluate(() => window.CLICKED)).toBe(42); - }); - it('should dispatch click after navigation ', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.dispatchEvent('button', 'click'); - await page.goto(server.PREFIX + '/input/button.html'); - await page.dispatchEvent('button', 'click'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should dispatch click after a cross origin navigation ', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.dispatchEvent('button', 'click'); - await page.goto(server.CROSS_PROCESS_PREFIX + '/input/button.html'); - await page.dispatchEvent('button', 'click'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should not fail when element is blocked on hover', async({page, server}) => { - await page.setContent(` - - -
-
`); - await page.dispatchEvent('button', 'click'); - expect(await page.evaluate(() => window.clicked)).toBeTruthy(); - }); - it('should dispatch click when node is added in shadow dom', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const watchdog = page.dispatchEvent('span', 'click'); - await page.evaluate(() => { - const div = document.createElement('div'); - div.attachShadow({mode: 'open'}); - document.body.appendChild(div); - }); - await page.evaluate(() => new Promise(f => setTimeout(f, 100))); - await page.evaluate(() => { - const span = document.createElement('span'); - span.textContent = 'Hello from shadow'; - span.addEventListener('click', () => window.clicked = true); - document.querySelector('div').shadowRoot.appendChild(span); - }); - await watchdog; - expect(await page.evaluate(() => window.clicked)).toBe(true); - }); - it('should be atomic', async({playwright, page}) => { - const createDummySelector = () => ({ - create(root, target) {}, - query(root, selector) { - const result = root.querySelector(selector); - if (result) - Promise.resolve().then(() => result.onclick = ""); - return result; - }, - queryAll(root, selector) { - const result = Array.from(root.querySelectorAll(selector)); - for (const e of result) - Promise.resolve().then(() => result.onclick = ""); - return result; - } - }); - await utils.registerEngine(playwright, 'dispatchEvent', createDummySelector); - await page.setContent(`
Hello
`); - await page.dispatchEvent('dispatchEvent=div', 'click'); - expect(await page.evaluate(() => window._clicked)).toBe(true); - }); -}); - -describe('Page.dispatchEvent(drag)', function() { - it.fail(WEBKIT)('should dispatch drag drop events', async({page, server}) => { - await page.goto(server.PREFIX + '/drag-n-drop.html'); - const dataTransfer = await page.evaluateHandle(() => new DataTransfer()); - await page.dispatchEvent('#source', 'dragstart', { dataTransfer }); - await page.dispatchEvent('#target', 'drop', { dataTransfer }); - expect(await page.evaluate(() => { - return source.parentElement === target; - })).toBeTruthy(); - }); -}); - -describe('ElementHandle.dispatchEvent(drag)', function() { - it.fail(WEBKIT)('should dispatch drag drop events', async({page, server}) => { - await page.goto(server.PREFIX + '/drag-n-drop.html'); - const dataTransfer = await page.evaluateHandle(() => new DataTransfer()); - const source = await page.$('#source'); - await source.dispatchEvent('dragstart', { dataTransfer }); - const target = await page.$('#target'); - await target.dispatchEvent('drop', { dataTransfer }); - expect(await page.evaluate(() => { - return source.parentElement === target; - })).toBeTruthy(); - }); -}); - -describe('ElementHandle.dispatchEvent(click)', function() { - it('should dispatch click event', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await button.dispatchEvent('click'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); -}); diff --git a/test/dispatchevent.spec.js b/test/dispatchevent.spec.js new file mode 100644 index 0000000000..33753b0361 --- /dev/null +++ b/test/dispatchevent.spec.js @@ -0,0 +1,156 @@ +/** + * Copyright Microsoft Corporation. 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 {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS} = testOptions; + +it('should dispatch click event', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.dispatchEvent('button', 'click'); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); + +it('should dispatch click event properties', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.dispatchEvent('button', 'click'); + expect(await page.evaluate(() => bubbles)).toBeTruthy(); + expect(await page.evaluate(() => cancelable)).toBeTruthy(); + expect(await page.evaluate(() => composed)).toBeTruthy(); +}); + +it('should dispatch click svg', async({page, server}) => { + await page.setContent(` + + + + `); + await page.dispatchEvent('circle', 'click'); + expect(await page.evaluate(() => window.__CLICKED)).toBe(42); +}); + +it('should dispatch click on a span with an inline element inside', async({page, server}) => { + await page.setContent(` + + + `); + await page.dispatchEvent('span', 'click'); + expect(await page.evaluate(() => window.CLICKED)).toBe(42); +}); + +it('should dispatch click after navigation ', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.dispatchEvent('button', 'click'); + await page.goto(server.PREFIX + '/input/button.html'); + await page.dispatchEvent('button', 'click'); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); + +it('should dispatch click after a cross origin navigation ', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.dispatchEvent('button', 'click'); + await page.goto(server.CROSS_PROCESS_PREFIX + '/input/button.html'); + await page.dispatchEvent('button', 'click'); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); + +it('should not fail when element is blocked on hover', async({page, server}) => { + await page.setContent(` + + +
+
`); + await page.dispatchEvent('button', 'click'); + expect(await page.evaluate(() => window.clicked)).toBeTruthy(); +}); + +it('should dispatch click when node is added in shadow dom', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const watchdog = page.dispatchEvent('span', 'click'); + await page.evaluate(() => { + const div = document.createElement('div'); + div.attachShadow({mode: 'open'}); + document.body.appendChild(div); + }); + await page.evaluate(() => new Promise(f => setTimeout(f, 100))); + await page.evaluate(() => { + const span = document.createElement('span'); + span.textContent = 'Hello from shadow'; + span.addEventListener('click', () => window.clicked = true); + document.querySelector('div').shadowRoot.appendChild(span); + }); + await watchdog; + expect(await page.evaluate(() => window.clicked)).toBe(true); +}); + +it('should be atomic', async({playwright, page}) => { + const createDummySelector = () => ({ + create(root, target) {}, + query(root, selector) { + const result = root.querySelector(selector); + if (result) + Promise.resolve().then(() => result.onclick = ""); + return result; + }, + queryAll(root, selector) { + const result = Array.from(root.querySelectorAll(selector)); + for (const e of result) + Promise.resolve().then(() => result.onclick = ""); + return result; + } + }); + await utils.registerEngine(playwright, 'dispatchEvent', createDummySelector); + await page.setContent(`
Hello
`); + await page.dispatchEvent('dispatchEvent=div', 'click'); + expect(await page.evaluate(() => window._clicked)).toBe(true); +}); + +it.fail(WEBKIT)('should dispatch drag drop events', async({page, server}) => { + await page.goto(server.PREFIX + '/drag-n-drop.html'); + const dataTransfer = await page.evaluateHandle(() => new DataTransfer()); + await page.dispatchEvent('#source', 'dragstart', { dataTransfer }); + await page.dispatchEvent('#target', 'drop', { dataTransfer }); + expect(await page.evaluate(() => { + return source.parentElement === target; + })).toBeTruthy(); +}); + +it.fail(WEBKIT)('should dispatch drag drop events', async({page, server}) => { + await page.goto(server.PREFIX + '/drag-n-drop.html'); + const dataTransfer = await page.evaluateHandle(() => new DataTransfer()); + const source = await page.$('#source'); + await source.dispatchEvent('dragstart', { dataTransfer }); + const target = await page.$('#target'); + await target.dispatchEvent('drop', { dataTransfer }); + expect(await page.evaluate(() => { + return source.parentElement === target; + })).toBeTruthy(); +}); + +it('should dispatch click event', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await button.dispatchEvent('click'); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); diff --git a/test/download.jest.js b/test/download.jest.js deleted file mode 100644 index 7d69f7b97e..0000000000 --- a/test/download.jest.js +++ /dev/null @@ -1,322 +0,0 @@ -/** - * 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 fs = require('fs'); -const path = require('path'); -const util = require('util'); -const os = require('os'); -const {mkdtempAsync, removeFolderAsync} = require('./utils'); - -const {FFOX, CHROMIUM, WEBKIT, HEADLESS} = testOptions; - -registerFixture('persistentDirectory', async ({}, test) => { - const persistentDirectory = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-')); - try { - await test(persistentDirectory); - } finally { - await removeFolderAsync(persistentDirectory); - } -}); - -describe('Download', function() { - beforeEach(async ({server}) => { - server.setRoute('/download', (req, res) => { - res.setHeader('Content-Type', 'application/octet-stream'); - res.setHeader('Content-Disposition', 'attachment'); - res.end(`Hello world`); - }); - server.setRoute('/downloadWithFilename', (req, res) => { - res.setHeader('Content-Type', 'application/octet-stream'); - res.setHeader('Content-Disposition', 'attachment; filename=file.txt'); - res.end(`Hello world`); - }); - }); - - it('should report downloads with acceptDownloads: false', async({page, server}) => { - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - let error; - expect(download.url()).toBe(`${server.PREFIX}/downloadWithFilename`); - expect(download.suggestedFilename()).toBe(`file.txt`); - await download.path().catch(e => error = e); - expect(await download.failure()).toContain('acceptDownloads'); - expect(error.message).toContain('acceptDownloads: true'); - }); - it('should report downloads with acceptDownloads: true', async({browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path = await download.path(); - expect(fs.existsSync(path)).toBeTruthy(); - expect(fs.readFileSync(path).toString()).toBe('Hello world'); - await page.close(); - }); - it('should save to user-specified path', async({persistentDirectory, browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const userPath = path.join(persistentDirectory, "download.txt"); - await download.saveAs(userPath); - expect(fs.existsSync(userPath)).toBeTruthy(); - expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); - await page.close(); - }); - it('should save to user-specified path without updating original path', async({persistentDirectory, browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const userPath = path.join(persistentDirectory, "download.txt"); - await download.saveAs(userPath); - expect(fs.existsSync(userPath)).toBeTruthy(); - expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); - - const originalPath = await download.path(); - expect(fs.existsSync(originalPath)).toBeTruthy(); - expect(fs.readFileSync(originalPath).toString()).toBe('Hello world'); - await page.close(); - }); - it('should save to two different paths with multiple saveAs calls', async({persistentDirectory, browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const userPath = path.join(persistentDirectory, "download.txt"); - await download.saveAs(userPath); - expect(fs.existsSync(userPath)).toBeTruthy(); - expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); - - const anotherUserPath = path.join(persistentDirectory, "download (2).txt"); - await download.saveAs(anotherUserPath); - expect(fs.existsSync(anotherUserPath)).toBeTruthy(); - expect(fs.readFileSync(anotherUserPath).toString()).toBe('Hello world'); - await page.close(); - }); - it('should save to overwritten filepath', async({persistentDirectory, browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const userPath = path.join(persistentDirectory, "download.txt"); - await download.saveAs(userPath); - expect((await util.promisify(fs.readdir)(persistentDirectory)).length).toBe(1); - await download.saveAs(userPath); - expect((await util.promisify(fs.readdir)(persistentDirectory)).length).toBe(1); - expect(fs.existsSync(userPath)).toBeTruthy(); - expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); - await page.close(); - }); - it('should create subdirectories when saving to non-existent user-specified path', async({persistentDirectory, browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const nestedPath = path.join(persistentDirectory, "these", "are", "directories", "download.txt"); - await download.saveAs(nestedPath) - expect(fs.existsSync(nestedPath)).toBeTruthy(); - expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world'); - await page.close(); - }); - it('should error when saving with downloads disabled', async({persistentDirectory, browser, server}) => { - const page = await browser.newPage({ acceptDownloads: false }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const userPath = path.join(persistentDirectory, "download.txt"); - const { message } = await download.saveAs(userPath).catch(e => e); - expect(message).toContain('Pass { acceptDownloads: true } when you are creating your browser context'); - await page.close(); - }); - it('should error when saving after deletion', async({persistentDirectory, browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const userPath = path.join(persistentDirectory, "download.txt"); - await download.delete(); - const { message } = await download.saveAs(userPath).catch(e => e); - expect(message).toContain('Download already deleted. Save before deleting.'); - await page.close(); - }); - it('should report non-navigation downloads', async({browser, server}) => { - // Mac WebKit embedder does not download in this case, although Safari does. - server.setRoute('/download', (req, res) => { - res.setHeader('Content-Type', 'application/octet-stream'); - res.end(`Hello world`); - }); - - const page = await browser.newPage({ acceptDownloads: true }); - await page.goto(server.EMPTY_PAGE); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - expect(download.suggestedFilename()).toBe(`file.txt`); - const path = await download.path(); - expect(fs.existsSync(path)).toBeTruthy(); - expect(fs.readFileSync(path).toString()).toBe('Hello world'); - await page.close(); - }); - it(`should report download path within page.on('download', …) handler for Files`, async({browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - const onDownloadPath = new Promise((res) => { - page.on('download', dl => { - dl.path().then(res); - }); - }); - await page.setContent(`download`); - await page.click('a'); - const path = await onDownloadPath; - expect(fs.readFileSync(path).toString()).toBe('Hello world'); - await page.close(); - }) - it(`should report download path within page.on('download', …) handler for Blobs`, async({browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - const onDownloadPath = new Promise((res) => { - page.on('download', dl => { - dl.path().then(res); - }); - }); - await page.goto(server.PREFIX + '/download-blob.html'); - await page.click('a'); - const path = await onDownloadPath; - expect(fs.readFileSync(path).toString()).toBe('Hello world'); - await page.close(); - }) - it.fail(FFOX || WEBKIT)('should report alt-click downloads', async({browser, server}) => { - // Firefox does not download on alt-click by default. - // Our WebKit embedder does not download on alt-click, although Safari does. - server.setRoute('/download', (req, res) => { - res.setHeader('Content-Type', 'application/octet-stream'); - res.end(`Hello world`); - }); - - const page = await browser.newPage({ acceptDownloads: true }); - await page.goto(server.EMPTY_PAGE); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a', { modifiers: ['Alt']}) - ]); - const path = await download.path(); - expect(fs.existsSync(path)).toBeTruthy(); - expect(fs.readFileSync(path).toString()).toBe('Hello world'); - await page.close(); - }); - it.fail(CHROMIUM && !HEADLESS)('should report new window downloads', async({browser, server}) => { - // TODO: - the test fails in headful Chromium as the popup page gets closed along - // with the session before download completed event arrives. - // - WebKit doesn't close the popup page - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path = await download.path(); - expect(fs.existsSync(path)).toBeTruthy(); - await page.close(); - }); - it('should delete file', async({browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path = await download.path(); - expect(fs.existsSync(path)).toBeTruthy(); - await download.delete(); - expect(fs.existsSync(path)).toBeFalsy(); - await page.close(); - }); - it('should expose stream', async({browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const stream = await download.createReadStream(); - let content = ''; - stream.on('data', data => content += data.toString()); - await new Promise(f => stream.on('end', f)); - expect(content).toBe('Hello world'); - await page.close(); - }); - it('should delete downloads on context destruction', async({browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download1 ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const [ download2 ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path1 = await download1.path(); - const path2 = await download2.path(); - expect(fs.existsSync(path1)).toBeTruthy(); - expect(fs.existsSync(path2)).toBeTruthy(); - await page.context().close(); - expect(fs.existsSync(path1)).toBeFalsy(); - expect(fs.existsSync(path2)).toBeFalsy(); - }); - it('should delete downloads on browser gone', async ({ server, browserType, defaultBrowserOptions }) => { - const browser = await browserType.launch(defaultBrowserOptions); - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download1 ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const [ download2 ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path1 = await download1.path(); - const path2 = await download2.path(); - expect(fs.existsSync(path1)).toBeTruthy(); - expect(fs.existsSync(path2)).toBeTruthy(); - await browser.close(); - expect(fs.existsSync(path1)).toBeFalsy(); - expect(fs.existsSync(path2)).toBeFalsy(); - expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy(); - }); -}); diff --git a/test/download.spec.js b/test/download.spec.js new file mode 100644 index 0000000000..d9aaa179c8 --- /dev/null +++ b/test/download.spec.js @@ -0,0 +1,335 @@ +/** + * 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 fs = require('fs'); +const path = require('path'); +const util = require('util'); +const os = require('os'); +const {mkdtempAsync, removeFolderAsync} = require('./utils'); + +const {FFOX, CHROMIUM, WEBKIT, HEADLESS} = testOptions; + +registerFixture('persistentDirectory', async ({}, test) => { + const persistentDirectory = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-')); + try { + await test(persistentDirectory); + } finally { + await removeFolderAsync(persistentDirectory); + } +}); + +beforeEach(async ({server}) => { + server.setRoute('/download', (req, res) => { + res.setHeader('Content-Type', 'application/octet-stream'); + res.setHeader('Content-Disposition', 'attachment'); + res.end(`Hello world`); + }); + server.setRoute('/downloadWithFilename', (req, res) => { + res.setHeader('Content-Type', 'application/octet-stream'); + res.setHeader('Content-Disposition', 'attachment; filename=file.txt'); + res.end(`Hello world`); + }); +}); + +it('should report downloads with acceptDownloads: false', async({page, server}) => { + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + let error; + expect(download.url()).toBe(`${server.PREFIX}/downloadWithFilename`); + expect(download.suggestedFilename()).toBe(`file.txt`); + await download.path().catch(e => error = e); + expect(await download.failure()).toContain('acceptDownloads'); + expect(error.message).toContain('acceptDownloads: true'); +}); + +it('should report downloads with acceptDownloads: true', async({browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path = await download.path(); + expect(fs.existsSync(path)).toBeTruthy(); + expect(fs.readFileSync(path).toString()).toBe('Hello world'); + await page.close(); +}); + +it('should save to user-specified path', async({persistentDirectory, browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const userPath = path.join(persistentDirectory, "download.txt"); + await download.saveAs(userPath); + expect(fs.existsSync(userPath)).toBeTruthy(); + expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); + await page.close(); +}); + +it('should save to user-specified path without updating original path', async({persistentDirectory, browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const userPath = path.join(persistentDirectory, "download.txt"); + await download.saveAs(userPath); + expect(fs.existsSync(userPath)).toBeTruthy(); + expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); + + const originalPath = await download.path(); + expect(fs.existsSync(originalPath)).toBeTruthy(); + expect(fs.readFileSync(originalPath).toString()).toBe('Hello world'); + await page.close(); +}); + +it('should save to two different paths with multiple saveAs calls', async({persistentDirectory, browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const userPath = path.join(persistentDirectory, "download.txt"); + await download.saveAs(userPath); + expect(fs.existsSync(userPath)).toBeTruthy(); + expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); + + const anotherUserPath = path.join(persistentDirectory, "download (2).txt"); + await download.saveAs(anotherUserPath); + expect(fs.existsSync(anotherUserPath)).toBeTruthy(); + expect(fs.readFileSync(anotherUserPath).toString()).toBe('Hello world'); + await page.close(); +}); + +it('should save to overwritten filepath', async({persistentDirectory, browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const userPath = path.join(persistentDirectory, "download.txt"); + await download.saveAs(userPath); + expect((await util.promisify(fs.readdir)(persistentDirectory)).length).toBe(1); + await download.saveAs(userPath); + expect((await util.promisify(fs.readdir)(persistentDirectory)).length).toBe(1); + expect(fs.existsSync(userPath)).toBeTruthy(); + expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); + await page.close(); +}); + +it('should create subdirectories when saving to non-existent user-specified path', async({persistentDirectory, browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const nestedPath = path.join(persistentDirectory, "these", "are", "directories", "download.txt"); + await download.saveAs(nestedPath) + expect(fs.existsSync(nestedPath)).toBeTruthy(); + expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world'); + await page.close(); +}); + +it('should error when saving with downloads disabled', async({persistentDirectory, browser, server}) => { + const page = await browser.newPage({ acceptDownloads: false }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const userPath = path.join(persistentDirectory, "download.txt"); + const { message } = await download.saveAs(userPath).catch(e => e); + expect(message).toContain('Pass { acceptDownloads: true } when you are creating your browser context'); + await page.close(); +}); + +it('should error when saving after deletion', async({persistentDirectory, browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const userPath = path.join(persistentDirectory, "download.txt"); + await download.delete(); + const { message } = await download.saveAs(userPath).catch(e => e); + expect(message).toContain('Download already deleted. Save before deleting.'); + await page.close(); +}); + +it('should report non-navigation downloads', async({browser, server}) => { + // Mac WebKit embedder does not download in this case, although Safari does. + server.setRoute('/download', (req, res) => { + res.setHeader('Content-Type', 'application/octet-stream'); + res.end(`Hello world`); + }); + + const page = await browser.newPage({ acceptDownloads: true }); + await page.goto(server.EMPTY_PAGE); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + expect(download.suggestedFilename()).toBe(`file.txt`); + const path = await download.path(); + expect(fs.existsSync(path)).toBeTruthy(); + expect(fs.readFileSync(path).toString()).toBe('Hello world'); + await page.close(); +}); + +it(`should report download path within page.on('download', …) handler for Files`, async({browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + const onDownloadPath = new Promise((res) => { + page.on('download', dl => { + dl.path().then(res); + }); + }); + await page.setContent(`download`); + await page.click('a'); + const path = await onDownloadPath; + expect(fs.readFileSync(path).toString()).toBe('Hello world'); + await page.close(); +}) +it(`should report download path within page.on('download', …) handler for Blobs`, async({browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + const onDownloadPath = new Promise((res) => { + page.on('download', dl => { + dl.path().then(res); + }); + }); + await page.goto(server.PREFIX + '/download-blob.html'); + await page.click('a'); + const path = await onDownloadPath; + expect(fs.readFileSync(path).toString()).toBe('Hello world'); + await page.close(); +}) +it.fail(FFOX || WEBKIT)('should report alt-click downloads', async({browser, server}) => { + // Firefox does not download on alt-click by default. + // Our WebKit embedder does not download on alt-click, although Safari does. + server.setRoute('/download', (req, res) => { + res.setHeader('Content-Type', 'application/octet-stream'); + res.end(`Hello world`); + }); + + const page = await browser.newPage({ acceptDownloads: true }); + await page.goto(server.EMPTY_PAGE); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a', { modifiers: ['Alt']}) + ]); + const path = await download.path(); + expect(fs.existsSync(path)).toBeTruthy(); + expect(fs.readFileSync(path).toString()).toBe('Hello world'); + await page.close(); +}); + +it.fail(CHROMIUM && !HEADLESS)('should report new window downloads', async({browser, server}) => { + // TODO: - the test fails in headful Chromium as the popup page gets closed along + // with the session before download completed event arrives. + // - WebKit doesn't close the popup page + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path = await download.path(); + expect(fs.existsSync(path)).toBeTruthy(); + await page.close(); +}); + +it('should delete file', async({browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path = await download.path(); + expect(fs.existsSync(path)).toBeTruthy(); + await download.delete(); + expect(fs.existsSync(path)).toBeFalsy(); + await page.close(); +}); + +it('should expose stream', async({browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const stream = await download.createReadStream(); + let content = ''; + stream.on('data', data => content += data.toString()); + await new Promise(f => stream.on('end', f)); + expect(content).toBe('Hello world'); + await page.close(); +}); + +it('should delete downloads on context destruction', async({browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download1 ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const [ download2 ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path1 = await download1.path(); + const path2 = await download2.path(); + expect(fs.existsSync(path1)).toBeTruthy(); + expect(fs.existsSync(path2)).toBeTruthy(); + await page.context().close(); + expect(fs.existsSync(path1)).toBeFalsy(); + expect(fs.existsSync(path2)).toBeFalsy(); +}); + +it('should delete downloads on browser gone', async ({ server, browserType, defaultBrowserOptions }) => { + const browser = await browserType.launch(defaultBrowserOptions); + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download1 ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const [ download2 ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path1 = await download1.path(); + const path2 = await download2.path(); + expect(fs.existsSync(path1)).toBeTruthy(); + expect(fs.existsSync(path2)).toBeTruthy(); + await browser.close(); + expect(fs.existsSync(path1)).toBeFalsy(); + expect(fs.existsSync(path2)).toBeFalsy(); + expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy(); +}); diff --git a/test/downloads-path.spec.js b/test/downloads-path.spec.js new file mode 100644 index 0000000000..c729188900 --- /dev/null +++ b/test/downloads-path.spec.js @@ -0,0 +1,135 @@ +/** + * 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 fs = require('fs'); +const os = require('os'); +const {mkdtempAsync, removeFolderAsync} = require('./utils'); + +registerFixture('downloadsPath', async ({}, test) => { + const downloadsPath = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-')); + try { + await test(downloadsPath); + } finally { + await removeFolderAsync(downloadsPath); + } +}); + +registerFixture('downloadsBrowser', async ({server, browserType, defaultBrowserOptions, downloadsPath}, test) => { + server.setRoute('/download', (req, res) => { + res.setHeader('Content-Type', 'application/octet-stream'); + res.setHeader('Content-Disposition', 'attachment; filename=file.txt'); + res.end(`Hello world`); + }); + const browser = await browserType.launch({ + ...defaultBrowserOptions, + downloadsPath: downloadsPath, + }); + try { + await test(browser); + } finally { + await browser.close(); + } +}); + +registerFixture('persistentDownloadsContext', async ({server, browserType, defaultBrowserOptions, downloadsPath}, test) => { + const userDataDir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-')); + server.setRoute('/download', (req, res) => { + res.setHeader('Content-Type', 'application/octet-stream'); + res.setHeader('Content-Disposition', 'attachment; filename=file.txt'); + res.end(`Hello world`); + }); + const context = await browserType.launchPersistentContext( + userDataDir, + { + ...defaultBrowserOptions, + downloadsPath, + acceptDownloads: true + } + ); + const page = context.pages()[0]; + page.setContent(`download`); + try { + await test(context); + } finally { + await context.close(); + await state.context.close(); + await removeFolderAsync(userDataDir); + } +}); + +it('should keep downloadsPath folder', async({downloadsBrowser, downloadsPath, server}) => { + const page = await downloadsBrowser.newPage(); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + expect(download.url()).toBe(`${server.PREFIX}/download`); + expect(download.suggestedFilename()).toBe(`file.txt`); + await download.path().catch(e => error = e); + await page.close(); + await downloadsBrowser.close(); + expect(fs.existsSync(downloadsPath)).toBeTruthy(); +}); + +it('should delete downloads when context closes', async({downloadsBrowser, downloadsPath, server}) => { + const page = await downloadsBrowser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path = await download.path(); + expect(fs.existsSync(path)).toBeTruthy(); + await page.close(); + expect(fs.existsSync(path)).toBeFalsy(); + +}); +it('should report downloads in downloadsPath folder', async({downloadsBrowser, downloadsPath, server}) => { + const page = await downloadsBrowser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path = await download.path(); + expect(path.startsWith(downloadsPath)).toBeTruthy(); + await page.close(); +}); + +it('should accept downloads', async({persistentDownloadsContext, downloadsPath, server}) => { + const page = persistentDownloadsContext.pages()[0]; + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + expect(download.url()).toBe(`${server.PREFIX}/download`); + expect(download.suggestedFilename()).toBe(`file.txt`); + const path = await download.path(); + expect(path.startsWith(downloadsPath)).toBeTruthy(); +}); + +it('should not delete downloads when the context closes', async({persistentDownloadsContext}) => { + const page = persistentDownloadsContext.pages()[0]; + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path = await download.path(); + await persistentDownloadsContext.close(); + expect(fs.existsSync(path)).toBeTruthy(); +}); diff --git a/test/downloadsPath.jest.js b/test/downloadsPath.jest.js deleted file mode 100644 index 5807c565d4..0000000000 --- a/test/downloadsPath.jest.js +++ /dev/null @@ -1,137 +0,0 @@ -/** - * 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 fs = require('fs'); -const os = require('os'); -const {mkdtempAsync, removeFolderAsync} = require('./utils'); - -registerFixture('downloadsPath', async ({}, test) => { - const downloadsPath = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-')); - try { - await test(downloadsPath); - } finally { - await removeFolderAsync(downloadsPath); - } -}); - -registerFixture('downloadsBrowser', async ({server, browserType, defaultBrowserOptions, downloadsPath}, test) => { - server.setRoute('/download', (req, res) => { - res.setHeader('Content-Type', 'application/octet-stream'); - res.setHeader('Content-Disposition', 'attachment; filename=file.txt'); - res.end(`Hello world`); - }); - const browser = await browserType.launch({ - ...defaultBrowserOptions, - downloadsPath: downloadsPath, - }); - try { - await test(browser); - } finally { - await browser.close(); - } -}); - -registerFixture('persistentDownloadsContext', async ({server, browserType, defaultBrowserOptions, downloadsPath}, test) => { - const userDataDir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-')); - server.setRoute('/download', (req, res) => { - res.setHeader('Content-Type', 'application/octet-stream'); - res.setHeader('Content-Disposition', 'attachment; filename=file.txt'); - res.end(`Hello world`); - }); - const context = await browserType.launchPersistentContext( - userDataDir, - { - ...defaultBrowserOptions, - downloadsPath, - acceptDownloads: true - } - ); - const page = context.pages()[0]; - page.setContent(`download`); - try { - await test(context); - } finally { - await context.close(); - await state.context.close(); - await removeFolderAsync(userDataDir); - } -}); - -describe('browserType.launch({downloadsPath})', function() { - it('should keep downloadsPath folder', async({downloadsBrowser, downloadsPath, server}) => { - const page = await downloadsBrowser.newPage(); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - expect(download.url()).toBe(`${server.PREFIX}/download`); - expect(download.suggestedFilename()).toBe(`file.txt`); - await download.path().catch(e => error = e); - await page.close(); - await downloadsBrowser.close(); - expect(fs.existsSync(downloadsPath)).toBeTruthy(); - }); - it('should delete downloads when context closes', async({downloadsBrowser, downloadsPath, server}) => { - const page = await downloadsBrowser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path = await download.path(); - expect(fs.existsSync(path)).toBeTruthy(); - await page.close(); - expect(fs.existsSync(path)).toBeFalsy(); - }); - it('should report downloads in downloadsPath folder', async({downloadsBrowser, downloadsPath, server}) => { - const page = await downloadsBrowser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path = await download.path(); - expect(path.startsWith(downloadsPath)).toBeTruthy(); - await page.close(); - }); -}); - -describe('browserType.launchPersistent({acceptDownloads})', function() { - it('should accept downloads', async({persistentDownloadsContext, downloadsPath, server}) => { - const page = persistentDownloadsContext.pages()[0]; - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - expect(download.url()).toBe(`${server.PREFIX}/download`); - expect(download.suggestedFilename()).toBe(`file.txt`); - const path = await download.path(); - expect(path.startsWith(downloadsPath)).toBeTruthy(); - }); - - it('should not delete downloads when the context closes', async({persistentDownloadsContext}) => { - const page = persistentDownloadsContext.pages()[0]; - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path = await download.path(); - await persistentDownloadsContext.close(); - expect(fs.existsSync(path)).toBeTruthy(); - }); -}); diff --git a/test/emulation-focus.spec.js b/test/emulation-focus.spec.js new file mode 100644 index 0000000000..ac0e22adb9 --- /dev/null +++ b/test/emulation-focus.spec.js @@ -0,0 +1,171 @@ +/** + * 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 utils = require('./utils'); +const {CHROMIUM, FFOX, MAC, HEADLESS} = testOptions; + +it('should think that it is focused by default', async({page}) => { + expect(await page.evaluate('document.hasFocus()')).toBe(true); +}); + +it('should think that all pages are focused', async({page}) => { + const page2 = await page.context().newPage(); + expect(await page.evaluate('document.hasFocus()')).toBe(true); + expect(await page2.evaluate('document.hasFocus()')).toBe(true); + await page2.close(); +}); + +it('should focus popups by default', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), + ]); + expect(await popup.evaluate('document.hasFocus()')).toBe(true); + expect(await page.evaluate('document.hasFocus()')).toBe(true); +}); + +it('should provide target for keyboard events', async({page, server}) => { + const page2 = await page.context().newPage(); + await Promise.all([ + page.goto(server.PREFIX + '/input/textarea.html'), + page2.goto(server.PREFIX + '/input/textarea.html'), + ]); + await Promise.all([ + page.focus('input'), + page2.focus('input'), + ]); + const text = 'first'; + const text2 = 'second'; + await Promise.all([ + page.keyboard.type(text), + page2.keyboard.type(text2), + ]); + const results = await Promise.all([ + page.evaluate('result'), + page2.evaluate('result'), + ]); + expect(results).toEqual([text, text2]); +}); + +it('should not affect mouse event target page', async({page, server}) => { + const page2 = await page.context().newPage(); + function clickCounter() { + document.onclick = () => window.clickCount = (window.clickCount || 0) + 1; + } + await Promise.all([ + page.evaluate(clickCounter), + page2.evaluate(clickCounter), + page.focus('body'), + page2.focus('body'), + ]); + await Promise.all([ + page.mouse.click(1, 1), + page2.mouse.click(1, 1), + ]); + const counters = await Promise.all([ + page.evaluate('window.clickCount'), + page2.evaluate('window.clickCount'), + ]); + expect(counters ).toEqual([1,1]); +}); + +it('should change document.activeElement', async({page, server}) => { + const page2 = await page.context().newPage(); + await Promise.all([ + page.goto(server.PREFIX + '/input/textarea.html'), + page2.goto(server.PREFIX + '/input/textarea.html'), + ]); + await Promise.all([ + page.focus('input'), + page2.focus('textarea'), + ]); + const active = await Promise.all([ + page.evaluate('document.activeElement.tagName'), + page2.evaluate('document.activeElement.tagName'), + ]); + expect(active).toEqual(['INPUT', 'TEXTAREA']); +}); + +it.skip(FFOX && !HEADLESS)('should not affect screenshots', async({page, server}) => { + // Firefox headful produces a different image. + const page2 = await page.context().newPage(); + await Promise.all([ + page.setViewportSize({width: 500, height: 500}), + page.goto(server.PREFIX + '/grid.html'), + page2.setViewportSize({width: 50, height: 50}), + page2.goto(server.PREFIX + '/grid.html'), + ]); + await Promise.all([ + page.focus('body'), + page2.focus('body'), + ]); + const screenshots = await Promise.all([ + page.screenshot(), + page2.screenshot(), + ]); + expect(screenshots[0]).toBeGolden('screenshot-sanity.png'); + expect(screenshots[1]).toBeGolden('grid-cell-0.png'); +}); + +it('should change focused iframe', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [frame1, frame2] = await Promise.all([ + utils.attachFrame(page, 'frame1', server.PREFIX + '/input/textarea.html'), + utils.attachFrame(page, 'frame2', server.PREFIX + '/input/textarea.html'), + ]); + function logger() { + self._events = []; + const element = document.querySelector('input'); + element.onfocus = element.onblur = (e) => self._events.push(e.type); + } + await Promise.all([ + frame1.evaluate(logger), + frame2.evaluate(logger), + ]); + const focused = await Promise.all([ + frame1.evaluate('document.hasFocus()'), + frame2.evaluate('document.hasFocus()'), + ]); + expect(focused).toEqual([false, false]); + { + await frame1.focus('input'); + const events = await Promise.all([ + frame1.evaluate('self._events'), + frame2.evaluate('self._events'), + ]); + expect(events).toEqual([['focus'], []]); + const focused = await Promise.all([ + frame1.evaluate('document.hasFocus()'), + frame2.evaluate('document.hasFocus()'), + ]); + expect(focused).toEqual([true, false]); + } + { + await frame2.focus('input'); + const events = await Promise.all([ + frame1.evaluate('self._events'), + frame2.evaluate('self._events'), + ]); + expect(events).toEqual([['focus', 'blur'], ['focus']]); + const focused = await Promise.all([ + frame1.evaluate('document.hasFocus()'), + frame2.evaluate('document.hasFocus()'), + ]); + expect(focused).toEqual([false, true]); + } +}); diff --git a/test/emulation.jest.js b/test/emulation.jest.js deleted file mode 100644 index e8433618b6..0000000000 --- a/test/emulation.jest.js +++ /dev/null @@ -1,690 +0,0 @@ -/** - * 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 utils = require('./utils'); -const {CHROMIUM, FFOX, MAC, HEADLESS} = testOptions; - -describe('BrowserContext({viewport})', function() { - it('should get the proper default viewport size', async({page, server}) => { - await utils.verifyViewport(page, 1280, 720); - }); - it('should set the proper viewport size', async({page, server}) => { - await utils.verifyViewport(page, 1280, 720); - await page.setViewportSize({width: 123, height: 456}); - await utils.verifyViewport(page, 123, 456); - }); - it('should return correct outerWidth and outerHeight', async({page}) => { - const size = await page.evaluate(() => { - return { - innerWidth: window.innerWidth, - innerHeight: window.innerHeight, - outerWidth: window.outerWidth, - outerHeight: window.outerHeight, - }; - }); - expect(size.innerWidth).toBe(1280); - expect(size.innerHeight).toBe(720); - expect(size.outerWidth >= size.innerWidth).toBeTruthy(); - expect(size.outerHeight >= size.innerHeight).toBeTruthy(); - }); - it('should emulate device width', async({page, server}) => { - expect(page.viewportSize()).toEqual({width: 1280, height: 720}); - await page.setViewportSize({width: 200, height: 200}); - expect(await page.evaluate(() => window.screen.width)).toBe(200); - expect(await page.evaluate(() => matchMedia('(min-device-width: 100px)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(min-device-width: 300px)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(max-device-width: 100px)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(max-device-width: 300px)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(device-width: 500px)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(device-width: 200px)').matches)).toBe(true); - await page.setViewportSize({width: 500, height: 500}); - expect(await page.evaluate(() => window.screen.width)).toBe(500); - expect(await page.evaluate(() => matchMedia('(min-device-width: 400px)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(min-device-width: 600px)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(max-device-width: 400px)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(max-device-width: 600px)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(device-width: 200px)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(device-width: 500px)').matches)).toBe(true); - }); - it('should emulate device height', async({page, server}) => { - expect(page.viewportSize()).toEqual({width: 1280, height: 720}); - await page.setViewportSize({width: 200, height: 200}); - expect(await page.evaluate(() => window.screen.height)).toBe(200); - expect(await page.evaluate(() => matchMedia('(min-device-height: 100px)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(min-device-height: 300px)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(max-device-height: 100px)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(max-device-height: 300px)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(device-height: 500px)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(device-height: 200px)').matches)).toBe(true); - await page.setViewportSize({width: 500, height: 500}); - expect(await page.evaluate(() => window.screen.height)).toBe(500); - expect(await page.evaluate(() => matchMedia('(min-device-height: 400px)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(min-device-height: 600px)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(max-device-height: 400px)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(max-device-height: 600px)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(device-height: 200px)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(device-height: 500px)').matches)).toBe(true); - }); - it('should not have touch by default', async({page, server}) => { - await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false); - await page.goto(server.PREFIX + '/detect-touch.html'); - expect(await page.evaluate(() => document.body.textContent.trim())).toBe('NO'); - }); - it('should support touch with null viewport', async({browser, server}) => { - const context = await browser.newContext({ viewport: null, hasTouch: true }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(true); - await context.close(); - }); - it('should report null viewportSize when given null viewport', async({browser, server}) => { - const context = await browser.newContext({ viewport: null }); - const page = await context.newPage(); - expect(page.viewportSize()).toBe(null); - await context.close(); - }); -}); - -describe.skip(FFOX)('viewport.isMobile', () => { - // Firefox does not support isMobile. - it('should support mobile emulation', async({playwright, browser, server}) => { - const iPhone = playwright.devices['iPhone 6']; - const context = await browser.newContext({ ...iPhone }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => window.innerWidth)).toBe(375); - await page.setViewportSize({width: 400, height: 300}); - expect(await page.evaluate(() => window.innerWidth)).toBe(400); - await context.close(); - }); - it('should support touch emulation', async({playwright, browser, server}) => { - const iPhone = playwright.devices['iPhone 6']; - const context = await browser.newContext({ ...iPhone }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(true); - expect(await page.evaluate(dispatchTouch)).toBe('Received touch'); - await context.close(); - - function dispatchTouch() { - let fulfill; - const promise = new Promise(x => fulfill = x); - window.ontouchstart = function(e) { - fulfill('Received touch'); - }; - window.dispatchEvent(new Event('touchstart')); - - fulfill('Did not receive touch'); - - return promise; - } - }); - it('should be detectable by Modernizr', async({playwright, browser, server}) => { - const iPhone = playwright.devices['iPhone 6']; - const context = await browser.newContext({ ...iPhone }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/detect-touch.html'); - expect(await page.evaluate(() => document.body.textContent.trim())).toBe('YES'); - await context.close(); - }); - it('should detect touch when applying viewport with touches', async({browser, server}) => { - const context = await browser.newContext({ viewport: { width: 800, height: 600 }, hasTouch: true }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({url: server.PREFIX + '/modernizr.js'}); - expect(await page.evaluate(() => Modernizr.touchevents)).toBe(true); - await context.close(); - }); - it('should support landscape emulation', async({playwright, browser, server}) => { - const iPhone = playwright.devices['iPhone 6']; - const iPhoneLandscape = playwright.devices['iPhone 6 landscape']; - const context1 = await browser.newContext({ ...iPhone }); - const page1 = await context1.newPage(); - await page1.goto(server.PREFIX + '/mobile.html'); - expect(await page1.evaluate(() => matchMedia('(orientation: landscape)').matches)).toBe(false); - const context2 = await browser.newContext({ ...iPhoneLandscape }); - const page2 = await context2.newPage(); - expect(await page2.evaluate(() => matchMedia('(orientation: landscape)').matches)).toBe(true); - await context1.close(); - await context2.close(); - }); - it('should support window.orientation emulation', async({browser, server}) => { - const context = await browser.newContext({ viewport: { width: 300, height: 400 }, isMobile: true }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => window.orientation)).toBe(0); - await page.setViewportSize({width: 400, height: 300}); - expect(await page.evaluate(() => window.orientation)).toBe(90); - await context.close(); - }); - it('should fire orientationchange event', async({browser, server}) => { - const context = await browser.newContext({ viewport: { width: 300, height: 400 }, isMobile: true }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/mobile.html'); - await page.evaluate(() => { - window.counter = 0; - window.addEventListener('orientationchange', () => console.log(++window.counter)); - }); - - const event1 = page.waitForEvent('console'); - await page.setViewportSize({width: 400, height: 300}); - expect((await event1).text()).toBe('1'); - - const event2 = page.waitForEvent('console'); - await page.setViewportSize({width: 300, height: 400}); - expect((await event2).text()).toBe('2'); - await context.close(); - }); - it('default mobile viewports to 980 width', async({browser, server}) => { - const context = await browser.newContext({ viewport: {width: 320, height: 480 }, isMobile: true }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/empty.html'); - expect(await page.evaluate(() => window.innerWidth)).toBe(980); - await context.close(); - }); - it('respect meta viewport tag', async({browser, server}) => { - const context = await browser.newContext({ viewport: {width: 320, height: 480 }, isMobile: true }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => window.innerWidth)).toBe(320); - await context.close(); - }); -}); - -describe.skip(FFOX)('Page.emulate', function() { - it('should work', async({playwright, browser, server}) => { - const iPhone = playwright.devices['iPhone 6']; - const context = await browser.newContext({ ...iPhone }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => window.innerWidth)).toBe(375); - expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone'); - await context.close(); - }); - it('should support clicking', async({playwright, browser, server}) => { - const iPhone = playwright.devices['iPhone 6']; - const context = await browser.newContext({ ...iPhone }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await page.evaluate(button => button.style.marginTop = '200px', button); - await button.click(); - expect(await page.evaluate(() => result)).toBe('Clicked'); - await context.close(); - }); - it('should scroll to click', async({browser, server}) => { - const context = await browser.newContext({ - viewport: { - width: 400, - height: 400, - }, - deviceScaleFactor: 1, - isMobile: true - }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/input/scrollable.html'); - const element = await page.$('#button-91'); - await element.click(); - expect(await element.textContent()).toBe('clicked'); - await context.close(); - }); -}); - -describe('Page.emulateMedia type', function() { - it('should work', async({page, server}) => { - expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false); - await page.emulateMedia({ media: 'print' }); - expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true); - await page.emulateMedia({}); - expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true); - await page.emulateMedia({ media: null }); - expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false); - }); - it('should throw in case of bad type argument', async({page, server}) => { - let error = null; - await page.emulateMedia({ media: 'bad' }).catch(e => error = e); - expect(error.message).toContain('media: expected one of (screen|print|null)'); - }); -}); - -describe('Page.emulateMedia colorScheme', function() { - it('should work', async({page, server}) => { - await page.emulateMedia({ colorScheme: 'light' }); - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false); - await page.emulateMedia({ colorScheme: 'dark' }); - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false); - }); - it('should default to light', async({page, server}) => { - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false); - - await page.emulateMedia({ colorScheme: 'dark' }); - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false); - - await page.emulateMedia({ colorScheme: null }); - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false); - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true); - }); - it('should throw in case of bad argument', async({page, server}) => { - let error = null; - await page.emulateMedia({ colorScheme: 'bad' }).catch(e => error = e); - expect(error.message).toContain('colorScheme: expected one of (dark|light|no-preference|null)'); - }); - it('should work during navigation', async({page, server}) => { - await page.emulateMedia({ colorScheme: 'light' }); - const navigated = page.goto(server.EMPTY_PAGE); - for (let i = 0; i < 9; i++) { - await Promise.all([ - page.emulateMedia({ colorScheme: ['dark', 'light'][i & 1] }), - new Promise(f => setTimeout(f, 1)), - ]); - } - await navigated; - expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); - }); - it('should work in popup', async({browser, server}) => { - { - const context = await browser.newContext({ colorScheme: 'dark' }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), - ]); - expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false); - expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); - await context.close(); - } - { - const page = await browser.newPage({ colorScheme: 'light' }); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), - ]); - expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true); - expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false); - await page.close(); - } - }); - it('should work in cross-process iframe', async({browser, server}) => { - const page = await browser.newPage({ colorScheme: 'dark' }); - await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html'); - const frame = page.frames()[1]; - expect(await frame.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); - await page.close(); - }); - it.fail(FFOX)('should change the actual colors in css', async({page}) => { - await page.setContent(` - -
Hello
- `); - function getBackgroundColor() { - return page.$eval('div', div => window.getComputedStyle(div).backgroundColor); - } - - await page.emulateMedia({ colorScheme: "light" }); - expect(await getBackgroundColor()).toBe('rgb(255, 255, 255)'); - - await page.emulateMedia({ colorScheme: "dark" }); - expect(await getBackgroundColor()).toBe('rgb(0, 0, 0)'); - - await page.emulateMedia({ colorScheme: "light" }); - expect(await getBackgroundColor()).toBe('rgb(255, 255, 255)'); - }) -}); - -describe('BrowserContext({timezoneId})', function() { - it('should work', async ({ browser }) => { - const func = () => new Date(1479579154987).toString(); - { - const context = await browser.newContext({ timezoneId: 'America/Jamaica' }); - const page = await context.newPage(); - expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)'); - await context.close(); - } - { - const context = await browser.newContext({ timezoneId: 'Pacific/Honolulu' }); - const page = await context.newPage(); - expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 08:12:34 GMT-1000 (Hawaii-Aleutian Standard Time)'); - await context.close(); - } - { - const context = await browser.newContext({ timezoneId: 'America/Buenos_Aires' }); - const page = await context.newPage(); - expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 15:12:34 GMT-0300 (Argentina Standard Time)'); - await context.close(); - } - { - const context = await browser.newContext({ timezoneId: 'Europe/Berlin' }); - const page = await context.newPage(); - expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 19:12:34 GMT+0100 (Central European Standard Time)'); - await context.close(); - } - }); - - it('should throw for invalid timezone IDs when creating pages', async({browser}) => { - for (const timezoneId of ['Foo/Bar', 'Baz/Qux']) { - let error = null; - const context = await browser.newContext({ timezoneId }); - const page = await context.newPage().catch(e => error = e); - expect(error.message).toContain(`Invalid timezone ID: ${timezoneId}`); - await context.close(); - } - }); - it('should work for multiple pages sharing same process', async({browser, server}) => { - const context = await browser.newContext({ timezoneId: 'Europe/Moscow' }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - let [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), - ]); - [popup] = await Promise.all([ - popup.waitForEvent('popup'), - popup.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), - ]); - await context.close(); - }); -}); - -describe('BrowserContext({locale})', function() { - it('should affect accept-language header', async({browser, server}) => { - const context = await browser.newContext({ locale: 'fr-CH' }); - const page = await context.newPage(); - const [request] = await Promise.all([ - server.waitForRequest('/empty.html'), - page.goto(server.EMPTY_PAGE), - ]); - expect(request.headers['accept-language'].substr(0, 5)).toBe('fr-CH'); - await context.close(); - }); - it('should affect navigator.language', async({browser, server}) => { - const context = await browser.newContext({ locale: 'fr-CH' }); - const page = await context.newPage(); - expect(await page.evaluate(() => navigator.language)).toBe('fr-CH'); - await context.close(); - }); - it('should format number', async({browser, server}) => { - { - const context = await browser.newContext({ locale: 'en-US' }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - expect(await page.evaluate(() => (1000000.50).toLocaleString())).toBe('1,000,000.5'); - await context.close(); - } - { - const context = await browser.newContext({ locale: 'fr-CH' }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - expect(await page.evaluate(() => (1000000.50).toLocaleString().replace(/\s/g, ' '))).toBe('1 000 000,5'); - await context.close(); - } - }); - it('should format date', async({browser, server}) => { - { - const context = await browser.newContext({ locale: 'en-US', timezoneId: 'America/Los_Angeles' }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const formatted = 'Sat Nov 19 2016 10:12:34 GMT-0800 (Pacific Standard Time)'; - expect(await page.evaluate(() => new Date(1479579154987).toString())).toBe(formatted); - await context.close(); - } - { - const context = await browser.newContext({ locale: 'de-DE', timezoneId: 'Europe/Berlin' }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - expect(await page.evaluate(() => new Date(1479579154987).toString())).toBe( - 'Sat Nov 19 2016 19:12:34 GMT+0100 (Mitteleuropäische Normalzeit)'); - await context.close(); - } - }); - it('should format number in popups', async({browser, server}) => { - const context = await browser.newContext({ locale: 'fr-CH' }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/formatted-number.html'), - ]); - await popup.waitForLoadState('domcontentloaded'); - const result = await popup.evaluate(() => window.result); - expect(result).toBe('1 000 000,5'); - await context.close(); - }); - it('should affect navigator.language in popups', async({browser, server}) => { - const context = await browser.newContext({ locale: 'fr-CH' }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/formatted-number.html'), - ]); - await popup.waitForLoadState('domcontentloaded'); - const result = await popup.evaluate(() => window.initialNavigatorLanguage); - expect(result).toBe('fr-CH'); - await context.close(); - }); - it('should work for multiple pages sharing same process', async({browser, server}) => { - const context = await browser.newContext({ locale: 'ru-RU' }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - let [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), - ]); - [popup] = await Promise.all([ - popup.waitForEvent('popup'), - popup.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), - ]); - await context.close(); - }); - it('should be isolated between contexts', async({browser, server}) => { - const context1 = await browser.newContext({ locale: 'en-US' }); - const promises = []; - // By default firefox limits number of child web processes to 8. - for (let i = 0; i< 8; i++) - promises.push(context1.newPage()); - await Promise.all(promises); - - const context2 = await browser.newContext({ locale: 'ru-RU' }); - const page2 = await context2.newPage(); - - const localeNumber = () => (1000000.50).toLocaleString(); - const numbers = await Promise.all(context1.pages().map(page => page.evaluate(localeNumber))); - - numbers.forEach(value => expect(value).toBe('1,000,000.5')); - expect(await page2.evaluate(localeNumber)).toBe('1 000 000,5'); - - await Promise.all([ - context1.close(), - context2.close() - ]); - }); -}); - -describe('focus', function() { - it('should think that it is focused by default', async({page}) => { - expect(await page.evaluate('document.hasFocus()')).toBe(true); - }); - it('should think that all pages are focused', async({page}) => { - const page2 = await page.context().newPage(); - expect(await page.evaluate('document.hasFocus()')).toBe(true); - expect(await page2.evaluate('document.hasFocus()')).toBe(true); - await page2.close(); - }); - it('should focus popups by default', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), - ]); - expect(await popup.evaluate('document.hasFocus()')).toBe(true); - expect(await page.evaluate('document.hasFocus()')).toBe(true); - }); - it('should provide target for keyboard events', async({page, server}) => { - const page2 = await page.context().newPage(); - await Promise.all([ - page.goto(server.PREFIX + '/input/textarea.html'), - page2.goto(server.PREFIX + '/input/textarea.html'), - ]); - await Promise.all([ - page.focus('input'), - page2.focus('input'), - ]); - const text = 'first'; - const text2 = 'second'; - await Promise.all([ - page.keyboard.type(text), - page2.keyboard.type(text2), - ]); - const results = await Promise.all([ - page.evaluate('result'), - page2.evaluate('result'), - ]); - expect(results).toEqual([text, text2]); - }); - it('should not affect mouse event target page', async({page, server}) => { - const page2 = await page.context().newPage(); - function clickCounter() { - document.onclick = () => window.clickCount = (window.clickCount || 0) + 1; - } - await Promise.all([ - page.evaluate(clickCounter), - page2.evaluate(clickCounter), - page.focus('body'), - page2.focus('body'), - ]); - await Promise.all([ - page.mouse.click(1, 1), - page2.mouse.click(1, 1), - ]); - const counters = await Promise.all([ - page.evaluate('window.clickCount'), - page2.evaluate('window.clickCount'), - ]); - expect(counters ).toEqual([1,1]); - }); - it('should change document.activeElement', async({page, server}) => { - const page2 = await page.context().newPage(); - await Promise.all([ - page.goto(server.PREFIX + '/input/textarea.html'), - page2.goto(server.PREFIX + '/input/textarea.html'), - ]); - await Promise.all([ - page.focus('input'), - page2.focus('textarea'), - ]); - const active = await Promise.all([ - page.evaluate('document.activeElement.tagName'), - page2.evaluate('document.activeElement.tagName'), - ]); - expect(active).toEqual(['INPUT', 'TEXTAREA']); - }); - it.skip(FFOX && !HEADLESS)('should not affect screenshots', async({page, server}) => { - // Firefox headful produces a different image. - const page2 = await page.context().newPage(); - await Promise.all([ - page.setViewportSize({width: 500, height: 500}), - page.goto(server.PREFIX + '/grid.html'), - page2.setViewportSize({width: 50, height: 50}), - page2.goto(server.PREFIX + '/grid.html'), - ]); - await Promise.all([ - page.focus('body'), - page2.focus('body'), - ]); - const screenshots = await Promise.all([ - page.screenshot(), - page2.screenshot(), - ]); - expect(screenshots[0]).toBeGolden('screenshot-sanity.png'); - expect(screenshots[1]).toBeGolden('grid-cell-0.png'); - }); - it('should change focused iframe', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [frame1, frame2] = await Promise.all([ - utils.attachFrame(page, 'frame1', server.PREFIX + '/input/textarea.html'), - utils.attachFrame(page, 'frame2', server.PREFIX + '/input/textarea.html'), - ]); - function logger() { - self._events = []; - const element = document.querySelector('input'); - element.onfocus = element.onblur = (e) => self._events.push(e.type); - } - await Promise.all([ - frame1.evaluate(logger), - frame2.evaluate(logger), - ]); - const focused = await Promise.all([ - frame1.evaluate('document.hasFocus()'), - frame2.evaluate('document.hasFocus()'), - ]); - expect(focused).toEqual([false, false]); - { - await frame1.focus('input'); - const events = await Promise.all([ - frame1.evaluate('self._events'), - frame2.evaluate('self._events'), - ]); - expect(events).toEqual([['focus'], []]); - const focused = await Promise.all([ - frame1.evaluate('document.hasFocus()'), - frame2.evaluate('document.hasFocus()'), - ]); - expect(focused).toEqual([true, false]); - } - { - await frame2.focus('input'); - const events = await Promise.all([ - frame1.evaluate('self._events'), - frame2.evaluate('self._events'), - ]); - expect(events).toEqual([['focus', 'blur'], ['focus']]); - const focused = await Promise.all([ - frame1.evaluate('document.hasFocus()'), - frame2.evaluate('document.hasFocus()'), - ]); - expect(focused).toEqual([false, true]); - } - }); - -}); diff --git a/test/page-emulate-media.spec.js b/test/page-emulate-media.spec.js new file mode 100644 index 0000000000..9ba13fed81 --- /dev/null +++ b/test/page-emulate-media.spec.js @@ -0,0 +1,148 @@ +/** + * 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 utils = require('./utils'); +const {CHROMIUM, FFOX, MAC, HEADLESS} = testOptions; + +it('should emulate type', async({page, server}) => { + expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false); + await page.emulateMedia({ media: 'print' }); + expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true); + await page.emulateMedia({}); + expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true); + await page.emulateMedia({ media: null }); + expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false); +}); + +it('should throw in case of bad type argument', async({page, server}) => { + let error = null; + await page.emulateMedia({ media: 'bad' }).catch(e => error = e); + expect(error.message).toContain('media: expected one of (screen|print|null)'); +}); + +it('should emulate scheme work', async({page, server}) => { + await page.emulateMedia({ colorScheme: 'light' }); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false); + await page.emulateMedia({ colorScheme: 'dark' }); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false); +}); + +it('should default to light', async({page, server}) => { + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false); + + await page.emulateMedia({ colorScheme: 'dark' }); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false); + + await page.emulateMedia({ colorScheme: null }); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true); +}); + +it('should throw in case of bad argument', async({page, server}) => { + let error = null; + await page.emulateMedia({ colorScheme: 'bad' }).catch(e => error = e); + expect(error.message).toContain('colorScheme: expected one of (dark|light|no-preference|null)'); +}); + +it('should work during navigation', async({page, server}) => { + await page.emulateMedia({ colorScheme: 'light' }); + const navigated = page.goto(server.EMPTY_PAGE); + for (let i = 0; i < 9; i++) { + await Promise.all([ + page.emulateMedia({ colorScheme: ['dark', 'light'][i & 1] }), + new Promise(f => setTimeout(f, 1)), + ]); + } + await navigated; + expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); +}); + +it('should work in popup', async({browser, server}) => { + { + const context = await browser.newContext({ colorScheme: 'dark' }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), + ]); + expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false); + expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); + await context.close(); + } + { + const page = await browser.newPage({ colorScheme: 'light' }); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE), + ]); + expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true); + expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false); + await page.close(); + } +}); + +it('should work in cross-process iframe', async({browser, server}) => { + const page = await browser.newPage({ colorScheme: 'dark' }); + await page.goto(server.EMPTY_PAGE); + await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html'); + const frame = page.frames()[1]; + expect(await frame.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); + await page.close(); +}); + +it.fail(FFOX)('should change the actual colors in css', async({page}) => { + await page.setContent(` + +
Hello
+ `); + function getBackgroundColor() { + return page.$eval('div', div => window.getComputedStyle(div).backgroundColor); + } + + await page.emulateMedia({ colorScheme: "light" }); + expect(await getBackgroundColor()).toBe('rgb(255, 255, 255)'); + + await page.emulateMedia({ colorScheme: "dark" }); + expect(await getBackgroundColor()).toBe('rgb(0, 0, 0)'); + + await page.emulateMedia({ colorScheme: "light" }); + expect(await getBackgroundColor()).toBe('rgb(255, 255, 255)'); +})