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
-
-
-
-
-
-
- This is a description!
-
- `);
- // 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(`
- `);
- 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:
-
`);
- 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:
-
`);
- 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:
`);
- 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:
`);
- 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:
`);
- 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
-
-
`);
- 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
-
-
`);
- 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
-
-
`);
- 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(`My Button `);
-
- 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(`My Button `);
- 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(`
-
- `);
-
- 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
+
+
+
+
+
+
+ This is a description!
+
+ `);
+ // 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(`
+ `);
+ 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:
+
`);
+ 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:
+
`);
+ 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:
`);
+ 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:
`);
+ 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:
`);
+ 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
+
+
`);
+ 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
+
+
`);
+ 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
+
+
`);
+ 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(`My Button `);
+
+ 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(`My Button `);
+ 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(`
+
+ `);
+
+ 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) {