From 57490b74c6c8d165151b3e26372122df205d3324 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 4 Aug 2020 16:32:10 -0700 Subject: [PATCH] test: remove describes (6) (#3295) --- test/accessibility.jest.js | 359 -------- test/accessibility.spec.js | 370 +++++++++ test/frame-goto.spec.js | 78 ++ test/input.jest.js | 258 ------ test/interception.jest.js | 831 ------------------- test/interception.spec.js | 114 +++ test/navigation.jest.js | 1099 ------------------------- test/navigation.spec.js | 37 + test/page-goto.spec.js | 485 +++++++++++ test/page-history.spec.js | 96 +++ test/page-network-idle.spec.js | 146 ++++ test/page-route.spec.js | 509 ++++++++++++ test/page-set-input-files.spec.js | 266 ++++++ test/page-wait-for-load-state.spec.js | 183 ++++ test/page-wait-for-navigation.spec.js | 250 ++++++ test/request-continue.spec.js | 115 +++ test/request-fulfill.spec.js | 179 ++++ test/utils.js | 16 + 18 files changed, 2844 insertions(+), 2547 deletions(-) delete mode 100644 test/accessibility.jest.js create mode 100644 test/accessibility.spec.js create mode 100644 test/frame-goto.spec.js delete mode 100644 test/input.jest.js delete mode 100644 test/interception.jest.js create mode 100644 test/interception.spec.js delete mode 100644 test/navigation.jest.js create mode 100644 test/navigation.spec.js create mode 100644 test/page-goto.spec.js create mode 100644 test/page-history.spec.js create mode 100644 test/page-network-idle.spec.js create mode 100644 test/page-route.spec.js create mode 100644 test/page-set-input-files.spec.js create mode 100644 test/page-wait-for-load-state.spec.js create mode 100644 test/page-wait-for-navigation.spec.js create mode 100644 test/request-continue.spec.js create mode 100644 test/request-fulfill.spec.js diff --git a/test/accessibility.jest.js b/test/accessibility.jest.js deleted file mode 100644 index aedbb73ef8c12..0000000000000 --- a/test/accessibility.jest.js +++ /dev/null @@ -1,359 +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} = testOptions; - -describe('Accessibility', function() { - it('should work', async function({page}) { - await page.setContent(` - - Accessibility Test - - -

Inputs

- - - - - - - - - `); - // autofocus happens after a delay in chrome these days - await page.waitForFunction(() => document.activeElement.hasAttribute('autofocus')); - - const golden = FFOX ? { - role: 'document', - name: 'Accessibility Test', - children: [ - {role: 'heading', name: 'Inputs', level: 1}, - {role: 'textbox', name: 'Empty input', focused: true}, - {role: 'textbox', name: 'readonly input', readonly: true}, - {role: 'textbox', name: 'disabled input', disabled: true}, - {role: 'textbox', name: 'Input with whitespace', value: ' '}, - {role: 'textbox', name: '', value: 'value only'}, - {role: 'textbox', name: '', value: 'and a value'}, // firefox doesn't use aria-placeholder for the name - {role: 'textbox', name: '', value: 'and a value', description: 'This is a description!'}, // and here - ] - } : CHROMIUM ? { - role: 'WebArea', - name: 'Accessibility Test', - children: [ - {role: 'heading', name: 'Inputs', level: 1}, - {role: 'textbox', name: 'Empty input', focused: true}, - {role: 'textbox', name: 'readonly input', readonly: true}, - {role: 'textbox', name: 'disabled input', disabled: true}, - {role: 'textbox', name: 'Input with whitespace', value: ' '}, - {role: 'textbox', name: '', value: 'value only'}, - {role: 'textbox', name: 'placeholder', value: 'and a value'}, - {role: 'textbox', name: 'placeholder', value: 'and a value', description: 'This is a description!'}, - ] - } : { - role: 'WebArea', - name: 'Accessibility Test', - children: [ - {role: 'heading', name: 'Inputs', level: 1}, - {role: 'textbox', name: 'Empty input', focused: true}, - {role: 'textbox', name: 'readonly input', readonly: true}, - {role: 'textbox', name: 'disabled input', disabled: true}, - {role: 'textbox', name: 'Input with whitespace', value: ' ' }, - {role: 'textbox', name: '', value: 'value only' }, - {role: 'textbox', name: 'placeholder', value: 'and a value'}, - {role: 'textbox', name: 'This is a description!',value: 'and a value'}, // webkit uses the description over placeholder for the name - ] - }; - expect(await page.accessibility.snapshot()).toEqual(golden); - }); - it('should work with regular text', async({page}) => { - await page.setContent(`
Hello World
`); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual({ - role: FFOX ? 'text leaf' : 'text', - name: 'Hello World', - }); - }); - it('roledescription', async({page}) => { - await page.setContent('
Hi
'); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0].roledescription).toEqual('foo'); - }); - it('orientation', async({page}) => { - await page.setContent('11'); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0].orientation).toEqual('vertical'); - }); - it('autocomplete', async({page}) => { - await page.setContent('
hi
'); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0].autocomplete).toEqual('list'); - }); - it('multiselectable', async({page}) => { - await page.setContent('
hey
'); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0].multiselectable).toEqual(true); - }); - it('keyshortcuts', async({page}) => { - await page.setContent('
hey
'); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0].keyshortcuts).toEqual('foo'); - }); - describe('filtering children of leaf nodes', function() { - it('should not report text nodes inside controls', async function({page}) { - await page.setContent(` -
-
Tab1
-
Tab2
-
`); - const golden = { - role: FFOX ? 'document' : 'WebArea', - name: '', - children: [{ - role: 'tab', - name: 'Tab1', - selected: true - }, { - role: 'tab', - name: 'Tab2' - }] - }; - expect(await page.accessibility.snapshot()).toEqual(golden); - }); - // WebKit rich text accessibility is iffy - it.skip(WEBKIT)('rich text editable fields should have children', async function({page}) { - await page.setContent(` -
- Edit this image: my fake image -
`); - const golden = FFOX ? { - role: 'section', - name: '', - children: [{ - role: 'text leaf', - name: 'Edit this image: ' - }, { - role: 'text', - name: 'my fake image' - }] - } : { - role: 'generic', - name: '', - value: 'Edit this image: ', - children: [{ - role: 'text', - name: 'Edit this image:' - }, { - role: 'img', - name: 'my fake image' - }] - }; - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual(golden); - }); - // WebKit rich text accessibility is iffy - it.skip(WEBKIT)('rich text editable fields with role should have children', async function({page}) { - await page.setContent(` -
- Edit this image: my fake image -
`); - const golden = FFOX ? { - role: 'textbox', - name: '', - value: 'Edit this image: my fake image', - children: [{ - role: 'text', - name: 'my fake image' - }] - } : { - role: 'textbox', - name: '', - value: 'Edit this image: ', - children: [{ - role: 'text', - name: 'Edit this image:' - }, { - role: 'img', - name: 'my fake image' - }] - }; - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual(golden); - }); - // Firefox does not support contenteditable="plaintext-only". - // WebKit rich text accessibility is iffy - describe.skip(FFOX || WEBKIT)('plaintext contenteditable', function() { - it('plain text field with role should not have children', async function({page}) { - await page.setContent(` -
Edit this image:my fake image
`); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual({ - role: 'textbox', - name: '', - value: 'Edit this image:' - }); - }); - it('plain text field without role should not have content', async function({page}) { - await page.setContent(` -
Edit this image:my fake image
`); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual({ - role: 'generic', - name: '' - }); - }); - it('plain text field with tabindex and without role should not have content', async function({page}) { - await page.setContent(` -
Edit this image:my fake image
`); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual({ - role: 'generic', - name: '' - }); - }); - }); - it('non editable textbox with role and tabIndex and label should not have children', async function({page}) { - await page.setContent(` -
- this is the inner content - yo -
`); - const golden = FFOX ? { - role: 'textbox', - name: 'my favorite textbox', - value: 'this is the inner content yo' - } : CHROMIUM ? { - role: 'textbox', - name: 'my favorite textbox', - value: 'this is the inner content ' - } : { - role: 'textbox', - name: 'my favorite textbox', - value: 'this is the inner content ', - }; - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual(golden); - }); - it('checkbox with and tabIndex and label should not have children', async function({page}) { - await page.setContent(` -
- this is the inner content - yo -
`); - const golden = { - role: 'checkbox', - name: 'my favorite checkbox', - checked: true - }; - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual(golden); - }); - it('checkbox without label should not have children', async function({page}) { - await page.setContent(` -
- this is the inner content - yo -
`); - const golden = FFOX ? { - role: 'checkbox', - name: 'this is the inner content yo', - checked: true - } : { - role: 'checkbox', - name: 'this is the inner content yo', - checked: true - }; - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual(golden); - }); - - describe('root option', function() { - it('should work a button', async({page}) => { - await page.setContent(``); - - const button = await page.$('button'); - expect(await page.accessibility.snapshot({root: button})).toEqual({ - role: 'button', - name: 'My Button' - }); - }); - it('should work an input', async({page}) => { - await page.setContent(``); - - const input = await page.$('input'); - expect(await page.accessibility.snapshot({root: input})).toEqual({ - role: 'textbox', - name: 'My Input', - value: 'My Value' - }); - }); - it('should work on a menu', async({page}) => { - await page.setContent(` -
-
First Item
-
Second Item
-
Third Item
-
- `); - - const menu = await page.$('div[role="menu"]'); - expect(await page.accessibility.snapshot({root: menu})).toEqual({ - role: 'menu', - name: 'My Menu', - children: - [ { role: 'menuitem', name: 'First Item' }, - { role: 'menuitem', name: 'Second Item' }, - { role: 'menuitem', name: 'Third Item' } ], - orientation: WEBKIT ? 'vertical' : undefined - }); - }); - it('should return null when the element is no longer in DOM', async({page}) => { - await page.setContent(``); - const button = await page.$('button'); - await page.$eval('button', button => button.remove()); - expect(await page.accessibility.snapshot({root: button})).toEqual(null); - }); - it('should show uninteresting nodes', async({page}) => { - await page.setContent(` -
-
- hello -
- world -
-
-
- `); - - const root = await page.$('#root'); - const snapshot = await page.accessibility.snapshot({root, interestingOnly: false}); - expect(snapshot.role).toBe('textbox'); - expect(snapshot.value).toContain('hello'); - expect(snapshot.value).toContain('world'); - expect(!!snapshot.children).toBe(true); - }); - }); - }); - it('should work when there is a title ', async ({page}) => { - await page.setContent(` - This is the title -
This is the content
- `); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.name).toBe('This is the title'); - expect(snapshot.children[0].name).toBe('This is the content'); - }); -}); diff --git a/test/accessibility.spec.js b/test/accessibility.spec.js new file mode 100644 index 0000000000000..82708384b4550 --- /dev/null +++ b/test/accessibility.spec.js @@ -0,0 +1,370 @@ +/** + * Copyright 2018 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const {FFOX, CHROMIUM, WEBKIT} = testOptions; + +it('should work', async function({page}) { + await page.setContent(` + + Accessibility Test + + +

Inputs

+ + + + + + + + + `); + // autofocus happens after a delay in chrome these days + await page.waitForFunction(() => document.activeElement.hasAttribute('autofocus')); + + const golden = FFOX ? { + role: 'document', + name: 'Accessibility Test', + children: [ + {role: 'heading', name: 'Inputs', level: 1}, + {role: 'textbox', name: 'Empty input', focused: true}, + {role: 'textbox', name: 'readonly input', readonly: true}, + {role: 'textbox', name: 'disabled input', disabled: true}, + {role: 'textbox', name: 'Input with whitespace', value: ' '}, + {role: 'textbox', name: '', value: 'value only'}, + {role: 'textbox', name: '', value: 'and a value'}, // firefox doesn't use aria-placeholder for the name + {role: 'textbox', name: '', value: 'and a value', description: 'This is a description!'}, // and here + ] + } : CHROMIUM ? { + role: 'WebArea', + name: 'Accessibility Test', + children: [ + {role: 'heading', name: 'Inputs', level: 1}, + {role: 'textbox', name: 'Empty input', focused: true}, + {role: 'textbox', name: 'readonly input', readonly: true}, + {role: 'textbox', name: 'disabled input', disabled: true}, + {role: 'textbox', name: 'Input with whitespace', value: ' '}, + {role: 'textbox', name: '', value: 'value only'}, + {role: 'textbox', name: 'placeholder', value: 'and a value'}, + {role: 'textbox', name: 'placeholder', value: 'and a value', description: 'This is a description!'}, + ] + } : { + role: 'WebArea', + name: 'Accessibility Test', + children: [ + {role: 'heading', name: 'Inputs', level: 1}, + {role: 'textbox', name: 'Empty input', focused: true}, + {role: 'textbox', name: 'readonly input', readonly: true}, + {role: 'textbox', name: 'disabled input', disabled: true}, + {role: 'textbox', name: 'Input with whitespace', value: ' ' }, + {role: 'textbox', name: '', value: 'value only' }, + {role: 'textbox', name: 'placeholder', value: 'and a value'}, + {role: 'textbox', name: 'This is a description!',value: 'and a value'}, // webkit uses the description over placeholder for the name + ] + }; + expect(await page.accessibility.snapshot()).toEqual(golden); +}); + +it('should work with regular text', async({page}) => { + await page.setContent(`
Hello World
`); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual({ + role: FFOX ? 'text leaf' : 'text', + name: 'Hello World', + }); +}); + +it('roledescription', async({page}) => { + await page.setContent('
Hi
'); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0].roledescription).toEqual('foo'); +}); + +it('orientation', async({page}) => { + await page.setContent('11'); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0].orientation).toEqual('vertical'); +}); + +it('autocomplete', async({page}) => { + await page.setContent('
hi
'); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0].autocomplete).toEqual('list'); +}); + +it('multiselectable', async({page}) => { + await page.setContent('
hey
'); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0].multiselectable).toEqual(true); +}); + +it('keyshortcuts', async({page}) => { + await page.setContent('
hey
'); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0].keyshortcuts).toEqual('foo'); +}); + +it('should not report text nodes inside controls', async function({page}) { + await page.setContent(` +
+
Tab1
+
Tab2
+
`); + const golden = { + role: FFOX ? 'document' : 'WebArea', + name: '', + children: [{ + role: 'tab', + name: 'Tab1', + selected: true + }, { + role: 'tab', + name: 'Tab2' + }] + }; + expect(await page.accessibility.snapshot()).toEqual(golden); +}); + +// WebKit rich text accessibility is iffy +it.skip(WEBKIT)('rich text editable fields should have children', async function({page}) { + await page.setContent(` +
+ Edit this image: my fake image +
`); + const golden = FFOX ? { + role: 'section', + name: '', + children: [{ + role: 'text leaf', + name: 'Edit this image: ' + }, { + role: 'text', + name: 'my fake image' + }] + } : { + role: 'generic', + name: '', + value: 'Edit this image: ', + children: [{ + role: 'text', + name: 'Edit this image:' + }, { + role: 'img', + name: 'my fake image' + }] + }; + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual(golden); +}); +// WebKit rich text accessibility is iffy +it.skip(WEBKIT)('rich text editable fields with role should have children', async function({page}) { + await page.setContent(` +
+ Edit this image: my fake image +
`); + const golden = FFOX ? { + role: 'textbox', + name: '', + value: 'Edit this image: my fake image', + children: [{ + role: 'text', + name: 'my fake image' + }] + } : { + role: 'textbox', + name: '', + value: 'Edit this image: ', + children: [{ + role: 'text', + name: 'Edit this image:' + }, { + role: 'img', + name: 'my fake image' + }] + }; + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual(golden); +}); + +it.skip(FFOX || WEBKIT)('plain text field with role should not have children', async function({page}) { + // Firefox does not support contenteditable="plaintext-only". + // WebKit rich text accessibility is iffy + await page.setContent(` +
Edit this image:my fake image
`); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual({ + role: 'textbox', + name: '', + value: 'Edit this image:' + }); +}); + +it.skip(FFOX || WEBKIT)('plain text field without role should not have content', async function({page}) { + await page.setContent(` +
Edit this image:my fake image
`); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual({ + role: 'generic', + name: '' + }); +}); + +it.skip(FFOX || WEBKIT)('plain text field with tabindex and without role should not have content', async function({page}) { + await page.setContent(` +
Edit this image:my fake image
`); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual({ + role: 'generic', + name: '' + }); +}); + +it('non editable textbox with role and tabIndex and label should not have children', async function({page}) { + await page.setContent(` +
+ this is the inner content + yo +
`); + const golden = FFOX ? { + role: 'textbox', + name: 'my favorite textbox', + value: 'this is the inner content yo' + } : CHROMIUM ? { + role: 'textbox', + name: 'my favorite textbox', + value: 'this is the inner content ' + } : { + role: 'textbox', + name: 'my favorite textbox', + value: 'this is the inner content ', + }; + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual(golden); +}); + +it('checkbox with and tabIndex and label should not have children', async function({page}) { + await page.setContent(` +
+ this is the inner content + yo +
`); + const golden = { + role: 'checkbox', + name: 'my favorite checkbox', + checked: true + }; + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual(golden); +}); + +it('checkbox without label should not have children', async function({page}) { + await page.setContent(` +
+ this is the inner content + yo +
`); + const golden = FFOX ? { + role: 'checkbox', + name: 'this is the inner content yo', + checked: true + } : { + role: 'checkbox', + name: 'this is the inner content yo', + checked: true + }; + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual(golden); +}); + +it('should work a button', async({page}) => { + await page.setContent(``); + + const button = await page.$('button'); + expect(await page.accessibility.snapshot({root: button})).toEqual({ + role: 'button', + name: 'My Button' + }); +}); + +it('should work an input', async({page}) => { + await page.setContent(``); + + const input = await page.$('input'); + expect(await page.accessibility.snapshot({root: input})).toEqual({ + role: 'textbox', + name: 'My Input', + value: 'My Value' + }); +}); + +it('should work on a menu', async({page}) => { + await page.setContent(` +
+
First Item
+
Second Item
+
Third Item
+
+ `); + + const menu = await page.$('div[role="menu"]'); + expect(await page.accessibility.snapshot({root: menu})).toEqual({ + role: 'menu', + name: 'My Menu', + children: + [ { role: 'menuitem', name: 'First Item' }, + { role: 'menuitem', name: 'Second Item' }, + { role: 'menuitem', name: 'Third Item' } ], + orientation: WEBKIT ? 'vertical' : undefined + }); +}); + +it('should return null when the element is no longer in DOM', async({page}) => { + await page.setContent(``); + const button = await page.$('button'); + await page.$eval('button', button => button.remove()); + expect(await page.accessibility.snapshot({root: button})).toEqual(null); +}); + +it('should show uninteresting nodes', async({page}) => { + await page.setContent(` +
+
+ hello +
+ world +
+
+
+ `); + + const root = await page.$('#root'); + const snapshot = await page.accessibility.snapshot({root, interestingOnly: false}); + expect(snapshot.role).toBe('textbox'); + expect(snapshot.value).toContain('hello'); + expect(snapshot.value).toContain('world'); + expect(!!snapshot.children).toBe(true); +}); + +it('should work when there is a title ', async ({page}) => { + await page.setContent(` + This is the title +
This is the content
+ `); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.name).toBe('This is the title'); + expect(snapshot.children[0].name).toBe('This is the content'); +}); diff --git a/test/frame-goto.spec.js b/test/frame-goto.spec.js new file mode 100644 index 0000000000000..dbd01b2d14b37 --- /dev/null +++ b/test/frame-goto.spec.js @@ -0,0 +1,78 @@ +/** + * 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 path = require('path'); +const url = require('url'); +const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; + +it('should navigate subframes', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + expect(page.frames()[0].url()).toContain('/frames/one-frame.html'); + expect(page.frames()[1].url()).toContain('/frames/frame.html'); + + const response = await page.frames()[1].goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + expect(response.frame()).toBe(page.frames()[1]); +}); + +it('should reject when frame detaches', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + + server.setRoute('/empty.html', () => {}); + const navigationPromise = page.frames()[1].goto(server.EMPTY_PAGE).catch(e => e); + await server.waitForRequest('/empty.html'); + + await page.$eval('iframe', frame => frame.remove()); + const error = await navigationPromise; + expect(error.message).toContain('frame was detached'); +}); + +it('should continue after client redirect', async({page, server}) => { + server.setRoute('/frames/script.js', () => {}); + const url = server.PREFIX + '/frames/child-redirect.html'; + const error = await page.goto(url, { timeout: 5000, waitUntil: 'networkidle' }).catch(e => e); + expect(error.message).toContain('page.goto: Timeout 5000ms exceeded.'); + expect(error.message).toContain(`navigating to "${url}", waiting until "networkidle"`); +}); + +it('should return matching responses', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + // Attach three frames. + const frames = [ + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE), + await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE), + await utils.attachFrame(page, 'frame3', server.EMPTY_PAGE), + ]; + const serverResponses = []; + server.setRoute('/0.html', (req, res) => serverResponses.push(res)); + server.setRoute('/1.html', (req, res) => serverResponses.push(res)); + server.setRoute('/2.html', (req, res) => serverResponses.push(res)); + const navigations = []; + for (let i = 0; i < 3; ++i) { + navigations.push(frames[i].goto(server.PREFIX + '/' + i + '.html')); + await server.waitForRequest('/' + i + '.html'); + } + // Respond from server out-of-order. + const serverResponseTexts = ['AAA', 'BBB', 'CCC']; + for (const i of [1, 2, 0]) { + serverResponses[i].end(serverResponseTexts[i]); + const response = await navigations[i]; + expect(response.frame()).toBe(frames[i]); + expect(await response.text()).toBe(serverResponseTexts[i]); + } +}); diff --git a/test/input.jest.js b/test/input.jest.js deleted file mode 100644 index af9aa65fa89f5..0000000000000 --- a/test/input.jest.js +++ /dev/null @@ -1,258 +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 path = require('path'); -const fs = require('fs'); -const formidable = require('formidable'); - -const FILE_TO_UPLOAD = path.join(__dirname, '/assets/file-to-upload.txt'); - -describe('input', function() { - it('should upload the file', async({page, server}) => { - await page.goto(server.PREFIX + '/input/fileupload.html'); - const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD); - const input = await page.$('input'); - await input.setInputFiles(filePath); - expect(await page.evaluate(e => e.files[0].name, input)).toBe('file-to-upload.txt'); - expect(await page.evaluate(e => { - const reader = new FileReader(); - const promise = new Promise(fulfill => reader.onload = fulfill); - reader.readAsText(e.files[0]); - return promise.then(() => reader.result); - }, input)).toBe('contents of the file'); - }); -}); - -describe('Page.setInputFiles', function() { - it('should work', async({page}) => { - await page.setContent(``); - await page.setInputFiles('input', path.join(__dirname, '/assets/file-to-upload.txt')); - expect(await page.$eval('input', input => input.files.length)).toBe(1); - expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt'); - }); - it('should set from memory', async({page}) => { - await page.setContent(``); - await page.setInputFiles('input', { - name: 'test.txt', - mimeType: 'text/plain', - buffer: Buffer.from('this is a test') - }); - expect(await page.$eval('input', input => input.files.length)).toBe(1); - expect(await page.$eval('input', input => input.files[0].name)).toBe('test.txt'); - }); -}); - -describe('Page.waitForFileChooser', function() { - it('should emit event', async({page, server}) => { - await page.setContent(``); - const [chooser] = await Promise.all([ - new Promise(f => page.once('filechooser', f)), - page.click('input'), - ]); - expect(chooser).toBeTruthy(); - }); - it('should work when file input is attached to DOM', async({page, server}) => { - await page.setContent(``); - const [chooser] = await Promise.all([ - page.waitForEvent('filechooser'), - page.click('input'), - ]); - expect(chooser).toBeTruthy(); - }); - it('should work when file input is not attached to DOM', async({page, server}) => { - const [chooser] = await Promise.all([ - page.waitForEvent('filechooser'), - page.evaluate(() => { - const el = document.createElement('input'); - el.type = 'file'; - el.click(); - }), - ]); - expect(chooser).toBeTruthy(); - }); - it('should respect timeout', async({page, playwright}) => { - let error = null; - await page.waitForEvent('filechooser', {timeout: 1}).catch(e => error = e); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should respect default timeout when there is no custom timeout', async({page, playwright}) => { - page.setDefaultTimeout(1); - let error = null; - await page.waitForEvent('filechooser').catch(e => error = e); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should prioritize exact timeout over default timeout', async({page, playwright}) => { - page.setDefaultTimeout(0); - let error = null; - await page.waitForEvent('filechooser', {timeout: 1}).catch(e => error = e); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should work with no timeout', async({page, server}) => { - const [chooser] = await Promise.all([ - page.waitForEvent('filechooser', {timeout: 0}), - page.evaluate(() => setTimeout(() => { - const el = document.createElement('input'); - el.type = 'file'; - el.click(); - }, 50)) - ]); - expect(chooser).toBeTruthy(); - }); - it('should return the same file chooser when there are many watchdogs simultaneously', async({page, server}) => { - await page.setContent(``); - const [fileChooser1, fileChooser2] = await Promise.all([ - page.waitForEvent('filechooser'), - page.waitForEvent('filechooser'), - page.$eval('input', input => input.click()), - ]); - expect(fileChooser1 === fileChooser2).toBe(true); - }); - it('should accept single file', async({page, server}) => { - await page.setContent(``); - const [fileChooser] = await Promise.all([ - page.waitForEvent('filechooser'), - page.click('input'), - ]); - expect(fileChooser.page()).toBe(page); - expect(fileChooser.element()).toBeTruthy(); - await fileChooser.setFiles(FILE_TO_UPLOAD); - expect(await page.$eval('input', input => input.files.length)).toBe(1); - expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt'); - }); - it('should detect mime type', async({page, server}) => { - let files; - server.setRoute('/upload', async (req, res) => { - const form = new formidable.IncomingForm(); - form.parse(req, function(err, fields, f) { - files = f; - res.end(); - }); - }); - await page.goto(server.EMPTY_PAGE); - await page.setContent(` -
- - - -
`) - await (await page.$('input[name=file1]')).setInputFiles(path.join(__dirname, '/assets/file-to-upload.txt')); - await (await page.$('input[name=file2]')).setInputFiles(path.join(__dirname, '/assets/pptr.png')); - await Promise.all([ - page.click('input[type=submit]'), - server.waitForRequest('/upload'), - ]); - const { file1, file2 } = files; - expect(file1.name).toBe('file-to-upload.txt'); - expect(file1.type).toBe('text/plain'); - expect(fs.readFileSync(file1.path).toString()).toBe( - fs.readFileSync(path.join(__dirname, '/assets/file-to-upload.txt')).toString()); - expect(file2.name).toBe('pptr.png'); - expect(file2.type).toBe('image/png'); - expect(fs.readFileSync(file2.path).toString()).toBe( - fs.readFileSync(path.join(__dirname, '/assets/pptr.png')).toString()); - }); - it('should be able to read selected file', async({page, server}) => { - await page.setContent(``); - const [, content] = await Promise.all([ - page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles(FILE_TO_UPLOAD)), - page.$eval('input', async picker => { - picker.click(); - await new Promise(x => picker.oninput = x); - const reader = new FileReader(); - const promise = new Promise(fulfill => reader.onload = fulfill); - reader.readAsText(picker.files[0]); - return promise.then(() => reader.result); - }), - ]); - expect(content).toBe('contents of the file'); - }); - it('should be able to reset selected files with empty file list', async({page, server}) => { - await page.setContent(``); - const [, fileLength1] = await Promise.all([ - page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles(FILE_TO_UPLOAD)), - page.$eval('input', async picker => { - picker.click(); - await new Promise(x => picker.oninput = x); - return picker.files.length; - }), - ]); - expect(fileLength1).toBe(1); - const [, fileLength2] = await Promise.all([ - page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles([])), - page.$eval('input', async picker => { - picker.click(); - await new Promise(x => picker.oninput = x); - return picker.files.length; - }), - ]); - expect(fileLength2).toBe(0); - }); - it('should not accept multiple files for single-file input', async({page, server}) => { - await page.setContent(``); - const [fileChooser] = await Promise.all([ - page.waitForEvent('filechooser'), - page.click('input'), - ]); - let error = null; - await fileChooser.setFiles([ - path.relative(process.cwd(), __dirname + '/assets/file-to-upload.txt'), - path.relative(process.cwd(), __dirname + '/assets/pptr.png') - ]).catch(e => error = e); - expect(error).not.toBe(null); - }); - it('should emit input and change events', async({page, server}) => { - const events = []; - await page.exposeFunction('eventHandled', e => events.push(e)); - await page.setContent(` - - `); - await (await page.$('input')).setInputFiles(FILE_TO_UPLOAD); - expect(events.length).toBe(2); - expect(events[0].type).toBe('input'); - expect(events[1].type).toBe('change'); - }); -}); - -describe('Page.waitForFileChooser isMultiple', () => { - it('should work for single file pick', async({page, server}) => { - await page.setContent(``); - const [fileChooser] = await Promise.all([ - page.waitForEvent('filechooser'), - page.click('input'), - ]); - expect(fileChooser.isMultiple()).toBe(false); - }); - it('should work for "multiple"', async({page, server}) => { - await page.setContent(``); - const [fileChooser] = await Promise.all([ - page.waitForEvent('filechooser'), - page.click('input'), - ]); - expect(fileChooser.isMultiple()).toBe(true); - }); - it('should work for "webkitdirectory"', async({page, server}) => { - await page.setContent(``); - const [fileChooser] = await Promise.all([ - page.waitForEvent('filechooser'), - page.click('input'), - ]); - expect(fileChooser.isMultiple()).toBe(true); - }); -}); diff --git a/test/interception.jest.js b/test/interception.jest.js deleted file mode 100644 index deadf914f4922..0000000000000 --- a/test/interception.jest.js +++ /dev/null @@ -1,831 +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 fs = require('fs'); -const path = require('path'); -const { helper } = require('../lib/helper'); -const vm = require('vm'); -const {FFOX, CHROMIUM, WEBKIT, HEADLESS} = testOptions; - -describe('Page.route', function() { - it('should intercept', async({page, server}) => { - let intercepted = false; - await page.route('**/empty.html', (route, request) => { - expect(route.request()).toBe(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(); - intercepted = true; - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - expect(intercepted).toBe(true); - }); - it('should unroute', async({page, server}) => { - let intercepted = []; - const handler1 = route => { - intercepted.push(1); - route.continue(); - }; - await page.route('**/empty.html', handler1); - await page.route('**/empty.html', route => { - intercepted.push(2); - route.continue(); - }); - await page.route('**/empty.html', route => { - intercepted.push(3); - route.continue(); - }); - await page.route('**/*', route => { - intercepted.push(4); - route.continue(); - }); - await page.goto(server.EMPTY_PAGE); - expect(intercepted).toEqual([1]); - - intercepted = []; - await page.unroute('**/empty.html', handler1); - await page.goto(server.EMPTY_PAGE); - expect(intercepted).toEqual([2]); - - intercepted = []; - await page.unroute('**/empty.html'); - await page.goto(server.EMPTY_PAGE); - expect(intercepted).toEqual([4]); - }); - it('should work when POST is redirected with 302', async({page, server}) => { - server.setRedirect('/rredirect', '/empty.html'); - await page.goto(server.EMPTY_PAGE); - await page.route('**/*', route => route.continue()); - await page.setContent(` -
- -
- `); - await Promise.all([ - page.$eval('form', form => form.submit()), - page.waitForNavigation() - ]); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/3973 - it('should work when header manipulation headers with redirect', async({page, server}) => { - server.setRedirect('/rrredirect', '/empty.html'); - await page.route('**/*', route => { - const headers = Object.assign({}, route.request().headers(), { - foo: 'bar' - }); - route.continue({ headers }); - }); - await page.goto(server.PREFIX + '/rrredirect'); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/4743 - it('should be able to remove headers', async({page, server}) => { - await page.route('**/*', route => { - const headers = Object.assign({}, route.request().headers(), { - foo: 'bar', - origin: undefined, // remove "origin" header - }); - route.continue({ headers }); - }); - - const [serverRequest] = await Promise.all([ - server.waitForRequest('/empty.html'), - page.goto(server.PREFIX + '/empty.html') - ]); - - expect(serverRequest.headers.origin).toBe(undefined); - }); - it('should contain referer header', async({page, server}) => { - const requests = []; - await page.route('**/*', route => { - requests.push(route.request()); - route.continue(); - }); - await page.goto(server.PREFIX + '/one-style.html'); - expect(requests[1].url()).toContain('/one-style.css'); - expect(requests[1].headers().referer).toContain('/one-style.html'); - }); - it('should properly return navigation response when URL has cookies', async({context, page, server}) => { - // Setup cookie. - await page.goto(server.EMPTY_PAGE); - await context.addCookies([{ url: server.EMPTY_PAGE, name: 'foo', value: 'bar'}]); - - // Setup request interception. - await page.route('**/*', route => route.continue()); - const response = await page.reload(); - expect(response.status()).toBe(200); - }); - it('should show custom HTTP headers', async({page, server}) => { - await page.setExtraHTTPHeaders({ - foo: 'bar' - }); - await page.route('**/*', route => { - expect(route.request().headers()['foo']).toBe('bar'); - route.continue(); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/4337 - it('should work with redirect inside sync XHR', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - server.setRedirect('/logo.png', '/pptr.png'); - await page.route('**/*', route => route.continue()); - const status = await page.evaluate(async() => { - const request = new XMLHttpRequest(); - request.open('GET', '/logo.png', false); // `false` makes the request synchronous - request.send(null); - return request.status; - }); - expect(status).toBe(200); - }); - it('should work with custom referer headers', async({page, server}) => { - await page.setExtraHTTPHeaders({ 'referer': server.EMPTY_PAGE }); - await page.route('**/*', route => { - expect(route.request().headers()['referer']).toBe(server.EMPTY_PAGE); - route.continue(); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - }); - it('should be abortable', async({page, server}) => { - await page.route(/\.css$/, route => route.abort()); - let failed = false; - page.on('requestfailed', request => { - if (request.url().includes('.css')) - failed = true; - }); - const response = await page.goto(server.PREFIX + '/one-style.html'); - expect(response.ok()).toBe(true); - expect(response.request().failure()).toBe(null); - expect(failed).toBe(true); - }); - it('should be abortable with custom error codes', async({page, server}) => { - await page.route('**/*', route => route.abort('internetdisconnected')); - let failedRequest = null; - page.on('requestfailed', request => failedRequest = request); - await page.goto(server.EMPTY_PAGE).catch(e => {}); - expect(failedRequest).toBeTruthy(); - if (WEBKIT) - expect(failedRequest.failure().errorText).toBe('Request intercepted'); - else if (FFOX) - expect(failedRequest.failure().errorText).toBe('NS_ERROR_OFFLINE'); - else - expect(failedRequest.failure().errorText).toBe('net::ERR_INTERNET_DISCONNECTED'); - }); - it('should send referer', async({page, server}) => { - await page.setExtraHTTPHeaders({ - referer: 'http://google.com/' - }); - await page.route('**/*', route => route.continue()); - const [request] = await Promise.all([ - server.waitForRequest('/grid.html'), - page.goto(server.PREFIX + '/grid.html'), - ]); - expect(request.headers['referer']).toBe('http://google.com/'); - }); - it('should fail navigation when aborting main resource', async({page, server}) => { - await page.route('**/*', route => route.abort()); - let error = null; - await page.goto(server.EMPTY_PAGE).catch(e => error = e); - expect(error).toBeTruthy(); - if (WEBKIT) - expect(error.message).toContain('Request intercepted'); - else if (FFOX) - expect(error.message).toContain('NS_ERROR_FAILURE'); - else - expect(error.message).toContain('net::ERR_FAILED'); - }); - it('should not work with redirects', async({page, server}) => { - const intercepted = []; - await page.route('**/*', route => { - route.continue(); - intercepted.push(route.request()); - }); - server.setRedirect('/non-existing-page.html', '/non-existing-page-2.html'); - server.setRedirect('/non-existing-page-2.html', '/non-existing-page-3.html'); - server.setRedirect('/non-existing-page-3.html', '/non-existing-page-4.html'); - server.setRedirect('/non-existing-page-4.html', '/empty.html'); - - const response = await page.goto(server.PREFIX + '/non-existing-page.html'); - expect(response.status()).toBe(200); - expect(response.url()).toContain('empty.html'); - - expect(intercepted.length).toBe(1); - expect(intercepted[0].resourceType()).toBe('document'); - expect(intercepted[0].isNavigationRequest()).toBe(true); - expect(intercepted[0].url()).toContain('/non-existing-page.html'); - - const chain = []; - for (let r = response.request(); r; r = r.redirectedFrom()) { - chain.push(r); - expect(r.isNavigationRequest()).toBe(true); - } - expect(chain.length).toBe(5); - expect(chain[0].url()).toContain('/empty.html'); - expect(chain[1].url()).toContain('/non-existing-page-4.html'); - expect(chain[2].url()).toContain('/non-existing-page-3.html'); - expect(chain[3].url()).toContain('/non-existing-page-2.html'); - expect(chain[4].url()).toContain('/non-existing-page.html'); - for (let i = 0; i < chain.length; i++) - expect(chain[i].redirectedTo()).toBe(i ? chain[i - 1] : null); - }); - it('should work with redirects for subresources', async({page, server}) => { - const intercepted = []; - await page.route('**/*', route => { - route.continue(); - intercepted.push(route.request()); - }); - server.setRedirect('/one-style.css', '/two-style.css'); - server.setRedirect('/two-style.css', '/three-style.css'); - server.setRedirect('/three-style.css', '/four-style.css'); - server.setRoute('/four-style.css', (req, res) => res.end('body {box-sizing: border-box; }')); - - const response = await page.goto(server.PREFIX + '/one-style.html'); - expect(response.status()).toBe(200); - expect(response.url()).toContain('one-style.html'); - - expect(intercepted.length).toBe(2); - expect(intercepted[0].resourceType()).toBe('document'); - expect(intercepted[0].url()).toContain('one-style.html'); - - let r = intercepted[1]; - for (const url of ['/one-style.css', '/two-style.css', '/three-style.css', '/four-style.css']) { - expect(r.resourceType()).toBe('stylesheet'); - expect(r.url()).toContain(url); - r = r.redirectedTo(); - } - expect(r).toBe(null); - }); - it('should work with equal requests', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - let responseCount = 1; - server.setRoute('/zzz', (req, res) => res.end((responseCount++) * 11 + '')); - - let spinner = false; - // Cancel 2nd request. - await page.route('**/*', route => { - spinner ? route.abort() : route.continue(); - spinner = !spinner; - }); - const results = []; - for (let i = 0; i < 3; i++) - results.push(await page.evaluate(() => fetch('/zzz').then(response => response.text()).catch(e => 'FAILED'))); - expect(results).toEqual(['11', 'FAILED', '22']); - }); - it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => { - const requests = []; - await page.route('**/*', route => { - requests.push(route.request()); - route.continue(); - }); - const dataURL = 'data:text/html,
yo
'; - const response = await page.goto(dataURL); - expect(response).toBe(null); - expect(requests.length).toBe(0); - }); - it('should be able to fetch dataURL and not fire dataURL requests', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const requests = []; - await page.route('**/*', route => { - requests.push(route.request()); - route.continue(); - }); - const dataURL = 'data:text/html,
yo
'; - const text = await page.evaluate(url => fetch(url).then(r => r.text()), dataURL); - expect(text).toBe('
yo
'); - expect(requests.length).toBe(0); - }); - it('should navigate to URL with hash and and fire requests without hash', async({page, server}) => { - const requests = []; - await page.route('**/*', route => { - requests.push(route.request()); - route.continue(); - }); - const response = await page.goto(server.EMPTY_PAGE + '#hash'); - expect(response.status()).toBe(200); - expect(response.url()).toBe(server.EMPTY_PAGE); - expect(requests.length).toBe(1); - expect(requests[0].url()).toBe(server.EMPTY_PAGE); - }); - it('should work with encoded server', async({page, server}) => { - // The requestWillBeSent will report encoded URL, whereas interception will - // report URL as-is. @see crbug.com/759388 - await page.route('**/*', route => route.continue()); - const response = await page.goto(server.PREFIX + '/some nonexisting page'); - expect(response.status()).toBe(404); - }); - it('should work with badly encoded server', async({page, server}) => { - server.setRoute('/malformed?rnd=%911', (req, res) => res.end()); - await page.route('**/*', route => route.continue()); - const response = await page.goto(server.PREFIX + '/malformed?rnd=%911'); - expect(response.status()).toBe(200); - }); - it('should work with encoded server - 2', async({page, server}) => { - // The requestWillBeSent will report URL as-is, whereas interception will - // report encoded URL for stylesheet. @see crbug.com/759388 - const requests = []; - await page.route('**/*', route => { - route.continue(); - requests.push(route.request()); - }); - const response = await page.goto(`data:text/html,`); - expect(response).toBe(null); - expect(requests.length).toBe(1); - expect((await requests[0].response()).status()).toBe(404); - }); - it('should not throw "Invalid Interception Id" if the request was cancelled', async({page, server}) => { - await page.setContent(''); - let route = null; - await page.route('**/*', async r => route = r); - page.$eval('iframe', (frame, url) => frame.src = url, server.EMPTY_PAGE), - // Wait for request interception. - await page.waitForEvent('request'); - // Delete frame to cause request to be canceled. - await page.$eval('iframe', frame => frame.remove()); - let error = null; - await route.continue().catch(e => error = e); - expect(error).toBe(null); - }); - it('should intercept main resource during cross-process navigation', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - let intercepted = false; - await page.route(server.CROSS_PROCESS_PREFIX + '/empty.html', route => { - intercepted = true; - route.continue(); - }); - const response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); - expect(response.ok()).toBe(true); - expect(intercepted).toBe(true); - }); - it('should create a redirect', async({page, server}) => { - await page.goto(server.PREFIX + '/empty.html'); - await page.route('**/*', async(route, request) => { - if (request.url() !== server.PREFIX + '/redirect_this') - return route.continue(); - await route.fulfill({ - status: 301, - headers: { - 'location': '/empty.html', - } - }); - }); - - const text = await page.evaluate(async url => { - const data = await fetch(url); - return data.text(); - }, server.PREFIX + '/redirect_this'); - expect(text).toBe(''); - }); - - it('should support cors with GET', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.route('**/cars*', async (route, request) => { - const headers = request.url().endsWith('allow') ? { 'access-control-allow-origin': '*' } : {}; - await route.fulfill({ - contentType: 'application/json', - headers, - status: 200, - body: JSON.stringify(['electric', 'gas']), - }); - }); - { - // Should succeed - const resp = await page.evaluate(async () => { - const response = await fetch('https://example.com/cars?allow', { mode: 'cors' }); - return response.json(); - }); - expect(resp).toEqual(['electric', 'gas']); - } - { - // Should be rejected - const error = await page.evaluate(async () => { - const response = await fetch('https://example.com/cars?reject', { mode: 'cors' }); - return response.json(); - }).catch(e => e); - expect(error.message).toContain('failed'); - } - }); - - it('should support cors with POST', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.route('**/cars', async (route) => { - await route.fulfill({ - contentType: 'application/json', - headers: { 'Access-Control-Allow-Origin': '*' }, - status: 200, - body: JSON.stringify(['electric', 'gas']), - }); - }); - const resp = await page.evaluate(async () => { - const response = await fetch('https://example.com/cars', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors', - body: JSON.stringify({ 'number': 1 }) - }); - return response.json(); - }); - expect(resp).toEqual(['electric', 'gas']); - }); - - it('should support cors for different methods', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.route('**/cars', async (route, request) => { - await route.fulfill({ - contentType: 'application/json', - headers: { 'Access-Control-Allow-Origin': '*' }, - status: 200, - body: JSON.stringify([request.method(), 'electric', 'gas']), - }); - }); - // First POST - { - const resp = await page.evaluate(async () => { - const response = await fetch('https://example.com/cars', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors', - body: JSON.stringify({ 'number': 1 }) - }); - return response.json(); - }); - expect(resp).toEqual(['POST', 'electric', 'gas']); - } - // Then DELETE - { - const resp = await page.evaluate(async () => { - const response = await fetch('https://example.com/cars', { - method: 'DELETE', - headers: {}, - mode: 'cors', - body: '' - }); - return response.json(); - }); - expect(resp).toEqual(['DELETE', 'electric', 'gas']); - } - }); -}); - -describe('Request.continue', function() { - it('should work', async({page, server}) => { - await page.route('**/*', route => route.continue()); - await page.goto(server.EMPTY_PAGE); - }); - it('should amend HTTP headers', async({page, server}) => { - await page.route('**/*', route => { - const headers = Object.assign({}, route.request().headers()); - headers['FOO'] = 'bar'; - route.continue({ headers }); - }); - await page.goto(server.EMPTY_PAGE); - const [request] = await Promise.all([ - server.waitForRequest('/sleep.zzz'), - page.evaluate(() => fetch('/sleep.zzz')) - ]); - expect(request.headers['foo']).toBe('bar'); - }); - it('should amend method', async({page, server}) => { - const sRequest = server.waitForRequest('/sleep.zzz'); - await page.goto(server.EMPTY_PAGE); - await page.route('**/*', route => route.continue({ method: 'POST' })); - const [request] = await Promise.all([ - server.waitForRequest('/sleep.zzz'), - page.evaluate(() => fetch('/sleep.zzz')) - ]); - expect(request.method).toBe('POST'); - expect((await sRequest).method).toBe('POST'); - }); - it('should amend method on main request', async({page, server}) => { - const request = server.waitForRequest('/empty.html'); - await page.route('**/*', route => route.continue({ method: 'POST' })); - await page.goto(server.EMPTY_PAGE); - expect((await request).method).toBe('POST'); - }); - it('should amend post data', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.route('**/*', route => { - route.continue({ postData: 'doggo' }); - }); - const [serverRequest] = await Promise.all([ - server.waitForRequest('/sleep.zzz'), - page.evaluate(() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })) - ]); - expect((await serverRequest.postBody).toString('utf8')).toBe('doggo'); - }); - it('should amend utf8 post data', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.route('**/*', route => { - route.continue({ postData: 'пушкин' }); - }); - const [serverRequest] = await Promise.all([ - server.waitForRequest('/sleep.zzz'), - page.evaluate(() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })) - ]); - expect(serverRequest.method).toBe('POST'); - expect((await serverRequest.postBody).toString('utf8')).toBe('пушкин'); - }); - it('should amend longer post data', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.route('**/*', route => { - route.continue({ postData: 'doggo-is-longer-than-birdy' }); - }); - const [serverRequest] = await Promise.all([ - server.waitForRequest('/sleep.zzz'), - page.evaluate(() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })) - ]); - expect(serverRequest.method).toBe('POST'); - expect((await serverRequest.postBody).toString('utf8')).toBe('doggo-is-longer-than-birdy'); - }); - it('should amend binary post data', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const arr = Array.from(Array(256).keys()); - await page.route('**/*', route => { - route.continue({ postData: Buffer.from(arr) }); - }); - const [serverRequest] = await Promise.all([ - server.waitForRequest('/sleep.zzz'), - page.evaluate(() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })) - ]); - expect(serverRequest.method).toBe('POST'); - const buffer = await serverRequest.postBody; - expect(buffer.length).toBe(arr.length); - for (let i = 0; i < arr.length; ++i) - expect(arr[i]).toBe(buffer[i]); - }); -}); - -describe('Request.fulfill', function() { - it('should work', async({page, server}) => { - await page.route('**/*', route => { - route.fulfill({ - status: 201, - headers: { - foo: 'bar' - }, - contentType: 'text/html', - body: 'Yo, page!' - }); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(201); - expect(response.headers().foo).toBe('bar'); - expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); - }); - it('should work with status code 422', async({page, server}) => { - await page.route('**/*', route => { - route.fulfill({ - status: 422, - body: 'Yo, page!' - }); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(422); - expect(response.statusText()).toBe('Unprocessable Entity'); - expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); - }); - it.skip(FFOX && !HEADLESS)('should allow mocking binary responses', async({page, server}) => { - // Firefox headful produces a different image. - await page.route('**/*', route => { - const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png')); - route.fulfill({ - contentType: 'image/png', - body: imageBuffer - }); - }); - await page.evaluate(PREFIX => { - const img = document.createElement('img'); - img.src = PREFIX + '/does-not-exist.png'; - document.body.appendChild(img); - return new Promise(fulfill => img.onload = fulfill); - }, server.PREFIX); - const img = await page.$('img'); - expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); - }); - it.skip(FFOX && !HEADLESS)('should allow mocking svg with charset', async({page, server}) => { - // Firefox headful produces a different image. - await page.route('**/*', route => { - route.fulfill({ - contentType: 'image/svg+xml ; charset=utf-8', - body: '' - }); - }); - await page.evaluate(PREFIX => { - const img = document.createElement('img'); - img.src = PREFIX + '/does-not-exist.svg'; - document.body.appendChild(img); - return new Promise((f, r) => { img.onload = f; img.onerror = r; }); - }, server.PREFIX); - const img = await page.$('img'); - expect(await img.screenshot()).toBeGolden('mock-svg.png'); - }); - it('should work with file path', async({page, server}) => { - await page.route('**/*', route => route.fulfill({ contentType: 'shouldBeIgnored', path: path.join(__dirname, 'assets', 'pptr.png') })); - await page.evaluate(PREFIX => { - const img = document.createElement('img'); - img.src = PREFIX + '/does-not-exist.png'; - document.body.appendChild(img); - return new Promise(fulfill => img.onload = fulfill); - }, server.PREFIX); - const img = await page.$('img'); - expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); - }); - it('should stringify intercepted request response headers', async({page, server}) => { - await page.route('**/*', route => { - route.fulfill({ - status: 200, - headers: { - 'foo': true - }, - body: 'Yo, page!' - }); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(200); - const headers = response.headers(); - expect(headers.foo).toBe('true'); - expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); - }); - it('should not modify the headers sent to the server', async({page, server}) => { - await page.goto(server.PREFIX + '/empty.html'); - const interceptedRequests = []; - - //this is just to enable request interception, which disables caching in chromium - await page.route(server.PREFIX + '/unused'); - - server.setRoute('/something', (request, response) => { - interceptedRequests.push(request); - response.writeHead(200, { 'Access-Control-Allow-Origin': '*' }); - response.end('done'); - }); - - const text = await page.evaluate(async url => { - const data = await fetch(url); - return data.text(); - }, server.CROSS_PROCESS_PREFIX + '/something'); - expect(text).toBe('done'); - - let playwrightRequest; - await page.route(server.CROSS_PROCESS_PREFIX + '/something', (route, request) => { - playwrightRequest = request; - route.continue({ - headers: { - ...request.headers() - } - }); - }); - - const textAfterRoute = await page.evaluate(async url => { - const data = await fetch(url); - return data.text(); - }, server.CROSS_PROCESS_PREFIX + '/something'); - expect(textAfterRoute).toBe('done'); - - expect(interceptedRequests.length).toBe(2); - expect(interceptedRequests[1].headers).toEqual(interceptedRequests[0].headers); - }); - it('should include the origin header', async({page, server}) => { - await page.goto(server.PREFIX + '/empty.html'); - let interceptedRequest; - await page.route(server.CROSS_PROCESS_PREFIX + '/something', (route, request) => { - interceptedRequest = request; - route.fulfill({ - headers: { - 'Access-Control-Allow-Origin': '*', - }, - contentType: 'text/plain', - body: 'done' - }); - }); - - const text = await page.evaluate(async url => { - const data = await fetch(url); - return data.text(); - }, server.CROSS_PROCESS_PREFIX + '/something'); - expect(text).toBe('done'); - expect(interceptedRequest.headers()['origin']).toEqual(server.PREFIX); - }); -}); - -describe('Interception vs isNavigationRequest', () => { - it('should work with request interception', async({page, server}) => { - const requests = new Map(); - await page.route('**/*', route => { - requests.set(route.request().url().split('/').pop(), route.request()); - route.continue(); - }); - server.setRedirect('/rrredirect', '/frames/one-frame.html'); - await page.goto(server.PREFIX + '/rrredirect'); - expect(requests.get('rrredirect').isNavigationRequest()).toBe(true); - expect(requests.get('frame.html').isNavigationRequest()).toBe(true); - expect(requests.get('script.js').isNavigationRequest()).toBe(false); - expect(requests.get('style.css').isNavigationRequest()).toBe(false); - }); -}); - -describe('ignoreHTTPSErrors', function() { - it('should work with request interception', async({browser, httpsServer}) => { - const context = await browser.newContext({ ignoreHTTPSErrors: true }); - const page = await context.newPage(); - - await page.route('**/*', route => route.continue()); - const response = await page.goto(httpsServer.EMPTY_PAGE); - expect(response.status()).toBe(200); - await context.close(); - }); -}); - -describe('service worker', function() { - it('should intercept after a service worker', async({browser, page, server, context}) => { - await page.goto(server.PREFIX + '/serviceworkers/fetchdummy/sw.html'); - await page.evaluate(() => window.activationPromise); - - // Sanity check. - const swResponse = await page.evaluate(() => fetchDummy('foo')); - expect(swResponse).toBe('responseFromServiceWorker:foo'); - - await page.route('**/foo', route => { - const slash = route.request().url().lastIndexOf('/'); - const name = route.request().url().substring(slash + 1); - route.fulfill({ - status: 200, - contentType: 'text/css', - body: 'responseFromInterception:' + name - }); - }); - - // Page route is applied after service worker fetch event. - const swResponse2 = await page.evaluate(() => fetchDummy('foo')); - expect(swResponse2).toBe('responseFromServiceWorker:foo'); - - // Page route is not applied to service worker initiated fetch. - const nonInterceptedResponse = await page.evaluate(() => fetchDummy('passthrough')); - expect(nonInterceptedResponse).toBe('FAILURE: Not Found'); - }); -}); - -describe('glob', function() { - it('should work with glob', async() => { - expect(helper.globToRegex('**/*.js').test('https://localhost:8080/foo.js')).toBeTruthy(); - expect(helper.globToRegex('**/*.css').test('https://localhost:8080/foo.js')).toBeFalsy(); - expect(helper.globToRegex('*.js').test('https://localhost:8080/foo.js')).toBeFalsy(); - expect(helper.globToRegex('https://**/*.js').test('https://localhost:8080/foo.js')).toBeTruthy(); - expect(helper.globToRegex('http://localhost:8080/simple/path.js').test('http://localhost:8080/simple/path.js')).toBeTruthy(); - expect(helper.globToRegex('http://localhost:8080/?imple/path.js').test('http://localhost:8080/Simple/path.js')).toBeTruthy(); - expect(helper.globToRegex('**/{a,b}.js').test('https://localhost:8080/a.js')).toBeTruthy(); - expect(helper.globToRegex('**/{a,b}.js').test('https://localhost:8080/b.js')).toBeTruthy(); - expect(helper.globToRegex('**/{a,b}.js').test('https://localhost:8080/c.js')).toBeFalsy(); - - expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.jpg')).toBeTruthy(); - expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.jpeg')).toBeTruthy(); - expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.png')).toBeTruthy(); - expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.css')).toBeFalsy(); - }); -}); - -describe('regexp', function() { - it('should work with regular expression passed from a different context', async({page, server}) => { - const ctx = vm.createContext(); - const regexp = vm.runInContext('new RegExp("empty\\.html")', ctx); - let intercepted = false; - - await page.route(regexp, (route, request) => { - expect(route.request()).toBe(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(); - intercepted = true; - }); - - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - expect(intercepted).toBe(true); - }); -}); diff --git a/test/interception.spec.js b/test/interception.spec.js new file mode 100644 index 0000000000000..0cf6134fded3d --- /dev/null +++ b/test/interception.spec.js @@ -0,0 +1,114 @@ +/** + * 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 fs = require('fs'); +const path = require('path'); +const { helper } = require('../lib/helper'); +const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT, HEADLESS} = testOptions; + +it('should work with navigation', async({page, server}) => { + const requests = new Map(); + await page.route('**/*', route => { + requests.set(route.request().url().split('/').pop(), route.request()); + route.continue(); + }); + server.setRedirect('/rrredirect', '/frames/one-frame.html'); + await page.goto(server.PREFIX + '/rrredirect'); + expect(requests.get('rrredirect').isNavigationRequest()).toBe(true); + expect(requests.get('frame.html').isNavigationRequest()).toBe(true); + expect(requests.get('script.js').isNavigationRequest()).toBe(false); + expect(requests.get('style.css').isNavigationRequest()).toBe(false); +}); + +it('should work with ignoreHTTPSErrors', async({browser, httpsServer}) => { + const context = await browser.newContext({ ignoreHTTPSErrors: true }); + const page = await context.newPage(); + + await page.route('**/*', route => route.continue()); + const response = await page.goto(httpsServer.EMPTY_PAGE); + expect(response.status()).toBe(200); + await context.close(); +}); + +it('should intercept after a service worker', async({browser, page, server, context}) => { + await page.goto(server.PREFIX + '/serviceworkers/fetchdummy/sw.html'); + await page.evaluate(() => window.activationPromise); + + // Sanity check. + const swResponse = await page.evaluate(() => fetchDummy('foo')); + expect(swResponse).toBe('responseFromServiceWorker:foo'); + + await page.route('**/foo', route => { + const slash = route.request().url().lastIndexOf('/'); + const name = route.request().url().substring(slash + 1); + route.fulfill({ + status: 200, + contentType: 'text/css', + body: 'responseFromInterception:' + name + }); + }); + + // Page route is applied after service worker fetch event. + const swResponse2 = await page.evaluate(() => fetchDummy('foo')); + expect(swResponse2).toBe('responseFromServiceWorker:foo'); + + // Page route is not applied to service worker initiated fetch. + const nonInterceptedResponse = await page.evaluate(() => fetchDummy('passthrough')); + expect(nonInterceptedResponse).toBe('FAILURE: Not Found'); +}); + +it('should work with glob', async() => { + expect(helper.globToRegex('**/*.js').test('https://localhost:8080/foo.js')).toBeTruthy(); + expect(helper.globToRegex('**/*.css').test('https://localhost:8080/foo.js')).toBeFalsy(); + expect(helper.globToRegex('*.js').test('https://localhost:8080/foo.js')).toBeFalsy(); + expect(helper.globToRegex('https://**/*.js').test('https://localhost:8080/foo.js')).toBeTruthy(); + expect(helper.globToRegex('http://localhost:8080/simple/path.js').test('http://localhost:8080/simple/path.js')).toBeTruthy(); + expect(helper.globToRegex('http://localhost:8080/?imple/path.js').test('http://localhost:8080/Simple/path.js')).toBeTruthy(); + expect(helper.globToRegex('**/{a,b}.js').test('https://localhost:8080/a.js')).toBeTruthy(); + expect(helper.globToRegex('**/{a,b}.js').test('https://localhost:8080/b.js')).toBeTruthy(); + expect(helper.globToRegex('**/{a,b}.js').test('https://localhost:8080/c.js')).toBeFalsy(); + + expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.jpg')).toBeTruthy(); + expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.jpeg')).toBeTruthy(); + expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.png')).toBeTruthy(); + expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.css')).toBeFalsy(); +}); + +it('should work with regular expression passed from a different context', async({page, server}) => { + const ctx = vm.createContext(); + const regexp = vm.runInContext('new RegExp("empty\\.html")', ctx); + let intercepted = false; + + await page.route(regexp, (route, request) => { + expect(route.request()).toBe(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(); + intercepted = true; + }); + + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + expect(intercepted).toBe(true); +}); diff --git a/test/navigation.jest.js b/test/navigation.jest.js deleted file mode 100644 index 28319edfb52ad..0000000000000 --- a/test/navigation.jest.js +++ /dev/null @@ -1,1099 +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 path = require('path'); -const url = require('url'); -const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; - -describe('Page.goto', function() { - it('should work', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - expect(page.url()).toBe(server.EMPTY_PAGE); - }); - it('should work with file URL', async({page, server}) => { - const fileurl = url.pathToFileURL(path.join(__dirname, 'assets', 'frames', 'two-frames.html')).href; - await page.goto(fileurl); - expect(page.url().toLowerCase()).toBe(fileurl.toLowerCase()); - expect(page.frames().length).toBe(3); - }); - it('should use http for no protocol', async({page, server}) => { - await page.goto(server.EMPTY_PAGE.substring('http://'.length)); - expect(page.url()).toBe(server.EMPTY_PAGE); - }); - it('should work cross-process', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - expect(page.url()).toBe(server.EMPTY_PAGE); - - const url = server.CROSS_PROCESS_PREFIX + '/empty.html'; - let requestFrame; - page.on('request', r => { - if (r.url() === url) - requestFrame = r.frame(); - }); - const response = await page.goto(url); - expect(page.url()).toBe(url); - expect(response.frame()).toBe(page.mainFrame()); - expect(requestFrame).toBe(page.mainFrame()); - expect(response.url()).toBe(url); - }); - it('should capture iframe navigation request', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - expect(page.url()).toBe(server.EMPTY_PAGE); - - let requestFrame; - page.on('request', r => { - if (r.url() === server.PREFIX + '/frames/frame.html') - requestFrame = r.frame(); - }); - const response = await page.goto(server.PREFIX + '/frames/one-frame.html'); - expect(page.url()).toBe(server.PREFIX + '/frames/one-frame.html'); - expect(response.frame()).toBe(page.mainFrame()); - expect(response.url()).toBe(server.PREFIX + '/frames/one-frame.html'); - - expect(page.frames().length).toBe(2); - expect(requestFrame).toBe(page.frames()[1]); - }); - it('should capture cross-process iframe navigation request', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - expect(page.url()).toBe(server.EMPTY_PAGE); - - let requestFrame; - page.on('request', r => { - if (r.url() === server.CROSS_PROCESS_PREFIX + '/frames/frame.html') - requestFrame = r.frame(); - }); - const response = await page.goto(server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'); - expect(page.url()).toBe(server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'); - expect(response.frame()).toBe(page.mainFrame()); - expect(response.url()).toBe(server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'); - - expect(page.frames().length).toBe(2); - expect(requestFrame).toBe(page.frames()[1]); - }); - it('should work with anchor navigation', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - expect(page.url()).toBe(server.EMPTY_PAGE); - await page.goto(server.EMPTY_PAGE + '#foo'); - expect(page.url()).toBe(server.EMPTY_PAGE + '#foo'); - await page.goto(server.EMPTY_PAGE + '#bar'); - expect(page.url()).toBe(server.EMPTY_PAGE + '#bar'); - }); - it('should work with redirects', async({page, server}) => { - server.setRedirect('/redirect/1.html', '/redirect/2.html'); - server.setRedirect('/redirect/2.html', '/empty.html'); - const response = await page.goto(server.PREFIX + '/redirect/1.html'); - expect(response.status()).toBe(200); - expect(page.url()).toBe(server.EMPTY_PAGE); - }); - it('should navigate to about:blank', async({page, server}) => { - const response = await page.goto('about:blank'); - expect(response).toBe(null); - }); - it('should return response when page changes its URL after load', async({page, server}) => { - const response = await page.goto(server.PREFIX + '/historyapi.html'); - expect(response.status()).toBe(200); - }); - it('should work with subframes return 204', async({page, server}) => { - server.setRoute('/frames/frame.html', (req, res) => { - res.statusCode = 204; - res.end(); - }); - await page.goto(server.PREFIX + '/frames/one-frame.html'); - }); - it('should fail when server returns 204', async({page, server}) => { - // Webkit just loads an empty page. - server.setRoute('/empty.html', (req, res) => { - res.statusCode = 204; - res.end(); - }); - let error = null; - await page.goto(server.EMPTY_PAGE).catch(e => error = e); - expect(error).not.toBe(null); - if (CHROMIUM) - expect(error.message).toContain('net::ERR_ABORTED'); - else if (WEBKIT) - expect(error.message).toContain('Aborted: 204 No Content'); - else - expect(error.message).toContain('NS_BINDING_ABORTED'); - }); - it('should navigate to empty page with domcontentloaded', async({page, server}) => { - const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'domcontentloaded'}); - expect(response.status()).toBe(200); - }); - it('should work when page calls history API in beforeunload', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => { - window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false); - }); - const response = await page.goto(server.PREFIX + '/grid.html'); - expect(response.status()).toBe(200); - }); - it('should fail when navigating to bad url', async({page, server}) => { - let error = null; - await page.goto('asdfasdf').catch(e => error = e); - if (CHROMIUM || WEBKIT) - expect(error.message).toContain('Cannot navigate to invalid URL'); - else - expect(error.message).toContain('Invalid url'); - }); - it('should fail when navigating to bad SSL', async({page, httpsServer}) => { - // Make sure that network events do not emit 'undefined'. - // @see https://crbug.com/750469 - page.on('request', request => expect(request).toBeTruthy()); - page.on('requestfinished', request => expect(request).toBeTruthy()); - page.on('requestfailed', request => expect(request).toBeTruthy()); - let error = null; - await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); - expectSSLError(error.message); - }); - it('should fail when navigating to bad SSL after redirects', async({page, server, httpsServer}) => { - server.setRedirect('/redirect/1.html', '/redirect/2.html'); - server.setRedirect('/redirect/2.html', '/empty.html'); - let error = null; - await page.goto(httpsServer.PREFIX + '/redirect/1.html').catch(e => error = e); - expectSSLError(error.message); - }); - it('should not crash when navigating to bad SSL after a cross origin navigation', async({page, server, httpsServer}) => { - await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); - await page.goto(httpsServer.EMPTY_PAGE).catch(e => void 0); - }); - it('should not throw if networkidle0 is passed as an option', async({page, server}) => { - let error = null; - await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle0'}); - }); - it('should throw if networkidle2 is passed as an option', async({page, server}) => { - let error = null; - await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle2'}).catch(err => error = err); - expect(error.message).toContain(`waitUntil: expected one of (load|domcontentloaded|networkidle)`); - }); - it('should fail when main resources failed to load', async({page, server}) => { - let error = null; - await page.goto('http://localhost:44123/non-existing-url').catch(e => error = e); - if (CHROMIUM) - expect(error.message).toContain('net::ERR_CONNECTION_REFUSED'); - else if (WEBKIT && WIN) - expect(error.message).toContain(`Couldn\'t connect to server`); - else if (WEBKIT) - expect(error.message).toContain('Could not connect'); - else - expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED'); - }); - it('should fail when exceeding maximum navigation timeout', async({page, server, playwright}) => { - // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); - let error = null; - await page.goto(server.PREFIX + '/empty.html', {timeout: 1}).catch(e => error = e); - expect(error.message).toContain('page.goto: Timeout 1ms exceeded.'); - expect(error.message).toContain(server.PREFIX + '/empty.html'); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should fail when exceeding default maximum navigation timeout', async({page, server, playwright}) => { - // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); - let error = null; - page.context().setDefaultNavigationTimeout(2); - page.setDefaultNavigationTimeout(1); - await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - expect(error.message).toContain('page.goto: Timeout 1ms exceeded.'); - expect(error.message).toContain(server.PREFIX + '/empty.html'); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should fail when exceeding browser context navigation timeout', async({page, server, playwright}) => { - // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); - let error = null; - page.context().setDefaultNavigationTimeout(2); - await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - expect(error.message).toContain('page.goto: Timeout 2ms exceeded.'); - expect(error.message).toContain(server.PREFIX + '/empty.html'); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should fail when exceeding default maximum timeout', async({page, server, playwright}) => { - // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); - let error = null; - page.context().setDefaultTimeout(2); - page.setDefaultTimeout(1); - await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - expect(error.message).toContain('page.goto: Timeout 1ms exceeded.'); - expect(error.message).toContain(server.PREFIX + '/empty.html'); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should fail when exceeding browser context timeout', async({page, server, playwright}) => { - // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); - let error = null; - page.context().setDefaultTimeout(2); - await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - expect(error.message).toContain('page.goto: Timeout 2ms exceeded.'); - expect(error.message).toContain(server.PREFIX + '/empty.html'); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should prioritize default navigation timeout over default timeout', async({page, server, playwright}) => { - // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); - let error = null; - page.setDefaultTimeout(0); - page.setDefaultNavigationTimeout(1); - await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - expect(error.message).toContain('page.goto: Timeout 1ms exceeded.'); - expect(error.message).toContain(server.PREFIX + '/empty.html'); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should disable timeout when its set to 0', async({page, server}) => { - let error = null; - let loaded = false; - page.once('load', () => loaded = true); - await page.goto(server.PREFIX + '/grid.html', {timeout: 0, waitUntil: 'load'}).catch(e => error = e); - expect(error).toBe(null); - expect(loaded).toBe(true); - }); - it('should fail when replaced by another navigation', async({page, server}) => { - let anotherPromise; - server.setRoute('/empty.html', (req, res) => { - anotherPromise = page.goto(server.PREFIX + '/one-style.html'); - // Hang request to empty.html. - }); - const error = await page.goto(server.PREFIX + '/empty.html').catch(e => e); - await anotherPromise; - if (CHROMIUM) - expect(error.message).toContain('net::ERR_ABORTED'); - else if (WEBKIT) - expect(error.message).toContain('cancelled'); - else - expect(error.message).toContain('NS_BINDING_ABORTED'); - }); - it('should work when navigating to valid url', async({page, server}) => { - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - }); - it('should work when navigating to data url', async({page, server}) => { - const response = await page.goto('data:text/html,hello'); - expect(response).toBe(null); - }); - it('should work when navigating to 404', async({page, server}) => { - const response = await page.goto(server.PREFIX + '/not-found'); - expect(response.ok()).toBe(false); - expect(response.status()).toBe(404); - }); - it('should return last response in redirect chain', async({page, server}) => { - server.setRedirect('/redirect/1.html', '/redirect/2.html'); - server.setRedirect('/redirect/2.html', '/redirect/3.html'); - server.setRedirect('/redirect/3.html', server.EMPTY_PAGE); - const response = await page.goto(server.PREFIX + '/redirect/1.html'); - expect(response.ok()).toBe(true); - expect(response.url()).toBe(server.EMPTY_PAGE); - }); - it('should not leak listeners during navigation', async({page, server}) => { - let warning = null; - const warningHandler = w => warning = w; - process.on('warning', warningHandler); - for (let i = 0; i < 20; ++i) - await page.goto(server.EMPTY_PAGE); - process.removeListener('warning', warningHandler); - expect(warning).toBe(null); - }); - it('should not leak listeners during bad navigation', async({page, server}) => { - let warning = null; - const warningHandler = w => warning = w; - process.on('warning', warningHandler); - for (let i = 0; i < 20; ++i) - await page.goto('asdf').catch(e => {/* swallow navigation error */}); - process.removeListener('warning', warningHandler); - expect(warning).toBe(null); - }); - it('should not leak listeners during navigation of 20 pages', async({page, context, server}) => { - let warning = null; - const warningHandler = w => warning = w; - process.on('warning', warningHandler); - const pages = await Promise.all([...Array(20)].map(() => context.newPage())); - await Promise.all(pages.map(page => page.goto(server.EMPTY_PAGE))); - await Promise.all(pages.map(page => page.close())); - process.removeListener('warning', warningHandler); - expect(warning).toBe(null); - }); - it('should not leak listeners during 20 waitForNavigation', async({page, context, server}) => { - let warning = null; - const warningHandler = w => warning = w; - process.on('warning', warningHandler); - const promises = [...Array(20)].map(() => page.waitForNavigation()); - await page.goto(server.EMPTY_PAGE); - await Promise.all(promises); - process.removeListener('warning', warningHandler); - expect(warning).toBe(null); - }); - it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => { - const requests = []; - page.on('request', request => requests.push(request)); - const dataURL = 'data:text/html,
yo
'; - const response = await page.goto(dataURL); - expect(response).toBe(null); - expect(requests.length).toBe(0); - }); - it('should navigate to URL with hash and fire requests without hash', async({page, server}) => { - const requests = []; - page.on('request', request => requests.push(request)); - const response = await page.goto(server.EMPTY_PAGE + '#hash'); - expect(response.status()).toBe(200); - expect(response.url()).toBe(server.EMPTY_PAGE); - expect(requests.length).toBe(1); - expect(requests[0].url()).toBe(server.EMPTY_PAGE); - }); - it('should work with self requesting page', async({page, server}) => { - const response = await page.goto(server.PREFIX + '/self-request.html'); - expect(response.status()).toBe(200); - expect(response.url()).toContain('self-request.html'); - }); - it('should fail when navigating and show the url at the error message', async function({page, server, httpsServer}) { - const url = httpsServer.PREFIX + '/redirect/1.html'; - let error = null; - try { - await page.goto(url); - } catch (e) { - error = e; - } - expect(error.message).toContain(url); - }); - it('should be able to navigate to a page controlled by service worker', async({page, server}) => { - await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html'); - await page.evaluate(() => window.activationPromise); - await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html'); - }); - it('should send referer', async({page, server}) => { - const [request1, request2] = await Promise.all([ - server.waitForRequest('/grid.html'), - server.waitForRequest('/digits/1.png'), - page.goto(server.PREFIX + '/grid.html', { - referer: 'http://google.com/', - }), - ]); - expect(request1.headers['referer']).toBe('http://google.com/'); - // Make sure subresources do not inherit referer. - expect(request2.headers['referer']).toBe(server.PREFIX + '/grid.html'); - expect(page.url()).toBe(server.PREFIX + '/grid.html'); - }); - it('should reject referer option when setExtraHTTPHeaders provides referer', async({page, server}) => { - await page.setExtraHTTPHeaders({ 'referer': 'http://microsoft.com/' }); - let error; - await page.goto(server.PREFIX + '/grid.html', { - referer: 'http://google.com/', - }).catch(e => error = e); - expect(error.message).toContain('"referer" is already specified as extra HTTP header'); - expect(error.message).toContain(server.PREFIX + '/grid.html'); - }); - it('should override referrer-policy', async({page, server}) => { - server.setRoute('/grid.html', (req, res) => { - res.setHeader('Referrer-Policy', 'no-referrer'); - server.serveFile(req, res, '/grid.html'); - }); - const [request1, request2] = await Promise.all([ - server.waitForRequest('/grid.html'), - server.waitForRequest('/digits/1.png'), - page.goto(server.PREFIX + '/grid.html', { - referer: 'http://microsoft.com/', - }), - ]); - expect(request1.headers['referer']).toBe('http://microsoft.com/'); - // Make sure subresources do not inherit referer. - expect(request2.headers['referer']).toBe(undefined); - expect(page.url()).toBe(server.PREFIX + '/grid.html'); - }); - it('should fail when canceled by another navigation', async({page, server}) => { - server.setRoute('/one-style.html', (req, res) => {}); - const failed = page.goto(server.PREFIX + '/one-style.html').catch(e => e); - await server.waitForRequest('/one-style.html'); - await page.goto(server.PREFIX + '/empty.html'); - const error = await failed; - expect(error.message).toBeTruthy(); - }); - it.skip(true)('extraHttpHeaders should be pushed to provisional page', async({page, server}) => { - // This test is flaky, because we cannot await page.setExtraHTTPHeaders. - // We need a way to test our implementation by more than just public api. - await page.goto(server.EMPTY_PAGE); - const pagePath = '/one-style.html'; - server.setRoute(pagePath, async (req, res) => { - page.setExtraHTTPHeaders({ foo: 'bar' }); - server.serveFile(req, res, pagePath); - }); - const [htmlReq, cssReq] = await Promise.all([ - server.waitForRequest(pagePath), - server.waitForRequest('/one-style.css'), - page.goto(server.CROSS_PROCESS_PREFIX + pagePath) - ]); - expect(htmlReq.headers['foo']).toBe(undefined); - expect(cssReq.headers['foo']).toBe('bar'); - }); - - describe('network idle', function() { - it('should navigate to empty page with networkidle', async({page, server}) => { - const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' }); - expect(response.status()).toBe(200); - }); - - /** - * @param {import('../index').Frame} frame - * @param {TestServer} server - * @param {() => Promise} action - * @param {boolean} isSetContent - */ - async function networkIdleTest(frame, server, action, isSetContent) { - const finishResponse = response => { - response.statusCode = 404; - response.end(`File not found`); - }; - const waitForRequest = suffix => { - return Promise.all([ - server.waitForRequest(suffix), - frame._page.waitForRequest(server.PREFIX + suffix), - ]); - } - let responses = {}; - // Hold on to a bunch of requests without answering. - server.setRoute('/fetch-request-a.js', (req, res) => responses.a = res); - const firstFetchResourceRequested = waitForRequest('/fetch-request-a.js'); - server.setRoute('/fetch-request-d.js', (req, res) => responses.d = res); - const secondFetchResourceRequested = waitForRequest('/fetch-request-d.js'); - - const waitForLoadPromise = isSetContent ? Promise.resolve() : frame.waitForNavigation({ waitUntil: 'load' }); - - // Navigate to a page which loads immediately and then does a bunch of - // requests via javascript's fetch method. - const actionPromise = action(); - - // Track when the action gets completed. - let actionFinished = false; - actionPromise.then(() => actionFinished = true); - - // Wait for the frame's 'load' event. - await waitForLoadPromise; - expect(actionFinished).toBe(false); - - // Wait for the initial resource to be requested. - await firstFetchResourceRequested; - expect(actionFinished).toBe(false); - - expect(responses.a).toBeTruthy(); - let timer; - let timerTriggered = false; - // Finishing response should trigger the second round. - finishResponse(responses.a); - - // Wait for the second round to be requested. - await secondFetchResourceRequested; - expect(actionFinished).toBe(false); - // Finishing the last response should trigger networkidle. - timer = setTimeout(() => timerTriggered = true, 500); - finishResponse(responses.d); - - const response = await actionPromise; - clearTimeout(timer); - expect(timerTriggered).toBe(true); - if (!isSetContent) - expect(response.ok()).toBe(true); - } - - it('should wait for networkidle to succeed navigation', async({page, server}) => { - await networkIdleTest(page.mainFrame(), server, () => { - return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle' }); - }); - }); - it('should wait for networkidle to succeed navigation with request from previous navigation', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - server.setRoute('/foo.js', () => {}); - await page.setContent(``); - await networkIdleTest(page.mainFrame(), server, () => { - return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle' }); - }); - }); - it('should wait for networkidle in waitForNavigation', async({page, server}) => { - await networkIdleTest(page.mainFrame(), server, () => { - const promise = page.waitForNavigation({ waitUntil: 'networkidle' }); - page.goto(server.PREFIX + '/networkidle.html'); - return promise; - }); - }); - it('should wait for networkidle in setContent', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await networkIdleTest(page.mainFrame(), server, () => { - return page.setContent(``, { waitUntil: 'networkidle' }); - }, true); - }); - it('should wait for networkidle in setContent with request from previous navigation', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - server.setRoute('/foo.js', () => {}); - await page.setContent(``); - await networkIdleTest(page.mainFrame(), server, () => { - return page.setContent(``, { waitUntil: 'networkidle' }); - }, true); - }); - it('should wait for networkidle when navigating iframe', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = page.mainFrame().childFrames()[0]; - await networkIdleTest(frame, server, () => frame.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle' })); - }); - it('should wait for networkidle in setContent from the child frame', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await networkIdleTest(page.mainFrame(), server, () => { - return page.setContent(``, { waitUntil: 'networkidle' }); - }, true); - }); - it('should wait for networkidle from the child frame', async({page, server}) => { - await networkIdleTest(page.mainFrame(), server, () => { - return page.goto(server.PREFIX + '/networkidle-frame.html', { waitUntil: 'networkidle' }); - }); - }); - }); -}); - -describe('Page.waitForNavigation', function() { - it('should work', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [response] = await Promise.all([ - page.waitForNavigation(), - page.evaluate(url => window.location.href = url, server.PREFIX + '/grid.html') - ]); - expect(response.ok()).toBe(true); - expect(response.url()).toContain('grid.html'); - }); - it('should respect timeout', async({page, server}) => { - const promise = page.waitForNavigation({ url: '**/frame.html', timeout: 5000 }); - await page.goto(server.EMPTY_PAGE); - const error = await promise.catch(e => e); - expect(error.message).toContain('page.waitForNavigation: Timeout 5000ms exceeded.'); - expect(error.message).toContain('waiting for navigation to "**/frame.html" until "load"'); - expect(error.message).toContain(`navigated to "${server.EMPTY_PAGE}"`); - }); - it('should work with both domcontentloaded and load', async({page, server}) => { - let response = null; - server.setRoute('/one-style.css', (req, res) => response = res); - const navigationPromise = page.goto(server.PREFIX + '/one-style.html'); - const domContentLoadedPromise = page.waitForNavigation({ - waitUntil: 'domcontentloaded' - }); - - let bothFired = false; - const bothFiredPromise = Promise.all([ - page.waitForNavigation({ waitUntil: 'load' }), - domContentLoadedPromise - ]).then(() => bothFired = true); - - await server.waitForRequest('/one-style.css'); - await domContentLoadedPromise; - expect(bothFired).toBe(false); - response.end(); - await bothFiredPromise; - await navigationPromise; - }); - it('should work with clicking on anchor links', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent(`foobar`); - const [response] = await Promise.all([ - page.waitForNavigation(), - page.click('a'), - ]); - expect(response).toBe(null); - expect(page.url()).toBe(server.EMPTY_PAGE + '#foobar'); - }); - it('should work with clicking on links which do not commit navigation', async({page, server, httpsServer}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent(`foobar`); - const [error] = await Promise.all([ - page.waitForNavigation().catch(e => e), - page.click('a'), - ]); - expectSSLError(error.message); - }); - it('should work with history.pushState()', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent(` - SPA - - `); - const [response] = await Promise.all([ - page.waitForNavigation(), - page.click('a'), - ]); - expect(response).toBe(null); - expect(page.url()).toBe(server.PREFIX + '/wow.html'); - }); - it('should work with history.replaceState()', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent(` - SPA - - `); - const [response] = await Promise.all([ - page.waitForNavigation(), - page.click('a'), - ]); - expect(response).toBe(null); - expect(page.url()).toBe(server.PREFIX + '/replaced.html'); - }); - it('should work with DOM history.back()/history.forward()', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent(` - back - forward - - `); - expect(page.url()).toBe(server.PREFIX + '/second.html'); - const [backResponse] = await Promise.all([ - page.waitForNavigation(), - page.click('a#back'), - ]); - expect(backResponse).toBe(null); - expect(page.url()).toBe(server.PREFIX + '/first.html'); - const [forwardResponse] = await Promise.all([ - page.waitForNavigation(), - page.click('a#forward'), - ]); - expect(forwardResponse).toBe(null); - expect(page.url()).toBe(server.PREFIX + '/second.html'); - }); - it('should work when subframe issues window.stop()', async({page, server}) => { - server.setRoute('/frames/style.css', (req, res) => {}); - const navigationPromise = page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = await new Promise(f => page.once('frameattached', f)); - await new Promise(fulfill => page.on('framenavigated', f => { - if (f === frame) - fulfill(); - })); - await Promise.all([ - frame.evaluate(() => window.stop()), - navigationPromise - ]); - }); - it('should work with url match', async({page, server}) => { - let response1 = null; - const response1Promise = page.waitForNavigation({ url: /one-style\.html/ }).then(response => response1 = response); - let response2 = null; - const response2Promise = page.waitForNavigation({ url: /\/frame.html/ }).then(response => response2 = response); - let response3 = null; - const response3Promise = page.waitForNavigation({ url: url => url.searchParams.get('foo') === 'bar' }).then(response => response3 = response); - expect(response1).toBe(null); - expect(response2).toBe(null); - expect(response3).toBe(null); - await page.goto(server.EMPTY_PAGE); - expect(response1).toBe(null); - expect(response2).toBe(null); - expect(response3).toBe(null); - await page.goto(server.PREFIX + '/frame.html'); - expect(response1).toBe(null); - await response2Promise; - expect(response2).not.toBe(null); - expect(response3).toBe(null); - await page.goto(server.PREFIX + '/one-style.html'); - await response1Promise; - expect(response1).not.toBe(null); - expect(response2).not.toBe(null); - expect(response3).toBe(null); - await page.goto(server.PREFIX + '/frame.html?foo=bar'); - await response3Promise; - expect(response1).not.toBe(null); - expect(response2).not.toBe(null); - expect(response3).not.toBe(null); - await page.goto(server.PREFIX + '/empty.html'); - expect(response1.url()).toBe(server.PREFIX + '/one-style.html'); - expect(response2.url()).toBe(server.PREFIX + '/frame.html'); - expect(response3.url()).toBe(server.PREFIX + '/frame.html?foo=bar'); - }); - it('should work with url match for same document navigations', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - let resolved = false; - const waitPromise = page.waitForNavigation({ url: /third\.html/ }).then(() => resolved = true); - expect(resolved).toBe(false); - await page.evaluate(() => { - history.pushState({}, '', '/first.html'); - }); - expect(resolved).toBe(false); - await page.evaluate(() => { - history.pushState({}, '', '/second.html'); - }); - expect(resolved).toBe(false); - await page.evaluate(() => { - history.pushState({}, '', '/third.html'); - }); - await waitPromise; - expect(resolved).toBe(true); - }); - it('should work for cross-process navigations', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const waitPromise = page.waitForNavigation({waitUntil: 'domcontentloaded'}); - const url = server.CROSS_PROCESS_PREFIX + '/empty.html'; - const gotoPromise = page.goto(url); - const response = await waitPromise; - expect(response.url()).toBe(url); - expect(page.url()).toBe(url); - expect(await page.evaluate('document.location.href')).toBe(url); - await gotoPromise; - }); -}); - -describe('Page.waitForLoadState', () => { - it('should pick up ongoing navigation', async({page, server}) => { - let response = null; - server.setRoute('/one-style.css', (req, res) => response = res); - await Promise.all([ - server.waitForRequest('/one-style.css'), - page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}), - ]); - const waitPromise = page.waitForLoadState(); - response.statusCode = 404; - response.end('Not found'); - await waitPromise; - }); - it('should respect timeout', async({page, server}) => { - server.setRoute('/one-style.css', (req, res) => response = res); - await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); - const error = await page.waitForLoadState('load', { timeout: 1 }).catch(e => e); - expect(error.message).toContain('page.waitForLoadState: Timeout 1ms exceeded.'); - }); - it('should resolve immediately if loaded', async({page, server}) => { - await page.goto(server.PREFIX + '/one-style.html'); - await page.waitForLoadState(); - }); - it('should throw for bad state', async({page, server}) => { - await page.goto(server.PREFIX + '/one-style.html'); - const error = await page.waitForLoadState('bad').catch(e => e); - expect(error.message).toContain(`state: expected one of (load|domcontentloaded|networkidle)`); - }); - it('should resolve immediately if load state matches', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - server.setRoute('/one-style.css', (req, res) => response = res); - await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); - await page.waitForLoadState('domcontentloaded'); - }); - it('should work with pages that have loaded before being connected to', async({page, context, server}) => { - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window._popup = window.open(document.location.href)), - ]); - // The url is about:blank in FF. - // expect(popup.url()).toBe(server.EMPTY_PAGE); - await popup.waitForLoadState(); - expect(popup.url()).toBe(server.EMPTY_PAGE); - }); - it('should wait for load state of empty url popup', async({browser, page}) => { - const [popup, readyState] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => { - const popup = window.open(''); - return popup.document.readyState; - }), - ]); - await popup.waitForLoadState(); - expect(readyState).toBe(FFOX ? 'uninitialized' : 'complete'); - expect(await popup.evaluate(() => document.readyState)).toBe(FFOX ? 'uninitialized' : 'complete'); - }); - it('should wait for load state of about:blank popup ', async({browser, page}) => { - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.open('about:blank') && 1), - ]); - await popup.waitForLoadState(); - expect(await popup.evaluate(() => document.readyState)).toBe('complete'); - }); - it('should wait for load state of about:blank popup with noopener ', async({browser, page}) => { - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.open('about:blank', null, 'noopener') && 1), - ]); - await popup.waitForLoadState(); - expect(await popup.evaluate(() => document.readyState)).toBe('complete'); - }); - it('should wait for load state of popup with network url ', async({browser, page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window.open(url) && 1, server.EMPTY_PAGE), - ]); - await popup.waitForLoadState(); - expect(await popup.evaluate(() => document.readyState)).toBe('complete'); - }); - it('should wait for load state of popup with network url and noopener ', async({browser, page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window.open(url, null, 'noopener') && 1, server.EMPTY_PAGE), - ]); - await popup.waitForLoadState(); - expect(await popup.evaluate(() => document.readyState)).toBe('complete'); - }); - it('should work with clicking target=_blank', async({browser, page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.click('a'), - ]); - await popup.waitForLoadState(); - expect(await popup.evaluate(() => document.readyState)).toBe('complete'); - }); - it('should wait for load state of newPage', async({browser, context, page, server}) => { - const [newPage] = await Promise.all([ - context.waitForEvent('page'), - context.newPage(), - ]); - await newPage.waitForLoadState(); - expect(await newPage.evaluate(() => document.readyState)).toBe('complete'); - }); - it('should resolve after popup load', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - // Stall the 'load' by delaying css. - let cssResponse; - server.setRoute('/one-style.css', (req, res) => cssResponse = res); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - server.waitForRequest('/one-style.css'), - page.evaluate(url => window.popup = window.open(url), server.PREFIX + '/one-style.html'), - ]); - let resolved = false; - const loadSatePromise = popup.waitForLoadState().then(() => resolved = true); - // Round trips! - for (let i = 0; i < 5; i++) - await page.evaluate('window'); - expect(resolved).toBe(false); - cssResponse.end(''); - await loadSatePromise; - expect(resolved).toBe(true); - expect(popup.url()).toBe(server.PREFIX + '/one-style.html'); - await context.close(); - }); -}); - -describe('Page.goBack', function() { - it('should work', async({page, server}) => { - expect(await page.goBack()).toBe(null); - - await page.goto(server.EMPTY_PAGE); - await page.goto(server.PREFIX + '/grid.html'); - - let response = await page.goBack(); - expect(response.ok()).toBe(true); - expect(response.url()).toContain(server.EMPTY_PAGE); - - response = await page.goForward(); - expect(response.ok()).toBe(true); - expect(response.url()).toContain('/grid.html'); - - response = await page.goForward(); - expect(response).toBe(null); - }); - it('should work with HistoryAPI', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => { - history.pushState({}, '', '/first.html'); - history.pushState({}, '', '/second.html'); - }); - expect(page.url()).toBe(server.PREFIX + '/second.html'); - - await page.goBack(); - expect(page.url()).toBe(server.PREFIX + '/first.html'); - await page.goBack(); - expect(page.url()).toBe(server.EMPTY_PAGE); - await page.goForward(); - expect(page.url()).toBe(server.PREFIX + '/first.html'); - }); - it.fail(WEBKIT && MAC)('should work for file urls', async ({page, server}) => { - // WebKit embedder fails to go back/forward to the file url. - const url1 = WIN - ? 'file:///' + path.join(ASSETS_DIR, 'empty.html').replace(/\\/g, '/') - : 'file://' + path.join(ASSETS_DIR, 'empty.html'); - const url2 = server.EMPTY_PAGE; - await page.goto(url1); - await page.setContent(`url2`); - expect(page.url().toLowerCase()).toBe(url1.toLowerCase()); - - await page.click('a'); - expect(page.url()).toBe(url2); - - await page.goBack(); - expect(page.url().toLowerCase()).toBe(url1.toLowerCase()); - // Should be able to evaluate in the new context, and - // not reach for the old cross-process one. - expect(await page.evaluate(() => window.scrollX)).toBe(0); - // Should be able to screenshot. - await page.screenshot(); - - await page.goForward(); - expect(page.url()).toBe(url2); - expect(await page.evaluate(() => window.scrollX)).toBe(0); - await page.screenshot(); - }); -}); - -describe('Frame.goto', function() { - it('should navigate subframes', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - expect(page.frames()[0].url()).toContain('/frames/one-frame.html'); - expect(page.frames()[1].url()).toContain('/frames/frame.html'); - - const response = await page.frames()[1].goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - expect(response.frame()).toBe(page.frames()[1]); - }); - it('should reject when frame detaches', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - - server.setRoute('/empty.html', () => {}); - const navigationPromise = page.frames()[1].goto(server.EMPTY_PAGE).catch(e => e); - await server.waitForRequest('/empty.html'); - - await page.$eval('iframe', frame => frame.remove()); - const error = await navigationPromise; - expect(error.message).toContain('frame was detached'); - }); - it('should continue after client redirect', async({page, server}) => { - server.setRoute('/frames/script.js', () => {}); - const url = server.PREFIX + '/frames/child-redirect.html'; - const error = await page.goto(url, { timeout: 5000, waitUntil: 'networkidle' }).catch(e => e); - expect(error.message).toContain('page.goto: Timeout 5000ms exceeded.'); - expect(error.message).toContain(`navigating to "${url}", waiting until "networkidle"`); - }); - it('should return matching responses', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - // Attach three frames. - const frames = [ - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE), - await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE), - await utils.attachFrame(page, 'frame3', server.EMPTY_PAGE), - ]; - const serverResponses = []; - server.setRoute('/0.html', (req, res) => serverResponses.push(res)); - server.setRoute('/1.html', (req, res) => serverResponses.push(res)); - server.setRoute('/2.html', (req, res) => serverResponses.push(res)); - const navigations = []; - for (let i = 0; i < 3; ++i) { - navigations.push(frames[i].goto(server.PREFIX + '/' + i + '.html')); - await server.waitForRequest('/' + i + '.html'); - } - // Respond from server out-of-order. - const serverResponseTexts = ['AAA', 'BBB', 'CCC']; - for (const i of [1, 2, 0]) { - serverResponses[i].end(serverResponseTexts[i]); - const response = await navigations[i]; - expect(response.frame()).toBe(frames[i]); - expect(await response.text()).toBe(serverResponseTexts[i]); - } - }); -}); - -describe('Frame.waitForNavigation', function() { - it('should work', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = page.frames()[1]; - const [response] = await Promise.all([ - frame.waitForNavigation(), - frame.evaluate(url => window.location.href = url, server.PREFIX + '/grid.html') - ]); - expect(response.ok()).toBe(true); - expect(response.url()).toContain('grid.html'); - expect(response.frame()).toBe(frame); - expect(page.url()).toContain('/frames/one-frame.html'); - }); - it('should fail when frame detaches', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = page.frames()[1]; - server.setRoute('/empty.html', () => {}); - let error = null; - await Promise.all([ - frame.waitForNavigation().catch(e => error = e), - frame.evaluate('window.location = "/empty.html"'), - page.evaluate('setTimeout(() => document.querySelector("iframe").remove())'), - ]).catch(e => error = e); - expect(error.message).toContain('waiting for navigation until "load"'); - expect(error.message).toContain('frame was detached'); - }); -}); - -describe('Frame.waitForLoadState', function() { - it('should work', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = page.frames()[1]; - - const requestPromise = new Promise(resolve => page.route(server.PREFIX + '/one-style.css',resolve)); - await frame.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); - const request = await requestPromise; - let resolved = false; - const loadPromise = frame.waitForLoadState().then(() => resolved = true); - // give the promise a chance to resolve, even though it shouldn't - await page.evaluate('1'); - expect(resolved).toBe(false); - request.continue(); - await loadPromise; - }); -}); - -describe('Page.reload', function() { - it('should work', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => window._foo = 10); - await page.reload(); - expect(await page.evaluate(() => window._foo)).toBe(undefined); - }); - it('should work with data url', async({page, server}) => { - await page.goto('data:text/html,hello'); - expect(await page.content()).toContain('hello'); - expect(await page.reload()).toBe(null); - expect(await page.content()).toContain('hello'); - }); -}); - -describe('Click navigation', function() { - it('should work with _blank target', async({page, server}) => { - server.setRoute('/empty.html', (req, res) => { - res.end(`Click me`); - }); - await page.goto(server.EMPTY_PAGE); - await page.click('"Click me"'); - }); - it('should work with cross-process _blank target', async({page, server}) => { - server.setRoute('/empty.html', (req, res) => { - res.end(`Click me`); - }); - await page.goto(server.EMPTY_PAGE); - await page.click('"Click me"'); - }); -}); - -function expectSSLError(errorMessage) { - if (CHROMIUM) { - expect(errorMessage).toContain('net::ERR_CERT_AUTHORITY_INVALID'); - } else if (WEBKIT) { - if (MAC) - expect(errorMessage).toContain('The certificate for this server is invalid'); - else if (WIN) - expect(errorMessage).toContain('SSL peer certificate or SSH remote key was not OK'); - else - expect(errorMessage).toContain('Unacceptable TLS certificate'); - } else { - expect(errorMessage).toContain('SSL_ERROR_UNKNOWN'); - } -} diff --git a/test/navigation.spec.js b/test/navigation.spec.js new file mode 100644 index 0000000000000..94de13d15a2a1 --- /dev/null +++ b/test/navigation.spec.js @@ -0,0 +1,37 @@ +/** + * 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 path = require('path'); +const url = require('url'); +const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; + +it('should work with _blank target', async({page, server}) => { + server.setRoute('/empty.html', (req, res) => { + res.end(`Click me`); + }); + await page.goto(server.EMPTY_PAGE); + await page.click('"Click me"'); +}); + +it('should work with cross-process _blank target', async({page, server}) => { + server.setRoute('/empty.html', (req, res) => { + res.end(`Click me`); + }); + await page.goto(server.EMPTY_PAGE); + await page.click('"Click me"'); +}); diff --git a/test/page-goto.spec.js b/test/page-goto.spec.js new file mode 100644 index 0000000000000..1e743557680be --- /dev/null +++ b/test/page-goto.spec.js @@ -0,0 +1,485 @@ +/** + * 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 path = require('path'); +const url = require('url'); +const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; + +it('should work', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + expect(page.url()).toBe(server.EMPTY_PAGE); +}); + +it('should work with file URL', async({page, server}) => { + const fileurl = url.pathToFileURL(path.join(__dirname, 'assets', 'frames', 'two-frames.html')).href; + await page.goto(fileurl); + expect(page.url().toLowerCase()).toBe(fileurl.toLowerCase()); + expect(page.frames().length).toBe(3); +}); + +it('should use http for no protocol', async({page, server}) => { + await page.goto(server.EMPTY_PAGE.substring('http://'.length)); + expect(page.url()).toBe(server.EMPTY_PAGE); +}); + +it('should work cross-process', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + expect(page.url()).toBe(server.EMPTY_PAGE); + + const url = server.CROSS_PROCESS_PREFIX + '/empty.html'; + let requestFrame; + page.on('request', r => { + if (r.url() === url) + requestFrame = r.frame(); + }); + const response = await page.goto(url); + expect(page.url()).toBe(url); + expect(response.frame()).toBe(page.mainFrame()); + expect(requestFrame).toBe(page.mainFrame()); + expect(response.url()).toBe(url); +}); + +it('should capture iframe navigation request', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + expect(page.url()).toBe(server.EMPTY_PAGE); + + let requestFrame; + page.on('request', r => { + if (r.url() === server.PREFIX + '/frames/frame.html') + requestFrame = r.frame(); + }); + const response = await page.goto(server.PREFIX + '/frames/one-frame.html'); + expect(page.url()).toBe(server.PREFIX + '/frames/one-frame.html'); + expect(response.frame()).toBe(page.mainFrame()); + expect(response.url()).toBe(server.PREFIX + '/frames/one-frame.html'); + + expect(page.frames().length).toBe(2); + expect(requestFrame).toBe(page.frames()[1]); +}); + +it('should capture cross-process iframe navigation request', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + expect(page.url()).toBe(server.EMPTY_PAGE); + + let requestFrame; + page.on('request', r => { + if (r.url() === server.CROSS_PROCESS_PREFIX + '/frames/frame.html') + requestFrame = r.frame(); + }); + const response = await page.goto(server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'); + expect(page.url()).toBe(server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'); + expect(response.frame()).toBe(page.mainFrame()); + expect(response.url()).toBe(server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'); + + expect(page.frames().length).toBe(2); + expect(requestFrame).toBe(page.frames()[1]); +}); + +it('should work with anchor navigation', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + expect(page.url()).toBe(server.EMPTY_PAGE); + await page.goto(server.EMPTY_PAGE + '#foo'); + expect(page.url()).toBe(server.EMPTY_PAGE + '#foo'); + await page.goto(server.EMPTY_PAGE + '#bar'); + expect(page.url()).toBe(server.EMPTY_PAGE + '#bar'); +}); + +it('should work with redirects', async({page, server}) => { + server.setRedirect('/redirect/1.html', '/redirect/2.html'); + server.setRedirect('/redirect/2.html', '/empty.html'); + const response = await page.goto(server.PREFIX + '/redirect/1.html'); + expect(response.status()).toBe(200); + expect(page.url()).toBe(server.EMPTY_PAGE); +}); + +it('should navigate to about:blank', async({page, server}) => { + const response = await page.goto('about:blank'); + expect(response).toBe(null); +}); + +it('should return response when page changes its URL after load', async({page, server}) => { + const response = await page.goto(server.PREFIX + '/historyapi.html'); + expect(response.status()).toBe(200); +}); + +it('should work with subframes return 204', async({page, server}) => { + server.setRoute('/frames/frame.html', (req, res) => { + res.statusCode = 204; + res.end(); + }); + await page.goto(server.PREFIX + '/frames/one-frame.html'); +}); + +it('should fail when server returns 204', async({page, server}) => { + // Webkit just loads an empty page. + server.setRoute('/empty.html', (req, res) => { + res.statusCode = 204; + res.end(); + }); + let error = null; + await page.goto(server.EMPTY_PAGE).catch(e => error = e); + expect(error).not.toBe(null); + if (CHROMIUM) + expect(error.message).toContain('net::ERR_ABORTED'); + else if (WEBKIT) + expect(error.message).toContain('Aborted: 204 No Content'); + else + expect(error.message).toContain('NS_BINDING_ABORTED'); +}); + +it('should navigate to empty page with domcontentloaded', async({page, server}) => { + const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'domcontentloaded'}); + expect(response.status()).toBe(200); +}); + +it('should work when page calls history API in beforeunload', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => { + window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false); + }); + const response = await page.goto(server.PREFIX + '/grid.html'); + expect(response.status()).toBe(200); +}); + +it('should fail when navigating to bad url', async({page, server}) => { + let error = null; + await page.goto('asdfasdf').catch(e => error = e); + if (CHROMIUM || WEBKIT) + expect(error.message).toContain('Cannot navigate to invalid URL'); + else + expect(error.message).toContain('Invalid url'); +}); + +it('should fail when navigating to bad SSL', async({page, httpsServer}) => { + // Make sure that network events do not emit 'undefined'. + // @see https://crbug.com/750469 + page.on('request', request => expect(request).toBeTruthy()); + page.on('requestfinished', request => expect(request).toBeTruthy()); + page.on('requestfailed', request => expect(request).toBeTruthy()); + let error = null; + await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); + utils.expectSSLError(error.message, ); +}); + +it('should fail when navigating to bad SSL after redirects', async({page, server, httpsServer}) => { + server.setRedirect('/redirect/1.html', '/redirect/2.html'); + server.setRedirect('/redirect/2.html', '/empty.html'); + let error = null; + await page.goto(httpsServer.PREFIX + '/redirect/1.html').catch(e => error = e); + utils.expectSSLError(error.message); +}); + +it('should not crash when navigating to bad SSL after a cross origin navigation', async({page, server, httpsServer}) => { + await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + await page.goto(httpsServer.EMPTY_PAGE).catch(e => void 0); +}); + +it('should not throw if networkidle0 is passed as an option', async({page, server}) => { + let error = null; + await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle0'}); +}); + +it('should throw if networkidle2 is passed as an option', async({page, server}) => { + let error = null; + await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle2'}).catch(err => error = err); + expect(error.message).toContain(`waitUntil: expected one of (load|domcontentloaded|networkidle)`); +}); + +it('should fail when main resources failed to load', async({page, server}) => { + let error = null; + await page.goto('http://localhost:44123/non-existing-url').catch(e => error = e); + if (CHROMIUM) + expect(error.message).toContain('net::ERR_CONNECTION_REFUSED'); + else if (WEBKIT && WIN) + expect(error.message).toContain(`Couldn\'t connect to server`); + else if (WEBKIT) + expect(error.message).toContain('Could not connect'); + else + expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED'); +}); + +it('should fail when exceeding maximum navigation timeout', async({page, server, playwright}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + await page.goto(server.PREFIX + '/empty.html', {timeout: 1}).catch(e => error = e); + expect(error.message).toContain('page.goto: Timeout 1ms exceeded.'); + expect(error.message).toContain(server.PREFIX + '/empty.html'); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); +}); + +it('should fail when exceeding default maximum navigation timeout', async({page, server, playwright}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + page.context().setDefaultNavigationTimeout(2); + page.setDefaultNavigationTimeout(1); + await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); + expect(error.message).toContain('page.goto: Timeout 1ms exceeded.'); + expect(error.message).toContain(server.PREFIX + '/empty.html'); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); +}); + +it('should fail when exceeding browser context navigation timeout', async({page, server, playwright}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + page.context().setDefaultNavigationTimeout(2); + await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); + expect(error.message).toContain('page.goto: Timeout 2ms exceeded.'); + expect(error.message).toContain(server.PREFIX + '/empty.html'); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); +}); + +it('should fail when exceeding default maximum timeout', async({page, server, playwright}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + page.context().setDefaultTimeout(2); + page.setDefaultTimeout(1); + await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); + expect(error.message).toContain('page.goto: Timeout 1ms exceeded.'); + expect(error.message).toContain(server.PREFIX + '/empty.html'); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); +}); + +it('should fail when exceeding browser context timeout', async({page, server, playwright}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + page.context().setDefaultTimeout(2); + await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); + expect(error.message).toContain('page.goto: Timeout 2ms exceeded.'); + expect(error.message).toContain(server.PREFIX + '/empty.html'); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); +}); + +it('should prioritize default navigation timeout over default timeout', async({page, server, playwright}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + page.setDefaultTimeout(0); + page.setDefaultNavigationTimeout(1); + await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); + expect(error.message).toContain('page.goto: Timeout 1ms exceeded.'); + expect(error.message).toContain(server.PREFIX + '/empty.html'); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); +}); + +it('should disable timeout when its set to 0', async({page, server}) => { + let error = null; + let loaded = false; + page.once('load', () => loaded = true); + await page.goto(server.PREFIX + '/grid.html', {timeout: 0, waitUntil: 'load'}).catch(e => error = e); + expect(error).toBe(null); + expect(loaded).toBe(true); +}); + +it('should fail when replaced by another navigation', async({page, server}) => { + let anotherPromise; + server.setRoute('/empty.html', (req, res) => { + anotherPromise = page.goto(server.PREFIX + '/one-style.html'); + // Hang request to empty.html. + }); + const error = await page.goto(server.PREFIX + '/empty.html').catch(e => e); + await anotherPromise; + if (CHROMIUM) + expect(error.message).toContain('net::ERR_ABORTED'); + else if (WEBKIT) + expect(error.message).toContain('cancelled'); + else + expect(error.message).toContain('NS_BINDING_ABORTED'); +}); + +it('should work when navigating to valid url', async({page, server}) => { + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); +}); + +it('should work when navigating to data url', async({page, server}) => { + const response = await page.goto('data:text/html,hello'); + expect(response).toBe(null); +}); + +it('should work when navigating to 404', async({page, server}) => { + const response = await page.goto(server.PREFIX + '/not-found'); + expect(response.ok()).toBe(false); + expect(response.status()).toBe(404); +}); + +it('should return last response in redirect chain', async({page, server}) => { + server.setRedirect('/redirect/1.html', '/redirect/2.html'); + server.setRedirect('/redirect/2.html', '/redirect/3.html'); + server.setRedirect('/redirect/3.html', server.EMPTY_PAGE); + const response = await page.goto(server.PREFIX + '/redirect/1.html'); + expect(response.ok()).toBe(true); + expect(response.url()).toBe(server.EMPTY_PAGE); +}); + +it('should not leak listeners during navigation', async({page, server}) => { + let warning = null; + const warningHandler = w => warning = w; + process.on('warning', warningHandler); + for (let i = 0; i < 20; ++i) + await page.goto(server.EMPTY_PAGE); + process.removeListener('warning', warningHandler); + expect(warning).toBe(null); +}); + +it('should not leak listeners during bad navigation', async({page, server}) => { + let warning = null; + const warningHandler = w => warning = w; + process.on('warning', warningHandler); + for (let i = 0; i < 20; ++i) + await page.goto('asdf').catch(e => {/* swallow navigation error */}); + process.removeListener('warning', warningHandler); + expect(warning).toBe(null); +}); + +it('should not leak listeners during navigation of 20 pages', async({page, context, server}) => { + let warning = null; + const warningHandler = w => warning = w; + process.on('warning', warningHandler); + const pages = await Promise.all([...Array(20)].map(() => context.newPage())); + await Promise.all(pages.map(page => page.goto(server.EMPTY_PAGE))); + await Promise.all(pages.map(page => page.close())); + process.removeListener('warning', warningHandler); + expect(warning).toBe(null); +}); + +it('should not leak listeners during 20 waitForNavigation', async({page, context, server}) => { + let warning = null; + const warningHandler = w => warning = w; + process.on('warning', warningHandler); + const promises = [...Array(20)].map(() => page.waitForNavigation()); + await page.goto(server.EMPTY_PAGE); + await Promise.all(promises); + process.removeListener('warning', warningHandler); + expect(warning).toBe(null); +}); + +it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => { + const requests = []; + page.on('request', request => requests.push(request)); + const dataURL = 'data:text/html,
yo
'; + const response = await page.goto(dataURL); + expect(response).toBe(null); + expect(requests.length).toBe(0); +}); + +it('should navigate to URL with hash and fire requests without hash', async({page, server}) => { + const requests = []; + page.on('request', request => requests.push(request)); + const response = await page.goto(server.EMPTY_PAGE + '#hash'); + expect(response.status()).toBe(200); + expect(response.url()).toBe(server.EMPTY_PAGE); + expect(requests.length).toBe(1); + expect(requests[0].url()).toBe(server.EMPTY_PAGE); +}); + +it('should work with self requesting page', async({page, server}) => { + const response = await page.goto(server.PREFIX + '/self-request.html'); + expect(response.status()).toBe(200); + expect(response.url()).toContain('self-request.html'); +}); + +it('should fail when navigating and show the url at the error message', async function({page, server, httpsServer}) { + const url = httpsServer.PREFIX + '/redirect/1.html'; + let error = null; + try { + await page.goto(url); + } catch (e) { + error = e; + } + expect(error.message).toContain(url); +}); + +it('should be able to navigate to a page controlled by service worker', async({page, server}) => { + await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html'); + await page.evaluate(() => window.activationPromise); + await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html'); +}); + +it('should send referer', async({page, server}) => { + const [request1, request2] = await Promise.all([ + server.waitForRequest('/grid.html'), + server.waitForRequest('/digits/1.png'), + page.goto(server.PREFIX + '/grid.html', { + referer: 'http://google.com/', + }), + ]); + expect(request1.headers['referer']).toBe('http://google.com/'); + // Make sure subresources do not inherit referer. + expect(request2.headers['referer']).toBe(server.PREFIX + '/grid.html'); + expect(page.url()).toBe(server.PREFIX + '/grid.html'); +}); + +it('should reject referer option when setExtraHTTPHeaders provides referer', async({page, server}) => { + await page.setExtraHTTPHeaders({ 'referer': 'http://microsoft.com/' }); + let error; + await page.goto(server.PREFIX + '/grid.html', { + referer: 'http://google.com/', + }).catch(e => error = e); + expect(error.message).toContain('"referer" is already specified as extra HTTP header'); + expect(error.message).toContain(server.PREFIX + '/grid.html'); +}); + +it('should override referrer-policy', async({page, server}) => { + server.setRoute('/grid.html', (req, res) => { + res.setHeader('Referrer-Policy', 'no-referrer'); + server.serveFile(req, res, '/grid.html'); + }); + const [request1, request2] = await Promise.all([ + server.waitForRequest('/grid.html'), + server.waitForRequest('/digits/1.png'), + page.goto(server.PREFIX + '/grid.html', { + referer: 'http://microsoft.com/', + }), + ]); + expect(request1.headers['referer']).toBe('http://microsoft.com/'); + // Make sure subresources do not inherit referer. + expect(request2.headers['referer']).toBe(undefined); + expect(page.url()).toBe(server.PREFIX + '/grid.html'); +}); + +it('should fail when canceled by another navigation', async({page, server}) => { + server.setRoute('/one-style.html', (req, res) => {}); + const failed = page.goto(server.PREFIX + '/one-style.html').catch(e => e); + await server.waitForRequest('/one-style.html'); + await page.goto(server.PREFIX + '/empty.html'); + const error = await failed; + expect(error.message).toBeTruthy(); +}); + +it.skip(true)('extraHttpHeaders should be pushed to provisional page', async({page, server}) => { + // This test is flaky, because we cannot await page.setExtraHTTPHeaders. + // We need a way to test our implementation by more than just public api. + await page.goto(server.EMPTY_PAGE); + const pagePath = '/one-style.html'; + server.setRoute(pagePath, async (req, res) => { + page.setExtraHTTPHeaders({ foo: 'bar' }); + server.serveFile(req, res, pagePath); + }); + const [htmlReq, cssReq] = await Promise.all([ + server.waitForRequest(pagePath), + server.waitForRequest('/one-style.css'), + page.goto(server.CROSS_PROCESS_PREFIX + pagePath) + ]); + expect(htmlReq.headers['foo']).toBe(undefined); + expect(cssReq.headers['foo']).toBe('bar'); +}); diff --git a/test/page-history.spec.js b/test/page-history.spec.js new file mode 100644 index 0000000000000..d7189a6855f5d --- /dev/null +++ b/test/page-history.spec.js @@ -0,0 +1,96 @@ +/** + * 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 path = require('path'); +const url = require('url'); +const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; + +it('page.goBack should work', async({page, server}) => { + expect(await page.goBack()).toBe(null); + + await page.goto(server.EMPTY_PAGE); + await page.goto(server.PREFIX + '/grid.html'); + + let response = await page.goBack(); + expect(response.ok()).toBe(true); + expect(response.url()).toContain(server.EMPTY_PAGE); + + response = await page.goForward(); + expect(response.ok()).toBe(true); + expect(response.url()).toContain('/grid.html'); + + response = await page.goForward(); + expect(response).toBe(null); +}); + +it('page.goBack should work with HistoryAPI', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => { + history.pushState({}, '', '/first.html'); + history.pushState({}, '', '/second.html'); + }); + expect(page.url()).toBe(server.PREFIX + '/second.html'); + + await page.goBack(); + expect(page.url()).toBe(server.PREFIX + '/first.html'); + await page.goBack(); + expect(page.url()).toBe(server.EMPTY_PAGE); + await page.goForward(); + expect(page.url()).toBe(server.PREFIX + '/first.html'); +}); + +it.fail(WEBKIT && MAC)('page.goBack should work for file urls', async ({page, server}) => { + // WebKit embedder fails to go back/forward to the file url. + const url1 = WIN + ? 'file:///' + path.join(ASSETS_DIR, 'empty.html').replace(/\\/g, '/') + : 'file://' + path.join(ASSETS_DIR, 'empty.html'); + const url2 = server.EMPTY_PAGE; + await page.goto(url1); + await page.setContent(`url2`); + expect(page.url().toLowerCase()).toBe(url1.toLowerCase()); + + await page.click('a'); + expect(page.url()).toBe(url2); + + await page.goBack(); + expect(page.url().toLowerCase()).toBe(url1.toLowerCase()); + // Should be able to evaluate in the new context, and + // not reach for the old cross-process one. + expect(await page.evaluate(() => window.scrollX)).toBe(0); + // Should be able to screenshot. + await page.screenshot(); + + await page.goForward(); + expect(page.url()).toBe(url2); + expect(await page.evaluate(() => window.scrollX)).toBe(0); + await page.screenshot(); +}); + +it('page.reload should work', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => window._foo = 10); + await page.reload(); + expect(await page.evaluate(() => window._foo)).toBe(undefined); +}); + +it('page.reload should work with data url', async({page, server}) => { + await page.goto('data:text/html,hello'); + expect(await page.content()).toContain('hello'); + expect(await page.reload()).toBe(null); + expect(await page.content()).toContain('hello'); +}); diff --git a/test/page-network-idle.spec.js b/test/page-network-idle.spec.js new file mode 100644 index 0000000000000..4209888c075ee --- /dev/null +++ b/test/page-network-idle.spec.js @@ -0,0 +1,146 @@ +/** + * 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 path = require('path'); +const url = require('url'); +const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; + +it('should navigate to empty page with networkidle', async({page, server}) => { + const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' }); + expect(response.status()).toBe(200); +}); + +/** + * @param {import('../index').Frame} frame + * @param {TestServer} server + * @param {() => Promise} action + * @param {boolean} isSetContent + */ +async function networkIdleTest(frame, server, action, isSetContent) { + const finishResponse = response => { + response.statusCode = 404; + response.end(`File not found`); + }; + const waitForRequest = suffix => { + return Promise.all([ + server.waitForRequest(suffix), + frame._page.waitForRequest(server.PREFIX + suffix), + ]); + } + let responses = {}; + // Hold on to a bunch of requests without answering. + server.setRoute('/fetch-request-a.js', (req, res) => responses.a = res); + const firstFetchResourceRequested = waitForRequest('/fetch-request-a.js'); + server.setRoute('/fetch-request-d.js', (req, res) => responses.d = res); + const secondFetchResourceRequested = waitForRequest('/fetch-request-d.js'); + + const waitForLoadPromise = isSetContent ? Promise.resolve() : frame.waitForNavigation({ waitUntil: 'load' }); + + // Navigate to a page which loads immediately and then does a bunch of + // requests via javascript's fetch method. + const actionPromise = action(); + + // Track when the action gets completed. + let actionFinished = false; + actionPromise.then(() => actionFinished = true); + + // Wait for the frame's 'load' event. + await waitForLoadPromise; + expect(actionFinished).toBe(false); + + // Wait for the initial resource to be requested. + await firstFetchResourceRequested; + expect(actionFinished).toBe(false); + + expect(responses.a).toBeTruthy(); + let timer; + let timerTriggered = false; + // Finishing response should trigger the second round. + finishResponse(responses.a); + + // Wait for the second round to be requested. + await secondFetchResourceRequested; + expect(actionFinished).toBe(false); + // Finishing the last response should trigger networkidle. + timer = setTimeout(() => timerTriggered = true, 500); + finishResponse(responses.d); + + const response = await actionPromise; + clearTimeout(timer); + expect(timerTriggered).toBe(true); + if (!isSetContent) + expect(response.ok()).toBe(true); +} + +it('should wait for networkidle to succeed navigation', async({page, server}) => { + await networkIdleTest(page.mainFrame(), server, () => { + return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle' }); + }); +}); + +it('should wait for networkidle to succeed navigation with request from previous navigation', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + server.setRoute('/foo.js', () => {}); + await page.setContent(``); + await networkIdleTest(page.mainFrame(), server, () => { + return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle' }); + }); +}); + +it('should wait for networkidle in waitForNavigation', async({page, server}) => { + await networkIdleTest(page.mainFrame(), server, () => { + const promise = page.waitForNavigation({ waitUntil: 'networkidle' }); + page.goto(server.PREFIX + '/networkidle.html'); + return promise; + }); +}); + +it('should wait for networkidle in setContent', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await networkIdleTest(page.mainFrame(), server, () => { + return page.setContent(``, { waitUntil: 'networkidle' }); + }, true); +}); + +it('should wait for networkidle in setContent with request from previous navigation', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + server.setRoute('/foo.js', () => {}); + await page.setContent(``); + await networkIdleTest(page.mainFrame(), server, () => { + return page.setContent(``, { waitUntil: 'networkidle' }); + }, true); +}); + +it('should wait for networkidle when navigating iframe', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = page.mainFrame().childFrames()[0]; + await networkIdleTest(frame, server, () => frame.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle' })); +}); + +it('should wait for networkidle in setContent from the child frame', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await networkIdleTest(page.mainFrame(), server, () => { + return page.setContent(``, { waitUntil: 'networkidle' }); + }, true); +}); + +it('should wait for networkidle from the child frame', async({page, server}) => { + await networkIdleTest(page.mainFrame(), server, () => { + return page.goto(server.PREFIX + '/networkidle-frame.html', { waitUntil: 'networkidle' }); + }); +}); diff --git a/test/page-route.spec.js b/test/page-route.spec.js new file mode 100644 index 0000000000000..70cb6def8b604 --- /dev/null +++ b/test/page-route.spec.js @@ -0,0 +1,509 @@ +/** + * 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 fs = require('fs'); +const path = require('path'); +const { helper } = require('../lib/helper'); +const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT, HEADLESS} = testOptions; + +it('should intercept', async({page, server}) => { + let intercepted = false; + await page.route('**/empty.html', (route, request) => { + expect(route.request()).toBe(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(); + intercepted = true; + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + expect(intercepted).toBe(true); +}); + +it('should unroute', async({page, server}) => { + let intercepted = []; + const handler1 = route => { + intercepted.push(1); + route.continue(); + }; + await page.route('**/empty.html', handler1); + await page.route('**/empty.html', route => { + intercepted.push(2); + route.continue(); + }); + await page.route('**/empty.html', route => { + intercepted.push(3); + route.continue(); + }); + await page.route('**/*', route => { + intercepted.push(4); + route.continue(); + }); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([1]); + + intercepted = []; + await page.unroute('**/empty.html', handler1); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([2]); + + intercepted = []; + await page.unroute('**/empty.html'); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([4]); +}); + +it('should work when POST is redirected with 302', async({page, server}) => { + server.setRedirect('/rredirect', '/empty.html'); + await page.goto(server.EMPTY_PAGE); + await page.route('**/*', route => route.continue()); + await page.setContent(` +
+ +
+ `); + await Promise.all([ + page.$eval('form', form => form.submit()), + page.waitForNavigation() + ]); +}); +// @see https://github.com/GoogleChrome/puppeteer/issues/3973 +it('should work when header manipulation headers with redirect', async({page, server}) => { + server.setRedirect('/rrredirect', '/empty.html'); + await page.route('**/*', route => { + const headers = Object.assign({}, route.request().headers(), { + foo: 'bar' + }); + route.continue({ headers }); + }); + await page.goto(server.PREFIX + '/rrredirect'); +}); +// @see https://github.com/GoogleChrome/puppeteer/issues/4743 +it('should be able to remove headers', async({page, server}) => { + await page.route('**/*', route => { + const headers = Object.assign({}, route.request().headers(), { + foo: 'bar', + origin: undefined, // remove "origin" header + }); + route.continue({ headers }); + }); + + const [serverRequest] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.goto(server.PREFIX + '/empty.html') + ]); + + expect(serverRequest.headers.origin).toBe(undefined); +}); + +it('should contain referer header', async({page, server}) => { + const requests = []; + await page.route('**/*', route => { + requests.push(route.request()); + route.continue(); + }); + await page.goto(server.PREFIX + '/one-style.html'); + expect(requests[1].url()).toContain('/one-style.css'); + expect(requests[1].headers().referer).toContain('/one-style.html'); +}); + +it('should properly return navigation response when URL has cookies', async({context, page, server}) => { + // Setup cookie. + await page.goto(server.EMPTY_PAGE); + await context.addCookies([{ url: server.EMPTY_PAGE, name: 'foo', value: 'bar'}]); + + // Setup request interception. + await page.route('**/*', route => route.continue()); + const response = await page.reload(); + expect(response.status()).toBe(200); +}); + +it('should show custom HTTP headers', async({page, server}) => { + await page.setExtraHTTPHeaders({ + foo: 'bar' + }); + await page.route('**/*', route => { + expect(route.request().headers()['foo']).toBe('bar'); + route.continue(); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); +}); +// @see https://github.com/GoogleChrome/puppeteer/issues/4337 +it('should work with redirect inside sync XHR', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + server.setRedirect('/logo.png', '/pptr.png'); + await page.route('**/*', route => route.continue()); + const status = await page.evaluate(async() => { + const request = new XMLHttpRequest(); + request.open('GET', '/logo.png', false); // `false` makes the request synchronous + request.send(null); + return request.status; + }); + expect(status).toBe(200); +}); + +it('should work with custom referer headers', async({page, server}) => { + await page.setExtraHTTPHeaders({ 'referer': server.EMPTY_PAGE }); + await page.route('**/*', route => { + expect(route.request().headers()['referer']).toBe(server.EMPTY_PAGE); + route.continue(); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); +}); + +it('should be abortable', async({page, server}) => { + await page.route(/\.css$/, route => route.abort()); + let failed = false; + page.on('requestfailed', request => { + if (request.url().includes('.css')) + failed = true; + }); + const response = await page.goto(server.PREFIX + '/one-style.html'); + expect(response.ok()).toBe(true); + expect(response.request().failure()).toBe(null); + expect(failed).toBe(true); +}); + +it('should be abortable with custom error codes', async({page, server}) => { + await page.route('**/*', route => route.abort('internetdisconnected')); + let failedRequest = null; + page.on('requestfailed', request => failedRequest = request); + await page.goto(server.EMPTY_PAGE).catch(e => {}); + expect(failedRequest).toBeTruthy(); + if (WEBKIT) + expect(failedRequest.failure().errorText).toBe('Request intercepted'); + else if (FFOX) + expect(failedRequest.failure().errorText).toBe('NS_ERROR_OFFLINE'); + else + expect(failedRequest.failure().errorText).toBe('net::ERR_INTERNET_DISCONNECTED'); +}); + +it('should send referer', async({page, server}) => { + await page.setExtraHTTPHeaders({ + referer: 'http://google.com/' + }); + await page.route('**/*', route => route.continue()); + const [request] = await Promise.all([ + server.waitForRequest('/grid.html'), + page.goto(server.PREFIX + '/grid.html'), + ]); + expect(request.headers['referer']).toBe('http://google.com/'); +}); + +it('should fail navigation when aborting main resource', async({page, server}) => { + await page.route('**/*', route => route.abort()); + let error = null; + await page.goto(server.EMPTY_PAGE).catch(e => error = e); + expect(error).toBeTruthy(); + if (WEBKIT) + expect(error.message).toContain('Request intercepted'); + else if (FFOX) + expect(error.message).toContain('NS_ERROR_FAILURE'); + else + expect(error.message).toContain('net::ERR_FAILED'); +}); + +it('should not work with redirects', async({page, server}) => { + const intercepted = []; + await page.route('**/*', route => { + route.continue(); + intercepted.push(route.request()); + }); + server.setRedirect('/non-existing-page.html', '/non-existing-page-2.html'); + server.setRedirect('/non-existing-page-2.html', '/non-existing-page-3.html'); + server.setRedirect('/non-existing-page-3.html', '/non-existing-page-4.html'); + server.setRedirect('/non-existing-page-4.html', '/empty.html'); + + const response = await page.goto(server.PREFIX + '/non-existing-page.html'); + expect(response.status()).toBe(200); + expect(response.url()).toContain('empty.html'); + + expect(intercepted.length).toBe(1); + expect(intercepted[0].resourceType()).toBe('document'); + expect(intercepted[0].isNavigationRequest()).toBe(true); + expect(intercepted[0].url()).toContain('/non-existing-page.html'); + + const chain = []; + for (let r = response.request(); r; r = r.redirectedFrom()) { + chain.push(r); + expect(r.isNavigationRequest()).toBe(true); + } + expect(chain.length).toBe(5); + expect(chain[0].url()).toContain('/empty.html'); + expect(chain[1].url()).toContain('/non-existing-page-4.html'); + expect(chain[2].url()).toContain('/non-existing-page-3.html'); + expect(chain[3].url()).toContain('/non-existing-page-2.html'); + expect(chain[4].url()).toContain('/non-existing-page.html'); + for (let i = 0; i < chain.length; i++) + expect(chain[i].redirectedTo()).toBe(i ? chain[i - 1] : null); +}); + +it('should work with redirects for subresources', async({page, server}) => { + const intercepted = []; + await page.route('**/*', route => { + route.continue(); + intercepted.push(route.request()); + }); + server.setRedirect('/one-style.css', '/two-style.css'); + server.setRedirect('/two-style.css', '/three-style.css'); + server.setRedirect('/three-style.css', '/four-style.css'); + server.setRoute('/four-style.css', (req, res) => res.end('body {box-sizing: border-box; }')); + + const response = await page.goto(server.PREFIX + '/one-style.html'); + expect(response.status()).toBe(200); + expect(response.url()).toContain('one-style.html'); + + expect(intercepted.length).toBe(2); + expect(intercepted[0].resourceType()).toBe('document'); + expect(intercepted[0].url()).toContain('one-style.html'); + + let r = intercepted[1]; + for (const url of ['/one-style.css', '/two-style.css', '/three-style.css', '/four-style.css']) { + expect(r.resourceType()).toBe('stylesheet'); + expect(r.url()).toContain(url); + r = r.redirectedTo(); + } + expect(r).toBe(null); +}); + +it('should work with equal requests', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + let responseCount = 1; + server.setRoute('/zzz', (req, res) => res.end((responseCount++) * 11 + '')); + + let spinner = false; + // Cancel 2nd request. + await page.route('**/*', route => { + spinner ? route.abort() : route.continue(); + spinner = !spinner; + }); + const results = []; + for (let i = 0; i < 3; i++) + results.push(await page.evaluate(() => fetch('/zzz').then(response => response.text()).catch(e => 'FAILED'))); + expect(results).toEqual(['11', 'FAILED', '22']); +}); + +it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => { + const requests = []; + await page.route('**/*', route => { + requests.push(route.request()); + route.continue(); + }); + const dataURL = 'data:text/html,
yo
'; + const response = await page.goto(dataURL); + expect(response).toBe(null); + expect(requests.length).toBe(0); +}); + +it('should be able to fetch dataURL and not fire dataURL requests', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const requests = []; + await page.route('**/*', route => { + requests.push(route.request()); + route.continue(); + }); + const dataURL = 'data:text/html,
yo
'; + const text = await page.evaluate(url => fetch(url).then(r => r.text()), dataURL); + expect(text).toBe('
yo
'); + expect(requests.length).toBe(0); +}); + +it('should navigate to URL with hash and and fire requests without hash', async({page, server}) => { + const requests = []; + await page.route('**/*', route => { + requests.push(route.request()); + route.continue(); + }); + const response = await page.goto(server.EMPTY_PAGE + '#hash'); + expect(response.status()).toBe(200); + expect(response.url()).toBe(server.EMPTY_PAGE); + expect(requests.length).toBe(1); + expect(requests[0].url()).toBe(server.EMPTY_PAGE); +}); + +it('should work with encoded server', async({page, server}) => { + // The requestWillBeSent will report encoded URL, whereas interception will + // report URL as-is. @see crbug.com/759388 + await page.route('**/*', route => route.continue()); + const response = await page.goto(server.PREFIX + '/some nonexisting page'); + expect(response.status()).toBe(404); +}); + +it('should work with badly encoded server', async({page, server}) => { + server.setRoute('/malformed?rnd=%911', (req, res) => res.end()); + await page.route('**/*', route => route.continue()); + const response = await page.goto(server.PREFIX + '/malformed?rnd=%911'); + expect(response.status()).toBe(200); +}); + +it('should work with encoded server - 2', async({page, server}) => { + // The requestWillBeSent will report URL as-is, whereas interception will + // report encoded URL for stylesheet. @see crbug.com/759388 + const requests = []; + await page.route('**/*', route => { + route.continue(); + requests.push(route.request()); + }); + const response = await page.goto(`data:text/html,`); + expect(response).toBe(null); + expect(requests.length).toBe(1); + expect((await requests[0].response()).status()).toBe(404); +}); + +it('should not throw "Invalid Interception Id" if the request was cancelled', async({page, server}) => { + await page.setContent(''); + let route = null; + await page.route('**/*', async r => route = r); + page.$eval('iframe', (frame, url) => frame.src = url, server.EMPTY_PAGE), + // Wait for request interception. + await page.waitForEvent('request'); + // Delete frame to cause request to be canceled. + await page.$eval('iframe', frame => frame.remove()); + let error = null; + await route.continue().catch(e => error = e); + expect(error).toBe(null); +}); + +it('should intercept main resource during cross-process navigation', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + let intercepted = false; + await page.route(server.CROSS_PROCESS_PREFIX + '/empty.html', route => { + intercepted = true; + route.continue(); + }); + const response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + expect(response.ok()).toBe(true); + expect(intercepted).toBe(true); +}); + +it('should create a redirect', async({page, server}) => { + await page.goto(server.PREFIX + '/empty.html'); + await page.route('**/*', async(route, request) => { + if (request.url() !== server.PREFIX + '/redirect_this') + return route.continue(); + await route.fulfill({ + status: 301, + headers: { + 'location': '/empty.html', + } + }); + }); + + const text = await page.evaluate(async url => { + const data = await fetch(url); + return data.text(); + }, server.PREFIX + '/redirect_this'); + expect(text).toBe(''); +}); + +it('should support cors with GET', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.route('**/cars*', async (route, request) => { + const headers = request.url().endsWith('allow') ? { 'access-control-allow-origin': '*' } : {}; + await route.fulfill({ + contentType: 'application/json', + headers, + status: 200, + body: JSON.stringify(['electric', 'gas']), + }); + }); + { + // Should succeed + const resp = await page.evaluate(async () => { + const response = await fetch('https://example.com/cars?allow', { mode: 'cors' }); + return response.json(); + }); + expect(resp).toEqual(['electric', 'gas']); + } + { + // Should be rejected + const error = await page.evaluate(async () => { + const response = await fetch('https://example.com/cars?reject', { mode: 'cors' }); + return response.json(); + }).catch(e => e); + expect(error.message).toContain('failed'); + } +}); + +it('should support cors with POST', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.route('**/cars', async (route) => { + await route.fulfill({ + contentType: 'application/json', + headers: { 'Access-Control-Allow-Origin': '*' }, + status: 200, + body: JSON.stringify(['electric', 'gas']), + }); + }); + const resp = await page.evaluate(async () => { + const response = await fetch('https://example.com/cars', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + mode: 'cors', + body: JSON.stringify({ 'number': 1 }) + }); + return response.json(); + }); + expect(resp).toEqual(['electric', 'gas']); +}); + +it('should support cors for different methods', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.route('**/cars', async (route, request) => { + await route.fulfill({ + contentType: 'application/json', + headers: { 'Access-Control-Allow-Origin': '*' }, + status: 200, + body: JSON.stringify([request.method(), 'electric', 'gas']), + }); + }); + // First POST + { + const resp = await page.evaluate(async () => { + const response = await fetch('https://example.com/cars', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + mode: 'cors', + body: JSON.stringify({ 'number': 1 }) + }); + return response.json(); + }); + expect(resp).toEqual(['POST', 'electric', 'gas']); + } + // Then DELETE + { + const resp = await page.evaluate(async () => { + const response = await fetch('https://example.com/cars', { + method: 'DELETE', + headers: {}, + mode: 'cors', + body: '' + }); + return response.json(); + }); + expect(resp).toEqual(['DELETE', 'electric', 'gas']); + } +}); diff --git a/test/page-set-input-files.spec.js b/test/page-set-input-files.spec.js new file mode 100644 index 0000000000000..5948b649c2c92 --- /dev/null +++ b/test/page-set-input-files.spec.js @@ -0,0 +1,266 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const path = require('path'); +const fs = require('fs'); +const formidable = require('formidable'); + +const FILE_TO_UPLOAD = path.join(__dirname, '/assets/file-to-upload.txt'); + +it('should upload the file', async({page, server}) => { + await page.goto(server.PREFIX + '/input/fileupload.html'); + const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD); + const input = await page.$('input'); + await input.setInputFiles(filePath); + expect(await page.evaluate(e => e.files[0].name, input)).toBe('file-to-upload.txt'); + expect(await page.evaluate(e => { + const reader = new FileReader(); + const promise = new Promise(fulfill => reader.onload = fulfill); + reader.readAsText(e.files[0]); + return promise.then(() => reader.result); + }, input)).toBe('contents of the file'); +}); + +it('should work', async({page}) => { + await page.setContent(``); + await page.setInputFiles('input', path.join(__dirname, '/assets/file-to-upload.txt')); + expect(await page.$eval('input', input => input.files.length)).toBe(1); + expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt'); +}); + +it('should set from memory', async({page}) => { + await page.setContent(``); + await page.setInputFiles('input', { + name: 'test.txt', + mimeType: 'text/plain', + buffer: Buffer.from('this is a test') + }); + expect(await page.$eval('input', input => input.files.length)).toBe(1); + expect(await page.$eval('input', input => input.files[0].name)).toBe('test.txt'); +}); + +it('should emit event', async({page, server}) => { + await page.setContent(``); + const [chooser] = await Promise.all([ + new Promise(f => page.once('filechooser', f)), + page.click('input'), + ]); + expect(chooser).toBeTruthy(); +}); + +it('should work when file input is attached to DOM', async({page, server}) => { + await page.setContent(``); + const [chooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(chooser).toBeTruthy(); +}); + +it('should work when file input is not attached to DOM', async({page, server}) => { + const [chooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.evaluate(() => { + const el = document.createElement('input'); + el.type = 'file'; + el.click(); + }), + ]); + expect(chooser).toBeTruthy(); +}); + +it('should respect timeout', async({page, playwright}) => { + let error = null; + await page.waitForEvent('filechooser', {timeout: 1}).catch(e => error = e); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); +}); + +it('should respect default timeout when there is no custom timeout', async({page, playwright}) => { + page.setDefaultTimeout(1); + let error = null; + await page.waitForEvent('filechooser').catch(e => error = e); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); +}); + +it('should prioritize exact timeout over default timeout', async({page, playwright}) => { + page.setDefaultTimeout(0); + let error = null; + await page.waitForEvent('filechooser', {timeout: 1}).catch(e => error = e); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); +}); + +it('should work with no timeout', async({page, server}) => { + const [chooser] = await Promise.all([ + page.waitForEvent('filechooser', {timeout: 0}), + page.evaluate(() => setTimeout(() => { + const el = document.createElement('input'); + el.type = 'file'; + el.click(); + }, 50)) + ]); + expect(chooser).toBeTruthy(); +}); + +it('should return the same file chooser when there are many watchdogs simultaneously', async({page, server}) => { + await page.setContent(``); + const [fileChooser1, fileChooser2] = await Promise.all([ + page.waitForEvent('filechooser'), + page.waitForEvent('filechooser'), + page.$eval('input', input => input.click()), + ]); + expect(fileChooser1 === fileChooser2).toBe(true); +}); + +it('should accept single file', async({page, server}) => { + await page.setContent(``); + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(fileChooser.page()).toBe(page); + expect(fileChooser.element()).toBeTruthy(); + await fileChooser.setFiles(FILE_TO_UPLOAD); + expect(await page.$eval('input', input => input.files.length)).toBe(1); + expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt'); +}); + +it('should detect mime type', async({page, server}) => { + let files; + server.setRoute('/upload', async (req, res) => { + const form = new formidable.IncomingForm(); + form.parse(req, function(err, fields, f) { + files = f; + res.end(); + }); + }); + await page.goto(server.EMPTY_PAGE); + await page.setContent(` +
+ + + +
`) + await (await page.$('input[name=file1]')).setInputFiles(path.join(__dirname, '/assets/file-to-upload.txt')); + await (await page.$('input[name=file2]')).setInputFiles(path.join(__dirname, '/assets/pptr.png')); + await Promise.all([ + page.click('input[type=submit]'), + server.waitForRequest('/upload'), + ]); + const { file1, file2 } = files; + expect(file1.name).toBe('file-to-upload.txt'); + expect(file1.type).toBe('text/plain'); + expect(fs.readFileSync(file1.path).toString()).toBe( + fs.readFileSync(path.join(__dirname, '/assets/file-to-upload.txt')).toString()); + expect(file2.name).toBe('pptr.png'); + expect(file2.type).toBe('image/png'); + expect(fs.readFileSync(file2.path).toString()).toBe( + fs.readFileSync(path.join(__dirname, '/assets/pptr.png')).toString()); +}); + +it('should be able to read selected file', async({page, server}) => { + await page.setContent(``); + const [, content] = await Promise.all([ + page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles(FILE_TO_UPLOAD)), + page.$eval('input', async picker => { + picker.click(); + await new Promise(x => picker.oninput = x); + const reader = new FileReader(); + const promise = new Promise(fulfill => reader.onload = fulfill); + reader.readAsText(picker.files[0]); + return promise.then(() => reader.result); + }), + ]); + expect(content).toBe('contents of the file'); +}); + +it('should be able to reset selected files with empty file list', async({page, server}) => { + await page.setContent(``); + const [, fileLength1] = await Promise.all([ + page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles(FILE_TO_UPLOAD)), + page.$eval('input', async picker => { + picker.click(); + await new Promise(x => picker.oninput = x); + return picker.files.length; + }), + ]); + expect(fileLength1).toBe(1); + const [, fileLength2] = await Promise.all([ + page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles([])), + page.$eval('input', async picker => { + picker.click(); + await new Promise(x => picker.oninput = x); + return picker.files.length; + }), + ]); + expect(fileLength2).toBe(0); +}); + +it('should not accept multiple files for single-file input', async({page, server}) => { + await page.setContent(``); + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + let error = null; + await fileChooser.setFiles([ + path.relative(process.cwd(), __dirname + '/assets/file-to-upload.txt'), + path.relative(process.cwd(), __dirname + '/assets/pptr.png') + ]).catch(e => error = e); + expect(error).not.toBe(null); +}); + +it('should emit input and change events', async({page, server}) => { + const events = []; + await page.exposeFunction('eventHandled', e => events.push(e)); + await page.setContent(` + + `); + await (await page.$('input')).setInputFiles(FILE_TO_UPLOAD); + expect(events.length).toBe(2); + expect(events[0].type).toBe('input'); + expect(events[1].type).toBe('change'); +}); + +it('should work for single file pick', async({page, server}) => { + await page.setContent(``); + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(fileChooser.isMultiple()).toBe(false); +}); + +it('should work for "multiple"', async({page, server}) => { + await page.setContent(``); + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(fileChooser.isMultiple()).toBe(true); +}); + +it('should work for "webkitdirectory"', async({page, server}) => { + await page.setContent(``); + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(fileChooser.isMultiple()).toBe(true); +}); diff --git a/test/page-wait-for-load-state.spec.js b/test/page-wait-for-load-state.spec.js new file mode 100644 index 0000000000000..37b87c68c019c --- /dev/null +++ b/test/page-wait-for-load-state.spec.js @@ -0,0 +1,183 @@ +/** + * 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 path = require('path'); +const url = require('url'); +const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; + +it('should pick up ongoing navigation', async({page, server}) => { + let response = null; + server.setRoute('/one-style.css', (req, res) => response = res); + await Promise.all([ + server.waitForRequest('/one-style.css'), + page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}), + ]); + const waitPromise = page.waitForLoadState(); + response.statusCode = 404; + response.end('Not found'); + await waitPromise; +}); + +it('should respect timeout', async({page, server}) => { + server.setRoute('/one-style.css', (req, res) => response = res); + await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); + const error = await page.waitForLoadState('load', { timeout: 1 }).catch(e => e); + expect(error.message).toContain('page.waitForLoadState: Timeout 1ms exceeded.'); +}); + +it('should resolve immediately if loaded', async({page, server}) => { + await page.goto(server.PREFIX + '/one-style.html'); + await page.waitForLoadState(); +}); + +it('should throw for bad state', async({page, server}) => { + await page.goto(server.PREFIX + '/one-style.html'); + const error = await page.waitForLoadState('bad').catch(e => e); + expect(error.message).toContain(`state: expected one of (load|domcontentloaded|networkidle)`); +}); + +it('should resolve immediately if load state matches', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + server.setRoute('/one-style.css', (req, res) => response = res); + await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); + await page.waitForLoadState('domcontentloaded'); +}); + +it('should work with pages that have loaded before being connected to', async({page, context, server}) => { + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window._popup = window.open(document.location.href)), + ]); + // The url is about:blank in FF. + // expect(popup.url()).toBe(server.EMPTY_PAGE); + await popup.waitForLoadState(); + expect(popup.url()).toBe(server.EMPTY_PAGE); +}); + +it('should wait for load state of empty url popup', async({browser, page}) => { + const [popup, readyState] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => { + const popup = window.open(''); + return popup.document.readyState; + }), + ]); + await popup.waitForLoadState(); + expect(readyState).toBe(FFOX ? 'uninitialized' : 'complete'); + expect(await popup.evaluate(() => document.readyState)).toBe(FFOX ? 'uninitialized' : 'complete'); +}); + +it('should wait for load state of about:blank popup ', async({browser, page}) => { + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('about:blank') && 1), + ]); + await popup.waitForLoadState(); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); +}); + +it('should wait for load state of about:blank popup with noopener ', async({browser, page}) => { + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('about:blank', null, 'noopener') && 1), + ]); + await popup.waitForLoadState(); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); +}); + +it('should wait for load state of popup with network url ', async({browser, page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window.open(url) && 1, server.EMPTY_PAGE), + ]); + await popup.waitForLoadState(); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); +}); + +it('should wait for load state of popup with network url and noopener ', async({browser, page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window.open(url, null, 'noopener') && 1, server.EMPTY_PAGE), + ]); + await popup.waitForLoadState(); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); +}); + +it('should work with clicking target=_blank', async({browser, page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.click('a'), + ]); + await popup.waitForLoadState(); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); +}); + +it('should wait for load state of newPage', async({browser, context, page, server}) => { + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + context.newPage(), + ]); + await newPage.waitForLoadState(); + expect(await newPage.evaluate(() => document.readyState)).toBe('complete'); +}); + +it('should resolve after popup load', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + // Stall the 'load' by delaying css. + let cssResponse; + server.setRoute('/one-style.css', (req, res) => cssResponse = res); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + server.waitForRequest('/one-style.css'), + page.evaluate(url => window.popup = window.open(url), server.PREFIX + '/one-style.html'), + ]); + let resolved = false; + const loadSatePromise = popup.waitForLoadState().then(() => resolved = true); + // Round trips! + for (let i = 0; i < 5; i++) + await page.evaluate('window'); + expect(resolved).toBe(false); + cssResponse.end(''); + await loadSatePromise; + expect(resolved).toBe(true); + expect(popup.url()).toBe(server.PREFIX + '/one-style.html'); + await context.close(); +}); + +it('should work for frame', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = page.frames()[1]; + + const requestPromise = new Promise(resolve => page.route(server.PREFIX + '/one-style.css',resolve)); + await frame.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); + const request = await requestPromise; + let resolved = false; + const loadPromise = frame.waitForLoadState().then(() => resolved = true); + // give the promise a chance to resolve, even though it shouldn't + await page.evaluate('1'); + expect(resolved).toBe(false); + request.continue(); + await loadPromise; +}); diff --git a/test/page-wait-for-navigation.spec.js b/test/page-wait-for-navigation.spec.js new file mode 100644 index 0000000000000..970aa8caa53d6 --- /dev/null +++ b/test/page-wait-for-navigation.spec.js @@ -0,0 +1,250 @@ +/** + * 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 path = require('path'); +const url = require('url'); +const {FFOX, CHROMIUM, WEBKIT, ASSETS_DIR, MAC, WIN} = testOptions; + +it('should work', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [response] = await Promise.all([ + page.waitForNavigation(), + page.evaluate(url => window.location.href = url, server.PREFIX + '/grid.html') + ]); + expect(response.ok()).toBe(true); + expect(response.url()).toContain('grid.html'); +}); + +it('should respect timeout', async({page, server}) => { + const promise = page.waitForNavigation({ url: '**/frame.html', timeout: 5000 }); + await page.goto(server.EMPTY_PAGE); + const error = await promise.catch(e => e); + expect(error.message).toContain('page.waitForNavigation: Timeout 5000ms exceeded.'); + expect(error.message).toContain('waiting for navigation to "**/frame.html" until "load"'); + expect(error.message).toContain(`navigated to "${server.EMPTY_PAGE}"`); +}); + +it('should work with both domcontentloaded and load', async({page, server}) => { + let response = null; + server.setRoute('/one-style.css', (req, res) => response = res); + const navigationPromise = page.goto(server.PREFIX + '/one-style.html'); + const domContentLoadedPromise = page.waitForNavigation({ + waitUntil: 'domcontentloaded' + }); + + let bothFired = false; + const bothFiredPromise = Promise.all([ + page.waitForNavigation({ waitUntil: 'load' }), + domContentLoadedPromise + ]).then(() => bothFired = true); + + await server.waitForRequest('/one-style.css'); + await domContentLoadedPromise; + expect(bothFired).toBe(false); + response.end(); + await bothFiredPromise; + await navigationPromise; +}); + +it('should work with clicking on anchor links', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(`foobar`); + const [response] = await Promise.all([ + page.waitForNavigation(), + page.click('a'), + ]); + expect(response).toBe(null); + expect(page.url()).toBe(server.EMPTY_PAGE + '#foobar'); +}); + +it('should work with clicking on links which do not commit navigation', async({page, server, httpsServer}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(`foobar`); + const [error] = await Promise.all([ + page.waitForNavigation().catch(e => e), + page.click('a'), + ]); + utils.expectSSLError(error.message); +}); + +it('should work with history.pushState()', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(` + SPA + + `); + const [response] = await Promise.all([ + page.waitForNavigation(), + page.click('a'), + ]); + expect(response).toBe(null); + expect(page.url()).toBe(server.PREFIX + '/wow.html'); +}); + +it('should work with history.replaceState()', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(` + SPA + + `); + const [response] = await Promise.all([ + page.waitForNavigation(), + page.click('a'), + ]); + expect(response).toBe(null); + expect(page.url()).toBe(server.PREFIX + '/replaced.html'); +}); + +it('should work with DOM history.back()/history.forward()', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(` + back + forward + + `); + expect(page.url()).toBe(server.PREFIX + '/second.html'); + const [backResponse] = await Promise.all([ + page.waitForNavigation(), + page.click('a#back'), + ]); + expect(backResponse).toBe(null); + expect(page.url()).toBe(server.PREFIX + '/first.html'); + const [forwardResponse] = await Promise.all([ + page.waitForNavigation(), + page.click('a#forward'), + ]); + expect(forwardResponse).toBe(null); + expect(page.url()).toBe(server.PREFIX + '/second.html'); +}); + +it('should work when subframe issues window.stop()', async({page, server}) => { + server.setRoute('/frames/style.css', (req, res) => {}); + const navigationPromise = page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = await new Promise(f => page.once('frameattached', f)); + await new Promise(fulfill => page.on('framenavigated', f => { + if (f === frame) + fulfill(); + })); + await Promise.all([ + frame.evaluate(() => window.stop()), + navigationPromise + ]); +}); + +it('should work with url match', async({page, server}) => { + let response1 = null; + const response1Promise = page.waitForNavigation({ url: /one-style\.html/ }).then(response => response1 = response); + let response2 = null; + const response2Promise = page.waitForNavigation({ url: /\/frame.html/ }).then(response => response2 = response); + let response3 = null; + const response3Promise = page.waitForNavigation({ url: url => url.searchParams.get('foo') === 'bar' }).then(response => response3 = response); + expect(response1).toBe(null); + expect(response2).toBe(null); + expect(response3).toBe(null); + await page.goto(server.EMPTY_PAGE); + expect(response1).toBe(null); + expect(response2).toBe(null); + expect(response3).toBe(null); + await page.goto(server.PREFIX + '/frame.html'); + expect(response1).toBe(null); + await response2Promise; + expect(response2).not.toBe(null); + expect(response3).toBe(null); + await page.goto(server.PREFIX + '/one-style.html'); + await response1Promise; + expect(response1).not.toBe(null); + expect(response2).not.toBe(null); + expect(response3).toBe(null); + await page.goto(server.PREFIX + '/frame.html?foo=bar'); + await response3Promise; + expect(response1).not.toBe(null); + expect(response2).not.toBe(null); + expect(response3).not.toBe(null); + await page.goto(server.PREFIX + '/empty.html'); + expect(response1.url()).toBe(server.PREFIX + '/one-style.html'); + expect(response2.url()).toBe(server.PREFIX + '/frame.html'); + expect(response3.url()).toBe(server.PREFIX + '/frame.html?foo=bar'); +}); + +it('should work with url match for same document navigations', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + let resolved = false; + const waitPromise = page.waitForNavigation({ url: /third\.html/ }).then(() => resolved = true); + expect(resolved).toBe(false); + await page.evaluate(() => { + history.pushState({}, '', '/first.html'); + }); + expect(resolved).toBe(false); + await page.evaluate(() => { + history.pushState({}, '', '/second.html'); + }); + expect(resolved).toBe(false); + await page.evaluate(() => { + history.pushState({}, '', '/third.html'); + }); + await waitPromise; + expect(resolved).toBe(true); +}); + +it('should work for cross-process navigations', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const waitPromise = page.waitForNavigation({waitUntil: 'domcontentloaded'}); + const url = server.CROSS_PROCESS_PREFIX + '/empty.html'; + const gotoPromise = page.goto(url); + const response = await waitPromise; + expect(response.url()).toBe(url); + expect(page.url()).toBe(url); + expect(await page.evaluate('document.location.href')).toBe(url); + await gotoPromise; +}); + +it('should work on frame', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = page.frames()[1]; + const [response] = await Promise.all([ + frame.waitForNavigation(), + frame.evaluate(url => window.location.href = url, server.PREFIX + '/grid.html') + ]); + expect(response.ok()).toBe(true); + expect(response.url()).toContain('grid.html'); + expect(response.frame()).toBe(frame); + expect(page.url()).toContain('/frames/one-frame.html'); +}); + +it('should fail when frame detaches', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = page.frames()[1]; + server.setRoute('/empty.html', () => {}); + let error = null; + await Promise.all([ + frame.waitForNavigation().catch(e => error = e), + frame.evaluate('window.location = "/empty.html"'), + page.evaluate('setTimeout(() => document.querySelector("iframe").remove())'), + ]).catch(e => error = e); + expect(error.message).toContain('waiting for navigation until "load"'); + expect(error.message).toContain('frame was detached'); +}); diff --git a/test/request-continue.spec.js b/test/request-continue.spec.js new file mode 100644 index 0000000000000..787a718f0eba5 --- /dev/null +++ b/test/request-continue.spec.js @@ -0,0 +1,115 @@ +/** + * 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 fs = require('fs'); +const path = require('path'); +const { helper } = require('../lib/helper'); +const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT, HEADLESS} = testOptions; + +it('should work', async({page, server}) => { + await page.route('**/*', route => route.continue()); + await page.goto(server.EMPTY_PAGE); +}); + +it('should amend HTTP headers', async({page, server}) => { + await page.route('**/*', route => { + const headers = Object.assign({}, route.request().headers()); + headers['FOO'] = 'bar'; + route.continue({ headers }); + }); + await page.goto(server.EMPTY_PAGE); + const [request] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz')) + ]); + expect(request.headers['foo']).toBe('bar'); +}); + +it('should amend method', async({page, server}) => { + const sRequest = server.waitForRequest('/sleep.zzz'); + await page.goto(server.EMPTY_PAGE); + await page.route('**/*', route => route.continue({ method: 'POST' })); + const [request] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz')) + ]); + expect(request.method).toBe('POST'); + expect((await sRequest).method).toBe('POST'); +}); + +it('should amend method on main request', async({page, server}) => { + const request = server.waitForRequest('/empty.html'); + await page.route('**/*', route => route.continue({ method: 'POST' })); + await page.goto(server.EMPTY_PAGE); + expect((await request).method).toBe('POST'); +}); + +it('should amend post data', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.route('**/*', route => { + route.continue({ postData: 'doggo' }); + }); + const [serverRequest] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })) + ]); + expect((await serverRequest.postBody).toString('utf8')).toBe('doggo'); +}); + +it('should amend utf8 post data', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.route('**/*', route => { + route.continue({ postData: 'пушкин' }); + }); + const [serverRequest] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })) + ]); + expect(serverRequest.method).toBe('POST'); + expect((await serverRequest.postBody).toString('utf8')).toBe('пушкин'); +}); + +it('should amend longer post data', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.route('**/*', route => { + route.continue({ postData: 'doggo-is-longer-than-birdy' }); + }); + const [serverRequest] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })) + ]); + expect(serverRequest.method).toBe('POST'); + expect((await serverRequest.postBody).toString('utf8')).toBe('doggo-is-longer-than-birdy'); +}); + +it('should amend binary post data', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const arr = Array.from(Array(256).keys()); + await page.route('**/*', route => { + route.continue({ postData: Buffer.from(arr) }); + }); + const [serverRequest] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })) + ]); + expect(serverRequest.method).toBe('POST'); + const buffer = await serverRequest.postBody; + expect(buffer.length).toBe(arr.length); + for (let i = 0; i < arr.length; ++i) + expect(arr[i]).toBe(buffer[i]); +}); diff --git a/test/request-fulfill.spec.js b/test/request-fulfill.spec.js new file mode 100644 index 0000000000000..b8d765c2136ae --- /dev/null +++ b/test/request-fulfill.spec.js @@ -0,0 +1,179 @@ +/** + * 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 fs = require('fs'); +const path = require('path'); +const { helper } = require('../lib/helper'); +const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT, HEADLESS} = testOptions; + +it('should work', async({page, server}) => { + await page.route('**/*', route => { + route.fulfill({ + status: 201, + headers: { + foo: 'bar' + }, + contentType: 'text/html', + body: 'Yo, page!' + }); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(201); + expect(response.headers().foo).toBe('bar'); + expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); +}); + +it('should work with status code 422', async({page, server}) => { + await page.route('**/*', route => { + route.fulfill({ + status: 422, + body: 'Yo, page!' + }); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(422); + expect(response.statusText()).toBe('Unprocessable Entity'); + expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); +}); + +it.skip(FFOX && !HEADLESS)('should allow mocking binary responses', async({page, server}) => { + // Firefox headful produces a different image. + await page.route('**/*', route => { + const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png')); + route.fulfill({ + contentType: 'image/png', + body: imageBuffer + }); + }); + await page.evaluate(PREFIX => { + const img = document.createElement('img'); + img.src = PREFIX + '/does-not-exist.png'; + document.body.appendChild(img); + return new Promise(fulfill => img.onload = fulfill); + }, server.PREFIX); + const img = await page.$('img'); + expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); +}); + +it.skip(FFOX && !HEADLESS)('should allow mocking svg with charset', async({page, server}) => { + // Firefox headful produces a different image. + await page.route('**/*', route => { + route.fulfill({ + contentType: 'image/svg+xml ; charset=utf-8', + body: '' + }); + }); + await page.evaluate(PREFIX => { + const img = document.createElement('img'); + img.src = PREFIX + '/does-not-exist.svg'; + document.body.appendChild(img); + return new Promise((f, r) => { img.onload = f; img.onerror = r; }); + }, server.PREFIX); + const img = await page.$('img'); + expect(await img.screenshot()).toBeGolden('mock-svg.png'); +}); + +it('should work with file path', async({page, server}) => { + await page.route('**/*', route => route.fulfill({ contentType: 'shouldBeIgnored', path: path.join(__dirname, 'assets', 'pptr.png') })); + await page.evaluate(PREFIX => { + const img = document.createElement('img'); + img.src = PREFIX + '/does-not-exist.png'; + document.body.appendChild(img); + return new Promise(fulfill => img.onload = fulfill); + }, server.PREFIX); + const img = await page.$('img'); + expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); +}); + +it('should stringify intercepted request response headers', async({page, server}) => { + await page.route('**/*', route => { + route.fulfill({ + status: 200, + headers: { + 'foo': true + }, + body: 'Yo, page!' + }); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(200); + const headers = response.headers(); + expect(headers.foo).toBe('true'); + expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); +}); + +it('should not modify the headers sent to the server', async({page, server}) => { + await page.goto(server.PREFIX + '/empty.html'); + const interceptedRequests = []; + + //this is just to enable request interception, which disables caching in chromium + await page.route(server.PREFIX + '/unused'); + + server.setRoute('/something', (request, response) => { + interceptedRequests.push(request); + response.writeHead(200, { 'Access-Control-Allow-Origin': '*' }); + response.end('done'); + }); + + const text = await page.evaluate(async url => { + const data = await fetch(url); + return data.text(); + }, server.CROSS_PROCESS_PREFIX + '/something'); + expect(text).toBe('done'); + + let playwrightRequest; + await page.route(server.CROSS_PROCESS_PREFIX + '/something', (route, request) => { + playwrightRequest = request; + route.continue({ + headers: { + ...request.headers() + } + }); + }); + + const textAfterRoute = await page.evaluate(async url => { + const data = await fetch(url); + return data.text(); + }, server.CROSS_PROCESS_PREFIX + '/something'); + expect(textAfterRoute).toBe('done'); + + expect(interceptedRequests.length).toBe(2); + expect(interceptedRequests[1].headers).toEqual(interceptedRequests[0].headers); +}); + +it('should include the origin header', async({page, server}) => { + await page.goto(server.PREFIX + '/empty.html'); + let interceptedRequest; + await page.route(server.CROSS_PROCESS_PREFIX + '/something', (route, request) => { + interceptedRequest = request; + route.fulfill({ + headers: { + 'Access-Control-Allow-Origin': '*', + }, + contentType: 'text/plain', + body: 'done' + }); + }); + + const text = await page.evaluate(async url => { + const data = await fetch(url); + return data.text(); + }, server.CROSS_PROCESS_PREFIX + '/something'); + expect(text).toBe('done'); + expect(interceptedRequest.headers()['origin']).toEqual(server.PREFIX); +}); diff --git a/test/utils.js b/test/utils.js index 0e6cf6a9992ee..423b293189ce5 100644 --- a/test/utils.js +++ b/test/utils.js @@ -23,6 +23,7 @@ const removeFolder = require('rimraf'); const {FlakinessDashboard} = require('../utils/flakiness-dashboard'); const PROJECT_ROOT = fs.existsSync(path.join(__dirname, '..', 'package.json')) ? path.join(__dirname, '..') : path.join(__dirname, '..', '..'); +const browserName = process.env.BROWSER || 'chromium'; let platform = os.platform(); @@ -246,6 +247,21 @@ const utils = module.exports = { }; return logger; }, + + expectSSLError(errorMessage) { + if (browserName === 'chromium') { + expect(errorMessage).toContain('net::ERR_CERT_AUTHORITY_INVALID'); + } else if (browserName === 'webkit') { + if (platform === 'darwin') + expect(errorMessage).toContain('The certificate for this server is invalid'); + else if (platform === 'win32') + expect(errorMessage).toContain('SSL peer certificate or SSH remote key was not OK'); + else + expect(errorMessage).toContain('Unacceptable TLS certificate'); + } else { + expect(errorMessage).toContain('SSL_ERROR_UNKNOWN'); + } + }, }; function valueFromEnv(name, defaultValue) {