test: remove describes (6) (#3295)
This commit is contained in:
parent
4cbfa09c2c
commit
57490b74c6
|
|
@ -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(`
|
||||
<head>
|
||||
<title>Accessibility Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Inputs</h1>
|
||||
<input placeholder="Empty input" autofocus />
|
||||
<input placeholder="readonly input" readonly />
|
||||
<input placeholder="disabled input" disabled />
|
||||
<input aria-label="Input with whitespace" value=" " />
|
||||
<input value="value only" />
|
||||
<input aria-placeholder="placeholder" value="and a value" />
|
||||
<div aria-hidden="true" id="desc">This is a description!</div>
|
||||
<input aria-placeholder="placeholder" value="and a value" aria-describedby="desc" />
|
||||
</body>`);
|
||||
// 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(`<div>Hello World</div>`);
|
||||
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('<div tabIndex=-1 aria-roledescription="foo">Hi</div>');
|
||||
const snapshot = await page.accessibility.snapshot();
|
||||
expect(snapshot.children[0].roledescription).toEqual('foo');
|
||||
});
|
||||
it('orientation', async({page}) => {
|
||||
await page.setContent('<a href="" role="slider" aria-orientation="vertical">11</a>');
|
||||
const snapshot = await page.accessibility.snapshot();
|
||||
expect(snapshot.children[0].orientation).toEqual('vertical');
|
||||
});
|
||||
it('autocomplete', async({page}) => {
|
||||
await page.setContent('<div role="textbox" aria-autocomplete="list">hi</div>');
|
||||
const snapshot = await page.accessibility.snapshot();
|
||||
expect(snapshot.children[0].autocomplete).toEqual('list');
|
||||
});
|
||||
it('multiselectable', async({page}) => {
|
||||
await page.setContent('<div role="grid" tabIndex=-1 aria-multiselectable=true>hey</div>');
|
||||
const snapshot = await page.accessibility.snapshot();
|
||||
expect(snapshot.children[0].multiselectable).toEqual(true);
|
||||
});
|
||||
it('keyshortcuts', async({page}) => {
|
||||
await page.setContent('<div role="grid" tabIndex=-1 aria-keyshortcuts="foo">hey</div>');
|
||||
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(`
|
||||
<div role="tablist">
|
||||
<div role="tab" aria-selected="true"><b>Tab1</b></div>
|
||||
<div role="tab">Tab2</div>
|
||||
</div>`);
|
||||
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(`
|
||||
<div contenteditable="true">
|
||||
Edit this image: <img src="fakeimage.png" alt="my fake image">
|
||||
</div>`);
|
||||
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(`
|
||||
<div contenteditable="true" role='textbox'>
|
||||
Edit this image: <img src="fakeimage.png" alt="my fake image">
|
||||
</div>`);
|
||||
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(`
|
||||
<div contenteditable="plaintext-only" role='textbox'>Edit this image:<img src="fakeimage.png" alt="my fake image"></div>`);
|
||||
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(`
|
||||
<div contenteditable="plaintext-only">Edit this image:<img src="fakeimage.png" alt="my fake image"></div>`);
|
||||
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(`
|
||||
<div contenteditable="plaintext-only" tabIndex=0>Edit this image:<img src="fakeimage.png" alt="my fake image"></div>`);
|
||||
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(`
|
||||
<div role="textbox" tabIndex=0 aria-checked="true" aria-label="my favorite textbox">
|
||||
this is the inner content
|
||||
<img alt="yo" src="fakeimg.png">
|
||||
</div>`);
|
||||
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(`
|
||||
<div role="checkbox" tabIndex=0 aria-checked="true" aria-label="my favorite checkbox">
|
||||
this is the inner content
|
||||
<img alt="yo" src="fakeimg.png">
|
||||
</div>`);
|
||||
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(`
|
||||
<div role="checkbox" aria-checked="true">
|
||||
this is the inner content
|
||||
<img alt="yo" src="fakeimg.png">
|
||||
</div>`);
|
||||
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(`<button>My Button</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(`<input title="My Input" value="My Value">`);
|
||||
|
||||
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(`
|
||||
<div role="menu" title="My Menu">
|
||||
<div role="menuitem">First Item</div>
|
||||
<div role="menuitem">Second Item</div>
|
||||
<div role="menuitem">Third Item</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
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(`<button>My Button</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(`
|
||||
<div id="root" role="textbox">
|
||||
<div>
|
||||
hello
|
||||
<div>
|
||||
world
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
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(`
|
||||
<title>This is the title</title>
|
||||
<div>This is the content</div>
|
||||
`);
|
||||
const snapshot = await page.accessibility.snapshot();
|
||||
expect(snapshot.name).toBe('This is the title');
|
||||
expect(snapshot.children[0].name).toBe('This is the content');
|
||||
});
|
||||
});
|
||||
370
test/accessibility.spec.js
Normal file
370
test/accessibility.spec.js
Normal file
|
|
@ -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(`
|
||||
<head>
|
||||
<title>Accessibility Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Inputs</h1>
|
||||
<input placeholder="Empty input" autofocus />
|
||||
<input placeholder="readonly input" readonly />
|
||||
<input placeholder="disabled input" disabled />
|
||||
<input aria-label="Input with whitespace" value=" " />
|
||||
<input value="value only" />
|
||||
<input aria-placeholder="placeholder" value="and a value" />
|
||||
<div aria-hidden="true" id="desc">This is a description!</div>
|
||||
<input aria-placeholder="placeholder" value="and a value" aria-describedby="desc" />
|
||||
</body>`);
|
||||
// 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(`<div>Hello World</div>`);
|
||||
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('<div tabIndex=-1 aria-roledescription="foo">Hi</div>');
|
||||
const snapshot = await page.accessibility.snapshot();
|
||||
expect(snapshot.children[0].roledescription).toEqual('foo');
|
||||
});
|
||||
|
||||
it('orientation', async({page}) => {
|
||||
await page.setContent('<a href="" role="slider" aria-orientation="vertical">11</a>');
|
||||
const snapshot = await page.accessibility.snapshot();
|
||||
expect(snapshot.children[0].orientation).toEqual('vertical');
|
||||
});
|
||||
|
||||
it('autocomplete', async({page}) => {
|
||||
await page.setContent('<div role="textbox" aria-autocomplete="list">hi</div>');
|
||||
const snapshot = await page.accessibility.snapshot();
|
||||
expect(snapshot.children[0].autocomplete).toEqual('list');
|
||||
});
|
||||
|
||||
it('multiselectable', async({page}) => {
|
||||
await page.setContent('<div role="grid" tabIndex=-1 aria-multiselectable=true>hey</div>');
|
||||
const snapshot = await page.accessibility.snapshot();
|
||||
expect(snapshot.children[0].multiselectable).toEqual(true);
|
||||
});
|
||||
|
||||
it('keyshortcuts', async({page}) => {
|
||||
await page.setContent('<div role="grid" tabIndex=-1 aria-keyshortcuts="foo">hey</div>');
|
||||
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(`
|
||||
<div role="tablist">
|
||||
<div role="tab" aria-selected="true"><b>Tab1</b></div>
|
||||
<div role="tab">Tab2</div>
|
||||
</div>`);
|
||||
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(`
|
||||
<div contenteditable="true">
|
||||
Edit this image: <img src="fakeimage.png" alt="my fake image">
|
||||
</div>`);
|
||||
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(`
|
||||
<div contenteditable="true" role='textbox'>
|
||||
Edit this image: <img src="fakeimage.png" alt="my fake image">
|
||||
</div>`);
|
||||
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(`
|
||||
<div contenteditable="plaintext-only" role='textbox'>Edit this image:<img src="fakeimage.png" alt="my fake image"></div>`);
|
||||
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(`
|
||||
<div contenteditable="plaintext-only">Edit this image:<img src="fakeimage.png" alt="my fake image"></div>`);
|
||||
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(`
|
||||
<div contenteditable="plaintext-only" tabIndex=0>Edit this image:<img src="fakeimage.png" alt="my fake image"></div>`);
|
||||
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(`
|
||||
<div role="textbox" tabIndex=0 aria-checked="true" aria-label="my favorite textbox">
|
||||
this is the inner content
|
||||
<img alt="yo" src="fakeimg.png">
|
||||
</div>`);
|
||||
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(`
|
||||
<div role="checkbox" tabIndex=0 aria-checked="true" aria-label="my favorite checkbox">
|
||||
this is the inner content
|
||||
<img alt="yo" src="fakeimg.png">
|
||||
</div>`);
|
||||
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(`
|
||||
<div role="checkbox" aria-checked="true">
|
||||
this is the inner content
|
||||
<img alt="yo" src="fakeimg.png">
|
||||
</div>`);
|
||||
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(`<button>My Button</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(`<input title="My Input" value="My Value">`);
|
||||
|
||||
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(`
|
||||
<div role="menu" title="My Menu">
|
||||
<div role="menuitem">First Item</div>
|
||||
<div role="menuitem">Second Item</div>
|
||||
<div role="menuitem">Third Item</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
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(`<button>My Button</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(`
|
||||
<div id="root" role="textbox">
|
||||
<div>
|
||||
hello
|
||||
<div>
|
||||
world
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
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(`
|
||||
<title>This is the title</title>
|
||||
<div>This is the content</div>
|
||||
`);
|
||||
const snapshot = await page.accessibility.snapshot();
|
||||
expect(snapshot.name).toBe('This is the title');
|
||||
expect(snapshot.children[0].name).toBe('This is the content');
|
||||
});
|
||||
78
test/frame-goto.spec.js
Normal file
78
test/frame-goto.spec.js
Normal file
|
|
@ -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]);
|
||||
}
|
||||
});
|
||||
|
|
@ -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(`<input type=file>`);
|
||||
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(`<input type=file>`);
|
||||
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(`<input type=file>`);
|
||||
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(`<input type=file>`);
|
||||
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(`<input type=file>`);
|
||||
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(`<input type=file oninput='javascript:console.timeStamp()'>`);
|
||||
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(`
|
||||
<form action="/upload" method="post" enctype="multipart/form-data" >
|
||||
<input type="file" name="file1">
|
||||
<input type="file" name="file2">
|
||||
<input type="submit" value="Submit">
|
||||
</form>`)
|
||||
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(`<input type=file>`);
|
||||
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(`<input type=file>`);
|
||||
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(`<input type=file>`);
|
||||
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(`
|
||||
<input id=input type=file></input>
|
||||
<script>
|
||||
input.addEventListener('input', e => eventHandled({ type: e.type }));
|
||||
input.addEventListener('change', e => eventHandled({ type: e.type }));
|
||||
</script>`);
|
||||
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(`<input type=file>`);
|
||||
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(`<input multiple type=file>`);
|
||||
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(`<input multiple webkitdirectory type=file>`);
|
||||
const [fileChooser] = await Promise.all([
|
||||
page.waitForEvent('filechooser'),
|
||||
page.click('input'),
|
||||
]);
|
||||
expect(fileChooser.isMultiple()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -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(`
|
||||
<form action='/rredirect' method='post'>
|
||||
<input type="hidden" id="foo" name="foo" value="FOOBAR">
|
||||
</form>
|
||||
`);
|
||||
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,<div>yo</div>';
|
||||
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,<div>yo</div>';
|
||||
const text = await page.evaluate(url => fetch(url).then(r => r.text()), dataURL);
|
||||
expect(text).toBe('<div>yo</div>');
|
||||
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,<link rel="stylesheet" href="${server.PREFIX}/fonts?helvetica|arial"/>`);
|
||||
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('<iframe></iframe>');
|
||||
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: '<svg width="50" height="50" version="1.1" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="30" height="30" stroke="black" fill="transparent" stroke-width="5"/></svg>'
|
||||
});
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
114
test/interception.spec.js
Normal file
114
test/interception.spec.js
Normal file
|
|
@ -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);
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
37
test/navigation.spec.js
Normal file
37
test/navigation.spec.js
Normal file
|
|
@ -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(`<a href="${server.EMPTY_PAGE}" target="_blank">Click me</a>`);
|
||||
});
|
||||
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(`<a href="${server.CROSS_PROCESS_PREFIX}/empty.html" target="_blank">Click me</a>`);
|
||||
});
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await page.click('"Click me"');
|
||||
});
|
||||
485
test/page-goto.spec.js
Normal file
485
test/page-goto.spec.js
Normal file
|
|
@ -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,<div>yo</div>';
|
||||
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');
|
||||
});
|
||||
96
test/page-history.spec.js
Normal file
96
test/page-history.spec.js
Normal file
|
|
@ -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(`<a href='${url2}'>url2</a>`);
|
||||
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');
|
||||
});
|
||||
146
test/page-network-idle.spec.js
Normal file
146
test/page-network-idle.spec.js
Normal file
|
|
@ -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<void>} 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(`<script>fetch('foo.js');</script>`);
|
||||
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(`<script src='networkidle.js'></script>`, { 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(`<script>fetch('foo.js');</script>`);
|
||||
await networkIdleTest(page.mainFrame(), server, () => {
|
||||
return page.setContent(`<script src='networkidle.js'></script>`, { 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(`<iframe src='networkidle.html'></iframe>`, { 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' });
|
||||
});
|
||||
});
|
||||
509
test/page-route.spec.js
Normal file
509
test/page-route.spec.js
Normal file
|
|
@ -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(`
|
||||
<form action='/rredirect' method='post'>
|
||||
<input type="hidden" id="foo" name="foo" value="FOOBAR">
|
||||
</form>
|
||||
`);
|
||||
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,<div>yo</div>';
|
||||
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,<div>yo</div>';
|
||||
const text = await page.evaluate(url => fetch(url).then(r => r.text()), dataURL);
|
||||
expect(text).toBe('<div>yo</div>');
|
||||
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,<link rel="stylesheet" href="${server.PREFIX}/fonts?helvetica|arial"/>`);
|
||||
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('<iframe></iframe>');
|
||||
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']);
|
||||
}
|
||||
});
|
||||
266
test/page-set-input-files.spec.js
Normal file
266
test/page-set-input-files.spec.js
Normal file
|
|
@ -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(`<input type=file>`);
|
||||
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(`<input type=file>`);
|
||||
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(`<input type=file>`);
|
||||
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(`<input type=file>`);
|
||||
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(`<input type=file>`);
|
||||
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(`<input type=file oninput='javascript:console.timeStamp()'>`);
|
||||
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(`
|
||||
<form action="/upload" method="post" enctype="multipart/form-data" >
|
||||
<input type="file" name="file1">
|
||||
<input type="file" name="file2">
|
||||
<input type="submit" value="Submit">
|
||||
</form>`)
|
||||
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(`<input type=file>`);
|
||||
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(`<input type=file>`);
|
||||
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(`<input type=file>`);
|
||||
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(`
|
||||
<input id=input type=file></input>
|
||||
<script>
|
||||
input.addEventListener('input', e => eventHandled({ type: e.type }));
|
||||
input.addEventListener('change', e => eventHandled({ type: e.type }));
|
||||
</script>`);
|
||||
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(`<input type=file>`);
|
||||
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(`<input multiple type=file>`);
|
||||
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(`<input multiple webkitdirectory type=file>`);
|
||||
const [fileChooser] = await Promise.all([
|
||||
page.waitForEvent('filechooser'),
|
||||
page.click('input'),
|
||||
]);
|
||||
expect(fileChooser.isMultiple()).toBe(true);
|
||||
});
|
||||
183
test/page-wait-for-load-state.spec.js
Normal file
183
test/page-wait-for-load-state.spec.js
Normal file
|
|
@ -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('<a target=_blank rel="opener" href="/one-style.html">yo</a>');
|
||||
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;
|
||||
});
|
||||
250
test/page-wait-for-navigation.spec.js
Normal file
250
test/page-wait-for-navigation.spec.js
Normal file
|
|
@ -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(`<a href='#foobar'>foobar</a>`);
|
||||
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(`<a href='${httpsServer.EMPTY_PAGE}'>foobar</a>`);
|
||||
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(`
|
||||
<a onclick='javascript:pushState()'>SPA</a>
|
||||
<script>
|
||||
function pushState() { history.pushState({}, '', 'wow.html') }
|
||||
</script>
|
||||
`);
|
||||
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(`
|
||||
<a onclick='javascript:replaceState()'>SPA</a>
|
||||
<script>
|
||||
function replaceState() { history.replaceState({}, '', '/replaced.html') }
|
||||
</script>
|
||||
`);
|
||||
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(`
|
||||
<a id=back onclick='javascript:goBack()'>back</a>
|
||||
<a id=forward onclick='javascript:goForward()'>forward</a>
|
||||
<script>
|
||||
function goBack() { history.back(); }
|
||||
function goForward() { history.forward(); }
|
||||
history.pushState({}, '', '/first.html');
|
||||
history.pushState({}, '', '/second.html');
|
||||
</script>
|
||||
`);
|
||||
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');
|
||||
});
|
||||
115
test/request-continue.spec.js
Normal file
115
test/request-continue.spec.js
Normal file
|
|
@ -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]);
|
||||
});
|
||||
179
test/request-fulfill.spec.js
Normal file
179
test/request-fulfill.spec.js
Normal file
|
|
@ -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: '<svg width="50" height="50" version="1.1" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="30" height="30" stroke="black" fill="transparent" stroke-width="5"/></svg>'
|
||||
});
|
||||
});
|
||||
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);
|
||||
});
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue