test: remove module.export.describe wrapper (#1716)

This commit is contained in:
Dmitry Gozman 2020-04-08 15:19:09 -07:00 committed by GitHub
parent 2ef8e26602
commit ade9d23c28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 10728 additions and 10940 deletions

View file

@ -15,340 +15,336 @@
* limitations under the License.
*/
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({FFOX, CHROMIUM, WEBKIT, MAC}) {
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
describe('Accessibility', function() {
it('should work', async function({page}) {
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(`
<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
]
<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);
});
it('should work with regular text', async({page}) => {
await page.setContent(`<div>Hello World</div>`);
// 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({
role: FFOX ? 'text leaf' : 'text',
name: 'Hello World',
});
expect(snapshot.children[0]).toEqual(golden);
});
it('roledescription', async({page}) => {
await page.setContent('<div tabIndex=-1 aria-roledescription="foo">Hi</div>');
// 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].roledescription).toEqual('foo');
expect(snapshot.children[0]).toEqual(golden);
});
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}) {
// 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 role="tablist">
<div role="tab" aria-selected="true"><b>Tab1</b></div>
<div role="tab">Tab2</div>
</div>`);
const golden = {
role: FFOX ? 'document' : 'WebArea',
<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: '',
children: [{
role: 'tab',
name: 'Tab1',
selected: true
}, {
role: 'tab',
name: 'Tab2'
}]
};
expect(await page.accessibility.snapshot()).toEqual(golden);
value: 'Edit this image:'
});
});
// WebKit rich text accessibility is iffy
it.skip(WEBKIT)('rich text editable fields should have children', async function({page}) {
it('plain text field without role should not have content', 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'
}]
} : {
<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: '',
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);
name: ''
});
});
// WebKit rich text accessibility is iffy
it.skip(WEBKIT)('rich text editable fields with role should have children', async function({page}) {
it('plain text field with tabindex and without role should not have content', 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'
}]
};
<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(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: ''
});
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}) {
});
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="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);
<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('checkbox with and tabIndex and label should not have children', async function({page}) {
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 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 id="root" role="textbox">
<div>
hello
<div>
hello
<div>
world
</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);
});
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);
});
});
});
};
});

View file

@ -48,56 +48,54 @@ function traceAPICoverage(apiCoverage, events, className, classType) {
}
}
module.exports.describe = function({browserType}) {
describe('**API COVERAGE**', () => {
const BROWSER_CONFIGS = [
{
name: 'Firefox',
events: require('../lib/events').Events,
missingCoverage: ['browserContext.setGeolocation', 'browserContext.setOffline', 'cDPSession.send', 'cDPSession.detach'],
describe.skip(!process.env.COVERAGE)('**API COVERAGE**', () => {
const BROWSER_CONFIGS = [
{
name: 'Firefox',
events: require('../lib/events').Events,
missingCoverage: ['browserContext.setGeolocation', 'browserContext.setOffline', 'cDPSession.send', 'cDPSession.detach'],
},
{
name: 'WebKit',
events: require('../lib/events').Events,
missingCoverage: ['browserContext.clearPermissions', 'cDPSession.send', 'cDPSession.detach'],
},
{
name: 'Chromium',
events: {
...require('../lib/events').Events,
...require('../lib/chromium/events').Events,
},
{
name: 'WebKit',
events: require('../lib/events').Events,
missingCoverage: ['browserContext.clearPermissions', 'cDPSession.send', 'cDPSession.detach'],
},
{
name: 'Chromium',
events: {
...require('../lib/events').Events,
...require('../lib/chromium/events').Events,
},
missingCoverage: [],
},
];
const browserConfig = BROWSER_CONFIGS.find(config => config.name.toLowerCase() === browserType.name());
const events = browserConfig.events;
const api = require('../lib/api');
missingCoverage: [],
},
];
const browserConfig = BROWSER_CONFIGS.find(config => config.name.toLowerCase() === browserType.name());
const events = browserConfig.events;
const api = require('../lib/api');
const coverage = new Map();
Object.keys(api).forEach(apiName => {
if (BROWSER_CONFIGS.some(config => apiName.startsWith(config.name)) && !apiName.startsWith(browserConfig.name))
return;
traceAPICoverage(coverage, events, apiName, api[apiName]);
});
it('should call all API methods', () => {
const ignoredMethods = new Set(browserConfig.missingCoverage);
const missingMethods = [];
const extraIgnoredMethods = [];
for (const method of coverage.keys()) {
// Sometimes we already have a background page while launching, before adding a listener.
if (method === 'chromiumBrowserContext.emit("backgroundpage")')
continue;
if (!coverage.get(method) && !ignoredMethods.has(method))
missingMethods.push(method);
else if (coverage.get(method) && ignoredMethods.has(method))
extraIgnoredMethods.push(method);
}
if (extraIgnoredMethods.length)
throw new Error('Certain API Methods are called and should not be ignored: ' + extraIgnoredMethods.join(', '));
if (missingMethods.length)
throw new Error('Certain API Methods are not called: ' + missingMethods.join(', '));
});
const coverage = new Map();
Object.keys(api).forEach(apiName => {
if (BROWSER_CONFIGS.some(config => apiName.startsWith(config.name)) && !apiName.startsWith(browserConfig.name))
return;
traceAPICoverage(coverage, events, apiName, api[apiName]);
});
};
it('should call all API methods', () => {
const ignoredMethods = new Set(browserConfig.missingCoverage);
const missingMethods = [];
const extraIgnoredMethods = [];
for (const method of coverage.keys()) {
// Sometimes we already have a background page while launching, before adding a listener.
if (method === 'chromiumBrowserContext.emit("backgroundpage")')
continue;
if (!coverage.get(method) && !ignoredMethods.has(method))
missingMethods.push(method);
else if (coverage.get(method) && ignoredMethods.has(method))
extraIgnoredMethods.push(method);
}
if (extraIgnoredMethods.length)
throw new Error('Certain API Methods are called and should not be ignored: ' + extraIgnoredMethods.join(', '));
if (missingMethods.length)
throw new Error('Certain API Methods are not called: ' + missingMethods.join(', '));
});
});

View file

@ -15,216 +15,212 @@
* limitations under the License.
*/
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({playwright, MAC, WIN, FFOX, CHROMIUM, WEBKIT}) {
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
describe('Auto waiting', () => {
it('should await navigation when clicking anchor', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
await page.setContent(`<a href="${server.EMPTY_PAGE}">empty.html</a>`);
await Promise.all([
page.click('a').then(() => messages.push('click')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|click');
describe('Auto waiting', () => {
it('should await navigation when clicking anchor', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
it('should await cross-process navigation when clicking anchor', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
await page.setContent(`<a href="${server.CROSS_PROCESS_PREFIX + '/empty.html'}">empty.html</a>`);
await page.setContent(`<a href="${server.EMPTY_PAGE}">empty.html</a>`);
await Promise.all([
page.click('a').then(() => messages.push('click')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|click');
await Promise.all([
page.click('a').then(() => messages.push('click')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|click');
});
it('should await cross-process navigation when clicking anchor', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
it('should await form-get on click', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html?foo=bar', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
await page.setContent(`
<form action="${server.EMPTY_PAGE}" method="get">
<input name="foo" value="bar">
<input type="submit" value="Submit">
</form>`);
await page.setContent(`<a href="${server.CROSS_PROCESS_PREFIX + '/empty.html'}">empty.html</a>`);
await Promise.all([
page.click('input[type=submit]').then(() => messages.push('click')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|click');
await Promise.all([
page.click('a').then(() => messages.push('click')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|click');
});
it('should await form-get on click', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html?foo=bar', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
it('should await form-post on click', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
await page.setContent(`
<form action="${server.EMPTY_PAGE}" method="post">
<input name="foo" value="bar">
<input type="submit" value="Submit">
</form>`);
await page.setContent(`
<form action="${server.EMPTY_PAGE}" method="get">
<input name="foo" value="bar">
<input type="submit" value="Submit">
</form>`);
await Promise.all([
page.click('input[type=submit]').then(() => messages.push('click')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|click');
await Promise.all([
page.click('input[type=submit]').then(() => messages.push('click')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|click');
});
it('should await form-post on click', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
it('should await navigation when assigning location', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
await Promise.all([
page.evaluate(`window.location.href = "${server.EMPTY_PAGE}"`).then(() => messages.push('evaluate')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|evaluate');
});
it.fail(CHROMIUM)('should await navigation when assigning location twice', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html?cancel', async (req, res) => { res.end('done'); });
server.setRoute('/empty.html?override', async (req, res) => { messages.push('routeoverride'); res.end('done'); });
await Promise.all([
page.evaluate(`
window.location.href = "${server.EMPTY_PAGE}?cancel";
window.location.href = "${server.EMPTY_PAGE}?override";
`).then(() => messages.push('evaluate')),
]);
expect(messages.join('|')).toBe('routeoverride|evaluate');
});
it('should await navigation when evaluating reload', async({page, server}) => {
const messages = [];
await page.goto(server.EMPTY_PAGE);
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
await Promise.all([
page.evaluate(`window.location.reload()`).then(() => messages.push('evaluate')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|evaluate');
});
it('should await navigating specified target', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
await page.setContent(`
<form action="${server.EMPTY_PAGE}" method="post">
<input name="foo" value="bar">
<input type="submit" value="Submit">
</form>`);
await page.setContent(`
<a href="${server.EMPTY_PAGE}" target=target>empty.html</a>
<iframe name=target></iframe>
`);
const frame = page.frame({ name: 'target' });
await Promise.all([
page.click('a').then(() => messages.push('click')),
frame.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(frame.url()).toBe(server.EMPTY_PAGE);
expect(messages.join('|')).toBe('route|domcontentloaded|click');
await Promise.all([
page.click('input[type=submit]').then(() => messages.push('click')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|click');
});
it('should await navigation when assigning location', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
it('should work with waitUntil: nowait', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
await page.setContent(`<a href="${server.EMPTY_PAGE}">empty.html</a>`);
await Promise.all([
page.click('a', { waitUntil: 'nowait' }).then(() => messages.push('click')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
page.waitForNavigation({ waitUntil: 'load' }).then(() => messages.push('load')),
]);
expect(messages.join('|')).toBe('click|domcontentloaded|load');
await Promise.all([
page.evaluate(`window.location.href = "${server.EMPTY_PAGE}"`).then(() => messages.push('evaluate')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|evaluate');
});
it.fail(CHROMIUM)('should await navigation when assigning location twice', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html?cancel', async (req, res) => { res.end('done'); });
server.setRoute('/empty.html?override', async (req, res) => { messages.push('routeoverride'); res.end('done'); });
await Promise.all([
page.evaluate(`
window.location.href = "${server.EMPTY_PAGE}?cancel";
window.location.href = "${server.EMPTY_PAGE}?override";
`).then(() => messages.push('evaluate')),
]);
expect(messages.join('|')).toBe('routeoverride|evaluate');
});
it('should await navigation when evaluating reload', async({page, server}) => {
const messages = [];
await page.goto(server.EMPTY_PAGE);
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
it('should work with waitUntil: load', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
await page.setContent(`<a href="${server.EMPTY_PAGE}">empty.html</a>`);
await Promise.all([
page.click('a', { waitUntil: 'load' }).then(() => messages.push('click')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
page.waitForNavigation({ waitUntil: 'load' }).then(() => messages.push('load')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|load|click');
await Promise.all([
page.evaluate(`window.location.reload()`).then(() => messages.push('evaluate')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|evaluate');
});
it('should await navigating specified target', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
await page.setContent(`
<a href="${server.EMPTY_PAGE}" target=target>empty.html</a>
<iframe name=target></iframe>
`);
const frame = page.frame({ name: 'target' });
await Promise.all([
page.click('a').then(() => messages.push('click')),
frame.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
]);
expect(frame.url()).toBe(server.EMPTY_PAGE);
expect(messages.join('|')).toBe('route|domcontentloaded|click');
});
it('should work with waitUntil: nowait', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
await page.setContent(`<a href="${server.EMPTY_PAGE}">empty.html</a>`);
await Promise.all([
page.click('a', { waitUntil: 'nowait' }).then(() => messages.push('click')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
page.waitForNavigation({ waitUntil: 'load' }).then(() => messages.push('load')),
]);
expect(messages.join('|')).toBe('click|domcontentloaded|load');
});
it('should work with waitUntil: load', async({page, server}) => {
const messages = [];
server.setRoute('/empty.html', async (req, res) => {
messages.push('route');
res.setHeader('Content-Type', 'text/html');
res.end(`<link rel='stylesheet' href='./one-style.css'>`);
});
await page.setContent(`<a href="${server.EMPTY_PAGE}">empty.html</a>`);
await Promise.all([
page.click('a', { waitUntil: 'load' }).then(() => messages.push('click')),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).then(() => messages.push('domcontentloaded')),
page.waitForNavigation({ waitUntil: 'load' }).then(() => messages.push('load')),
]);
expect(messages.join('|')).toBe('route|domcontentloaded|load|click');
});
});
describe('Auto waiting should not hang when', () => {
it('clicking on links which do not commit navigation', async({page, server, httpsServer}) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<a href='${httpsServer.EMPTY_PAGE}'>foobar</a>`);
await page.click('a');
});
it('calling window.stop async', async({page, server, httpsServer}) => {
server.setRoute('/empty.html', async (req, res) => {});
await page.evaluate((url) => {
window.location.href = url;
setTimeout(() => window.stop(), 100);
}, server.EMPTY_PAGE);
});
it.fail(CHROMIUM)('calling window.stop sync', async({page, server, httpsServer}) => {
// Flaky, see https://github.com/microsoft/playwright/pull/1630/checks?check_run_id=553475173.
// We only get Page.frameStoppedLoading, but do not know that navigation was aborted or
// that navigation request was cancelled.
await page.evaluate((url) => {
window.location.href = url;
window.stop();
}, server.EMPTY_PAGE);
});
it('assigning location to about:blank', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(`window.location.href = "about:blank";`);
});
it('assigning location to about:blank after non-about:blank', async({page, server}) => {
server.setRoute('/empty.html', async (req, res) => {});
await page.evaluate(`
window.location.href = "${server.EMPTY_PAGE}";
window.location.href = "about:blank";`);
});
it('calling window.open and window.close', async function({page, server}) {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
const popup = window.open(window.location.href);
popup.close();
});
});
describe('Auto waiting should not hang when', () => {
it('clicking on links which do not commit navigation', async({page, server, httpsServer}) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<a href='${httpsServer.EMPTY_PAGE}'>foobar</a>`);
await page.click('a');
});
it('calling window.stop async', async({page, server, httpsServer}) => {
server.setRoute('/empty.html', async (req, res) => {});
await page.evaluate((url) => {
window.location.href = url;
setTimeout(() => window.stop(), 100);
}, server.EMPTY_PAGE);
});
it.fail(CHROMIUM)('calling window.stop sync', async({page, server, httpsServer}) => {
// Flaky, see https://github.com/microsoft/playwright/pull/1630/checks?check_run_id=553475173.
// We only get Page.frameStoppedLoading, but do not know that navigation was aborted or
// that navigation request was cancelled.
await page.evaluate((url) => {
window.location.href = url;
window.stop();
}, server.EMPTY_PAGE);
});
it('assigning location to about:blank', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(`window.location.href = "about:blank";`);
});
it('assigning location to about:blank after non-about:blank', async({page, server}) => {
server.setRoute('/empty.html', async (req, res) => {});
await page.evaluate(`
window.location.href = "${server.EMPTY_PAGE}";
window.location.href = "about:blank";`);
});
it('calling window.open and window.close', async function({page, server}) {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
const popup = window.open(window.location.href);
popup.close();
});
});
});
};
});

View file

@ -14,31 +14,27 @@
* limitations under the License.
*/
/**
* @type {BrowserTestSuite}
*/
module.exports.describe = function({playwright, CHROMIUM, WEBKIT}) {
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
describe('Browser.newPage', function() {
it('should create new page', async function({browser}) {
const page1 = await browser.newPage();
expect(browser.contexts().length).toBe(1);
describe('Browser.newPage', function() {
it('should create new page', async function({browser}) {
const page1 = await browser.newPage();
expect(browser.contexts().length).toBe(1);
const page2 = await browser.newPage();
expect(browser.contexts().length).toBe(2);
const page2 = await browser.newPage();
expect(browser.contexts().length).toBe(2);
await page1.close();
expect(browser.contexts().length).toBe(1);
await page1.close();
expect(browser.contexts().length).toBe(1);
await page2.close();
expect(browser.contexts().length).toBe(0);
});
it('should throw upon second create new page', async function({browser}) {
const page = await browser.newPage();
let error;
await page.context().newPage().catch(e => error = e);
await page.close();
expect(error.message).toContain('Please use browser.newContext()');
});
await page2.close();
expect(browser.contexts().length).toBe(0);
});
};
it('should throw upon second create new page', async function({browser}) {
const page = await browser.newPage();
let error;
await page.context().newPage().catch(e => error = e);
await page.close();
expect(error.message).toContain('Please use browser.newContext()');
});
});

File diff suppressed because it is too large Load diff

View file

@ -14,15 +14,11 @@
* limitations under the License.
*/
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({WIN, WEBKIT}) {
const {FFOX, CHROMIUM, WEBKIT, WIN} = require('./utils').testOptions(browserType);
describe('Capabilities', function() {
it.fail(WEBKIT && WIN)('Web Assembly should work', async function({page, server}) {
await page.goto(server.PREFIX + '/wasm/table2.html');
expect(await page.evaluate(() => loadTable())).toBe('42, 83');
});
describe('Capabilities', function() {
it.fail(WEBKIT && WIN)('Web Assembly should work', async function({page, server}) {
await page.goto(server.PREFIX + '/wasm/table2.html');
expect(await page.evaluate(() => loadTable())).toBe('42, 83');
});
};
});

View file

@ -14,67 +14,62 @@
* limitations under the License.
*/
/**
* @type {ChromiumTestSuite}
*/
module.exports.describe = function({playwright, FFOX, CHROMIUM, WEBKIT}) {
const {FFOX, CHROMIUM, WEBKIT} = require('../utils').testOptions(browserType);
describe('ChromiumBrowserContext', function() {
it('should create a worker from a service worker', async({browser, page, server, context}) => {
const [worker] = await Promise.all([
context.waitForEvent('serviceworker'),
page.goto(server.PREFIX + '/serviceworkers/empty/sw.html')
]);
expect(await worker.evaluate(() => self.toString())).toBe('[object ServiceWorkerGlobalScope]');
});
it('serviceWorkers() should return current workers', async({browser, page, server, context}) => {
const [worker1] = await Promise.all([
context.waitForEvent('serviceworker'),
page.goto(server.PREFIX + '/serviceworkers/empty/sw.html')
]);
let workers = context.serviceWorkers();
expect(workers.length).toBe(1);
const [worker2] = await Promise.all([
context.waitForEvent('serviceworker'),
page.goto(server.CROSS_PROCESS_PREFIX + '/serviceworkers/empty/sw.html')
]);
workers = context.serviceWorkers();
expect(workers.length).toBe(2);
expect(workers).toContain(worker1);
expect(workers).toContain(worker2);
});
it('should not create a worker from a shared worker', async({browser, page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
let serviceWorkerCreated;
context.once('serviceworker', () => serviceWorkerCreated = true);
await page.evaluate(() => {
new SharedWorker('data:text/javascript,console.log("hi")');
});
expect(serviceWorkerCreated).not.toBeTruthy();
});
describe('ChromiumBrowserContext', function() {
it('should create a worker from a service worker', async({browser, page, server, context}) => {
const [worker] = await Promise.all([
context.waitForEvent('serviceworker'),
page.goto(server.PREFIX + '/serviceworkers/empty/sw.html')
]);
expect(await worker.evaluate(() => self.toString())).toBe('[object ServiceWorkerGlobalScope]');
});
it('serviceWorkers() should return current workers', async({browser, page, server, context}) => {
const [worker1] = await Promise.all([
context.waitForEvent('serviceworker'),
page.goto(server.PREFIX + '/serviceworkers/empty/sw.html')
]);
let workers = context.serviceWorkers();
expect(workers.length).toBe(1);
describe('Chromium-Specific Page Tests', function() {
it('Page.route should work with intervention headers', async({server, page}) => {
server.setRoute('/intervention', (req, res) => res.end(`
<script>
document.write('<script src="${server.CROSS_PROCESS_PREFIX}/intervention.js">' + '</scr' + 'ipt>');
</script>
`));
server.setRedirect('/intervention.js', '/redirect.js');
let serverRequest = null;
server.setRoute('/redirect.js', (req, res) => {
serverRequest = req;
res.end('console.log(1);');
});
await page.route('*', route => route.continue());
await page.goto(server.PREFIX + '/intervention');
// Check for feature URL substring rather than https://www.chromestatus.com to
// make it work with Edgium.
expect(serverRequest.headers.intervention).toContain('feature/5718547946799104');
});
const [worker2] = await Promise.all([
context.waitForEvent('serviceworker'),
page.goto(server.CROSS_PROCESS_PREFIX + '/serviceworkers/empty/sw.html')
]);
workers = context.serviceWorkers();
expect(workers.length).toBe(2);
expect(workers).toContain(worker1);
expect(workers).toContain(worker2);
});
it('should not create a worker from a shared worker', async({browser, page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
let serviceWorkerCreated;
context.once('serviceworker', () => serviceWorkerCreated = true);
await page.evaluate(() => {
new SharedWorker('data:text/javascript,console.log("hi")');
});
expect(serviceWorkerCreated).not.toBeTruthy();
});
});
};
describe('Chromium-Specific Page Tests', function() {
it('Page.route should work with intervention headers', async({server, page}) => {
server.setRoute('/intervention', (req, res) => res.end(`
<script>
document.write('<script src="${server.CROSS_PROCESS_PREFIX}/intervention.js">' + '</scr' + 'ipt>');
</script>
`));
server.setRedirect('/intervention.js', '/redirect.js');
let serverRequest = null;
server.setRoute('/redirect.js', (req, res) => {
serverRequest = req;
res.end('console.log(1);');
});
await page.route('*', route => route.continue());
await page.goto(server.PREFIX + '/intervention');
// Check for feature URL substring rather than https://www.chromestatus.com to
// make it work with Edgium.
expect(serverRequest.headers.intervention).toContain('feature/5718547946799104');
});
});

View file

@ -14,175 +14,171 @@
* limitations under the License.
*/
/**
* @type {ChromiumTestSuite}
*/
module.exports.describe = function({FFOX, CHROMIUM, WEBKIT}) {
const {FFOX, CHROMIUM, WEBKIT} = require('../utils').testOptions(browserType);
describe('JSCoverage', function() {
it('should work', async function({page, server}) {
await page.coverage.startJSCoverage();
await page.goto(server.PREFIX + '/jscoverage/simple.html', { waitUntil: 'load' });
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(1);
expect(coverage[0].url).toContain('/jscoverage/simple.html');
expect(coverage[0].functions.find(f => f.functionName === 'foo').ranges[0].count).toEqual(1);
});
it('should report sourceURLs', async function({page, server}) {
await page.coverage.startJSCoverage();
await page.goto(server.PREFIX + '/jscoverage/sourceurl.html');
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(1);
expect(coverage[0].url).toBe('nicename.js');
});
it('should ignore eval() scripts by default', async function({page, server}) {
await page.coverage.startJSCoverage();
await page.goto(server.PREFIX + '/jscoverage/eval.html');
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(1);
});
it('shouldn\'t ignore eval() scripts if reportAnonymousScripts is true', async function({page, server}) {
await page.coverage.startJSCoverage({reportAnonymousScripts: true});
await page.goto(server.PREFIX + '/jscoverage/eval.html');
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.find(entry => entry.url.startsWith('debugger://'))).not.toBe(null);
expect(coverage.length).toBe(2);
});
it('should ignore playwright internal scripts if reportAnonymousScripts is true', async function({page, server}) {
await page.coverage.startJSCoverage({reportAnonymousScripts: true});
await page.goto(server.EMPTY_PAGE);
await page.evaluate('console.log("foo")');
await page.evaluate(() => console.log('bar'));
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(0);
});
it('should report multiple scripts', async function({page, server}) {
await page.coverage.startJSCoverage();
describe('JSCoverage', function() {
it('should work', async function({page, server}) {
await page.coverage.startJSCoverage();
await page.goto(server.PREFIX + '/jscoverage/simple.html', { waitUntil: 'load' });
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(1);
expect(coverage[0].url).toContain('/jscoverage/simple.html');
expect(coverage[0].functions.find(f => f.functionName === 'foo').ranges[0].count).toEqual(1);
});
it('should report sourceURLs', async function({page, server}) {
await page.coverage.startJSCoverage();
await page.goto(server.PREFIX + '/jscoverage/sourceurl.html');
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(1);
expect(coverage[0].url).toBe('nicename.js');
});
it('should ignore eval() scripts by default', async function({page, server}) {
await page.coverage.startJSCoverage();
await page.goto(server.PREFIX + '/jscoverage/eval.html');
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(1);
});
it('shouldn\'t ignore eval() scripts if reportAnonymousScripts is true', async function({page, server}) {
await page.coverage.startJSCoverage({reportAnonymousScripts: true});
await page.goto(server.PREFIX + '/jscoverage/eval.html');
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.find(entry => entry.url.startsWith('debugger://'))).not.toBe(null);
expect(coverage.length).toBe(2);
});
it('should ignore playwright internal scripts if reportAnonymousScripts is true', async function({page, server}) {
await page.coverage.startJSCoverage({reportAnonymousScripts: true});
await page.goto(server.EMPTY_PAGE);
await page.evaluate('console.log("foo")');
await page.evaluate(() => console.log('bar'));
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(0);
});
it('should report multiple scripts', async function({page, server}) {
await page.coverage.startJSCoverage();
await page.goto(server.PREFIX + '/jscoverage/multiple.html');
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(2);
coverage.sort((a, b) => a.url.localeCompare(b.url));
expect(coverage[0].url).toContain('/jscoverage/script1.js');
expect(coverage[1].url).toContain('/jscoverage/script2.js');
});
describe('resetOnNavigation', function() {
it('should report scripts across navigations when disabled', async function({page, server}) {
await page.coverage.startJSCoverage({resetOnNavigation: false});
await page.goto(server.PREFIX + '/jscoverage/multiple.html');
await page.goto(server.EMPTY_PAGE);
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(2);
coverage.sort((a, b) => a.url.localeCompare(b.url));
expect(coverage[0].url).toContain('/jscoverage/script1.js');
expect(coverage[1].url).toContain('/jscoverage/script2.js');
});
describe('resetOnNavigation', function() {
it('should report scripts across navigations when disabled', async function({page, server}) {
await page.coverage.startJSCoverage({resetOnNavigation: false});
await page.goto(server.PREFIX + '/jscoverage/multiple.html');
await page.goto(server.EMPTY_PAGE);
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(2);
});
it('should NOT report scripts across navigations when enabled', async function({page, server}) {
await page.coverage.startJSCoverage(); // Enabled by default.
await page.goto(server.PREFIX + '/jscoverage/multiple.html');
await page.goto(server.EMPTY_PAGE);
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(0);
});
});
it('should not hang when there is a debugger statement', async function({page, server}) {
await page.coverage.startJSCoverage();
it('should NOT report scripts across navigations when enabled', async function({page, server}) {
await page.coverage.startJSCoverage(); // Enabled by default.
await page.goto(server.PREFIX + '/jscoverage/multiple.html');
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
debugger; // eslint-disable-line no-debugger
});
await page.coverage.stopJSCoverage();
const coverage = await page.coverage.stopJSCoverage();
expect(coverage.length).toBe(0);
});
});
it('should not hang when there is a debugger statement', async function({page, server}) {
await page.coverage.startJSCoverage();
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
debugger; // eslint-disable-line no-debugger
});
await page.coverage.stopJSCoverage();
});
});
describe('CSSCoverage', function() {
it('should work', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/simple.html');
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1);
expect(coverage[0].url).toContain('/csscoverage/simple.html');
expect(coverage[0].ranges).toEqual([
{start: 1, end: 22}
]);
const range = coverage[0].ranges[0];
expect(coverage[0].text.substring(range.start, range.end)).toBe('div { color: green; }');
});
it('should report sourceURLs', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/sourceurl.html');
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1);
expect(coverage[0].url).toBe('nicename.css');
});
it('should report multiple stylesheets', async function({page, server}) {
await page.coverage.startCSSCoverage();
describe('CSSCoverage', function() {
it('should work', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/simple.html');
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1);
expect(coverage[0].url).toContain('/csscoverage/simple.html');
expect(coverage[0].ranges).toEqual([
{start: 1, end: 22}
]);
const range = coverage[0].ranges[0];
expect(coverage[0].text.substring(range.start, range.end)).toBe('div { color: green; }');
});
it('should report sourceURLs', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/sourceurl.html');
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1);
expect(coverage[0].url).toBe('nicename.css');
});
it('should report multiple stylesheets', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/multiple.html');
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(2);
coverage.sort((a, b) => a.url.localeCompare(b.url));
expect(coverage[0].url).toContain('/csscoverage/stylesheet1.css');
expect(coverage[1].url).toContain('/csscoverage/stylesheet2.css');
});
it('should report stylesheets that have no coverage', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/unused.html');
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1);
expect(coverage[0].url).toBe('unused.css');
expect(coverage[0].ranges.length).toBe(0);
});
it('should work with media queries', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/media.html');
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1);
expect(coverage[0].url).toContain('/csscoverage/media.html');
expect(coverage[0].ranges).toEqual([
{start: 17, end: 38}
]);
});
it('should work with complicated usecases', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/involved.html');
const coverage = await page.coverage.stopCSSCoverage();
expect(JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':<PORT>/')).toBeGolden('csscoverage-involved.txt');
});
it('should ignore injected stylesheets', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.addStyleTag({content: 'body { margin: 10px;}'});
// trigger style recalc
const margin = await page.evaluate(() => window.getComputedStyle(document.body).margin);
expect(margin).toBe('10px');
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(0);
});
describe('resetOnNavigation', function() {
it('should report stylesheets across navigations', async function({page, server}) {
await page.coverage.startCSSCoverage({resetOnNavigation: false});
await page.goto(server.PREFIX + '/csscoverage/multiple.html');
await page.goto(server.EMPTY_PAGE);
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(2);
coverage.sort((a, b) => a.url.localeCompare(b.url));
expect(coverage[0].url).toContain('/csscoverage/stylesheet1.css');
expect(coverage[1].url).toContain('/csscoverage/stylesheet2.css');
});
it('should report stylesheets that have no coverage', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/unused.html');
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1);
expect(coverage[0].url).toBe('unused.css');
expect(coverage[0].ranges.length).toBe(0);
});
it('should work with media queries', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/media.html');
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1);
expect(coverage[0].url).toContain('/csscoverage/media.html');
expect(coverage[0].ranges).toEqual([
{start: 17, end: 38}
]);
});
it('should work with complicated usecases', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/involved.html');
const coverage = await page.coverage.stopCSSCoverage();
expect(JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':<PORT>/')).toBeGolden('csscoverage-involved.txt');
});
it('should ignore injected stylesheets', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.addStyleTag({content: 'body { margin: 10px;}'});
// trigger style recalc
const margin = await page.evaluate(() => window.getComputedStyle(document.body).margin);
expect(margin).toBe('10px');
it('should NOT report scripts across navigations', async function({page, server}) {
await page.coverage.startCSSCoverage(); // Enabled by default.
await page.goto(server.PREFIX + '/csscoverage/multiple.html');
await page.goto(server.EMPTY_PAGE);
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(0);
});
describe('resetOnNavigation', function() {
it('should report stylesheets across navigations', async function({page, server}) {
await page.coverage.startCSSCoverage({resetOnNavigation: false});
await page.goto(server.PREFIX + '/csscoverage/multiple.html');
await page.goto(server.EMPTY_PAGE);
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(2);
});
it('should NOT report scripts across navigations', async function({page, server}) {
await page.coverage.startCSSCoverage(); // Enabled by default.
await page.goto(server.PREFIX + '/csscoverage/multiple.html');
await page.goto(server.EMPTY_PAGE);
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(0);
});
});
it('should work with a recently loaded stylesheet', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.evaluate(async url => {
document.body.textContent = 'hello, world';
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
document.head.appendChild(link);
await new Promise(x => link.onload = x);
await new Promise(f => requestAnimationFrame(f));
}, server.PREFIX + '/csscoverage/stylesheet1.css');
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1);
});
});
};
it('should work with a recently loaded stylesheet', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.evaluate(async url => {
document.body.textContent = 'hello, world';
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
document.head.appendChild(link);
await new Promise(x => link.onload = x);
await new Promise(f => requestAnimationFrame(f));
}, server.PREFIX + '/csscoverage/stylesheet1.css');
const coverage = await page.coverage.stopCSSCoverage();
expect(coverage.length).toBe(1);
});
});

View file

@ -14,87 +14,80 @@
* limitations under the License.
*/
const util = require('util');
const fs = require('fs');
const path = require('path');
const os = require('os');
const { makeUserDataDir, removeUserDataDir } = require('../utils');
const utils = require('../utils');
const {makeUserDataDir, removeUserDataDir} = utils;
const {FFOX, CHROMIUM, WEBKIT, WIN, defaultBrowserOptions} = utils.testOptions(browserType);
/**
* @type {TestSuite}
*/
module.exports.describe = function({defaultBrowserOptions, browserType, WIN}) {
const headfulOptions = Object.assign({}, defaultBrowserOptions, {
headless: false
});
const extensionPath = path.join(__dirname, '..', 'assets', 'simple-extension');
const extensionOptions = Object.assign({}, defaultBrowserOptions, {
headless: false,
args: [
`--disable-extensions-except=${extensionPath}`,
`--load-extension=${extensionPath}`,
],
});
const headfulOptions = Object.assign({}, defaultBrowserOptions, {
headless: false
describe('launcher', function() {
it('should throw with remote-debugging-pipe argument', async({browserType}) => {
const options = Object.assign({}, defaultBrowserOptions);
options.args = ['--remote-debugging-pipe'].concat(options.args || []);
const error = await browserType.launchServer(options).catch(e => e);
expect(error.message).toContain('Playwright manages remote debugging connection itself');
});
const extensionPath = path.join(__dirname, '..', 'assets', 'simple-extension');
const extensionOptions = Object.assign({}, defaultBrowserOptions, {
headless: false,
args: [
`--disable-extensions-except=${extensionPath}`,
`--load-extension=${extensionPath}`,
],
it('should throw with remote-debugging-port argument', async({browserType}) => {
const options = Object.assign({}, defaultBrowserOptions);
options.args = ['--remote-debugging-port=9222'].concat(options.args || []);
const error = await browserType.launchServer(options).catch(e => e);
expect(error.message).toContain('Playwright manages remote debugging connection itself');
});
it('should open devtools when "devtools: true" option is given', async({browserType}) => {
const browser = await browserType.launch(Object.assign({devtools: true}, headfulOptions));
const context = await browser.newContext();
const browserSession = await browser.newBrowserCDPSession();
await browserSession.send('Target.setDiscoverTargets', { discover: true });
const devtoolsPagePromise = new Promise(fulfill => browserSession.on('Target.targetCreated', async ({targetInfo}) => {
if (targetInfo.type === 'other' && targetInfo.url.includes('devtools://'))
fulfill();
}));
await Promise.all([
devtoolsPagePromise,
context.newPage()
]);
await browser.close();
});
});
describe('launcher', function() {
it('should throw with remote-debugging-pipe argument', async() => {
const options = Object.assign({}, defaultBrowserOptions);
options.args = ['--remote-debugging-pipe'].concat(options.args || []);
const error = await browserType.launchServer(options).catch(e => e);
expect(error.message).toContain('Playwright manages remote debugging connection itself');
});
it('should throw with remote-debugging-port argument', async() => {
const options = Object.assign({}, defaultBrowserOptions);
options.args = ['--remote-debugging-port=9222'].concat(options.args || []);
const error = await browserType.launchServer(options).catch(e => e);
expect(error.message).toContain('Playwright manages remote debugging connection itself');
});
it('should open devtools when "devtools: true" option is given', async({server}) => {
const browser = await browserType.launch(Object.assign({devtools: true}, headfulOptions));
const context = await browser.newContext();
const browserSession = await browser.newBrowserCDPSession();
await browserSession.send('Target.setDiscoverTargets', { discover: true });
const devtoolsPagePromise = new Promise(fulfill => browserSession.on('Target.targetCreated', async ({targetInfo}) => {
if (targetInfo.type === 'other' && targetInfo.url.includes('devtools://'))
fulfill();
}));
await Promise.all([
devtoolsPagePromise,
context.newPage()
]);
await browser.close();
});
describe('extensions', () => {
it('should return background pages', async({browserType}) => {
const userDataDir = await makeUserDataDir();
const context = await browserType.launchPersistentContext(userDataDir, extensionOptions);
const backgroundPages = context.backgroundPages();
let backgroundPage = backgroundPages.length
? backgroundPages[0]
: await context.waitForEvent('backgroundpage');
expect(backgroundPage).toBeTruthy();
expect(context.backgroundPages()).toContain(backgroundPage);
expect(context.pages()).not.toContain(backgroundPage);
await removeUserDataDir(userDataDir);
});
});
describe('extensions', () => {
it('should return background pages', async() => {
const userDataDir = await makeUserDataDir();
const context = await browserType.launchPersistentContext(userDataDir, extensionOptions);
const backgroundPages = context.backgroundPages();
let backgroundPage = backgroundPages.length
? backgroundPages[0]
: await context.waitForEvent('backgroundpage');
expect(backgroundPage).toBeTruthy();
expect(context.backgroundPages()).toContain(backgroundPage);
expect(context.pages()).not.toContain(backgroundPage);
await removeUserDataDir(userDataDir);
describe('BrowserContext', function() {
it('should not create pages automatically', async ({browserType}) => {
const browser = await browserType.launch();
const browserSession = await browser.newBrowserCDPSession();
const targets = [];
browserSession.on('Target.targetCreated', async ({targetInfo}) => {
if (targetInfo.type !== 'browser')
targets.push(targetInfo);
});
await browserSession.send('Target.setDiscoverTargets', { discover: true });
await browser.newContext();
await browser.close();
expect(targets.length).toBe(0);
});
describe('BrowserContext', function() {
it('should not create pages automatically', async function() {
const browser = await browserType.launch();
const browserSession = await browser.newBrowserCDPSession();
const targets = [];
browserSession.on('Target.targetCreated', async ({targetInfo}) => {
if (targetInfo.type !== 'browser')
targets.push(targetInfo);
});
await browserSession.send('Target.setDiscoverTargets', { discover: true });
await browser.newContext();
await browser.close();
expect(targets.length).toBe(0);
});
});
};
});

View file

@ -14,92 +14,88 @@
* limitations under the License.
*/
/**
* @type {ChromiumTestSuite}
*/
module.exports.describe = function({defaultBrowserOptions, browserType, FFOX, CHROMIUM, WEBKIT}) {
const {FFOX, CHROMIUM, WEBKIT, defaultBrowserOptions} = require('../utils').testOptions(browserType);
const headfulOptions = Object.assign({}, defaultBrowserOptions, {
headless: false
});
const headfulOptions = Object.assign({}, defaultBrowserOptions, {
headless: false
});
describe('OOPIF', function() {
beforeAll(async function(state) {
state.browser = await browserType.launch(Object.assign({}, defaultBrowserOptions, {
args: (defaultBrowserOptions.args || []).concat(['--site-per-process']),
}));
});
beforeEach(async function(state) {
state.context = await state.browser.newContext();
state.page = await state.context.newPage();
});
afterEach(async function(state) {
await state.context.close();
state.page = null;
state.context = null;
});
afterAll(async function(state) {
await state.browser.close();
state.browser = null;
});
it('should report oopif frames', async function({browser, page, server, context}) {
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1);
expect(page.frames().length).toBe(2);
expect(await page.frames()[1].evaluate(() => '' + location.href)).toBe(server.CROSS_PROCESS_PREFIX + '/grid.html');
});
it('should handle remote -> local -> remote transitions', async function({browser, page, server, context}) {
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2);
expect(await page.frames()[1].evaluate(() => '' + location.href)).toBe(server.CROSS_PROCESS_PREFIX + '/grid.html');
await Promise.all([
page.frames()[1].waitForNavigation(),
page.evaluate(() => goLocal()),
]);
expect(await page.frames()[1].evaluate(() => '' + location.href)).toBe(server.PREFIX + '/grid.html');
await Promise.all([
page.frames()[1].waitForNavigation(),
page.evaluate(() => goRemote()),
]);
expect(await page.frames()[1].evaluate(() => '' + location.href)).toBe(server.CROSS_PROCESS_PREFIX + '/grid.html');
});
it('should load oopif iframes with subresources and request interception', async function({browser, page, server, context}) {
await page.route('**/*', route => route.continue());
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1);
});
// @see https://github.com/microsoft/playwright/issues/1240
xit('should click a button when it overlays oopif', async function({browser, page, server, context}) {
await page.goto(server.PREFIX + '/button-overlay-oopif.html');
expect(await countOOPIFs(browser)).toBe(1);
await page.click('button');
expect(await page.evaluate(() => window.BUTTON_CLICKED)).toBe(true);
});
it('should report google.com frame with headful', async({server}) => {
// TODO: Support OOOPIF. @see https://github.com/GoogleChrome/puppeteer/issues/2548
// https://google.com is isolated by default in Chromium embedder.
const browser = await browserType.launch(headfulOptions);
const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE);
await page.route('**/*', route => {
route.fulfill({body: 'YO, GOOGLE.COM'});
});
await page.evaluate(() => {
const frame = document.createElement('iframe');
frame.setAttribute('src', 'https://google.com/');
document.body.appendChild(frame);
return new Promise(x => frame.onload = x);
});
await page.waitForSelector('iframe[src="https://google.com/"]');
const urls = page.frames().map(frame => frame.url());
expect(urls).toEqual([
server.EMPTY_PAGE,
'https://google.com/'
]);
await browser.close();
});
describe('OOPIF', function() {
beforeAll(async function(state) {
state.browser = await state.browserType.launch(Object.assign({}, defaultBrowserOptions, {
args: (defaultBrowserOptions.args || []).concat(['--site-per-process']),
}));
});
};
beforeEach(async function(state) {
state.context = await state.browser.newContext();
state.page = await state.context.newPage();
});
afterEach(async function(state) {
await state.context.close();
state.page = null;
state.context = null;
});
afterAll(async function(state) {
await state.browser.close();
state.browser = null;
});
it('should report oopif frames', async function({browser, page, server, context}) {
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1);
expect(page.frames().length).toBe(2);
expect(await page.frames()[1].evaluate(() => '' + location.href)).toBe(server.CROSS_PROCESS_PREFIX + '/grid.html');
});
it('should handle remote -> local -> remote transitions', async function({browser, page, server, context}) {
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2);
expect(await page.frames()[1].evaluate(() => '' + location.href)).toBe(server.CROSS_PROCESS_PREFIX + '/grid.html');
await Promise.all([
page.frames()[1].waitForNavigation(),
page.evaluate(() => goLocal()),
]);
expect(await page.frames()[1].evaluate(() => '' + location.href)).toBe(server.PREFIX + '/grid.html');
await Promise.all([
page.frames()[1].waitForNavigation(),
page.evaluate(() => goRemote()),
]);
expect(await page.frames()[1].evaluate(() => '' + location.href)).toBe(server.CROSS_PROCESS_PREFIX + '/grid.html');
});
it('should load oopif iframes with subresources and request interception', async function({browser, page, server, context}) {
await page.route('**/*', route => route.continue());
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1);
});
// @see https://github.com/microsoft/playwright/issues/1240
xit('should click a button when it overlays oopif', async function({browser, page, server, context}) {
await page.goto(server.PREFIX + '/button-overlay-oopif.html');
expect(await countOOPIFs(browser)).toBe(1);
await page.click('button');
expect(await page.evaluate(() => window.BUTTON_CLICKED)).toBe(true);
});
it('should report google.com frame with headful', async({browserType, server}) => {
// TODO: Support OOOPIF. @see https://github.com/GoogleChrome/puppeteer/issues/2548
// https://google.com is isolated by default in Chromium embedder.
const browser = await browserType.launch(headfulOptions);
const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE);
await page.route('**/*', route => {
route.fulfill({body: 'YO, GOOGLE.COM'});
});
await page.evaluate(() => {
const frame = document.createElement('iframe');
frame.setAttribute('src', 'https://google.com/');
document.body.appendChild(frame);
return new Promise(x => frame.onload = x);
});
await page.waitForSelector('iframe[src="https://google.com/"]');
const urls = page.frames().map(frame => frame.url());
expect(urls).toEqual([
server.EMPTY_PAGE,
'https://google.com/'
]);
await browser.close();
});
});
async function countOOPIFs(browser) {
const browserSession = await browser.newBrowserCDPSession();

View file

@ -16,19 +16,14 @@
const fs = require('fs');
const path = require('path');
const {FFOX, CHROMIUM, WEBKIT, headless, OUTPUT_DIR} = require('../utils').testOptions(browserType);
/**
* @type {ChromiumTestSuite}
*/
module.exports.describe = function({headless, OUTPUT_DIR}) {
// Printing to pdf is currently only supported in headless
describe.fail(!headless)('Page.pdf', function() {
it('should be able to save file', async({page, server}) => {
const outputFile = path.join(OUTPUT_DIR, 'output.pdf');
await page.pdf({path: outputFile});
expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0);
fs.unlinkSync(outputFile);
});
// Printing to pdf is currently only supported in headless
describe.fail(!headless)('Page.pdf', function() {
it('should be able to save file', async({page, server}) => {
const outputFile = path.join(OUTPUT_DIR, 'output.pdf');
await page.pdf({path: outputFile});
expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0);
fs.unlinkSync(outputFile);
});
};
});

View file

@ -14,94 +14,90 @@
* limitations under the License.
*/
/**
* @type {ChromiumTestSuite}
*/
module.exports.describe = function({FFOX, CHROMIUM, WEBKIT}) {
const {FFOX, CHROMIUM, WEBKIT} = require('../utils').testOptions(browserType);
describe('ChromiumBrowserContext.createSession', function() {
it('should work', async function({page, browser, server}) {
const client = await page.context().newCDPSession(page);
describe('ChromiumBrowserContext.createSession', function() {
it('should work', async function({page, browser, server}) {
const client = await page.context().newCDPSession(page);
await Promise.all([
client.send('Runtime.enable'),
client.send('Runtime.evaluate', { expression: 'window.foo = "bar"' })
]);
const foo = await page.evaluate(() => window.foo);
expect(foo).toBe('bar');
});
it('should send events', async function({page, browser, server}) {
const client = await page.context().newCDPSession(page);
await client.send('Network.enable');
const events = [];
client.on('Network.requestWillBeSent', event => events.push(event));
await page.goto(server.EMPTY_PAGE);
expect(events.length).toBe(1);
});
it('should enable and disable domains independently', async function({page, browser, server}) {
const client = await page.context().newCDPSession(page);
await client.send('Runtime.enable');
await client.send('Debugger.enable');
// JS coverage enables and then disables Debugger domain.
await page.coverage.startJSCoverage();
await page.coverage.stopJSCoverage();
// generate a script in page and wait for the event.
const [event] = await Promise.all([
new Promise(f => client.on('Debugger.scriptParsed', f)),
page.evaluate('//# sourceURL=foo.js')
]);
// expect events to be dispatched.
expect(event.url).toBe('foo.js');
});
it('should be able to detach session', async function({page, browser, server}) {
const client = await page.context().newCDPSession(page);
await client.send('Runtime.enable');
const evalResponse = await client.send('Runtime.evaluate', {expression: '1 + 2', returnByValue: true});
expect(evalResponse.result.value).toBe(3);
await client.detach();
let error = null;
try {
await client.send('Runtime.evaluate', {expression: '3 + 1', returnByValue: true});
} catch (e) {
error = e;
}
expect(error.message).toContain('Session closed.');
});
it('should throw nice errors', async function({page, browser}) {
const client = await page.context().newCDPSession(page);
const error = await theSourceOfTheProblems().catch(error => error);
expect(error.stack).toContain('theSourceOfTheProblems');
expect(error.message).toContain('ThisCommand.DoesNotExist');
async function theSourceOfTheProblems() {
await client.send('ThisCommand.DoesNotExist');
}
});
it('should not break page.close()', async function({browser, server}) {
const context = await browser.newContext();
const page = await context.newPage();
const session = await page.context().newCDPSession(page);
await session.detach();
await page.close();
await context.close();
});
it('should detach when page closes', async function({browser, server}) {
const context = await browser.newContext();
const page = await context.newPage();
const session = await context.newCDPSession(page);
await page.close();
let error;
await session.detach().catch(e => error = e);
expect(error).toBeTruthy('Calling detach on a closed page\'s session should throw');
await context.close();
});
await Promise.all([
client.send('Runtime.enable'),
client.send('Runtime.evaluate', { expression: 'window.foo = "bar"' })
]);
const foo = await page.evaluate(() => window.foo);
expect(foo).toBe('bar');
});
describe('ChromiumBrowser.newBrowserCDPSession', function() {
it('should work', async function({page, browser, server}) {
const session = await browser.newBrowserCDPSession();
const version = await session.send('Browser.getVersion');
expect(version.userAgent).toBeTruthy();
await session.detach();
});
it('should send events', async function({page, browser, server}) {
const client = await page.context().newCDPSession(page);
await client.send('Network.enable');
const events = [];
client.on('Network.requestWillBeSent', event => events.push(event));
await page.goto(server.EMPTY_PAGE);
expect(events.length).toBe(1);
});
};
it('should enable and disable domains independently', async function({page, browser, server}) {
const client = await page.context().newCDPSession(page);
await client.send('Runtime.enable');
await client.send('Debugger.enable');
// JS coverage enables and then disables Debugger domain.
await page.coverage.startJSCoverage();
await page.coverage.stopJSCoverage();
// generate a script in page and wait for the event.
const [event] = await Promise.all([
new Promise(f => client.on('Debugger.scriptParsed', f)),
page.evaluate('//# sourceURL=foo.js')
]);
// expect events to be dispatched.
expect(event.url).toBe('foo.js');
});
it('should be able to detach session', async function({page, browser, server}) {
const client = await page.context().newCDPSession(page);
await client.send('Runtime.enable');
const evalResponse = await client.send('Runtime.evaluate', {expression: '1 + 2', returnByValue: true});
expect(evalResponse.result.value).toBe(3);
await client.detach();
let error = null;
try {
await client.send('Runtime.evaluate', {expression: '3 + 1', returnByValue: true});
} catch (e) {
error = e;
}
expect(error.message).toContain('Session closed.');
});
it('should throw nice errors', async function({page, browser}) {
const client = await page.context().newCDPSession(page);
const error = await theSourceOfTheProblems().catch(error => error);
expect(error.stack).toContain('theSourceOfTheProblems');
expect(error.message).toContain('ThisCommand.DoesNotExist');
async function theSourceOfTheProblems() {
await client.send('ThisCommand.DoesNotExist');
}
});
it('should not break page.close()', async function({browser, server}) {
const context = await browser.newContext();
const page = await context.newPage();
const session = await page.context().newCDPSession(page);
await session.detach();
await page.close();
await context.close();
});
it('should detach when page closes', async function({browser, server}) {
const context = await browser.newContext();
const page = await context.newPage();
const session = await context.newCDPSession(page);
await page.close();
let error;
await session.detach().catch(e => error = e);
expect(error).toBeTruthy('Calling detach on a closed page\'s session should throw');
await context.close();
});
});
describe('ChromiumBrowser.newBrowserCDPSession', function() {
it('should work', async function({page, browser, server}) {
const session = await browser.newBrowserCDPSession();
const version = await session.send('Browser.getVersion');
expect(version.userAgent).toBeTruthy();
await session.detach();
});
});

View file

@ -16,67 +16,62 @@
const fs = require('fs');
const path = require('path');
const {FFOX, CHROMIUM, WEBKIT, OUTPUT_DIR, defaultBrowserOptions} = require('../utils').testOptions(browserType);
/**
* @type {ChromiumTestSuite}
*/
module.exports.describe = function({defaultBrowserOptions, browserType, OUTPUT_DIR}) {
describe('Chromium.startTracing', function() {
beforeEach(async function(state) {
state.outputFile = path.join(OUTPUT_DIR, `trace-${state.parallelIndex}.json`);
state.browser = await browserType.launch(defaultBrowserOptions);
state.page = await state.browser.newPage();
});
afterEach(async function(state) {
await state.browser.close();
state.browser = null;
state.page = null;
if (fs.existsSync(state.outputFile)) {
fs.unlinkSync(state.outputFile);
state.outputFile = null;
}
});
it('should output a trace', async({browser, page, server, outputFile}) => {
await browser.startTracing(page, {screenshots: true, path: outputFile});
await page.goto(server.PREFIX + '/grid.html');
await browser.stopTracing();
expect(fs.existsSync(outputFile)).toBe(true);
});
it('should run with custom categories if provided', async({browser, page, outputFile}) => {
await browser.startTracing(page, {path: outputFile, categories: ['disabled-by-default-v8.cpu_profiler.hires']});
await browser.stopTracing();
const traceJson = JSON.parse(fs.readFileSync(outputFile).toString());
expect(traceJson.metadata['trace-config']).toContain('disabled-by-default-v8.cpu_profiler.hires', 'Does not contain expected category');
});
it('should throw if tracing on two pages', async({browser, page, server, outputFile}) => {
await browser.startTracing(page, {path: outputFile});
const newPage = await browser.newPage();
let error = null;
await browser.startTracing(newPage, {path: outputFile}).catch(e => error = e);
await newPage.close();
expect(error).toBeTruthy();
await browser.stopTracing();
});
it('should return a buffer', async({browser, page, server, outputFile}) => {
await browser.startTracing(page, {screenshots: true, path: outputFile});
await page.goto(server.PREFIX + '/grid.html');
const trace = await browser.stopTracing();
const buf = fs.readFileSync(outputFile);
expect(trace.toString()).toEqual(buf.toString(), 'Tracing buffer mismatch');
});
it('should work without options', async({browser, page, server, outputFile}) => {
await browser.startTracing(page);
await page.goto(server.PREFIX + '/grid.html');
const trace = await browser.stopTracing();
expect(trace).toBeTruthy();
});
it('should support a buffer without a path', async({browser, page, server}) => {
await browser.startTracing(page, {screenshots: true});
await page.goto(server.PREFIX + '/grid.html');
const trace = await browser.stopTracing();
expect(trace.toString()).toContain('screenshot', 'Does not contain screenshot');
});
describe('Chromium.startTracing', function() {
beforeEach(async function(state) {
state.outputFile = path.join(OUTPUT_DIR, `trace-${state.parallelIndex}.json`);
state.browser = await state.browserType.launch(defaultBrowserOptions);
state.page = await state.browser.newPage();
});
};
afterEach(async function(state) {
await state.browser.close();
state.browser = null;
state.page = null;
if (fs.existsSync(state.outputFile)) {
fs.unlinkSync(state.outputFile);
state.outputFile = null;
}
});
it('should output a trace', async({browser, page, server, outputFile}) => {
await browser.startTracing(page, {screenshots: true, path: outputFile});
await page.goto(server.PREFIX + '/grid.html');
await browser.stopTracing();
expect(fs.existsSync(outputFile)).toBe(true);
});
it('should run with custom categories if provided', async({browser, page, outputFile}) => {
await browser.startTracing(page, {path: outputFile, categories: ['disabled-by-default-v8.cpu_profiler.hires']});
await browser.stopTracing();
const traceJson = JSON.parse(fs.readFileSync(outputFile).toString());
expect(traceJson.metadata['trace-config']).toContain('disabled-by-default-v8.cpu_profiler.hires', 'Does not contain expected category');
});
it('should throw if tracing on two pages', async({browser, page, server, outputFile}) => {
await browser.startTracing(page, {path: outputFile});
const newPage = await browser.newPage();
let error = null;
await browser.startTracing(newPage, {path: outputFile}).catch(e => error = e);
await newPage.close();
expect(error).toBeTruthy();
await browser.stopTracing();
});
it('should return a buffer', async({browser, page, server, outputFile}) => {
await browser.startTracing(page, {screenshots: true, path: outputFile});
await page.goto(server.PREFIX + '/grid.html');
const trace = await browser.stopTracing();
const buf = fs.readFileSync(outputFile);
expect(trace.toString()).toEqual(buf.toString(), 'Tracing buffer mismatch');
});
it('should work without options', async({browser, page, server, outputFile}) => {
await browser.startTracing(page);
await page.goto(server.PREFIX + '/grid.html');
const trace = await browser.stopTracing();
expect(trace).toBeTruthy();
});
it('should support a buffer without a path', async({browser, page, server}) => {
await browser.startTracing(page, {screenshots: true});
await page.goto(server.PREFIX + '/grid.html');
const trace = await browser.stopTracing();
expect(trace.toString()).toContain('screenshot', 'Does not contain screenshot');
});
});

File diff suppressed because it is too large Load diff

View file

@ -15,458 +15,454 @@
* limitations under the License.
*/
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({browserType, defaultBrowserOptions, MAC, FFOX, CHROMIUM, WEBKIT}) {
const {FFOX, CHROMIUM, WEBKIT, MAC, defaultBrowserOptions} = require('./utils').testOptions(browserType);
describe('BrowserContext.cookies', function() {
it('should return no cookies in pristine browser context', async({context, page, server}) => {
expect(await context.cookies()).toEqual([]);
});
it('should get a cookie', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
document.cookie = 'username=John Doe';
});
expect(await context.cookies()).toEqual([{
name: 'username',
value: 'John Doe',
domain: 'localhost',
path: '/',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
}]);
});
it('should get a non-session cookie', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
// @see https://en.wikipedia.org/wiki/Year_2038_problem
const date = +(new Date('1/1/2038'));
await page.evaluate(timestamp => {
const date = new Date(timestamp);
document.cookie = `username=John Doe;expires=${date.toUTCString()}`;
}, date);
expect(await context.cookies()).toEqual([{
name: 'username',
value: 'John Doe',
domain: 'localhost',
path: '/',
expires: date / 1000,
httpOnly: false,
secure: false,
sameSite: 'None',
}]);
});
it('should properly report httpOnly cookie', async({context, page, server}) => {
server.setRoute('/empty.html', (req, res) => {
res.setHeader('Set-Cookie', 'name=value;HttpOnly; Path=/');
res.end();
});
await page.goto(server.EMPTY_PAGE);
const cookies = await context.cookies();
expect(cookies.length).toBe(1);
expect(cookies[0].httpOnly).toBe(true);
});
it.fail(WEBKIT && !MAC)('should properly report "Strict" sameSite cookie', async({context, page, server}) => {
server.setRoute('/empty.html', (req, res) => {
res.setHeader('Set-Cookie', 'name=value;SameSite=Strict');
res.end();
});
await page.goto(server.EMPTY_PAGE);
const cookies = await context.cookies();
expect(cookies.length).toBe(1);
expect(cookies[0].sameSite).toBe('Strict');
});
it.fail(WEBKIT && !MAC)('should properly report "Lax" sameSite cookie', async({context, page, server}) => {
server.setRoute('/empty.html', (req, res) => {
res.setHeader('Set-Cookie', 'name=value;SameSite=Lax');
res.end();
});
await page.goto(server.EMPTY_PAGE);
const cookies = await context.cookies();
expect(cookies.length).toBe(1);
expect(cookies[0].sameSite).toBe('Lax');
});
it('should get multiple cookies', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
document.cookie = 'username=John Doe';
document.cookie = 'password=1234';
});
const cookies = await context.cookies();
cookies.sort((a, b) => a.name.localeCompare(b.name));
expect(cookies).toEqual([
{
name: 'password',
value: '1234',
domain: 'localhost',
path: '/',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
},
{
name: 'username',
value: 'John Doe',
domain: 'localhost',
path: '/',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
},
]);
});
it('should get cookies from multiple urls', async({context}) => {
await context.addCookies([{
url: 'https://foo.com',
name: 'doggo',
value: 'woofs',
}, {
url: 'https://bar.com',
name: 'catto',
value: 'purrs',
}, {
url: 'https://baz.com',
name: 'birdo',
value: 'tweets',
}]);
const cookies = await context.cookies(['https://foo.com', 'https://baz.com']);
cookies.sort((a, b) => a.name.localeCompare(b.name));
expect(cookies).toEqual([{
name: 'birdo',
value: 'tweets',
domain: 'baz.com',
path: '/',
expires: -1,
httpOnly: false,
secure: true,
sameSite: 'None',
}, {
name: 'doggo',
value: 'woofs',
domain: 'foo.com',
path: '/',
expires: -1,
httpOnly: false,
secure: true,
sameSite: 'None',
}]);
});
describe('BrowserContext.cookies', function() {
it('should return no cookies in pristine browser context', async({context, page, server}) => {
expect(await context.cookies()).toEqual([]);
});
describe('BrowserContext.addCookies', function() {
it('should work', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
await context.addCookies([{
url: server.EMPTY_PAGE,
it('should get a cookie', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
document.cookie = 'username=John Doe';
});
expect(await context.cookies()).toEqual([{
name: 'username',
value: 'John Doe',
domain: 'localhost',
path: '/',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
}]);
});
it('should get a non-session cookie', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
// @see https://en.wikipedia.org/wiki/Year_2038_problem
const date = +(new Date('1/1/2038'));
await page.evaluate(timestamp => {
const date = new Date(timestamp);
document.cookie = `username=John Doe;expires=${date.toUTCString()}`;
}, date);
expect(await context.cookies()).toEqual([{
name: 'username',
value: 'John Doe',
domain: 'localhost',
path: '/',
expires: date / 1000,
httpOnly: false,
secure: false,
sameSite: 'None',
}]);
});
it('should properly report httpOnly cookie', async({context, page, server}) => {
server.setRoute('/empty.html', (req, res) => {
res.setHeader('Set-Cookie', 'name=value;HttpOnly; Path=/');
res.end();
});
await page.goto(server.EMPTY_PAGE);
const cookies = await context.cookies();
expect(cookies.length).toBe(1);
expect(cookies[0].httpOnly).toBe(true);
});
it.fail(WEBKIT && !MAC)('should properly report "Strict" sameSite cookie', async({context, page, server}) => {
server.setRoute('/empty.html', (req, res) => {
res.setHeader('Set-Cookie', 'name=value;SameSite=Strict');
res.end();
});
await page.goto(server.EMPTY_PAGE);
const cookies = await context.cookies();
expect(cookies.length).toBe(1);
expect(cookies[0].sameSite).toBe('Strict');
});
it.fail(WEBKIT && !MAC)('should properly report "Lax" sameSite cookie', async({context, page, server}) => {
server.setRoute('/empty.html', (req, res) => {
res.setHeader('Set-Cookie', 'name=value;SameSite=Lax');
res.end();
});
await page.goto(server.EMPTY_PAGE);
const cookies = await context.cookies();
expect(cookies.length).toBe(1);
expect(cookies[0].sameSite).toBe('Lax');
});
it('should get multiple cookies', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
document.cookie = 'username=John Doe';
document.cookie = 'password=1234';
});
const cookies = await context.cookies();
cookies.sort((a, b) => a.name.localeCompare(b.name));
expect(cookies).toEqual([
{
name: 'password',
value: '123456'
}]);
expect(await page.evaluate(() => document.cookie)).toEqual('password=123456');
});
it('should roundtrip cookie', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
// @see https://en.wikipedia.org/wiki/Year_2038_problem
const date = +(new Date('1/1/2038'));
await page.evaluate(timestamp => {
const date = new Date(timestamp);
document.cookie = `username=John Doe;expires=${date.toUTCString()}`;
}, date);
const cookies = await context.cookies();
await context.clearCookies();
expect(await context.cookies()).toEqual([]);
await context.addCookies(cookies);
expect(await context.cookies()).toEqual(cookies);
});
it('should send cookie header', async({server, context}) => {
let cookie = '';
server.setRoute('/empty.html', (req, res) => {
cookie = req.headers.cookie;
res.end();
});
await context.addCookies([{url: server.EMPTY_PAGE, name: 'cookie', value: 'value'}]);
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(cookie).toBe('cookie=value');
});
it('should isolate cookies in browser contexts', async({context, server, browser}) => {
const anotherContext = await browser.newContext();
await context.addCookies([{url: server.EMPTY_PAGE, name: 'isolatecookie', value: 'page1value'}]);
await anotherContext.addCookies([{url: server.EMPTY_PAGE, name: 'isolatecookie', value: 'page2value'}]);
value: '1234',
domain: 'localhost',
path: '/',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
},
{
name: 'username',
value: 'John Doe',
domain: 'localhost',
path: '/',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
},
]);
});
it('should get cookies from multiple urls', async({context}) => {
await context.addCookies([{
url: 'https://foo.com',
name: 'doggo',
value: 'woofs',
}, {
url: 'https://bar.com',
name: 'catto',
value: 'purrs',
}, {
url: 'https://baz.com',
name: 'birdo',
value: 'tweets',
}]);
const cookies = await context.cookies(['https://foo.com', 'https://baz.com']);
cookies.sort((a, b) => a.name.localeCompare(b.name));
expect(cookies).toEqual([{
name: 'birdo',
value: 'tweets',
domain: 'baz.com',
path: '/',
expires: -1,
httpOnly: false,
secure: true,
sameSite: 'None',
}, {
name: 'doggo',
value: 'woofs',
domain: 'foo.com',
path: '/',
expires: -1,
httpOnly: false,
secure: true,
sameSite: 'None',
}]);
});
});
const cookies1 = await context.cookies();
const cookies2 = await anotherContext.cookies();
expect(cookies1.length).toBe(1);
expect(cookies2.length).toBe(1);
expect(cookies1[0].name).toBe('isolatecookie');
expect(cookies1[0].value).toBe('page1value');
expect(cookies2[0].name).toBe('isolatecookie');
expect(cookies2[0].value).toBe('page2value');
await anotherContext.close();
describe('BrowserContext.addCookies', function() {
it('should work', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
await context.addCookies([{
url: server.EMPTY_PAGE,
name: 'password',
value: '123456'
}]);
expect(await page.evaluate(() => document.cookie)).toEqual('password=123456');
});
it('should roundtrip cookie', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
// @see https://en.wikipedia.org/wiki/Year_2038_problem
const date = +(new Date('1/1/2038'));
await page.evaluate(timestamp => {
const date = new Date(timestamp);
document.cookie = `username=John Doe;expires=${date.toUTCString()}`;
}, date);
const cookies = await context.cookies();
await context.clearCookies();
expect(await context.cookies()).toEqual([]);
await context.addCookies(cookies);
expect(await context.cookies()).toEqual(cookies);
});
it('should send cookie header', async({server, context}) => {
let cookie = '';
server.setRoute('/empty.html', (req, res) => {
cookie = req.headers.cookie;
res.end();
});
it('should isolate session cookies', async({context, server, browser}) => {
server.setRoute('/setcookie.html', (req, res) => {
res.setHeader('Set-Cookie', 'session=value');
res.end();
});
{
const page = await context.newPage();
await page.goto(server.PREFIX + '/setcookie.html');
}
{
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const cookies = await context.cookies();
expect(cookies.length).toBe(1);
expect(cookies.map(c => c.value).join(',')).toBe('value');
}
{
const context2 = await browser.newContext();
const page = await context2.newPage();
await page.goto(server.EMPTY_PAGE);
const cookies = await context2.cookies();
expect(cookies[0] && cookies[0].name).toBe(undefined);
await context2.close();
}
await context.addCookies([{url: server.EMPTY_PAGE, name: 'cookie', value: 'value'}]);
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(cookie).toBe('cookie=value');
});
it('should isolate cookies in browser contexts', async({context, server, browser}) => {
const anotherContext = await browser.newContext();
await context.addCookies([{url: server.EMPTY_PAGE, name: 'isolatecookie', value: 'page1value'}]);
await anotherContext.addCookies([{url: server.EMPTY_PAGE, name: 'isolatecookie', value: 'page2value'}]);
const cookies1 = await context.cookies();
const cookies2 = await anotherContext.cookies();
expect(cookies1.length).toBe(1);
expect(cookies2.length).toBe(1);
expect(cookies1[0].name).toBe('isolatecookie');
expect(cookies1[0].value).toBe('page1value');
expect(cookies2[0].name).toBe('isolatecookie');
expect(cookies2[0].value).toBe('page2value');
await anotherContext.close();
});
it('should isolate session cookies', async({context, server, browser}) => {
server.setRoute('/setcookie.html', (req, res) => {
res.setHeader('Set-Cookie', 'session=value');
res.end();
});
it('should isolate persistent cookies', async({context, server, browser}) => {
server.setRoute('/setcookie.html', (req, res) => {
res.setHeader('Set-Cookie', 'persistent=persistent-value; max-age=3600');
res.end();
});
{
const page = await context.newPage();
await page.goto(server.PREFIX + '/setcookie.html');
const context1 = context;
}
{
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const cookies = await context.cookies();
expect(cookies.length).toBe(1);
expect(cookies.map(c => c.value).join(',')).toBe('value');
}
{
const context2 = await browser.newContext();
const [page1, page2] = await Promise.all([context1.newPage(), context2.newPage()]);
await Promise.all([page1.goto(server.EMPTY_PAGE), page2.goto(server.EMPTY_PAGE)]);
const [cookies1, cookies2] = await Promise.all([context1.cookies(), context2.cookies()]);
expect(cookies1.length).toBe(1);
expect(cookies1[0].name).toBe('persistent');
expect(cookies1[0].value).toBe('persistent-value');
expect(cookies2.length).toBe(0);
await context2.close();
});
it('should isolate send cookie header', async({server, context, browser}) => {
let cookie = [];
server.setRoute('/empty.html', (req, res) => {
cookie = req.headers.cookie || '';
res.end();
});
await context.addCookies([{url: server.EMPTY_PAGE, name: 'sendcookie', value: 'value'}]);
{
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(cookie).toBe('sendcookie=value');
}
{
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(cookie).toBe('');
await context.close();
}
});
it.slow()('should isolate cookies between launches', async({server}) => {
const browser1 = await browserType.launch(defaultBrowserOptions);
const context1 = await browser1.newContext();
await context1.addCookies([{url: server.EMPTY_PAGE, name: 'cookie-in-context-1', value: 'value', expires: Date.now() / 1000 + 10000}]);
await browser1.close();
const browser2 = await browserType.launch(defaultBrowserOptions);
const context2 = await browser2.newContext();
const page = await context2.newPage();
await page.goto(server.EMPTY_PAGE);
const cookies = await context2.cookies();
expect(cookies.length).toBe(0);
await browser2.close();
expect(cookies[0] && cookies[0].name).toBe(undefined);
await context2.close();
}
});
it('should isolate persistent cookies', async({context, server, browser}) => {
server.setRoute('/setcookie.html', (req, res) => {
res.setHeader('Set-Cookie', 'persistent=persistent-value; max-age=3600');
res.end();
});
it('should set multiple cookies', async({context, page, server}) => {
const page = await context.newPage();
await page.goto(server.PREFIX + '/setcookie.html');
const context1 = context;
const context2 = await browser.newContext();
const [page1, page2] = await Promise.all([context1.newPage(), context2.newPage()]);
await Promise.all([page1.goto(server.EMPTY_PAGE), page2.goto(server.EMPTY_PAGE)]);
const [cookies1, cookies2] = await Promise.all([context1.cookies(), context2.cookies()]);
expect(cookies1.length).toBe(1);
expect(cookies1[0].name).toBe('persistent');
expect(cookies1[0].value).toBe('persistent-value');
expect(cookies2.length).toBe(0);
await context2.close();
});
it('should isolate send cookie header', async({server, context, browser}) => {
let cookie = [];
server.setRoute('/empty.html', (req, res) => {
cookie = req.headers.cookie || '';
res.end();
});
await context.addCookies([{url: server.EMPTY_PAGE, name: 'sendcookie', value: 'value'}]);
{
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await context.addCookies([{
url: server.EMPTY_PAGE,
name: 'multiple-1',
value: '123456'
}, {
url: server.EMPTY_PAGE,
name: 'multiple-2',
value: 'bar'
}]);
expect(await page.evaluate(() => {
const cookies = document.cookie.split(';');
return cookies.map(cookie => cookie.trim()).sort();
})).toEqual([
'multiple-1=123456',
'multiple-2=bar',
]);
});
it('should have |expires| set to |-1| for session cookies', async({context, server}) => {
await context.addCookies([{
url: server.EMPTY_PAGE,
name: 'expires',
value: '123456'
}]);
const cookies = await context.cookies();
expect(cookies[0].expires).toBe(-1);
});
it('should set cookie with reasonable defaults', async({context, server}) => {
await context.addCookies([{
url: server.EMPTY_PAGE,
name: 'defaults',
value: '123456'
}]);
const cookies = await context.cookies();
expect(cookies.sort((a, b) => a.name.localeCompare(b.name))).toEqual([{
name: 'defaults',
value: '123456',
domain: 'localhost',
path: '/',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
}]);
});
it('should set a cookie with a path', async({context, page, server}) => {
await page.goto(server.PREFIX + '/grid.html');
await context.addCookies([{
domain: 'localhost',
path: '/grid.html',
name: 'gridcookie',
value: 'GRID',
}]);
expect(await context.cookies()).toEqual([{
name: 'gridcookie',
value: 'GRID',
domain: 'localhost',
path: '/grid.html',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
}]);
expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID');
await page.goto(server.EMPTY_PAGE);
expect(await page.evaluate('document.cookie')).toBe('');
await page.goto(server.PREFIX + '/grid.html');
expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID');
});
it('should not set a cookie with blank page URL', async function({context, server}) {
let error = null;
try {
await context.addCookies([
{url: server.EMPTY_PAGE, name: 'example-cookie', value: 'best'},
{url: 'about:blank', name: 'example-cookie-blank', value: 'best'}
]);
} catch (e) {
error = e;
}
expect(error.message).toEqual(
`Blank page can not have cookie "example-cookie-blank"`
);
});
it('should not set a cookie on a data URL page', async function({context}) {
let error = null;
try {
await context.addCookies([{url: 'data:,Hello%2C%20World!', name: 'example-cookie', value: 'best'}]);
} catch (e) {
error = e;
}
expect(error.message).toContain('Data URL page can not have cookie "example-cookie"');
});
it('should default to setting secure cookie for HTTPS websites', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
const SECURE_URL = 'https://example.com';
await context.addCookies([{
url: SECURE_URL,
name: 'foo',
value: 'bar',
}]);
const [cookie] = await context.cookies(SECURE_URL);
expect(cookie.secure).toBe(true);
});
it('should be able to set unsecure cookie for HTTP website', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
const HTTP_URL = 'http://example.com';
await context.addCookies([{
url: HTTP_URL,
name: 'foo',
value: 'bar',
}]);
const [cookie] = await context.cookies(HTTP_URL);
expect(cookie.secure).toBe(false);
});
it('should set a cookie on a different domain', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
await context.addCookies([{
url: 'https://www.example.com',
name: 'example-cookie',
value: 'best',
}]);
expect(await page.evaluate('document.cookie')).toBe('');
expect(await context.cookies('https://www.example.com')).toEqual([{
name: 'example-cookie',
value: 'best',
domain: 'www.example.com',
path: '/',
expires: -1,
httpOnly: false,
secure: true,
sameSite: 'None',
}]);
});
it('should set cookies for a frame', async({context, page, server}) => {
expect(cookie).toBe('sendcookie=value');
}
{
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(cookie).toBe('');
await context.close();
}
});
it.slow()('should isolate cookies between launches', async({browserType, server}) => {
const browser1 = await browserType.launch(defaultBrowserOptions);
const context1 = await browser1.newContext();
await context1.addCookies([{url: server.EMPTY_PAGE, name: 'cookie-in-context-1', value: 'value', expires: Date.now() / 1000 + 10000}]);
await browser1.close();
const browser2 = await browserType.launch(defaultBrowserOptions);
const context2 = await browser2.newContext();
const cookies = await context2.cookies();
expect(cookies.length).toBe(0);
await browser2.close();
});
it('should set multiple cookies', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
await context.addCookies([{
url: server.EMPTY_PAGE,
name: 'multiple-1',
value: '123456'
}, {
url: server.EMPTY_PAGE,
name: 'multiple-2',
value: 'bar'
}]);
expect(await page.evaluate(() => {
const cookies = document.cookie.split(';');
return cookies.map(cookie => cookie.trim()).sort();
})).toEqual([
'multiple-1=123456',
'multiple-2=bar',
]);
});
it('should have |expires| set to |-1| for session cookies', async({context, server}) => {
await context.addCookies([{
url: server.EMPTY_PAGE,
name: 'expires',
value: '123456'
}]);
const cookies = await context.cookies();
expect(cookies[0].expires).toBe(-1);
});
it('should set cookie with reasonable defaults', async({context, server}) => {
await context.addCookies([{
url: server.EMPTY_PAGE,
name: 'defaults',
value: '123456'
}]);
const cookies = await context.cookies();
expect(cookies.sort((a, b) => a.name.localeCompare(b.name))).toEqual([{
name: 'defaults',
value: '123456',
domain: 'localhost',
path: '/',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
}]);
});
it('should set a cookie with a path', async({context, page, server}) => {
await page.goto(server.PREFIX + '/grid.html');
await context.addCookies([{
domain: 'localhost',
path: '/grid.html',
name: 'gridcookie',
value: 'GRID',
}]);
expect(await context.cookies()).toEqual([{
name: 'gridcookie',
value: 'GRID',
domain: 'localhost',
path: '/grid.html',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
}]);
expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID');
await page.goto(server.EMPTY_PAGE);
expect(await page.evaluate('document.cookie')).toBe('');
await page.goto(server.PREFIX + '/grid.html');
expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID');
});
it('should not set a cookie with blank page URL', async function({context, server}) {
let error = null;
try {
await context.addCookies([
{url: server.PREFIX, name: 'frame-cookie', value: 'value'}
{url: server.EMPTY_PAGE, name: 'example-cookie', value: 'best'},
{url: 'about:blank', name: 'example-cookie-blank', value: 'best'}
]);
await page.evaluate(src => {
let fulfill;
const promise = new Promise(x => fulfill = x);
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.onload = fulfill;
iframe.src = src;
return promise;
}, server.PREFIX + '/grid.html');
expect(await page.frames()[1].evaluate('document.cookie')).toBe('frame-cookie=value');
});
} catch (e) {
error = e;
}
expect(error.message).toEqual(
`Blank page can not have cookie "example-cookie-blank"`
);
});
describe('BrowserContext.clearCookies', function() {
it('should clear cookies', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
await context.addCookies([{
url: server.EMPTY_PAGE,
name: 'cookie1',
value: '1'
}]);
expect(await page.evaluate('document.cookie')).toBe('cookie1=1');
await context.clearCookies();
expect(await context.cookies()).toEqual([]);
await page.reload();
expect(await page.evaluate('document.cookie')).toBe('');
});
it('should isolate cookies when clearing', async({context, server, browser}) => {
const anotherContext = await browser.newContext();
await context.addCookies([{url: server.EMPTY_PAGE, name: 'page1cookie', value: 'page1value'}]);
await anotherContext.addCookies([{url: server.EMPTY_PAGE, name: 'page2cookie', value: 'page2value'}]);
expect((await context.cookies()).length).toBe(1);
expect((await anotherContext.cookies()).length).toBe(1);
await context.clearCookies();
expect((await context.cookies()).length).toBe(0);
expect((await anotherContext.cookies()).length).toBe(1);
await anotherContext.clearCookies();
expect((await context.cookies()).length).toBe(0);
expect((await anotherContext.cookies()).length).toBe(0);
await anotherContext.close();
});
it('should not set a cookie on a data URL page', async function({context}) {
let error = null;
try {
await context.addCookies([{url: 'data:,Hello%2C%20World!', name: 'example-cookie', value: 'best'}]);
} catch (e) {
error = e;
}
expect(error.message).toContain('Data URL page can not have cookie "example-cookie"');
});
};
it('should default to setting secure cookie for HTTPS websites', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
const SECURE_URL = 'https://example.com';
await context.addCookies([{
url: SECURE_URL,
name: 'foo',
value: 'bar',
}]);
const [cookie] = await context.cookies(SECURE_URL);
expect(cookie.secure).toBe(true);
});
it('should be able to set unsecure cookie for HTTP website', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
const HTTP_URL = 'http://example.com';
await context.addCookies([{
url: HTTP_URL,
name: 'foo',
value: 'bar',
}]);
const [cookie] = await context.cookies(HTTP_URL);
expect(cookie.secure).toBe(false);
});
it('should set a cookie on a different domain', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
await context.addCookies([{
url: 'https://www.example.com',
name: 'example-cookie',
value: 'best',
}]);
expect(await page.evaluate('document.cookie')).toBe('');
expect(await context.cookies('https://www.example.com')).toEqual([{
name: 'example-cookie',
value: 'best',
domain: 'www.example.com',
path: '/',
expires: -1,
httpOnly: false,
secure: true,
sameSite: 'None',
}]);
});
it('should set cookies for a frame', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
await context.addCookies([
{url: server.PREFIX, name: 'frame-cookie', value: 'value'}
]);
await page.evaluate(src => {
let fulfill;
const promise = new Promise(x => fulfill = x);
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.onload = fulfill;
iframe.src = src;
return promise;
}, server.PREFIX + '/grid.html');
expect(await page.frames()[1].evaluate('document.cookie')).toBe('frame-cookie=value');
});
});
describe('BrowserContext.clearCookies', function() {
it('should clear cookies', async({context, page, server}) => {
await page.goto(server.EMPTY_PAGE);
await context.addCookies([{
url: server.EMPTY_PAGE,
name: 'cookie1',
value: '1'
}]);
expect(await page.evaluate('document.cookie')).toBe('cookie1=1');
await context.clearCookies();
expect(await context.cookies()).toEqual([]);
await page.reload();
expect(await page.evaluate('document.cookie')).toBe('');
});
it('should isolate cookies when clearing', async({context, server, browser}) => {
const anotherContext = await browser.newContext();
await context.addCookies([{url: server.EMPTY_PAGE, name: 'page1cookie', value: 'page1value'}]);
await anotherContext.addCookies([{url: server.EMPTY_PAGE, name: 'page2cookie', value: 'page2value'}]);
expect((await context.cookies()).length).toBe(1);
expect((await anotherContext.cookies()).length).toBe(1);
await context.clearCookies();
expect((await context.cookies()).length).toBe(0);
expect((await anotherContext.cookies()).length).toBe(1);
await anotherContext.clearCookies();
expect((await context.cookies()).length).toBe(0);
expect((await anotherContext.cookies()).length).toBe(0);
await anotherContext.close();
});
});

View file

@ -15,76 +15,72 @@
* limitations under the License.
*/
const { makeUserDataDir, removeUserDataDir } = require('./utils');
const utils = require('./utils');
const {makeUserDataDir, removeUserDataDir} = utils;
const {FFOX, CHROMIUM, WEBKIT, defaultBrowserOptions} = utils.testOptions(browserType);
/**
* @type {PageTestSuite}
*/
module.exports.describe = function ({ defaultBrowserOptions, browserType, WEBKIT }) {
describe('launchPersistentContext()', function() {
beforeEach(async state => {
state.userDataDir = await makeUserDataDir();
state.browserContext = await browserType.launchPersistentContext(state.userDataDir, defaultBrowserOptions);
state.page = await state.browserContext.newPage();
});
afterEach(async state => {
await state.browserContext.close();
delete state.browserContext;
delete state.page;
await removeUserDataDir(state.userDataDir);
});
it('context.cookies() should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
document.cookie = 'username=John Doe';
});
expect(await page.context().cookies()).toEqual([{
name: 'username',
value: 'John Doe',
domain: 'localhost',
path: '/',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
}]);
});
it('context.addCookies() should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.context().addCookies([{
url: server.EMPTY_PAGE,
name: 'username',
value: 'John Doe'
}]);
expect(await page.evaluate(() => document.cookie)).toBe('username=John Doe');
expect(await page.context().cookies()).toEqual([{
name: 'username',
value: 'John Doe',
domain: 'localhost',
path: '/',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
}]);
});
it('context.clearCookies() should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.context().addCookies([{
url: server.EMPTY_PAGE,
name: 'cookie1',
value: '1'
}, {
url: server.EMPTY_PAGE,
name: 'cookie2',
value: '2'
}]);
expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie2=2');
await page.context().clearCookies();
await page.reload();
expect(await page.context().cookies([])).toEqual([]);
expect(await page.evaluate('document.cookie')).toBe('');
});
describe('launchPersistentContext()', function() {
beforeEach(async state => {
state.userDataDir = await makeUserDataDir();
state.browserContext = await state.browserType.launchPersistentContext(state.userDataDir, defaultBrowserOptions);
state.page = await state.browserContext.newPage();
});
};
afterEach(async state => {
await state.browserContext.close();
delete state.browserContext;
delete state.page;
await removeUserDataDir(state.userDataDir);
});
it('context.cookies() should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
document.cookie = 'username=John Doe';
});
expect(await page.context().cookies()).toEqual([{
name: 'username',
value: 'John Doe',
domain: 'localhost',
path: '/',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
}]);
});
it('context.addCookies() should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.context().addCookies([{
url: server.EMPTY_PAGE,
name: 'username',
value: 'John Doe'
}]);
expect(await page.evaluate(() => document.cookie)).toBe('username=John Doe');
expect(await page.context().cookies()).toEqual([{
name: 'username',
value: 'John Doe',
domain: 'localhost',
path: '/',
expires: -1,
httpOnly: false,
secure: false,
sameSite: 'None',
}]);
});
it('context.clearCookies() should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.context().addCookies([{
url: server.EMPTY_PAGE,
name: 'cookie1',
value: '1'
}, {
url: server.EMPTY_PAGE,
name: 'cookie2',
value: '2'
}]);
expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie2=2');
await page.context().clearCookies();
await page.reload();
expect(await page.context().cookies([])).toEqual([]);
expect(await page.evaluate('document.cookie')).toBe('');
});
});

View file

@ -15,51 +15,47 @@
* limitations under the License.
*/
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({FFOX, CHROMIUM, WEBKIT}) {
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
describe('Page.Events.Dialog', function() {
it('should fire', async({page, server}) => {
page.on('dialog', dialog => {
expect(dialog.type()).toBe('alert');
expect(dialog.defaultValue()).toBe('');
expect(dialog.message()).toBe('yo');
dialog.accept();
});
await page.evaluate(() => alert('yo'));
});
it('should allow accepting prompts', async({page, server}) => {
page.on('dialog', dialog => {
expect(dialog.type()).toBe('prompt');
expect(dialog.defaultValue()).toBe('yes.');
expect(dialog.message()).toBe('question?');
dialog.accept('answer!');
});
const result = await page.evaluate(() => prompt('question?', 'yes.'));
expect(result).toBe('answer!');
});
it('should dismiss the prompt', async({page, server}) => {
page.on('dialog', dialog => {
dialog.dismiss();
});
const result = await page.evaluate(() => prompt('question?'));
expect(result).toBe(null);
});
it('should accept the confirm prompt', async({page, server}) => {
page.on('dialog', dialog => {
dialog.accept();
});
const result = await page.evaluate(() => confirm('boolean?'));
expect(result).toBe(true);
});
it('should dismiss the confirm prompt', async({page, server}) => {
page.on('dialog', dialog => {
dialog.dismiss();
});
const result = await page.evaluate(() => confirm('boolean?'));
expect(result).toBe(false);
describe('Page.Events.Dialog', function() {
it('should fire', async({page, server}) => {
page.on('dialog', dialog => {
expect(dialog.type()).toBe('alert');
expect(dialog.defaultValue()).toBe('');
expect(dialog.message()).toBe('yo');
dialog.accept();
});
await page.evaluate(() => alert('yo'));
});
};
it('should allow accepting prompts', async({page, server}) => {
page.on('dialog', dialog => {
expect(dialog.type()).toBe('prompt');
expect(dialog.defaultValue()).toBe('yes.');
expect(dialog.message()).toBe('question?');
dialog.accept('answer!');
});
const result = await page.evaluate(() => prompt('question?', 'yes.'));
expect(result).toBe('answer!');
});
it('should dismiss the prompt', async({page, server}) => {
page.on('dialog', dialog => {
dialog.dismiss();
});
const result = await page.evaluate(() => prompt('question?'));
expect(result).toBe(null);
});
it('should accept the confirm prompt', async({page, server}) => {
page.on('dialog', dialog => {
dialog.accept();
});
const result = await page.evaluate(() => confirm('boolean?'));
expect(result).toBe(true);
});
it('should dismiss the confirm prompt', async({page, server}) => {
page.on('dialog', dialog => {
dialog.dismiss();
});
const result = await page.evaluate(() => confirm('boolean?'));
expect(result).toBe(false);
});
});

View file

@ -16,107 +16,105 @@
const fs = require('fs');
const path = require('path');
const {FFOX, CHROMIUM, WEBKIT, defaultBrowserOptions} = require('./utils').testOptions(browserType);
module.exports.describe = function({browserType, defaultBrowserOptions, CHROMIUM, WEBKIT, FFOX, WIN, MAC}) {
describe('Download', function() {
beforeEach(async(state) => {
state.server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment');
res.end(`Hello world`);
});
});
it('should report downloads with acceptDownloads: false', async({page, server}) => {
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
let error;
expect(download.url()).toBe(`${server.PREFIX}/download`);
await download.path().catch(e => error = e);
expect(await download.failure()).toContain('acceptDownloads');
expect(error.message).toContain('acceptDownloads: true');
});
it('should report downloads with acceptDownloads: true', async({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
it('should delete file', async({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
await download.delete();
expect(fs.existsSync(path)).toBeFalsy();
});
it('should expose stream', async({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const stream = await download.createReadStream();
let content = '';
stream.on('data', data => content += data.toString());
await new Promise(f => stream.on('end', f));
expect(content).toBe('Hello world');
stream.close();
});
it('should delete downloads on context destruction', async({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
const [ download1 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const [ download2 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path1 = await download1.path();
const path2 = await download2.path();
expect(fs.existsSync(path1)).toBeTruthy();
expect(fs.existsSync(path2)).toBeTruthy();
await page.context().close();
expect(fs.existsSync(path1)).toBeFalsy();
expect(fs.existsSync(path2)).toBeFalsy();
});
it('should delete downloads on browser gone', async ({ server }) => {
const browser = await browserType.launch(defaultBrowserOptions);
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
const [ download1 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const [ download2 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path1 = await download1.path();
const path2 = await download2.path();
expect(fs.existsSync(path1)).toBeTruthy();
expect(fs.existsSync(path2)).toBeTruthy();
await browser.close();
expect(fs.existsSync(path1)).toBeFalsy();
expect(fs.existsSync(path2)).toBeFalsy();
expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy();
describe('Download', function() {
beforeEach(async(state) => {
state.server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment');
res.end(`Hello world`);
});
});
};
it('should report downloads with acceptDownloads: false', async({page, server}) => {
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
let error;
expect(download.url()).toBe(`${server.PREFIX}/download`);
await download.path().catch(e => error = e);
expect(await download.failure()).toContain('acceptDownloads');
expect(error.message).toContain('acceptDownloads: true');
});
it('should report downloads with acceptDownloads: true', async({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
it('should delete file', async({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
await download.delete();
expect(fs.existsSync(path)).toBeFalsy();
});
it('should expose stream', async({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const stream = await download.createReadStream();
let content = '';
stream.on('data', data => content += data.toString());
await new Promise(f => stream.on('end', f));
expect(content).toBe('Hello world');
stream.close();
});
it('should delete downloads on context destruction', async({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
const [ download1 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const [ download2 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path1 = await download1.path();
const path2 = await download2.path();
expect(fs.existsSync(path1)).toBeTruthy();
expect(fs.existsSync(path2)).toBeTruthy();
await page.context().close();
expect(fs.existsSync(path1)).toBeFalsy();
expect(fs.existsSync(path2)).toBeFalsy();
});
it('should delete downloads on browser gone', async ({ server, browserType }) => {
const browser = await browserType.launch(defaultBrowserOptions);
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
const [ download1 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const [ download2 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path1 = await download1.path();
const path2 = await download2.path();
expect(fs.existsSync(path1)).toBeTruthy();
expect(fs.existsSync(path2)).toBeTruthy();
await browser.close();
expect(fs.existsSync(path1)).toBeFalsy();
expect(fs.existsSync(path2)).toBeFalsy();
expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy();
});
});

View file

@ -16,318 +16,313 @@
*/
const utils = require('./utils');
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({FFOX, CHROMIUM, WEBKIT}) {
describe('ElementHandle.boundingBox', function() {
it('should work', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const elementHandle = await page.$('.box:nth-of-type(13)');
const box = await elementHandle.boundingBox();
expect(box).toEqual({ x: 100, y: 50, width: 50, height: 50 });
});
it('should handle nested frames', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/frames/nested-frames.html');
const nestedFrame = page.frames().find(frame => frame.name() === 'dos');
const elementHandle = await nestedFrame.$('div');
const box = await elementHandle.boundingBox();
expect(box).toEqual({ x: 24, y: 224, width: 268, height: 18 });
});
it('should return null for invisible elements', async({page, server}) => {
await page.setContent('<div style="display:none">hi</div>');
const element = await page.$('div');
expect(await element.boundingBox()).toBe(null);
});
it('should force a layout', async({page, server}) => {
await page.setViewportSize({ width: 500, height: 500 });
await page.setContent('<div style="width: 100px; height: 100px">hello</div>');
const elementHandle = await page.$('div');
await page.evaluate(element => element.style.height = '200px', elementHandle);
const box = await elementHandle.boundingBox();
expect(box).toEqual({ x: 8, y: 8, width: 100, height: 200 });
});
it('should work with SVG nodes', async({page, server}) => {
await page.setContent(`
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
<rect id="theRect" x="30" y="50" width="200" height="300"></rect>
</svg>
`);
const element = await page.$('#therect');
const pwBoundingBox = await element.boundingBox();
const webBoundingBox = await page.evaluate(e => {
const rect = e.getBoundingClientRect();
return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};
}, element);
expect(pwBoundingBox).toEqual(webBoundingBox);
});
it.skip(FFOX)('should work with page scale', async({browser, server}) => {
const context = await browser.newContext({ viewport: { width: 400, height: 400, isMobile: true} });
const page = await context.newPage();
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await button.evaluate(button => {
document.body.style.margin = '0';
button.style.borderWidth = '0';
button.style.width = '200px';
button.style.height = '20px';
button.style.marginLeft = '17px';
button.style.marginTop = '23px';
});
const box = await button.boundingBox();
expect(Math.round(box.x * 100)).toBe(17 * 100);
expect(Math.round(box.y * 100)).toBe(23 * 100);
expect(Math.round(box.width * 100)).toBe(200 * 100);
expect(Math.round(box.height * 100)).toBe(20 * 100);
await context.close();
});
it('should work when inline box child is outside of viewport', async({page, server}) => {
await page.setContent(`
<style>
i {
position: absolute;
top: -1000px;
}
body {
margin: 0;
font-size: 12px;
}
</style>
<span><i>woof</i><b>doggo</b></span>
`);
const handle = await page.$('span');
const box = await handle.boundingBox();
const webBoundingBox = await handle.evaluate(e => {
const rect = e.getBoundingClientRect();
return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};
});
const round = box => ({
x: Math.round(box.x * 100),
y: Math.round(box.y * 100),
width: Math.round(box.width * 100),
height: Math.round(box.height * 100),
});
expect(round(box)).toEqual(round(webBoundingBox));
});
describe('ElementHandle.boundingBox', function() {
it('should work', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const elementHandle = await page.$('.box:nth-of-type(13)');
const box = await elementHandle.boundingBox();
expect(box).toEqual({ x: 100, y: 50, width: 50, height: 50 });
});
describe('ElementHandle.contentFrame', function() {
it('should work', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const elementHandle = await page.$('#frame1');
const frame = await elementHandle.contentFrame();
expect(frame).toBe(page.frames()[1]);
});
it('should work for cross-process iframes', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html');
const elementHandle = await page.$('#frame1');
const frame = await elementHandle.contentFrame();
expect(frame).toBe(page.frames()[1]);
});
it('should work for cross-frame evaluations', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
const elementHandle = await frame.evaluateHandle(() => window.top.document.querySelector('#frame1'));
expect(await elementHandle.contentFrame()).toBe(frame);
});
it('should return null for non-iframes', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
const elementHandle = await frame.evaluateHandle(() => document.body);
expect(await elementHandle.contentFrame()).toBe(null);
});
it('should return null for document.documentElement', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
const elementHandle = await frame.evaluateHandle(() => document.documentElement);
expect(await elementHandle.contentFrame()).toBe(null);
});
it('should handle nested frames', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/frames/nested-frames.html');
const nestedFrame = page.frames().find(frame => frame.name() === 'dos');
const elementHandle = await nestedFrame.$('div');
const box = await elementHandle.boundingBox();
expect(box).toEqual({ x: 24, y: 224, width: 268, height: 18 });
});
describe('ElementHandle.ownerFrame', function() {
it('should work', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
const elementHandle = await frame.evaluateHandle(() => document.body);
expect(await elementHandle.ownerFrame()).toBe(frame);
});
it('should work for cross-process iframes', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html');
const frame = page.frames()[1];
const elementHandle = await frame.evaluateHandle(() => document.body);
expect(await elementHandle.ownerFrame()).toBe(frame);
});
it('should work for document', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
const elementHandle = await frame.evaluateHandle(() => document);
expect(await elementHandle.ownerFrame()).toBe(frame);
});
it('should work for iframe elements', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.mainFrame();
const elementHandle = await frame.evaluateHandle(() => document.querySelector('#frame1'));
expect(await elementHandle.ownerFrame()).toBe(frame);
});
it('should work for cross-frame evaluations', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.mainFrame();
const elementHandle = await frame.evaluateHandle(() => document.querySelector('#frame1').contentWindow.document.body);
expect(await elementHandle.ownerFrame()).toBe(frame.childFrames()[0]);
});
it('should work for detached elements', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
const divHandle = await page.evaluateHandle(() => {
const div = document.createElement('div');
document.body.appendChild(div);
return div;
});
expect(await divHandle.ownerFrame()).toBe(page.mainFrame());
await page.evaluate(() => {
const div = document.querySelector('div');
document.body.removeChild(div);
});
expect(await divHandle.ownerFrame()).toBe(page.mainFrame());
});
it('should work for adopted elements', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE),
]);
const divHandle = await page.evaluateHandle(() => {
const div = document.createElement('div');
document.body.appendChild(div);
return div;
});
expect(await divHandle.ownerFrame()).toBe(page.mainFrame());
await popup.waitForLoadState('domcontentloaded');
await page.evaluate(() => {
const div = document.querySelector('div');
window.__popup.document.body.appendChild(div);
});
expect(await divHandle.ownerFrame()).toBe(popup.mainFrame());
});
it('should return null for invisible elements', async({page, server}) => {
await page.setContent('<div style="display:none">hi</div>');
const element = await page.$('div');
expect(await element.boundingBox()).toBe(null);
});
describe('ElementHandle.click', function() {
it('should work', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await button.click();
expect(await page.evaluate(() => result)).toBe('Clicked');
});
it('should work with Node removed', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
await page.evaluate(() => delete window['Node']);
const button = await page.$('button');
await button.click();
expect(await page.evaluate(() => result)).toBe('Clicked');
});
it('should work for Shadow DOM v1', async({page, server}) => {
await page.goto(server.PREFIX + '/shadow.html');
const buttonHandle = await page.evaluateHandle(() => button);
await buttonHandle.click();
expect(await page.evaluate(() => clicked)).toBe(true);
});
it('should work for TextNodes', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild);
await buttonTextNode.click();
expect(await page.evaluate(() => result)).toBe('Clicked');
});
it('should throw for detached nodes', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await page.evaluate(button => button.remove(), button);
let error = null;
await button.click().catch(err => error = err);
expect(error.message).toContain('Element is not attached to the DOM');
});
it('should throw for hidden nodes', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await page.evaluate(button => button.style.display = 'none', button);
const error = await button.click({ force: true }).catch(err => err);
expect(error.message).toBe('Node is either not visible or not an HTMLElement');
});
it('should throw for recursively hidden nodes', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await page.evaluate(button => button.parentElement.style.display = 'none', button);
const error = await button.click({ force: true }).catch(err => err);
expect(error.message).toBe('Node is either not visible or not an HTMLElement');
});
it('should throw for <br> elements', async({page, server}) => {
await page.setContent('hello<br>goodbye');
const br = await page.$('br');
const error = await br.click({ force: true }).catch(err => err);
expect(error.message).toBe('Node is either not visible or not an HTMLElement');
});
it('should force a layout', async({page, server}) => {
await page.setViewportSize({ width: 500, height: 500 });
await page.setContent('<div style="width: 100px; height: 100px">hello</div>');
const elementHandle = await page.$('div');
await page.evaluate(element => element.style.height = '200px', elementHandle);
const box = await elementHandle.boundingBox();
expect(box).toEqual({ x: 8, y: 8, width: 100, height: 200 });
});
describe('ElementHandle.hover', function() {
it('should work', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html');
const button = await page.$('#button-6');
await button.hover();
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
});
it('should work when Node is removed', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() => delete window['Node']);
const button = await page.$('#button-6');
await button.hover();
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
});
it('should work with SVG nodes', async({page, server}) => {
await page.setContent(`
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
<rect id="theRect" x="30" y="50" width="200" height="300"></rect>
</svg>
`);
const element = await page.$('#therect');
const pwBoundingBox = await element.boundingBox();
const webBoundingBox = await page.evaluate(e => {
const rect = e.getBoundingClientRect();
return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};
}, element);
expect(pwBoundingBox).toEqual(webBoundingBox);
});
describe('ElementHandle.scrollIntoViewIfNeeded', function() {
it.fail(FFOX)('should work', async({page, server}) => {
await page.goto(server.PREFIX + '/offscreenbuttons.html');
for (let i = 0; i < 11; ++i) {
const button = await page.$('#btn' + i);
const before = await button.evaluate(button => {
return button.getBoundingClientRect().right - window.innerWidth;
});
expect(before).toBe(10 * i);
await button.scrollIntoViewIfNeeded();
const after = await button.evaluate(button => {
return button.getBoundingClientRect().right - window.innerWidth;
});
expect(after <= 0).toBe(true);
await page.evaluate(() => window.scrollTo(0, 0));
it.skip(FFOX)('should work with page scale', async({browser, server}) => {
const context = await browser.newContext({ viewport: { width: 400, height: 400, isMobile: true} });
const page = await context.newPage();
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await button.evaluate(button => {
document.body.style.margin = '0';
button.style.borderWidth = '0';
button.style.width = '200px';
button.style.height = '20px';
button.style.marginLeft = '17px';
button.style.marginTop = '23px';
});
const box = await button.boundingBox();
expect(Math.round(box.x * 100)).toBe(17 * 100);
expect(Math.round(box.y * 100)).toBe(23 * 100);
expect(Math.round(box.width * 100)).toBe(200 * 100);
expect(Math.round(box.height * 100)).toBe(20 * 100);
await context.close();
});
it('should work when inline box child is outside of viewport', async({page, server}) => {
await page.setContent(`
<style>
i {
position: absolute;
top: -1000px;
}
body {
margin: 0;
font-size: 12px;
}
</style>
<span><i>woof</i><b>doggo</b></span>
`);
const handle = await page.$('span');
const box = await handle.boundingBox();
const webBoundingBox = await handle.evaluate(e => {
const rect = e.getBoundingClientRect();
return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};
});
const round = box => ({
x: Math.round(box.x * 100),
y: Math.round(box.y * 100),
width: Math.round(box.width * 100),
height: Math.round(box.height * 100),
});
expect(round(box)).toEqual(round(webBoundingBox));
});
});
describe('ElementHandle.fill', function() {
it('should fill input', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const handle = await page.$('input');
await handle.fill('some value');
expect(await page.evaluate(() => result)).toBe('some value');
});
it('should fill input when Node is removed', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.evaluate(() => delete window['Node']);
const handle = await page.$('input');
await handle.fill('some value');
expect(await page.evaluate(() => result)).toBe('some value');
});
describe('ElementHandle.contentFrame', function() {
it('should work', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const elementHandle = await page.$('#frame1');
const frame = await elementHandle.contentFrame();
expect(frame).toBe(page.frames()[1]);
});
};
it('should work for cross-process iframes', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html');
const elementHandle = await page.$('#frame1');
const frame = await elementHandle.contentFrame();
expect(frame).toBe(page.frames()[1]);
});
it('should work for cross-frame evaluations', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
const elementHandle = await frame.evaluateHandle(() => window.top.document.querySelector('#frame1'));
expect(await elementHandle.contentFrame()).toBe(frame);
});
it('should return null for non-iframes', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
const elementHandle = await frame.evaluateHandle(() => document.body);
expect(await elementHandle.contentFrame()).toBe(null);
});
it('should return null for document.documentElement', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
const elementHandle = await frame.evaluateHandle(() => document.documentElement);
expect(await elementHandle.contentFrame()).toBe(null);
});
});
describe('ElementHandle.ownerFrame', function() {
it('should work', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
const elementHandle = await frame.evaluateHandle(() => document.body);
expect(await elementHandle.ownerFrame()).toBe(frame);
});
it('should work for cross-process iframes', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html');
const frame = page.frames()[1];
const elementHandle = await frame.evaluateHandle(() => document.body);
expect(await elementHandle.ownerFrame()).toBe(frame);
});
it('should work for document', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
const elementHandle = await frame.evaluateHandle(() => document);
expect(await elementHandle.ownerFrame()).toBe(frame);
});
it('should work for iframe elements', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.mainFrame();
const elementHandle = await frame.evaluateHandle(() => document.querySelector('#frame1'));
expect(await elementHandle.ownerFrame()).toBe(frame);
});
it('should work for cross-frame evaluations', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.mainFrame();
const elementHandle = await frame.evaluateHandle(() => document.querySelector('#frame1').contentWindow.document.body);
expect(await elementHandle.ownerFrame()).toBe(frame.childFrames()[0]);
});
it('should work for detached elements', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
const divHandle = await page.evaluateHandle(() => {
const div = document.createElement('div');
document.body.appendChild(div);
return div;
});
expect(await divHandle.ownerFrame()).toBe(page.mainFrame());
await page.evaluate(() => {
const div = document.querySelector('div');
document.body.removeChild(div);
});
expect(await divHandle.ownerFrame()).toBe(page.mainFrame());
});
it('should work for adopted elements', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE),
]);
const divHandle = await page.evaluateHandle(() => {
const div = document.createElement('div');
document.body.appendChild(div);
return div;
});
expect(await divHandle.ownerFrame()).toBe(page.mainFrame());
await popup.waitForLoadState('domcontentloaded');
await page.evaluate(() => {
const div = document.querySelector('div');
window.__popup.document.body.appendChild(div);
});
expect(await divHandle.ownerFrame()).toBe(popup.mainFrame());
});
});
describe('ElementHandle.click', function() {
it('should work', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await button.click();
expect(await page.evaluate(() => result)).toBe('Clicked');
});
it('should work with Node removed', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
await page.evaluate(() => delete window['Node']);
const button = await page.$('button');
await button.click();
expect(await page.evaluate(() => result)).toBe('Clicked');
});
it('should work for Shadow DOM v1', async({page, server}) => {
await page.goto(server.PREFIX + '/shadow.html');
const buttonHandle = await page.evaluateHandle(() => button);
await buttonHandle.click();
expect(await page.evaluate(() => clicked)).toBe(true);
});
it('should work for TextNodes', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild);
await buttonTextNode.click();
expect(await page.evaluate(() => result)).toBe('Clicked');
});
it('should throw for detached nodes', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await page.evaluate(button => button.remove(), button);
let error = null;
await button.click().catch(err => error = err);
expect(error.message).toContain('Element is not attached to the DOM');
});
it('should throw for hidden nodes', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await page.evaluate(button => button.style.display = 'none', button);
const error = await button.click({ force: true }).catch(err => err);
expect(error.message).toBe('Node is either not visible or not an HTMLElement');
});
it('should throw for recursively hidden nodes', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await page.evaluate(button => button.parentElement.style.display = 'none', button);
const error = await button.click({ force: true }).catch(err => err);
expect(error.message).toBe('Node is either not visible or not an HTMLElement');
});
it('should throw for <br> elements', async({page, server}) => {
await page.setContent('hello<br>goodbye');
const br = await page.$('br');
const error = await br.click({ force: true }).catch(err => err);
expect(error.message).toBe('Node is either not visible or not an HTMLElement');
});
});
describe('ElementHandle.hover', function() {
it('should work', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html');
const button = await page.$('#button-6');
await button.hover();
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
});
it('should work when Node is removed', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() => delete window['Node']);
const button = await page.$('#button-6');
await button.hover();
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
});
});
describe('ElementHandle.scrollIntoViewIfNeeded', function() {
it.fail(FFOX)('should work', async({page, server}) => {
await page.goto(server.PREFIX + '/offscreenbuttons.html');
for (let i = 0; i < 11; ++i) {
const button = await page.$('#btn' + i);
const before = await button.evaluate(button => {
return button.getBoundingClientRect().right - window.innerWidth;
});
expect(before).toBe(10 * i);
await button.scrollIntoViewIfNeeded();
const after = await button.evaluate(button => {
return button.getBoundingClientRect().right - window.innerWidth;
});
expect(after <= 0).toBe(true);
await page.evaluate(() => window.scrollTo(0, 0));
}
});
});
describe('ElementHandle.fill', function() {
it('should fill input', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const handle = await page.$('input');
await handle.fill('some value');
expect(await page.evaluate(() => result)).toBe('some value');
});
it('should fill input when Node is removed', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.evaluate(() => delete window['Node']);
const handle = await page.$('input');
await handle.fill('some value');
expect(await page.evaluate(() => result)).toBe('some value');
});
});

View file

@ -16,449 +16,444 @@
*/
const utils = require('./utils');
const {FFOX, CHROMIUM, WEBKIT, headless} = utils.testOptions(browserType);
const iPhone = playwright.devices['iPhone 6'];
const iPhoneLandscape = playwright.devices['iPhone 6 landscape'];
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({playwright, headless, FFOX, CHROMIUM, WEBKIT, MAC, WIN}) {
const iPhone = playwright.devices['iPhone 6'];
const iPhoneLandscape = playwright.devices['iPhone 6 landscape'];
describe('BrowserContext({viewport})', function() {
it('should get the proper viewport size', async({page, server}) => {
expect(page.viewportSize()).toEqual({width: 1280, height: 720});
expect(await page.evaluate(() => window.innerWidth)).toBe(1280);
expect(await page.evaluate(() => window.innerHeight)).toBe(720);
});
it('should set the proper viewport size', async({page, server}) => {
expect(page.viewportSize()).toEqual({width: 1280, height: 720});
await page.setViewportSize({width: 123, height: 456});
expect(page.viewportSize()).toEqual({width: 123, height: 456});
expect(await page.evaluate(() => window.innerWidth)).toBe(123);
expect(await page.evaluate(() => window.innerHeight)).toBe(456);
});
it('should emulate device width', async({page, server}) => {
expect(page.viewportSize()).toEqual({width: 1280, height: 720});
await page.setViewportSize({width: 200, height: 200});
expect(await page.evaluate(() => window.screen.width)).toBe(200);
expect(await page.evaluate(() => matchMedia('(min-device-width: 100px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(min-device-width: 300px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-width: 100px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-width: 300px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(device-width: 500px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(device-width: 200px)').matches)).toBe(true);
await page.setViewportSize({width: 500, height: 500});
expect(await page.evaluate(() => window.screen.width)).toBe(500);
expect(await page.evaluate(() => matchMedia('(min-device-width: 400px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(min-device-width: 600px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-width: 400px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-width: 600px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(device-width: 200px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(device-width: 500px)').matches)).toBe(true);
});
it('should emulate device height', async({page, server}) => {
expect(page.viewportSize()).toEqual({width: 1280, height: 720});
await page.setViewportSize({width: 200, height: 200});
expect(await page.evaluate(() => window.screen.height)).toBe(200);
expect(await page.evaluate(() => matchMedia('(min-device-height: 100px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(min-device-height: 300px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-height: 100px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-height: 300px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(device-height: 500px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(device-height: 200px)').matches)).toBe(true);
await page.setViewportSize({width: 500, height: 500});
expect(await page.evaluate(() => window.screen.height)).toBe(500);
expect(await page.evaluate(() => matchMedia('(min-device-height: 400px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(min-device-height: 600px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-height: 400px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-height: 600px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(device-height: 200px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(device-height: 500px)').matches)).toBe(true);
});
it('should not have touch by default', async({page, server}) => {
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false);
await page.goto(server.PREFIX + '/detect-touch.html');
expect(await page.evaluate(() => document.body.textContent.trim())).toBe('NO');
});
describe('BrowserContext({viewport})', function() {
it('should get the proper viewport size', async({page, server}) => {
expect(page.viewportSize()).toEqual({width: 1280, height: 720});
expect(await page.evaluate(() => window.innerWidth)).toBe(1280);
expect(await page.evaluate(() => window.innerHeight)).toBe(720);
});
it('should set the proper viewport size', async({page, server}) => {
expect(page.viewportSize()).toEqual({width: 1280, height: 720});
await page.setViewportSize({width: 123, height: 456});
expect(page.viewportSize()).toEqual({width: 123, height: 456});
expect(await page.evaluate(() => window.innerWidth)).toBe(123);
expect(await page.evaluate(() => window.innerHeight)).toBe(456);
});
it('should emulate device width', async({page, server}) => {
expect(page.viewportSize()).toEqual({width: 1280, height: 720});
await page.setViewportSize({width: 200, height: 200});
expect(await page.evaluate(() => window.screen.width)).toBe(200);
expect(await page.evaluate(() => matchMedia('(min-device-width: 100px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(min-device-width: 300px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-width: 100px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-width: 300px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(device-width: 500px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(device-width: 200px)').matches)).toBe(true);
await page.setViewportSize({width: 500, height: 500});
expect(await page.evaluate(() => window.screen.width)).toBe(500);
expect(await page.evaluate(() => matchMedia('(min-device-width: 400px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(min-device-width: 600px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-width: 400px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-width: 600px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(device-width: 200px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(device-width: 500px)').matches)).toBe(true);
});
it('should emulate device height', async({page, server}) => {
expect(page.viewportSize()).toEqual({width: 1280, height: 720});
await page.setViewportSize({width: 200, height: 200});
expect(await page.evaluate(() => window.screen.height)).toBe(200);
expect(await page.evaluate(() => matchMedia('(min-device-height: 100px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(min-device-height: 300px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-height: 100px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-height: 300px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(device-height: 500px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(device-height: 200px)').matches)).toBe(true);
await page.setViewportSize({width: 500, height: 500});
expect(await page.evaluate(() => window.screen.height)).toBe(500);
expect(await page.evaluate(() => matchMedia('(min-device-height: 400px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(min-device-height: 600px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-height: 400px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(max-device-height: 600px)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(device-height: 200px)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(device-height: 500px)').matches)).toBe(true);
});
it('should not have touch by default', async({page, server}) => {
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false);
await page.goto(server.PREFIX + '/detect-touch.html');
expect(await page.evaluate(() => document.body.textContent.trim())).toBe('NO');
});
});
describe.skip(FFOX)('viewport.isMobile', () => {
// Firefox does not support isMobile.
it('should support mobile emulation', async({browser, server}) => {
const context = await browser.newContext({ ...iPhone });
const page = await context.newPage();
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => window.innerWidth)).toBe(375);
await page.setViewportSize({width: 400, height: 300});
expect(await page.evaluate(() => window.innerWidth)).toBe(400);
await context.close();
describe.skip(FFOX)('viewport.isMobile', () => {
// Firefox does not support isMobile.
it('should support mobile emulation', async({browser, server}) => {
const context = await browser.newContext({ ...iPhone });
const page = await context.newPage();
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => window.innerWidth)).toBe(375);
await page.setViewportSize({width: 400, height: 300});
expect(await page.evaluate(() => window.innerWidth)).toBe(400);
await context.close();
});
it('should support touch emulation', async({browser, server}) => {
const context = await browser.newContext({ ...iPhone });
const page = await context.newPage();
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(true);
expect(await page.evaluate(dispatchTouch)).toBe('Received touch');
await context.close();
function dispatchTouch() {
let fulfill;
const promise = new Promise(x => fulfill = x);
window.ontouchstart = function(e) {
fulfill('Received touch');
};
window.dispatchEvent(new Event('touchstart'));
fulfill('Did not receive touch');
return promise;
}
});
it('should be detectable by Modernizr', async({browser, server}) => {
const context = await browser.newContext({ ...iPhone });
const page = await context.newPage();
await page.goto(server.PREFIX + '/detect-touch.html');
expect(await page.evaluate(() => document.body.textContent.trim())).toBe('YES');
await context.close();
});
it('should detect touch when applying viewport with touches', async({browser, server}) => {
const context = await browser.newContext({ viewport: { width: 800, height: 600 }, hasTouch: true });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.addScriptTag({url: server.PREFIX + '/modernizr.js'});
expect(await page.evaluate(() => Modernizr.touchevents)).toBe(true);
await context.close();
});
it('should support landscape emulation', async({browser, server}) => {
const context1 = await browser.newContext({ ...iPhone });
const page1 = await context1.newPage();
await page1.goto(server.PREFIX + '/mobile.html');
expect(await page1.evaluate(() => matchMedia('(orientation: landscape)').matches)).toBe(false);
const context2 = await browser.newContext({ ...iPhoneLandscape });
const page2 = await context2.newPage();
expect(await page2.evaluate(() => matchMedia('(orientation: landscape)').matches)).toBe(true);
await context1.close();
await context2.close();
});
it.fail(WEBKIT)('should fire orientationchange event', async({browser, server}) => {
const context = await browser.newContext({ viewport: { width: 300, height: 400 }, isMobile: true });
const page = await context.newPage();
await page.goto(server.PREFIX + '/mobile.html');
await page.evaluate(() => {
window.counter = 0;
window.addEventListener('orientationchange', () => console.log(++window.counter));
});
it('should support touch emulation', async({browser, server}) => {
const context = await browser.newContext({ ...iPhone });
const page = await context.newPage();
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(true);
expect(await page.evaluate(dispatchTouch)).toBe('Received touch');
await context.close();
function dispatchTouch() {
let fulfill;
const promise = new Promise(x => fulfill = x);
window.ontouchstart = function(e) {
fulfill('Received touch');
};
window.dispatchEvent(new Event('touchstart'));
const event1 = page.waitForEvent('console');
await page.setViewportSize({width: 400, height: 300});
expect((await event1).text()).toBe('1');
fulfill('Did not receive touch');
const event2 = page.waitForEvent('console');
await page.setViewportSize({width: 300, height: 400});
expect((await event2).text()).toBe('2');
await context.close();
});
it('default mobile viewports to 980 width', async({browser, server}) => {
const context = await browser.newContext({ viewport: {width: 320, height: 480 }, isMobile: true });
const page = await context.newPage();
await page.goto(server.PREFIX + '/empty.html');
expect(await page.evaluate(() => window.innerWidth)).toBe(980);
await context.close();
});
it('respect meta viewport tag', async({browser, server}) => {
const context = await browser.newContext({ viewport: {width: 320, height: 480 }, isMobile: true });
const page = await context.newPage();
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => window.innerWidth)).toBe(320);
await context.close();
});
});
return promise;
}
});
it('should be detectable by Modernizr', async({browser, server}) => {
const context = await browser.newContext({ ...iPhone });
const page = await context.newPage();
await page.goto(server.PREFIX + '/detect-touch.html');
expect(await page.evaluate(() => document.body.textContent.trim())).toBe('YES');
await context.close();
});
it('should detect touch when applying viewport with touches', async({browser, server}) => {
const context = await browser.newContext({ viewport: { width: 800, height: 600 }, hasTouch: true });
describe.skip(FFOX)('Page.emulate', function() {
it('should work', async({browser, server}) => {
const context = await browser.newContext({ ...iPhone });
const page = await context.newPage();
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => window.innerWidth)).toBe(375);
expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone');
await context.close();
});
it('should support clicking', async({browser, server}) => {
const context = await browser.newContext({ ...iPhone });
const page = await context.newPage();
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await page.evaluate(button => button.style.marginTop = '200px', button);
await button.click();
expect(await page.evaluate(() => result)).toBe('Clicked');
await context.close();
});
});
describe('Page.emulateMedia type', function() {
it('should work', async({page, server}) => {
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false);
await page.emulateMedia({ media: 'print' });
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true);
await page.emulateMedia({});
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true);
await page.emulateMedia({ media: '' });
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false);
});
it('should throw in case of bad type argument', async({page, server}) => {
let error = null;
await page.emulateMedia({ media: 'bad' }).catch(e => error = e);
expect(error.message).toBe('Unsupported media: bad');
});
});
describe('Page.emulateMedia colorScheme', function() {
it('should work', async({page, server}) => {
await page.emulateMedia({ colorScheme: 'light' });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
await page.emulateMedia({ colorScheme: 'dark' });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
if (!WEBKIT) {
// WebKit will always provide the value.
await page.emulateMedia({ colorScheme: 'no-preference' });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(true);
}
});
it('should default to light', async({page, server}) => {
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
await page.emulateMedia({ colorScheme: 'dark' });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
await page.emulateMedia({ colorScheme: null });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
});
it('should throw in case of bad argument', async({page, server}) => {
let error = null;
await page.emulateMedia({ colorScheme: 'bad' }).catch(e => error = e);
expect(error.message).toBe('Unsupported color scheme: bad');
});
it('should work during navigation', async({page, server}) => {
await page.emulateMedia({ colorScheme: 'light' });
const navigated = page.goto(server.EMPTY_PAGE);
for (let i = 0; i < 9; i++) {
await Promise.all([
page.emulateMedia({ colorScheme: ['dark', 'light'][i & 1] }),
new Promise(f => setTimeout(f, 1)),
]);
}
await navigated;
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
});
it('should work in popup', async({browser, server}) => {
{
const context = await browser.newContext({ colorScheme: 'dark' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.addScriptTag({url: server.PREFIX + '/modernizr.js'});
expect(await page.evaluate(() => Modernizr.touchevents)).toBe(true);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE),
]);
expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false);
expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
await context.close();
});
it('should support landscape emulation', async({browser, server}) => {
const context1 = await browser.newContext({ ...iPhone });
const page1 = await context1.newPage();
await page1.goto(server.PREFIX + '/mobile.html');
expect(await page1.evaluate(() => matchMedia('(orientation: landscape)').matches)).toBe(false);
const context2 = await browser.newContext({ ...iPhoneLandscape });
const page2 = await context2.newPage();
expect(await page2.evaluate(() => matchMedia('(orientation: landscape)').matches)).toBe(true);
await context1.close();
await context2.close();
});
it.fail(WEBKIT)('should fire orientationchange event', async({browser, server}) => {
const context = await browser.newContext({ viewport: { width: 300, height: 400 }, isMobile: true });
const page = await context.newPage();
await page.goto(server.PREFIX + '/mobile.html');
await page.evaluate(() => {
window.counter = 0;
window.addEventListener('orientationchange', () => console.log(++window.counter));
});
const event1 = page.waitForEvent('console');
await page.setViewportSize({width: 400, height: 300});
expect((await event1).text()).toBe('1');
const event2 = page.waitForEvent('console');
await page.setViewportSize({width: 300, height: 400});
expect((await event2).text()).toBe('2');
await context.close();
});
it('default mobile viewports to 980 width', async({browser, server}) => {
const context = await browser.newContext({ viewport: {width: 320, height: 480 }, isMobile: true });
const page = await context.newPage();
await page.goto(server.PREFIX + '/empty.html');
expect(await page.evaluate(() => window.innerWidth)).toBe(980);
await context.close();
});
it('respect meta viewport tag', async({browser, server}) => {
const context = await browser.newContext({ viewport: {width: 320, height: 480 }, isMobile: true });
const page = await context.newPage();
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => window.innerWidth)).toBe(320);
await context.close();
});
});
describe.skip(FFOX)('Page.emulate', function() {
it('should work', async({browser, server}) => {
const context = await browser.newContext({ ...iPhone });
const page = await context.newPage();
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => window.innerWidth)).toBe(375);
expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone');
await context.close();
});
it('should support clicking', async({browser, server}) => {
const context = await browser.newContext({ ...iPhone });
const page = await context.newPage();
await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button');
await page.evaluate(button => button.style.marginTop = '200px', button);
await button.click();
expect(await page.evaluate(() => result)).toBe('Clicked');
await context.close();
});
});
describe('Page.emulateMedia type', function() {
it('should work', async({page, server}) => {
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false);
await page.emulateMedia({ media: 'print' });
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true);
await page.emulateMedia({});
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true);
await page.emulateMedia({ media: '' });
expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false);
});
it('should throw in case of bad type argument', async({page, server}) => {
let error = null;
await page.emulateMedia({ media: 'bad' }).catch(e => error = e);
expect(error.message).toBe('Unsupported media: bad');
});
});
describe('Page.emulateMedia colorScheme', function() {
it('should work', async({page, server}) => {
await page.emulateMedia({ colorScheme: 'light' });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
await page.emulateMedia({ colorScheme: 'dark' });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
if (!WEBKIT) {
// WebKit will always provide the value.
await page.emulateMedia({ colorScheme: 'no-preference' });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(true);
}
});
it('should default to light', async({page, server}) => {
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
await page.emulateMedia({ colorScheme: 'dark' });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
await page.emulateMedia({ colorScheme: null });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches)).toBe(false);
});
it('should throw in case of bad argument', async({page, server}) => {
let error = null;
await page.emulateMedia({ colorScheme: 'bad' }).catch(e => error = e);
expect(error.message).toBe('Unsupported color scheme: bad');
});
it('should work during navigation', async({page, server}) => {
await page.emulateMedia({ colorScheme: 'light' });
const navigated = page.goto(server.EMPTY_PAGE);
for (let i = 0; i < 9; i++) {
await Promise.all([
page.emulateMedia({ colorScheme: ['dark', 'light'][i & 1] }),
new Promise(f => setTimeout(f, 1)),
]);
}
await navigated;
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
});
it('should work in popup', async({browser, server}) => {
{
const context = await browser.newContext({ colorScheme: 'dark' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE),
]);
expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false);
expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
await context.close();
}
{
const page = await browser.newPage({ colorScheme: 'light' });
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE),
]);
expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true);
expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false);
await page.close();
}
});
it('should work in cross-process iframe', async({browser, server}) => {
const page = await browser.newPage({ colorScheme: 'dark' });
}
{
const page = await browser.newPage({ colorScheme: 'light' });
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html');
const frame = page.frames()[1];
expect(await frame.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE),
]);
expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(true);
expect(await popup.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(false);
await page.close();
});
}
});
it('should work in cross-process iframe', async({browser, server}) => {
const page = await browser.newPage({ colorScheme: 'dark' });
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html');
const frame = page.frames()[1];
expect(await frame.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
await page.close();
});
});
describe('BrowserContext({timezoneId})', function() {
it('should work', async ({ browser }) => {
const func = () => new Date(1479579154987).toString();
{
const context = await browser.newContext({ timezoneId: 'America/Jamaica' });
const page = await context.newPage();
expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)');
await context.close();
}
{
const context = await browser.newContext({ timezoneId: 'Pacific/Honolulu' });
const page = await context.newPage();
expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 08:12:34 GMT-1000 (Hawaii-Aleutian Standard Time)');
await context.close();
}
{
const context = await browser.newContext({ timezoneId: 'America/Buenos_Aires' });
const page = await context.newPage();
expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 15:12:34 GMT-0300 (Argentina Standard Time)');
await context.close();
}
{
const context = await browser.newContext({ timezoneId: 'Europe/Berlin' });
const page = await context.newPage();
expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 19:12:34 GMT+0100 (Central European Standard Time)');
await context.close();
}
});
describe('BrowserContext({timezoneId})', function() {
it('should work', async ({ browser }) => {
const func = () => new Date(1479579154987).toString();
{
const context = await browser.newContext({ timezoneId: 'America/Jamaica' });
const page = await context.newPage();
expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)');
await context.close();
}
{
const context = await browser.newContext({ timezoneId: 'Pacific/Honolulu' });
const page = await context.newPage();
expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 08:12:34 GMT-1000 (Hawaii-Aleutian Standard Time)');
await context.close();
}
{
const context = await browser.newContext({ timezoneId: 'America/Buenos_Aires' });
const page = await context.newPage();
expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 15:12:34 GMT-0300 (Argentina Standard Time)');
await context.close();
}
{
const context = await browser.newContext({ timezoneId: 'Europe/Berlin' });
const page = await context.newPage();
expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 19:12:34 GMT+0100 (Central European Standard Time)');
await context.close();
}
});
it('should throw for invalid timezone IDs when creating pages', async({browser}) => {
for (const timezoneId of ['Foo/Bar', 'Baz/Qux']) {
let error = null;
const context = await browser.newContext({ timezoneId });
const page = await context.newPage().catch(e => error = e);
expect(error.message).toBe(`Invalid timezone ID: ${timezoneId}`);
await context.close();
}
});
it('should work for multiple pages sharing same process', async({browser, server}) => {
const context = await browser.newContext({ timezoneId: 'Europe/Moscow' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
let [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE),
]);
[popup] = await Promise.all([
popup.waitForEvent('popup'),
popup.evaluate(url => { window.open(url); }, server.EMPTY_PAGE),
]);
await context.close();
});
});
it('should throw for invalid timezone IDs when creating pages', async({browser}) => {
for (const timezoneId of ['Foo/Bar', 'Baz/Qux']) {
let error = null;
const context = await browser.newContext({ timezoneId });
const page = await context.newPage().catch(e => error = e);
expect(error.message).toBe(`Invalid timezone ID: ${timezoneId}`);
await context.close();
}
});
it('should work for multiple pages sharing same process', async({browser, server}) => {
const context = await browser.newContext({ timezoneId: 'Europe/Moscow' });
describe('BrowserContext({locale})', function() {
it('should affect accept-language header', async({browser, server}) => {
const context = await browser.newContext({ locale: 'fr-CH' });
const page = await context.newPage();
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
expect(request.headers['accept-language'].substr(0, 5)).toBe('fr-CH');
await context.close();
});
it('should affect navigator.language', async({browser, server}) => {
const context = await browser.newContext({ locale: 'fr-CH' });
const page = await context.newPage();
expect(await page.evaluate(() => navigator.language)).toBe('fr-CH');
await context.close();
});
it('should format number', async({browser, server}) => {
{
const context = await browser.newContext({ locale: 'en-US' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
let [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE),
]);
[popup] = await Promise.all([
popup.waitForEvent('popup'),
popup.evaluate(url => { window.open(url); }, server.EMPTY_PAGE),
]);
expect(await page.evaluate(() => (1000000.50).toLocaleString())).toBe('1,000,000.5');
await context.close();
});
});
describe('BrowserContext({locale})', function() {
it('should affect accept-language header', async({browser, server}) => {
const context = await browser.newContext({ locale: 'fr-CH' });
const page = await context.newPage();
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
expect(request.headers['accept-language'].substr(0, 5)).toBe('fr-CH');
await context.close();
});
it('should affect navigator.language', async({browser, server}) => {
const context = await browser.newContext({ locale: 'fr-CH' });
const page = await context.newPage();
expect(await page.evaluate(() => navigator.language)).toBe('fr-CH');
await context.close();
});
it('should format number', async({browser, server}) => {
{
const context = await browser.newContext({ locale: 'en-US' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(await page.evaluate(() => (1000000.50).toLocaleString())).toBe('1,000,000.5');
await context.close();
}
{
const context = await browser.newContext({ locale: 'fr-CH' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(await page.evaluate(() => (1000000.50).toLocaleString().replace(/\s/g, ' '))).toBe('1 000 000,5');
await context.close();
}
});
it('should format date', async({browser, server}) => {
{
const context = await browser.newContext({ locale: 'en-US', timezoneId: 'America/Los_Angeles' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const formatted = 'Sat Nov 19 2016 10:12:34 GMT-0800 (Pacific Standard Time)';
expect(await page.evaluate(() => new Date(1479579154987).toString())).toBe(formatted);
await context.close();
}
{
const context = await browser.newContext({ locale: 'de-DE', timezoneId: 'Europe/Berlin' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(await page.evaluate(() => new Date(1479579154987).toString())).toBe(
'Sat Nov 19 2016 19:12:34 GMT+0100 (Mitteleuropäische Normalzeit)');
await context.close();
}
});
it('should format number in popups', async({browser, server}) => {
}
{
const context = await browser.newContext({ locale: 'fr-CH' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/formatted-number.html'),
]);
await popup.waitForLoadState('domcontentloaded');
const result = await popup.evaluate(() => window.result);
expect(result).toBe('1 000 000,5');
expect(await page.evaluate(() => (1000000.50).toLocaleString().replace(/\s/g, ' '))).toBe('1 000 000,5');
await context.close();
});
it('should affect navigator.language in popups', async({browser, server}) => {
const context = await browser.newContext({ locale: 'fr-CH' });
}
});
it('should format date', async({browser, server}) => {
{
const context = await browser.newContext({ locale: 'en-US', timezoneId: 'America/Los_Angeles' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/formatted-number.html'),
]);
await popup.waitForLoadState('domcontentloaded');
const result = await popup.evaluate(() => window.initialNavigatorLanguage);
expect(result).toBe('fr-CH');
const formatted = 'Sat Nov 19 2016 10:12:34 GMT-0800 (Pacific Standard Time)';
expect(await page.evaluate(() => new Date(1479579154987).toString())).toBe(formatted);
await context.close();
});
it('should work for multiple pages sharing same process', async({browser, server}) => {
const context = await browser.newContext({ locale: 'ru-RU' });
}
{
const context = await browser.newContext({ locale: 'de-DE', timezoneId: 'Europe/Berlin' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
let [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE),
]);
[popup] = await Promise.all([
popup.waitForEvent('popup'),
popup.evaluate(url => { window.open(url); }, server.EMPTY_PAGE),
]);
expect(await page.evaluate(() => new Date(1479579154987).toString())).toBe(
'Sat Nov 19 2016 19:12:34 GMT+0100 (Mitteleuropäische Normalzeit)');
await context.close();
});
}
});
it('should format number in popups', async({browser, server}) => {
const context = await browser.newContext({ locale: 'fr-CH' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
describe.fail(!headless)('focus', function() {
it('should think that it is focused by default', async({page}) => {
expect(await page.evaluate('document.hasFocus()')).toBe(true);
});
it.fail(FFOX)('should think that all pages are focused', async({page}) => {
const page2 = await page.context().newPage();
expect(await page.evaluate('document.hasFocus()')).toBe(true);
expect(await page2.evaluate('document.hasFocus()')).toBe(true);
await page2.close();
});
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/formatted-number.html'),
]);
await popup.waitForLoadState('domcontentloaded');
const result = await popup.evaluate(() => window.result);
expect(result).toBe('1 000 000,5');
await context.close();
});
};
it('should affect navigator.language in popups', async({browser, server}) => {
const context = await browser.newContext({ locale: 'fr-CH' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/formatted-number.html'),
]);
await popup.waitForLoadState('domcontentloaded');
const result = await popup.evaluate(() => window.initialNavigatorLanguage);
expect(result).toBe('fr-CH');
await context.close();
});
it('should work for multiple pages sharing same process', async({browser, server}) => {
const context = await browser.newContext({ locale: 'ru-RU' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
let [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => { window.open(url); }, server.EMPTY_PAGE),
]);
[popup] = await Promise.all([
popup.waitForEvent('popup'),
popup.evaluate(url => { window.open(url); }, server.EMPTY_PAGE),
]);
await context.close();
});
});
describe.fail(!headless)('focus', function() {
it('should think that it is focused by default', async({page}) => {
expect(await page.evaluate('document.hasFocus()')).toBe(true);
});
it.fail(FFOX)('should think that all pages are focused', async({page}) => {
const page2 = await page.context().newPage();
expect(await page.evaluate('document.hasFocus()')).toBe(true);
expect(await page2.evaluate('document.hasFocus()')).toBe(true);
await page2.close();
});
});

View file

@ -17,441 +17,436 @@
const utils = require('./utils');
const path = require('path');
const {FFOX, CHROMIUM, WEBKIT} = utils.testOptions(browserType);
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({FFOX, CHROMIUM, WEBKIT, LINUX}) {
describe('Page.evaluate', function() {
it('should work', async({page, server}) => {
const result = await page.evaluate(() => 7 * 3);
expect(result).toBe(21);
describe('Page.evaluate', function() {
it('should work', async({page, server}) => {
const result = await page.evaluate(() => 7 * 3);
expect(result).toBe(21);
});
it('should transfer NaN', async({page, server}) => {
const result = await page.evaluate(a => a, NaN);
expect(Object.is(result, NaN)).toBe(true);
});
it('should transfer -0', async({page, server}) => {
const result = await page.evaluate(a => a, -0);
expect(Object.is(result, -0)).toBe(true);
});
it('should transfer Infinity', async({page, server}) => {
const result = await page.evaluate(a => a, Infinity);
expect(Object.is(result, Infinity)).toBe(true);
});
it('should transfer -Infinity', async({page, server}) => {
const result = await page.evaluate(a => a, -Infinity);
expect(Object.is(result, -Infinity)).toBe(true);
});
it('should transfer arrays', async({page, server}) => {
const result = await page.evaluate(a => a, [1, 2, 3]);
expect(result).toEqual([1,2,3]);
});
it('should transfer arrays as arrays, not objects', async({page, server}) => {
const result = await page.evaluate(a => Array.isArray(a), [1, 2, 3]);
expect(result).toBe(true);
});
it('should transfer maps as empty objects', async({page, server}) => {
const result = await page.evaluate(a => a.x.constructor.name + ' ' + JSON.stringify(a.x), {x: new Map([[1, 2]])});
expect(result).toBe('Object {}');
});
it('should modify global environment', async({page}) => {
await page.evaluate(() => window.globalVar = 123);
expect(await page.evaluate('globalVar')).toBe(123);
});
it('should evaluate in the page context', async({page, server}) => {
await page.goto(server.PREFIX + '/global-var.html');
expect(await page.evaluate('globalVar')).toBe(123);
});
it('should return undefined for objects with symbols', async({page, server}) => {
expect(await page.evaluate(() => [Symbol('foo4')])).toBe(undefined);
expect(await page.evaluate(() => {
const a = { };
a[Symbol('foo4')] = 42;
return a;
})).toEqual({});
expect(await page.evaluate(() => {
return { foo: [{ a: Symbol('foo4') }] };
})).toBe(undefined);
});
it('should work with function shorthands', async({page, server}) => {
const a = {
sum([a, b]) { return a + b; },
async mult([a, b]) { return a * b; }
};
expect(await page.evaluate(a.sum, [1, 2])).toBe(3);
expect(await page.evaluate(a.mult, [2, 4])).toBe(8);
});
it('should work with unicode chars', async({page, server}) => {
const result = await page.evaluate(a => a['中文字符'], {'中文字符': 42});
expect(result).toBe(42);
});
it('should throw when evaluation triggers reload', async({page, server}) => {
let error = null;
await page.evaluate(() => {
location.reload();
return new Promise(() => {});
}).catch(e => error = e);
expect(error.message).toContain('navigation');
});
it('should await promise', async({page, server}) => {
const result = await page.evaluate(() => Promise.resolve(8 * 7));
expect(result).toBe(56);
});
it('should work right after framenavigated', async({page, server}) => {
let frameEvaluation = null;
page.on('framenavigated', async frame => {
frameEvaluation = frame.evaluate(() => 6 * 7);
});
it('should transfer NaN', async({page, server}) => {
const result = await page.evaluate(a => a, NaN);
expect(Object.is(result, NaN)).toBe(true);
});
it('should transfer -0', async({page, server}) => {
const result = await page.evaluate(a => a, -0);
expect(Object.is(result, -0)).toBe(true);
});
it('should transfer Infinity', async({page, server}) => {
const result = await page.evaluate(a => a, Infinity);
expect(Object.is(result, Infinity)).toBe(true);
});
it('should transfer -Infinity', async({page, server}) => {
const result = await page.evaluate(a => a, -Infinity);
expect(Object.is(result, -Infinity)).toBe(true);
});
it('should transfer arrays', async({page, server}) => {
const result = await page.evaluate(a => a, [1, 2, 3]);
expect(result).toEqual([1,2,3]);
});
it('should transfer arrays as arrays, not objects', async({page, server}) => {
const result = await page.evaluate(a => Array.isArray(a), [1, 2, 3]);
expect(result).toBe(true);
});
it('should transfer maps as empty objects', async({page, server}) => {
const result = await page.evaluate(a => a.x.constructor.name + ' ' + JSON.stringify(a.x), {x: new Map([[1, 2]])});
expect(result).toBe('Object {}');
});
it('should modify global environment', async({page}) => {
await page.evaluate(() => window.globalVar = 123);
expect(await page.evaluate('globalVar')).toBe(123);
});
it('should evaluate in the page context', async({page, server}) => {
await page.goto(server.PREFIX + '/global-var.html');
expect(await page.evaluate('globalVar')).toBe(123);
});
it('should return undefined for objects with symbols', async({page, server}) => {
expect(await page.evaluate(() => [Symbol('foo4')])).toBe(undefined);
expect(await page.evaluate(() => {
const a = { };
a[Symbol('foo4')] = 42;
return a;
})).toEqual({});
expect(await page.evaluate(() => {
return { foo: [{ a: Symbol('foo4') }] };
})).toBe(undefined);
});
it('should work with function shorthands', async({page, server}) => {
const a = {
sum([a, b]) { return a + b; },
async mult([a, b]) { return a * b; }
};
expect(await page.evaluate(a.sum, [1, 2])).toBe(3);
expect(await page.evaluate(a.mult, [2, 4])).toBe(8);
});
it('should work with unicode chars', async({page, server}) => {
const result = await page.evaluate(a => a['中文字符'], {'中文字符': 42});
expect(result).toBe(42);
});
it('should throw when evaluation triggers reload', async({page, server}) => {
let error = null;
await page.evaluate(() => {
location.reload();
return new Promise(() => {});
}).catch(e => error = e);
expect(error.message).toContain('navigation');
});
it('should await promise', async({page, server}) => {
const result = await page.evaluate(() => Promise.resolve(8 * 7));
expect(result).toBe(56);
});
it('should work right after framenavigated', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
expect(await frameEvaluation).toBe(42);
});
it('should work right after a cross-origin navigation', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
let frameEvaluation = null;
page.on('framenavigated', async frame => {
frameEvaluation = frame.evaluate(() => 6 * 7);
});
await page.goto(server.EMPTY_PAGE);
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
expect(await frameEvaluation).toBe(42);
});
it('should work from-inside an exposed function', async({page, server}) => {
// Setup inpage callback, which calls Page.evaluate
await page.exposeFunction('callController', async function(a, b) {
return await page.evaluate(({ a, b }) => a * b, { a, b });
});
it('should work right after a cross-origin navigation', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
let frameEvaluation = null;
page.on('framenavigated', async frame => {
frameEvaluation = frame.evaluate(() => 6 * 7);
});
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
expect(await frameEvaluation).toBe(42);
const result = await page.evaluate(async function() {
return await callController(9, 3);
});
it('should work from-inside an exposed function', async({page, server}) => {
// Setup inpage callback, which calls Page.evaluate
await page.exposeFunction('callController', async function(a, b) {
return await page.evaluate(({ a, b }) => a * b, { a, b });
});
const result = await page.evaluate(async function() {
return await callController(9, 3);
});
expect(result).toBe(27);
expect(result).toBe(27);
});
it('should reject promise with exception', async({page, server}) => {
let error = null;
await page.evaluate(() => not_existing_object.property).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('not_existing_object');
});
it('should support thrown strings as error messages', async({page, server}) => {
let error = null;
await page.evaluate(() => { throw 'qwerty'; }).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('qwerty');
});
it('should support thrown numbers as error messages', async({page, server}) => {
let error = null;
await page.evaluate(() => { throw 100500; }).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('100500');
});
it('should return complex objects', async({page, server}) => {
const object = {foo: 'bar!'};
const result = await page.evaluate(a => a, object);
expect(result).not.toBe(object);
expect(result).toEqual(object);
});
it('should return NaN', async({page, server}) => {
const result = await page.evaluate(() => NaN);
expect(Object.is(result, NaN)).toBe(true);
});
it('should return -0', async({page, server}) => {
const result = await page.evaluate(() => -0);
expect(Object.is(result, -0)).toBe(true);
});
it('should return Infinity', async({page, server}) => {
const result = await page.evaluate(() => Infinity);
expect(Object.is(result, Infinity)).toBe(true);
});
it('should return -Infinity', async({page, server}) => {
const result = await page.evaluate(() => -Infinity);
expect(Object.is(result, -Infinity)).toBe(true);
});
it('should accept "undefined" as one of multiple parameters', async({page, server}) => {
const result = await page.evaluate(({ a, b }) => Object.is(a, undefined) && Object.is(b, 'foo'), { a: undefined, b: 'foo' });
expect(result).toBe(true);
});
it('should properly serialize undefined arguments', async({page}) => {
expect(await page.evaluate(x => ({a: x}), undefined)).toEqual({});
});
it('should properly serialize undefined fields', async({page}) => {
expect(await page.evaluate(() => ({a: undefined}))).toEqual({});
});
it('should properly serialize null arguments', async({page}) => {
expect(await page.evaluate(x => x, null)).toEqual(null);
});
it('should properly serialize null fields', async({page}) => {
expect(await page.evaluate(() => ({a: null}))).toEqual({a: null});
});
it('should return undefined for non-serializable objects', async({page, server}) => {
expect(await page.evaluate(() => window)).toBe(undefined);
});
it('should fail for circular object', async({page, server}) => {
const result = await page.evaluate(() => {
const a = {};
const b = {a};
a.b = b;
return a;
});
it('should reject promise with exception', async({page, server}) => {
let error = null;
await page.evaluate(() => not_existing_object.property).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('not_existing_object');
expect(result).toBe(undefined);
});
it('should be able to throw a tricky error', async({page, server}) => {
const windowHandle = await page.evaluateHandle(() => window);
const errorText = await windowHandle.jsonValue().catch(e => e.message);
const error = await page.evaluate(errorText => {
throw new Error(errorText);
}, errorText).catch(e => e);
expect(error.message).toContain(errorText);
});
it('should accept a string', async({page, server}) => {
const result = await page.evaluate('1 + 2');
expect(result).toBe(3);
});
it('should accept a string with semi colons', async({page, server}) => {
const result = await page.evaluate('1 + 5;');
expect(result).toBe(6);
});
it('should accept a string with comments', async({page, server}) => {
const result = await page.evaluate('2 + 5;\n// do some math!');
expect(result).toBe(7);
});
it('should accept element handle as an argument', async({page, server}) => {
await page.setContent('<section>42</section>');
const element = await page.$('section');
const text = await page.evaluate(e => e.textContent, element);
expect(text).toBe('42');
});
it('should throw if underlying element was disposed', async({page, server}) => {
await page.setContent('<section>39</section>');
const element = await page.$('section');
expect(element).toBeTruthy();
await element.dispose();
let error = null;
await page.evaluate(e => e.textContent, element).catch(e => error = e);
expect(error.message).toContain('JSHandle is disposed');
});
it('should simulate a user gesture', async({page, server}) => {
const result = await page.evaluate(() => {
document.body.appendChild(document.createTextNode('test'));
document.execCommand('selectAll');
return document.execCommand('copy');
});
it('should support thrown strings as error messages', async({page, server}) => {
let error = null;
await page.evaluate(() => { throw 'qwerty'; }).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('qwerty');
expect(result).toBe(true);
});
it('should throw a nice error after a navigation', async({page, server}) => {
const errorPromise = page.evaluate(() => new Promise(f => window.__resolve = f)).catch(e => e);
await Promise.all([
page.waitForNavigation(),
page.evaluate(() => {
window.location.reload();
setTimeout(() => window.__resolve(42), 1000);
})
]);
const error = await errorPromise;
expect(error.message).toContain('navigation');
});
it('should not throw an error when evaluation does a navigation', async({page, server}) => {
await page.goto(server.PREFIX + '/one-style.html');
const result = await page.evaluate(() => {
window.location = '/empty.html';
return [42];
});
it('should support thrown numbers as error messages', async({page, server}) => {
let error = null;
await page.evaluate(() => { throw 100500; }).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('100500');
expect(result).toEqual([42]);
});
it.slow()('should transfer 100Mb of data from page to node.js', async({page, server}) => {
const a = await page.evaluate(() => Array(100 * 1024 * 1024 + 1).join('a'));
expect(a.length).toBe(100 * 1024 * 1024);
});
it('should throw error with detailed information on exception inside promise ', async({page, server}) => {
let error = null;
await page.evaluate(() => new Promise(() => {
throw new Error('Error in promise');
})).catch(e => error = e);
expect(error.message).toContain('Error in promise');
});
it('should work even when JSON is set to null', async ({ page }) => {
await page.evaluate(() => { window.JSON.stringify = null; window.JSON = null; });
const result = await page.evaluate(() => ({abc: 123}));
expect(result).toEqual({abc: 123});
});
it('should await promise from popup', async function({page, server}) {
await page.goto(server.EMPTY_PAGE);
const result = await page.evaluate(() => {
const win = window.open('about:blank');
return new win.Promise(f => f(42));
});
it('should return complex objects', async({page, server}) => {
const object = {foo: 'bar!'};
const result = await page.evaluate(a => a, object);
expect(result).not.toBe(object);
expect(result).toEqual(object);
expect(result).toBe(42);
});
it('should work with new Function() and CSP', async({page, server}) => {
server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
await page.goto(server.PREFIX + '/empty.html');
expect(await page.evaluate(() => new Function('return true')())).toBe(true);
});
});
describe('Page.addInitScript', function() {
it('should evaluate before anything else on the page', async({page, server}) => {
await page.addInitScript(function(){
window.injected = 123;
});
it('should return NaN', async({page, server}) => {
const result = await page.evaluate(() => NaN);
expect(Object.is(result, NaN)).toBe(true);
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
});
it('should work with a path', async({page, server}) => {
await page.addInitScript({ path: path.join(__dirname, 'assets/injectedfile.js') });
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
});
it('should work with content', async({page, server}) => {
await page.addInitScript({ content: 'window.injected = 123' });
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
});
it('should throw without path and content', async({page, server}) => {
const error = await page.addInitScript({ foo: 'bar' }).catch(e => e);
expect(error.message).toBe('Either path or content property must be present');
});
it('should work with browser context scripts', async({browser, server}) => {
const context = await browser.newContext();
await context.addInitScript(() => window.temp = 123);
const page = await context.newPage();
await page.addInitScript(() => window.injected = window.temp);
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
await context.close();
});
it('should work with browser context scripts with a path', async({browser, server}) => {
const context = await browser.newContext();
await context.addInitScript({ path: path.join(__dirname, 'assets/injectedfile.js') });
const page = await context.newPage();
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
await context.close();
});
it('should work with browser context scripts for already created pages', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
await context.addInitScript(() => window.temp = 123);
await page.addInitScript(() => window.injected = window.temp);
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
await context.close();
});
it('should support multiple scripts', async({page, server}) => {
await page.addInitScript(function(){
window.script1 = 1;
});
it('should return -0', async({page, server}) => {
const result = await page.evaluate(() => -0);
expect(Object.is(result, -0)).toBe(true);
await page.addInitScript(function(){
window.script2 = 2;
});
it('should return Infinity', async({page, server}) => {
const result = await page.evaluate(() => Infinity);
expect(Object.is(result, Infinity)).toBe(true);
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.script1)).toBe(1);
expect(await page.evaluate(() => window.script2)).toBe(2);
});
it('should work with CSP', async({page, server}) => {
server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
await page.addInitScript(function(){
window.injected = 123;
});
it('should return -Infinity', async({page, server}) => {
const result = await page.evaluate(() => -Infinity);
expect(Object.is(result, -Infinity)).toBe(true);
});
it('should accept "undefined" as one of multiple parameters', async({page, server}) => {
const result = await page.evaluate(({ a, b }) => Object.is(a, undefined) && Object.is(b, 'foo'), { a: undefined, b: 'foo' });
expect(result).toBe(true);
});
it('should properly serialize undefined arguments', async({page}) => {
expect(await page.evaluate(x => ({a: x}), undefined)).toEqual({});
});
it('should properly serialize undefined fields', async({page}) => {
expect(await page.evaluate(() => ({a: undefined}))).toEqual({});
});
it('should properly serialize null arguments', async({page}) => {
expect(await page.evaluate(x => x, null)).toEqual(null);
});
it('should properly serialize null fields', async({page}) => {
expect(await page.evaluate(() => ({a: null}))).toEqual({a: null});
});
it('should return undefined for non-serializable objects', async({page, server}) => {
expect(await page.evaluate(() => window)).toBe(undefined);
});
it('should fail for circular object', async({page, server}) => {
const result = await page.evaluate(() => {
const a = {};
const b = {a};
a.b = b;
return a;
});
expect(result).toBe(undefined);
});
it('should be able to throw a tricky error', async({page, server}) => {
const windowHandle = await page.evaluateHandle(() => window);
const errorText = await windowHandle.jsonValue().catch(e => e.message);
const error = await page.evaluate(errorText => {
throw new Error(errorText);
}, errorText).catch(e => e);
expect(error.message).toContain(errorText);
});
it('should accept a string', async({page, server}) => {
const result = await page.evaluate('1 + 2');
expect(result).toBe(3);
});
it('should accept a string with semi colons', async({page, server}) => {
const result = await page.evaluate('1 + 5;');
expect(result).toBe(6);
});
it('should accept a string with comments', async({page, server}) => {
const result = await page.evaluate('2 + 5;\n// do some math!');
expect(result).toBe(7);
});
it('should accept element handle as an argument', async({page, server}) => {
await page.setContent('<section>42</section>');
const element = await page.$('section');
const text = await page.evaluate(e => e.textContent, element);
expect(text).toBe('42');
});
it('should throw if underlying element was disposed', async({page, server}) => {
await page.setContent('<section>39</section>');
const element = await page.$('section');
expect(element).toBeTruthy();
await element.dispose();
let error = null;
await page.evaluate(e => e.textContent, element).catch(e => error = e);
expect(error.message).toContain('JSHandle is disposed');
});
it('should simulate a user gesture', async({page, server}) => {
const result = await page.evaluate(() => {
document.body.appendChild(document.createTextNode('test'));
document.execCommand('selectAll');
return document.execCommand('copy');
});
expect(result).toBe(true);
});
it('should throw a nice error after a navigation', async({page, server}) => {
const errorPromise = page.evaluate(() => new Promise(f => window.__resolve = f)).catch(e => e);
await Promise.all([
page.waitForNavigation(),
page.evaluate(() => {
window.location.reload();
setTimeout(() => window.__resolve(42), 1000);
})
]);
const error = await errorPromise;
expect(error.message).toContain('navigation');
});
it('should not throw an error when evaluation does a navigation', async({page, server}) => {
await page.goto(server.PREFIX + '/one-style.html');
const result = await page.evaluate(() => {
window.location = '/empty.html';
return [42];
});
expect(result).toEqual([42]);
});
it.slow()('should transfer 100Mb of data from page to node.js', async({page, server}) => {
const a = await page.evaluate(() => Array(100 * 1024 * 1024 + 1).join('a'));
expect(a.length).toBe(100 * 1024 * 1024);
});
it('should throw error with detailed information on exception inside promise ', async({page, server}) => {
let error = null;
await page.evaluate(() => new Promise(() => {
throw new Error('Error in promise');
})).catch(e => error = e);
expect(error.message).toContain('Error in promise');
});
it('should work even when JSON is set to null', async ({ page }) => {
await page.evaluate(() => { window.JSON.stringify = null; window.JSON = null; });
const result = await page.evaluate(() => ({abc: 123}));
expect(result).toEqual({abc: 123});
});
it('should await promise from popup', async function({page, server}) {
await page.goto(server.EMPTY_PAGE);
const result = await page.evaluate(() => {
const win = window.open('about:blank');
return new win.Promise(f => f(42));
});
expect(result).toBe(42);
});
it('should work with new Function() and CSP', async({page, server}) => {
server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
await page.goto(server.PREFIX + '/empty.html');
expect(await page.evaluate(() => new Function('return true')())).toBe(true);
await page.goto(server.PREFIX + '/empty.html');
expect(await page.evaluate(() => window.injected)).toBe(123);
// Make sure CSP works.
await page.addScriptTag({content: 'window.e = 10;'}).catch(e => void e);
expect(await page.evaluate(() => window.e)).toBe(undefined);
});
it('should work after a cross origin navigation', async({page, server}) => {
await page.goto(server.CROSS_PROCESS_PREFIX);
await page.addInitScript(function(){
window.injected = 123;
});
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
});
});
describe('Frame.evaluate', function() {
it('should have different execution contexts', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(page.frames().length).toBe(2);
await page.frames()[0].evaluate(() => window.FOO = 'foo');
await page.frames()[1].evaluate(() => window.FOO = 'bar');
expect(await page.frames()[0].evaluate(() => window.FOO)).toBe('foo');
expect(await page.frames()[1].evaluate(() => window.FOO)).toBe('bar');
});
it('should have correct execution contexts', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames().length).toBe(2);
expect(await page.frames()[0].evaluate(() => document.body.textContent.trim())).toBe('');
expect(await page.frames()[1].evaluate(() => document.body.textContent.trim())).toBe(`Hi, I'm frame`);
});
describe('Page.addInitScript', function() {
it('should evaluate before anything else on the page', async({page, server}) => {
await page.addInitScript(function(){
window.injected = 123;
});
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
});
it('should work with a path', async({page, server}) => {
await page.addInitScript({ path: path.join(__dirname, 'assets/injectedfile.js') });
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
});
it('should work with content', async({page, server}) => {
await page.addInitScript({ content: 'window.injected = 123' });
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
});
it('should throw without path and content', async({page, server}) => {
const error = await page.addInitScript({ foo: 'bar' }).catch(e => e);
expect(error.message).toBe('Either path or content property must be present');
});
it('should work with browser context scripts', async({browser, server}) => {
const context = await browser.newContext();
await context.addInitScript(() => window.temp = 123);
const page = await context.newPage();
await page.addInitScript(() => window.injected = window.temp);
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
await context.close();
});
it('should work with browser context scripts with a path', async({browser, server}) => {
const context = await browser.newContext();
await context.addInitScript({ path: path.join(__dirname, 'assets/injectedfile.js') });
const page = await context.newPage();
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
await context.close();
});
it('should work with browser context scripts for already created pages', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
await context.addInitScript(() => window.temp = 123);
await page.addInitScript(() => window.injected = window.temp);
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
await context.close();
});
it('should support multiple scripts', async({page, server}) => {
await page.addInitScript(function(){
window.script1 = 1;
});
await page.addInitScript(function(){
window.script2 = 2;
});
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.script1)).toBe(1);
expect(await page.evaluate(() => window.script2)).toBe(2);
});
it('should work with CSP', async({page, server}) => {
server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
await page.addInitScript(function(){
window.injected = 123;
});
await page.goto(server.PREFIX + '/empty.html');
expect(await page.evaluate(() => window.injected)).toBe(123);
// Make sure CSP works.
await page.addScriptTag({content: 'window.e = 10;'}).catch(e => void e);
expect(await page.evaluate(() => window.e)).toBe(undefined);
});
it('should work after a cross origin navigation', async({page, server}) => {
await page.goto(server.CROSS_PROCESS_PREFIX);
await page.addInitScript(function(){
window.injected = 123;
});
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
});
function expectContexts(page, count) {
if (CHROMIUM)
expect(page._delegate._mainFrameSession._contextIdToContext.size).toBe(count);
else
expect(page._delegate._contextIdToContext.size).toBe(count);
}
it('should dispose context on navigation', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames().length).toBe(2);
expectContexts(page, 4);
await page.goto(server.EMPTY_PAGE);
expectContexts(page, 2);
});
it('should dispose context on cross-origin navigation', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames().length).toBe(2);
expectContexts(page, 4);
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
expectContexts(page, 2);
});
describe('Frame.evaluate', function() {
it('should have different execution contexts', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(page.frames().length).toBe(2);
await page.frames()[0].evaluate(() => window.FOO = 'foo');
await page.frames()[1].evaluate(() => window.FOO = 'bar');
expect(await page.frames()[0].evaluate(() => window.FOO)).toBe('foo');
expect(await page.frames()[1].evaluate(() => window.FOO)).toBe('bar');
});
it('should have correct execution contexts', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames().length).toBe(2);
expect(await page.frames()[0].evaluate(() => document.body.textContent.trim())).toBe('');
expect(await page.frames()[1].evaluate(() => document.body.textContent.trim())).toBe(`Hi, I'm frame`);
});
function expectContexts(page, count) {
if (CHROMIUM)
expect(page._delegate._mainFrameSession._contextIdToContext.size).toBe(count);
else
expect(page._delegate._contextIdToContext.size).toBe(count);
}
it('should dispose context on navigation', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames().length).toBe(2);
expectContexts(page, 4);
await page.goto(server.EMPTY_PAGE);
expectContexts(page, 2);
});
it('should dispose context on cross-origin navigation', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames().length).toBe(2);
expectContexts(page, 4);
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
expectContexts(page, 2);
});
it('should execute after cross-site navigation', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const mainFrame = page.mainFrame();
expect(await mainFrame.evaluate(() => window.location.href)).toContain('localhost');
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
expect(await mainFrame.evaluate(() => window.location.href)).toContain('127');
});
it('should not allow cross-frame js handles', async({page, server}) => {
// TODO: this should actually be possible because frames script each other,
// but protocol implementations do not support this. For now, assume current
// behavior.
await page.goto(server.PREFIX + '/frames/one-frame.html');
const handle = await page.evaluateHandle(() => {
const iframe = document.querySelector('iframe');
const foo = { bar: 'baz' };
iframe.contentWindow.__foo = foo;
return foo;
});
const childFrame = page.mainFrame().childFrames()[0];
const childResult = await childFrame.evaluate(() => window.__foo);
expect(childResult).toEqual({ bar: 'baz' });
const error = await childFrame.evaluate(foo => foo.bar, handle).catch(e => e);
expect(error.message).toBe('JSHandles can be evaluated only in the context they were created!');
});
it('should allow cross-frame element handles', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
const bodyHandle = await page.mainFrame().childFrames()[0].$('body');
const result = await page.evaluate(body => body.innerHTML, bodyHandle);
expect(result.trim()).toBe('<div>Hi, I\'m frame</div>');
});
it('should not allow cross-frame element handles when frames do not script each other', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html');
const bodyHandle = await frame.$('body');
const error = await page.evaluate(body => body.innerHTML, bodyHandle).catch(e => e);
expect(error.message).toContain('Unable to adopt element handle from a different document');
});
it.fail(FFOX)('should return non-empty Node.constructor.name in utility context', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
const context = await frame._utilityContext();
const elementHandle = await context.evaluateHandleInternal(() => window.top.document.querySelector('#frame1'));
const constructorName = await context.evaluateInternal(node => node.constructor.name, elementHandle);
expect(constructorName).toBe('HTMLIFrameElement');
});
it('should execute after cross-site navigation', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const mainFrame = page.mainFrame();
expect(await mainFrame.evaluate(() => window.location.href)).toContain('localhost');
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
expect(await mainFrame.evaluate(() => window.location.href)).toContain('127');
});
};
it('should not allow cross-frame js handles', async({page, server}) => {
// TODO: this should actually be possible because frames script each other,
// but protocol implementations do not support this. For now, assume current
// behavior.
await page.goto(server.PREFIX + '/frames/one-frame.html');
const handle = await page.evaluateHandle(() => {
const iframe = document.querySelector('iframe');
const foo = { bar: 'baz' };
iframe.contentWindow.__foo = foo;
return foo;
});
const childFrame = page.mainFrame().childFrames()[0];
const childResult = await childFrame.evaluate(() => window.__foo);
expect(childResult).toEqual({ bar: 'baz' });
const error = await childFrame.evaluate(foo => foo.bar, handle).catch(e => e);
expect(error.message).toBe('JSHandles can be evaluated only in the context they were created!');
});
it('should allow cross-frame element handles', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
const bodyHandle = await page.mainFrame().childFrames()[0].$('body');
const result = await page.evaluate(body => body.innerHTML, bodyHandle);
expect(result.trim()).toBe('<div>Hi, I\'m frame</div>');
});
it('should not allow cross-frame element handles when frames do not script each other', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html');
const bodyHandle = await frame.$('body');
const error = await page.evaluate(body => body.innerHTML, bodyHandle).catch(e => e);
expect(error.message).toContain('Unable to adopt element handle from a different document');
});
it.fail(FFOX)('should return non-empty Node.constructor.name in utility context', async({page,server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
const context = await frame._utilityContext();
const elementHandle = await context.evaluateHandleInternal(() => window.top.document.querySelector('#frame1'));
const constructorName = await context.evaluateInternal(node => node.constructor.name, elementHandle);
expect(constructorName).toBe('HTMLIFrameElement');
});
});

View file

@ -17,133 +17,128 @@
const path = require('path');
const {spawn, execSync} = require('child_process');
const {FFOX, CHROMIUM, WEBKIT, WIN, playwrightPath, defaultBrowserOptions} = require('./utils').testOptions(browserType);
/**
* @type {TestSuite}
*/
module.exports.describe = function({browserType, playwrightPath, defaultBrowserOptions, WIN, FFOX, CHROMIUM, WEBKIT}) {
async function testSignal(browserType, action, exitOnClose) {
const options = Object.assign({}, defaultBrowserOptions, {
// Disable DUMPIO to cleanly read stdout.
dumpio: false,
handleSIGINT: true,
handleSIGTERM: true,
handleSIGHUP: true,
});
const res = spawn('node', [path.join(__dirname, 'fixtures', 'closeme.js'), playwrightPath, browserType.name(), JSON.stringify(options), exitOnClose ? 'true' : '']);
let wsEndPointCallback;
const wsEndPointPromise = new Promise(x => wsEndPointCallback = x);
let output = '';
let browserExitCode = 'none';
let browserSignal = 'none';
let browserPid;
res.stdout.on('data', data => {
output += data.toString();
// Uncomment to debug these tests.
// console.log(data.toString());
let match = output.match(/browserWS:(.+):browserWS/);
if (match)
wsEndPointCallback(match[1]);
match = output.match(/browserClose:([^:]+):([^:]+):browserClose/);
if (match) {
browserExitCode = match[1];
browserSignal = match[2];
}
match = output.match(/browserPid:([^:]+):browserPid/);
if (match)
browserPid = +match[1];
});
res.on('error', (...args) => console.log("ERROR", ...args));
const browser = await browserType.connect({ wsEndpoint: await wsEndPointPromise });
const promises = [
new Promise(resolve => browser.once('disconnected', resolve)),
new Promise(resolve => res.on('exit', resolve)),
];
action(res, browserPid);
const [, exitCode] = await Promise.all(promises);
return { exitCode, browserSignal, browserExitCode, output };
}
async function testSignal(action, exitOnClose) {
const options = Object.assign({}, defaultBrowserOptions, {
// Disable DUMPIO to cleanly read stdout.
dumpio: false,
handleSIGINT: true,
handleSIGTERM: true,
handleSIGHUP: true,
describe('Fixtures', function() {
it.slow()('should dump browser process stderr', async({browserType}) => {
let dumpioData = '';
const res = spawn('node', [path.join(__dirname, 'fixtures', 'dumpio.js'), playwrightPath, browserType.name()]);
res.stdout.on('data', data => dumpioData += data.toString('utf8'));
await new Promise(resolve => res.on('close', resolve));
expect(dumpioData).toContain('message from dumpio');
});
it.slow()('should close the browser when the node process closes', async ({browserType}) => {
const result = await testSignal(browserType, child => {
if (WIN)
execSync(`taskkill /pid ${child.pid} /T /F`);
else
process.kill(child.pid);
});
const res = spawn('node', [path.join(__dirname, 'fixtures', 'closeme.js'), playwrightPath, browserType.name(), JSON.stringify(options), exitOnClose ? 'true' : '']);
let wsEndPointCallback;
const wsEndPointPromise = new Promise(x => wsEndPointCallback = x);
let output = '';
let browserExitCode = 'none';
let browserSignal = 'none';
let browserPid;
res.stdout.on('data', data => {
output += data.toString();
// Uncomment to debug these tests.
// console.log(data.toString());
let match = output.match(/browserWS:(.+):browserWS/);
if (match)
wsEndPointCallback(match[1]);
match = output.match(/browserClose:([^:]+):([^:]+):browserClose/);
if (match) {
browserExitCode = match[1];
browserSignal = match[2];
}
match = output.match(/browserPid:([^:]+):browserPid/);
if (match)
browserPid = +match[1];
});
res.on('error', (...args) => console.log("ERROR", ...args));
const browser = await browserType.connect({ wsEndpoint: await wsEndPointPromise });
const promises = [
new Promise(resolve => browser.once('disconnected', resolve)),
new Promise(resolve => res.on('exit', resolve)),
];
action(res, browserPid);
const [, exitCode] = await Promise.all(promises);
return { exitCode, browserSignal, browserExitCode, output };
}
expect(result.exitCode).toBe(WIN ? 1 : 0);
// We might not get browser exitCode in time when killing the parent node process,
// so we don't check it here.
});
describe('Fixtures', function() {
it.slow()('should dump browser process stderr', async({server}) => {
let dumpioData = '';
const res = spawn('node', [path.join(__dirname, 'fixtures', 'dumpio.js'), playwrightPath, browserType.name()]);
res.stdout.on('data', data => dumpioData += data.toString('utf8'));
await new Promise(resolve => res.on('close', resolve));
expect(dumpioData).toContain('message from dumpio');
describe.skip(WIN)('signals', () => {
// Cannot reliably send signals on Windows.
it.slow()('should report browser close signal', async ({browserType}) => {
const result = await testSignal(browserType, (child, browserPid) => process.kill(browserPid), true);
expect(result.exitCode).toBe(0);
expect(result.browserExitCode).toBe('null');
expect(result.browserSignal).toBe('SIGTERM');
});
it.slow()('should close the browser when the node process closes', async () => {
const result = await testSignal(child => {
if (WIN)
execSync(`taskkill /pid ${child.pid} /T /F`);
else
process.kill(child.pid);
});
expect(result.exitCode).toBe(WIN ? 1 : 0);
// We might not get browser exitCode in time when killing the parent node process,
// so we don't check it here.
it.slow()('should report browser close signal 2', async ({browserType}) => {
const result = await testSignal(browserType, (child, browserPid) => process.kill(browserPid, 'SIGKILL'), true);
expect(result.exitCode).toBe(0);
expect(result.browserExitCode).toBe('null');
expect(result.browserSignal).toBe('SIGKILL');
});
describe.skip(WIN)('signals', () => {
// Cannot reliably send signals on Windows.
it.slow()('should report browser close signal', async () => {
const result = await testSignal((child, browserPid) => process.kill(browserPid), true);
expect(result.exitCode).toBe(0);
expect(result.browserExitCode).toBe('null');
expect(result.browserSignal).toBe('SIGTERM');
it.slow()('should close the browser on SIGINT', async ({browserType}) => {
const result = await testSignal(browserType, child => process.kill(child.pid, 'SIGINT'));
expect(result.exitCode).toBe(130);
expect(result.browserExitCode).toBe('0');
expect(result.browserSignal).toBe('null');
});
it.slow()('should close the browser on SIGTERM', async ({browserType}) => {
const result = await testSignal(browserType, child => process.kill(child.pid, 'SIGTERM'));
expect(result.exitCode).toBe(0);
expect(result.browserExitCode).toBe('0');
expect(result.browserSignal).toBe('null');
});
it.slow()('should close the browser on SIGHUP', async ({browserType}) => {
const result = await testSignal(browserType, child => process.kill(child.pid, 'SIGHUP'));
expect(result.exitCode).toBe(0);
expect(result.browserExitCode).toBe('0');
expect(result.browserSignal).toBe('null');
});
it.slow()('should kill the browser on double SIGINT', async ({browserType}) => {
const result = await testSignal(browserType, child => {
process.kill(child.pid, 'SIGINT');
process.kill(child.pid, 'SIGINT');
});
it.slow()('should report browser close signal 2', async (state, test) => {
const result = await testSignal((child, browserPid) => process.kill(browserPid, 'SIGKILL'), true);
expect(result.exitCode).toBe(0);
expect(result.browserExitCode).toBe('null');
expect(result.browserSignal).toBe('SIGKILL');
expect(result.exitCode).toBe(130);
// TODO: ideally, we would expect the SIGKILL on the browser from
// force kill, but that's racy with sending two signals.
});
it.slow()('should kill the browser on SIGINT + SIGTERM', async ({browserType}) => {
const result = await testSignal(browserType, child => {
process.kill(child.pid, 'SIGINT');
process.kill(child.pid, 'SIGTERM');
});
it.slow()('should close the browser on SIGINT', async () => {
const result = await testSignal(child => process.kill(child.pid, 'SIGINT'));
expect(result.exitCode).toBe(130);
expect(result.browserExitCode).toBe('0');
expect(result.browserSignal).toBe('null');
});
it.slow()('should close the browser on SIGTERM', async () => {
const result = await testSignal(child => process.kill(child.pid, 'SIGTERM'));
expect(result.exitCode).toBe(0);
expect(result.browserExitCode).toBe('0');
expect(result.browserSignal).toBe('null');
});
it.slow()('should close the browser on SIGHUP', async () => {
const result = await testSignal(child => process.kill(child.pid, 'SIGHUP'));
expect(result.exitCode).toBe(0);
expect(result.browserExitCode).toBe('0');
expect(result.browserSignal).toBe('null');
});
it.slow()('should kill the browser on double SIGINT', async () => {
const result = await testSignal(child => {
process.kill(child.pid, 'SIGINT');
process.kill(child.pid, 'SIGINT');
});
expect(result.exitCode).toBe(130);
// TODO: ideally, we would expect the SIGKILL on the browser from
// force kill, but that's racy with sending two signals.
});
it.slow()('should kill the browser on SIGINT + SIGTERM', async () => {
const result = await testSignal(child => {
process.kill(child.pid, 'SIGINT');
process.kill(child.pid, 'SIGTERM');
});
expect(result.exitCode).toBe(130);
// TODO: ideally, we would expect the SIGKILL on the browser from
// force kill, but that's racy with sending two signals.
});
it.slow()('should kill the browser on SIGTERM + SIGINT', async () => {
const result = await testSignal(child => {
process.kill(child.pid, 'SIGTERM');
process.kill(child.pid, 'SIGINT');
});
expect(result.exitCode).toBe(130);
// TODO: ideally, we would expect the SIGKILL on the browser from
// force kill, but that's racy with sending two signals.
expect(result.exitCode).toBe(130);
// TODO: ideally, we would expect the SIGKILL on the browser from
// force kill, but that's racy with sending two signals.
});
it.slow()('should kill the browser on SIGTERM + SIGINT', async ({browserType}) => {
const result = await testSignal(browserType, child => {
process.kill(child.pid, 'SIGTERM');
process.kill(child.pid, 'SIGINT');
});
expect(result.exitCode).toBe(130);
// TODO: ideally, we would expect the SIGKILL on the browser from
// force kill, but that's racy with sending two signals.
});
});
};
});

View file

@ -14,38 +14,34 @@
* limitations under the License.
*/
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({}) {
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
describe('Page.focus', function() {
it('should work', async function({page, server}) {
await page.setContent(`<div id=d1 tabIndex=0></div>`);
expect(await page.evaluate(() => document.activeElement.nodeName)).toBe('BODY');
await page.focus('#d1');
expect(await page.evaluate(() => document.activeElement.id)).toBe('d1');
});
it('should emit focus event', async function({page, server}) {
await page.setContent(`<div id=d1 tabIndex=0></div>`);
let focused = false;
await page.exposeFunction('focusEvent', () => focused = true);
await page.evaluate(() => d1.addEventListener('focus', focusEvent));
await page.focus('#d1');
expect(focused).toBe(true);
});
it('should emit blur event', async function({page, server}) {
await page.setContent(`<div id=d1 tabIndex=0>DIV1</div><div id=d2 tabIndex=0>DIV2</div>`);
await page.focus('#d1');
let focused = false;
let blurred = false;
await page.exposeFunction('focusEvent', () => focused = true);
await page.exposeFunction('blurEvent', () => blurred = true);
await page.evaluate(() => d1.addEventListener('blur', blurEvent));
await page.evaluate(() => d2.addEventListener('focus', focusEvent));
await page.focus('#d2');
expect(focused).toBe(true);
expect(blurred).toBe(true);
});
describe('Page.focus', function() {
it('should work', async function({page, server}) {
await page.setContent(`<div id=d1 tabIndex=0></div>`);
expect(await page.evaluate(() => document.activeElement.nodeName)).toBe('BODY');
await page.focus('#d1');
expect(await page.evaluate(() => document.activeElement.id)).toBe('d1');
});
};
it('should emit focus event', async function({page, server}) {
await page.setContent(`<div id=d1 tabIndex=0></div>`);
let focused = false;
await page.exposeFunction('focusEvent', () => focused = true);
await page.evaluate(() => d1.addEventListener('focus', focusEvent));
await page.focus('#d1');
expect(focused).toBe(true);
});
it('should emit blur event', async function({page, server}) {
await page.setContent(`<div id=d1 tabIndex=0>DIV1</div><div id=d2 tabIndex=0>DIV2</div>`);
await page.focus('#d1');
let focused = false;
let blurred = false;
await page.exposeFunction('focusEvent', () => focused = true);
await page.exposeFunction('blurEvent', () => blurred = true);
await page.evaluate(() => d1.addEventListener('blur', blurEvent));
await page.evaluate(() => d2.addEventListener('focus', focusEvent));
await page.focus('#d2');
expect(focused).toBe(true);
expect(blurred).toBe(true);
});
});

View file

@ -16,222 +16,217 @@
*/
const utils = require('./utils');
const {FFOX, CHROMIUM, WEBKIT} = utils.testOptions(browserType);
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({FFOX, CHROMIUM, WEBKIT}) {
describe('Frame.evaluateHandle', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const mainFrame = page.mainFrame();
const windowHandle = await mainFrame.evaluateHandle(() => window);
expect(windowHandle).toBeTruthy();
});
describe('Frame.evaluateHandle', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const mainFrame = page.mainFrame();
const windowHandle = await mainFrame.evaluateHandle(() => window);
expect(windowHandle).toBeTruthy();
});
});
describe('Frame.frameElement', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame2 = await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame3 = await utils.attachFrame(page, 'frame3', server.EMPTY_PAGE);
const frame1handle1 = await page.$('#frame1');
const frame1handle2 = await frame1.frameElement();
const frame3handle1 = await page.$('#frame3');
const frame3handle2 = await frame3.frameElement();
expect(await frame1handle1.evaluate((a, b) => a === b, frame1handle2)).toBe(true);
expect(await frame3handle1.evaluate((a, b) => a === b, frame3handle2)).toBe(true);
expect(await frame1handle1.evaluate((a, b) => a === b, frame3handle1)).toBe(false);
});
it('should work with contentFrame', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const handle = await frame.frameElement();
const contentFrame = await handle.contentFrame();
expect(contentFrame).toBe(frame);
});
it('should throw when detached', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await page.$eval('#frame1', e => e.remove());
const error = await frame1.frameElement().catch(e => e);
expect(error.message).toBe('Frame has been detached.');
});
describe('Frame.frameElement', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame2 = await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame3 = await utils.attachFrame(page, 'frame3', server.EMPTY_PAGE);
const frame1handle1 = await page.$('#frame1');
const frame1handle2 = await frame1.frameElement();
const frame3handle1 = await page.$('#frame3');
const frame3handle2 = await frame3.frameElement();
expect(await frame1handle1.evaluate((a, b) => a === b, frame1handle2)).toBe(true);
expect(await frame3handle1.evaluate((a, b) => a === b, frame3handle2)).toBe(true);
expect(await frame1handle1.evaluate((a, b) => a === b, frame3handle1)).toBe(false);
});
describe('Frame.evaluate', function() {
it('should throw for detached frames', async({page, server}) => {
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.detachFrame(page, 'frame1');
let error = null;
await frame1.evaluate(() => 7 * 8).catch(e => error = e);
expect(error.message).toContain('Execution Context is not available in detached frame');
});
it('should be isolated between frames', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(page.frames().length).toBe(2);
const [frame1, frame2] = page.frames();
expect(frame1 !== frame2).toBeTruthy();
await Promise.all([
frame1.evaluate(() => window.a = 1),
frame2.evaluate(() => window.a = 2)
]);
const [a1, a2] = await Promise.all([
frame1.evaluate(() => window.a),
frame2.evaluate(() => window.a)
]);
expect(a1).toBe(1);
expect(a2).toBe(2);
});
it('should work with contentFrame', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const handle = await frame.frameElement();
const contentFrame = await handle.contentFrame();
expect(contentFrame).toBe(frame);
});
describe('Frame Management', function() {
it('should handle nested frames', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/nested-frames.html');
expect(utils.dumpFrames(page.mainFrame())).toEqual([
'http://localhost:<PORT>/frames/nested-frames.html',
' http://localhost:<PORT>/frames/frame.html (aframe)',
' http://localhost:<PORT>/frames/two-frames.html (2frames)',
' http://localhost:<PORT>/frames/frame.html (dos)',
' http://localhost:<PORT>/frames/frame.html (uno)',
]);
});
it('should send events when frames are manipulated dynamically', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
// validate frameattached events
const attachedFrames = [];
page.on('frameattached', frame => attachedFrames.push(frame));
await utils.attachFrame(page, 'frame1', './assets/frame.html');
expect(attachedFrames.length).toBe(1);
expect(attachedFrames[0].url()).toContain('/assets/frame.html');
// validate framenavigated events
const navigatedFrames = [];
page.on('framenavigated', frame => navigatedFrames.push(frame));
await page.evaluate(() => {
const frame = document.getElementById('frame1');
frame.src = './empty.html';
return new Promise(x => frame.onload = x);
});
expect(navigatedFrames.length).toBe(1);
expect(navigatedFrames[0].url()).toBe(server.EMPTY_PAGE);
// validate framedetached events
const detachedFrames = [];
page.on('framedetached', frame => detachedFrames.push(frame));
await utils.detachFrame(page, 'frame1');
expect(detachedFrames.length).toBe(1);
expect(detachedFrames[0].isDetached()).toBe(true);
});
it('should send "framenavigated" when navigating on anchor URLs', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await Promise.all([
page.goto(server.EMPTY_PAGE + '#foo'),
page.waitForEvent('framenavigated')
]);
expect(page.url()).toBe(server.EMPTY_PAGE + '#foo');
});
it('should persist mainFrame on cross-process navigation', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const mainFrame = page.mainFrame();
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
expect(page.mainFrame() === mainFrame).toBeTruthy();
});
it('should not send attach/detach events for main frame', async({page, server}) => {
let hasEvents = false;
page.on('frameattached', frame => hasEvents = true);
page.on('framedetached', frame => hasEvents = true);
await page.goto(server.EMPTY_PAGE);
expect(hasEvents).toBe(false);
});
it('should detach child frames on navigation', async({page, server}) => {
let attachedFrames = [];
let detachedFrames = [];
let navigatedFrames = [];
page.on('frameattached', frame => attachedFrames.push(frame));
page.on('framedetached', frame => detachedFrames.push(frame));
page.on('framenavigated', frame => navigatedFrames.push(frame));
await page.goto(server.PREFIX + '/frames/nested-frames.html');
expect(attachedFrames.length).toBe(4);
expect(detachedFrames.length).toBe(0);
expect(navigatedFrames.length).toBe(5);
attachedFrames = [];
detachedFrames = [];
navigatedFrames = [];
await page.goto(server.EMPTY_PAGE);
expect(attachedFrames.length).toBe(0);
expect(detachedFrames.length).toBe(4);
expect(navigatedFrames.length).toBe(1);
});
it('should support framesets', async({page, server}) => {
let attachedFrames = [];
let detachedFrames = [];
let navigatedFrames = [];
page.on('frameattached', frame => attachedFrames.push(frame));
page.on('framedetached', frame => detachedFrames.push(frame));
page.on('framenavigated', frame => navigatedFrames.push(frame));
await page.goto(server.PREFIX + '/frames/frameset.html');
expect(attachedFrames.length).toBe(4);
expect(detachedFrames.length).toBe(0);
expect(navigatedFrames.length).toBe(5);
attachedFrames = [];
detachedFrames = [];
navigatedFrames = [];
await page.goto(server.EMPTY_PAGE);
expect(attachedFrames.length).toBe(0);
expect(detachedFrames.length).toBe(4);
expect(navigatedFrames.length).toBe(1);
});
it('should report frame from-inside shadow DOM', async({page, server}) => {
await page.goto(server.PREFIX + '/shadow.html');
await page.evaluate(async url => {
const frame = document.createElement('iframe');
frame.src = url;
document.body.shadowRoot.appendChild(frame);
await new Promise(x => frame.onload = x);
}, server.EMPTY_PAGE);
expect(page.frames().length).toBe(2);
expect(page.frames()[1].url()).toBe(server.EMPTY_PAGE);
});
it('should report frame.name()', async({page, server}) => {
await utils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE);
await page.evaluate(url => {
const frame = document.createElement('iframe');
frame.name = 'theFrameName';
frame.src = url;
document.body.appendChild(frame);
return new Promise(x => frame.onload = x);
}, server.EMPTY_PAGE);
expect(page.frames()[0].name()).toBe('');
expect(page.frames()[1].name()).toBe('theFrameId');
expect(page.frames()[2].name()).toBe('theFrameName');
});
it('should report frame.parent()', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
expect(page.frames()[0].parentFrame()).toBe(null);
expect(page.frames()[1].parentFrame()).toBe(page.mainFrame());
expect(page.frames()[2].parentFrame()).toBe(page.mainFrame());
});
it('should report different frame instance when frame re-attaches', async({page, server}) => {
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await page.evaluate(() => {
window.frame = document.querySelector('#frame1');
window.frame.remove();
});
expect(frame1.isDetached()).toBe(true);
const [frame2] = await Promise.all([
page.waitForEvent('frameattached'),
page.evaluate(() => document.body.appendChild(window.frame)),
]);
expect(frame2.isDetached()).toBe(false);
expect(frame1).not.toBe(frame2);
});
it('should throw when detached', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await page.$eval('#frame1', e => e.remove());
const error = await frame1.frameElement().catch(e => e);
expect(error.message).toBe('Frame has been detached.');
});
};
});
describe('Frame.evaluate', function() {
it('should throw for detached frames', async({page, server}) => {
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.detachFrame(page, 'frame1');
let error = null;
await frame1.evaluate(() => 7 * 8).catch(e => error = e);
expect(error.message).toContain('Execution Context is not available in detached frame');
});
it('should be isolated between frames', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(page.frames().length).toBe(2);
const [frame1, frame2] = page.frames();
expect(frame1 !== frame2).toBeTruthy();
await Promise.all([
frame1.evaluate(() => window.a = 1),
frame2.evaluate(() => window.a = 2)
]);
const [a1, a2] = await Promise.all([
frame1.evaluate(() => window.a),
frame2.evaluate(() => window.a)
]);
expect(a1).toBe(1);
expect(a2).toBe(2);
});
});
describe('Frame Management', function() {
it('should handle nested frames', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/nested-frames.html');
expect(utils.dumpFrames(page.mainFrame())).toEqual([
'http://localhost:<PORT>/frames/nested-frames.html',
' http://localhost:<PORT>/frames/frame.html (aframe)',
' http://localhost:<PORT>/frames/two-frames.html (2frames)',
' http://localhost:<PORT>/frames/frame.html (dos)',
' http://localhost:<PORT>/frames/frame.html (uno)',
]);
});
it('should send events when frames are manipulated dynamically', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
// validate frameattached events
const attachedFrames = [];
page.on('frameattached', frame => attachedFrames.push(frame));
await utils.attachFrame(page, 'frame1', './assets/frame.html');
expect(attachedFrames.length).toBe(1);
expect(attachedFrames[0].url()).toContain('/assets/frame.html');
// validate framenavigated events
const navigatedFrames = [];
page.on('framenavigated', frame => navigatedFrames.push(frame));
await page.evaluate(() => {
const frame = document.getElementById('frame1');
frame.src = './empty.html';
return new Promise(x => frame.onload = x);
});
expect(navigatedFrames.length).toBe(1);
expect(navigatedFrames[0].url()).toBe(server.EMPTY_PAGE);
// validate framedetached events
const detachedFrames = [];
page.on('framedetached', frame => detachedFrames.push(frame));
await utils.detachFrame(page, 'frame1');
expect(detachedFrames.length).toBe(1);
expect(detachedFrames[0].isDetached()).toBe(true);
});
it('should send "framenavigated" when navigating on anchor URLs', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await Promise.all([
page.goto(server.EMPTY_PAGE + '#foo'),
page.waitForEvent('framenavigated')
]);
expect(page.url()).toBe(server.EMPTY_PAGE + '#foo');
});
it('should persist mainFrame on cross-process navigation', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const mainFrame = page.mainFrame();
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
expect(page.mainFrame() === mainFrame).toBeTruthy();
});
it('should not send attach/detach events for main frame', async({page, server}) => {
let hasEvents = false;
page.on('frameattached', frame => hasEvents = true);
page.on('framedetached', frame => hasEvents = true);
await page.goto(server.EMPTY_PAGE);
expect(hasEvents).toBe(false);
});
it('should detach child frames on navigation', async({page, server}) => {
let attachedFrames = [];
let detachedFrames = [];
let navigatedFrames = [];
page.on('frameattached', frame => attachedFrames.push(frame));
page.on('framedetached', frame => detachedFrames.push(frame));
page.on('framenavigated', frame => navigatedFrames.push(frame));
await page.goto(server.PREFIX + '/frames/nested-frames.html');
expect(attachedFrames.length).toBe(4);
expect(detachedFrames.length).toBe(0);
expect(navigatedFrames.length).toBe(5);
attachedFrames = [];
detachedFrames = [];
navigatedFrames = [];
await page.goto(server.EMPTY_PAGE);
expect(attachedFrames.length).toBe(0);
expect(detachedFrames.length).toBe(4);
expect(navigatedFrames.length).toBe(1);
});
it('should support framesets', async({page, server}) => {
let attachedFrames = [];
let detachedFrames = [];
let navigatedFrames = [];
page.on('frameattached', frame => attachedFrames.push(frame));
page.on('framedetached', frame => detachedFrames.push(frame));
page.on('framenavigated', frame => navigatedFrames.push(frame));
await page.goto(server.PREFIX + '/frames/frameset.html');
expect(attachedFrames.length).toBe(4);
expect(detachedFrames.length).toBe(0);
expect(navigatedFrames.length).toBe(5);
attachedFrames = [];
detachedFrames = [];
navigatedFrames = [];
await page.goto(server.EMPTY_PAGE);
expect(attachedFrames.length).toBe(0);
expect(detachedFrames.length).toBe(4);
expect(navigatedFrames.length).toBe(1);
});
it('should report frame from-inside shadow DOM', async({page, server}) => {
await page.goto(server.PREFIX + '/shadow.html');
await page.evaluate(async url => {
const frame = document.createElement('iframe');
frame.src = url;
document.body.shadowRoot.appendChild(frame);
await new Promise(x => frame.onload = x);
}, server.EMPTY_PAGE);
expect(page.frames().length).toBe(2);
expect(page.frames()[1].url()).toBe(server.EMPTY_PAGE);
});
it('should report frame.name()', async({page, server}) => {
await utils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE);
await page.evaluate(url => {
const frame = document.createElement('iframe');
frame.name = 'theFrameName';
frame.src = url;
document.body.appendChild(frame);
return new Promise(x => frame.onload = x);
}, server.EMPTY_PAGE);
expect(page.frames()[0].name()).toBe('');
expect(page.frames()[1].name()).toBe('theFrameId');
expect(page.frames()[2].name()).toBe('theFrameName');
});
it('should report frame.parent()', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
expect(page.frames()[0].parentFrame()).toBe(null);
expect(page.frames()[1].parentFrame()).toBe(page.mainFrame());
expect(page.frames()[2].parentFrame()).toBe(page.mainFrame());
});
it('should report different frame instance when frame re-attaches', async({page, server}) => {
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await page.evaluate(() => {
window.frame = document.querySelector('#frame1');
window.frame.remove();
});
expect(frame1.isDetached()).toBe(true);
const [frame2] = await Promise.all([
page.waitForEvent('frameattached'),
page.evaluate(() => document.body.appendChild(window.frame)),
]);
expect(frame2.isDetached()).toBe(false);
expect(frame1).not.toBe(frame2);
});
});

View file

@ -15,110 +15,106 @@
* limitations under the License.
*/
/**
* @type {PageTestSuite}
*/
module.exports.describe = function ({ FFOX, WEBKIT }) {
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
describe('Overrides.setGeolocation', function() {
it('should work', async({page, server, context}) => {
await context.grantPermissions(['geolocation']);
await page.goto(server.EMPTY_PAGE);
await context.setGeolocation({longitude: 10, latitude: 10});
const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => {
resolve({latitude: position.coords.latitude, longitude: position.coords.longitude});
})));
expect(geolocation).toEqual({
latitude: 10,
longitude: 10
});
});
it('should throw when invalid longitude', async({context}) => {
let error = null;
try {
await context.setGeolocation({longitude: 200, latitude: 10});
} catch (e) {
error = e;
}
expect(error.message).toContain('Invalid longitude "200"');
});
it('should throw with missing latitude', async({context}) => {
let error = null;
try {
await context.setGeolocation({longitude: 10});
} catch (e) {
error = e;
}
expect(error.message).toContain('Invalid latitude "undefined"');
});
it('should not modify passed default options object', async({browser}) => {
const geolocation = { longitude: 10, latitude: 10 };
const options = { geolocation };
const context = await browser.newContext(options);
await context.setGeolocation({ longitude: 20, latitude: 20 });
expect(options.geolocation).toBe(geolocation);
await context.close();
});
it('should throw with missing longitude in default options', async({browser}) => {
let error = null;
try {
const context = await browser.newContext({ geolocation: {latitude: 10} });
await context.close();
} catch (e) {
error = e;
}
expect(error.message).toContain('Invalid longitude "undefined"');
});
it('should use context options', async({browser, server}) => {
const options = { geolocation: { longitude: 10, latitude: 10 }, permissions: ['geolocation'] };
const context = await browser.newContext(options);
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => {
resolve({latitude: position.coords.latitude, longitude: position.coords.longitude});
})));
expect(geolocation).toEqual({
latitude: 10,
longitude: 10
});
await context.close();
});
it('watchPosition should be notified', async({page, server, context}) => {
await context.grantPermissions(['geolocation']);
await page.goto(server.EMPTY_PAGE);
const messages = [];
page.on('console', message => messages.push(message.text()));
await context.setGeolocation({latitude: 0, longitude: 0});
await page.evaluate(() => {
navigator.geolocation.watchPosition(pos => {
const coords = pos.coords;
console.log(`lat=${coords.latitude} lng=${coords.longitude}`);
}, err => {});
});
await context.setGeolocation({latitude: 0, longitude: 10});
await page.waitForEvent('console', message => message.text().includes('lat=0 lng=10'));
await context.setGeolocation({latitude: 20, longitude: 30});
await page.waitForEvent('console', message => message.text().includes('lat=20 lng=30'));
await context.setGeolocation({latitude: 40, longitude: 50});
await page.waitForEvent('console', message => message.text().includes('lat=40 lng=50'));
const allMessages = messages.join('|');
expect(allMessages).toContain('lat=0 lng=10');
expect(allMessages).toContain('lat=20 lng=30');
expect(allMessages).toContain('lat=40 lng=50');
});
it('should use context options for popup', async({page, context, server}) => {
await context.grantPermissions(['geolocation']);
await context.setGeolocation({ longitude: 10, latitude: 10 });
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/geolocation.html'),
]);
await popup.waitForLoadState();
const geolocation = await popup.evaluate(() => window.geolocationPromise);
expect(geolocation).toEqual({ longitude: 10, latitude: 10 });
describe('Overrides.setGeolocation', function() {
it('should work', async({page, server, context}) => {
await context.grantPermissions(['geolocation']);
await page.goto(server.EMPTY_PAGE);
await context.setGeolocation({longitude: 10, latitude: 10});
const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => {
resolve({latitude: position.coords.latitude, longitude: position.coords.longitude});
})));
expect(geolocation).toEqual({
latitude: 10,
longitude: 10
});
});
};
it('should throw when invalid longitude', async({context}) => {
let error = null;
try {
await context.setGeolocation({longitude: 200, latitude: 10});
} catch (e) {
error = e;
}
expect(error.message).toContain('Invalid longitude "200"');
});
it('should throw with missing latitude', async({context}) => {
let error = null;
try {
await context.setGeolocation({longitude: 10});
} catch (e) {
error = e;
}
expect(error.message).toContain('Invalid latitude "undefined"');
});
it('should not modify passed default options object', async({browser}) => {
const geolocation = { longitude: 10, latitude: 10 };
const options = { geolocation };
const context = await browser.newContext(options);
await context.setGeolocation({ longitude: 20, latitude: 20 });
expect(options.geolocation).toBe(geolocation);
await context.close();
});
it('should throw with missing longitude in default options', async({browser}) => {
let error = null;
try {
const context = await browser.newContext({ geolocation: {latitude: 10} });
await context.close();
} catch (e) {
error = e;
}
expect(error.message).toContain('Invalid longitude "undefined"');
});
it('should use context options', async({browser, server}) => {
const options = { geolocation: { longitude: 10, latitude: 10 }, permissions: ['geolocation'] };
const context = await browser.newContext(options);
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => {
resolve({latitude: position.coords.latitude, longitude: position.coords.longitude});
})));
expect(geolocation).toEqual({
latitude: 10,
longitude: 10
});
await context.close();
});
it('watchPosition should be notified', async({page, server, context}) => {
await context.grantPermissions(['geolocation']);
await page.goto(server.EMPTY_PAGE);
const messages = [];
page.on('console', message => messages.push(message.text()));
await context.setGeolocation({latitude: 0, longitude: 0});
await page.evaluate(() => {
navigator.geolocation.watchPosition(pos => {
const coords = pos.coords;
console.log(`lat=${coords.latitude} lng=${coords.longitude}`);
}, err => {});
});
await context.setGeolocation({latitude: 0, longitude: 10});
await page.waitForEvent('console', message => message.text().includes('lat=0 lng=10'));
await context.setGeolocation({latitude: 20, longitude: 30});
await page.waitForEvent('console', message => message.text().includes('lat=20 lng=30'));
await context.setGeolocation({latitude: 40, longitude: 50});
await page.waitForEvent('console', message => message.text().includes('lat=40 lng=50'));
const allMessages = messages.join('|');
expect(allMessages).toContain('lat=0 lng=10');
expect(allMessages).toContain('lat=20 lng=30');
expect(allMessages).toContain('lat=40 lng=50');
});
it('should use context options for popup', async({page, context, server}) => {
await context.grantPermissions(['geolocation']);
await context.setGeolocation({ longitude: 10, latitude: 10 });
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/geolocation.html'),
]);
await popup.waitForLoadState();
const geolocation = await popup.evaluate(() => window.geolocationPromise);
expect(geolocation).toEqual({ longitude: 10, latitude: 10 });
});
});

View file

@ -14,58 +14,54 @@
* limitations under the License.
*/
const { makeUserDataDir, removeUserDataDir } = require('./utils');
const utils = require('./utils');
const { makeUserDataDir, removeUserDataDir } = utils;
const {FFOX, CHROMIUM, WEBKIT, WIN, defaultBrowserOptions} = utils.testOptions(browserType);
/**
* @type {TestSuite}
*/
module.exports.describe = function({browserType, defaultBrowserOptions, FFOX, CHROMIUM, WEBKIT, WIN}) {
const headfulOptions = Object.assign({}, defaultBrowserOptions, {
headless: false
});
const headlessOptions = Object.assign({}, defaultBrowserOptions, {
headless: true
});
const headfulOptions = Object.assign({}, defaultBrowserOptions, {
headless: false
describe('Headful', function() {
it('should have default url when launching browser', async ({browserType}) => {
const userDataDir = await makeUserDataDir();
const browserContext = await browserType.launchPersistentContext(userDataDir, headfulOptions);
const urls = browserContext.pages().map(page => page.url());
expect(urls).toEqual(['about:blank']);
await browserContext.close();
await removeUserDataDir(userDataDir);
});
const headlessOptions = Object.assign({}, defaultBrowserOptions, {
headless: true
it.slow().fail(WIN && CHROMIUM)('headless should be able to read cookies written by headful', async({browserType, server}) => {
// see https://github.com/microsoft/playwright/issues/717
const userDataDir = await makeUserDataDir();
// Write a cookie in headful chrome
const headfulContext = await browserType.launchPersistentContext(userDataDir, headfulOptions);
const headfulPage = await headfulContext.newPage();
await headfulPage.goto(server.EMPTY_PAGE);
await headfulPage.evaluate(() => document.cookie = 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT');
await headfulContext.close();
// Read the cookie from headless chrome
const headlessContext = await browserType.launchPersistentContext(userDataDir, headlessOptions);
const headlessPage = await headlessContext.newPage();
await headlessPage.goto(server.EMPTY_PAGE);
const cookie = await headlessPage.evaluate(() => document.cookie);
await headlessContext.close();
// This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
await removeUserDataDir(userDataDir);
expect(cookie).toBe('foo=true');
});
describe('Headful', function() {
it('should have default url when launching browser', async function() {
const userDataDir = await makeUserDataDir();
const browserContext = await browserType.launchPersistentContext(userDataDir, headfulOptions);
const urls = browserContext.pages().map(page => page.url());
expect(urls).toEqual(['about:blank']);
await browserContext.close();
await removeUserDataDir(userDataDir);
});
it.slow().fail(WIN && CHROMIUM)('headless should be able to read cookies written by headful', async({server}) => {
// see https://github.com/microsoft/playwright/issues/717
const userDataDir = await makeUserDataDir();
// Write a cookie in headful chrome
const headfulContext = await browserType.launchPersistentContext(userDataDir, headfulOptions);
const headfulPage = await headfulContext.newPage();
await headfulPage.goto(server.EMPTY_PAGE);
await headfulPage.evaluate(() => document.cookie = 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT');
await headfulContext.close();
// Read the cookie from headless chrome
const headlessContext = await browserType.launchPersistentContext(userDataDir, headlessOptions);
const headlessPage = await headlessContext.newPage();
await headlessPage.goto(server.EMPTY_PAGE);
const cookie = await headlessPage.evaluate(() => document.cookie);
await headlessContext.close();
// This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
await removeUserDataDir(userDataDir);
expect(cookie).toBe('foo=true');
});
it.slow()('should close browser with beforeunload page', async({server}) => {
const userDataDir = await makeUserDataDir();
const browserContext = await browserType.launchPersistentContext(userDataDir, headfulOptions);
const page = await browserContext.newPage();
await page.goto(server.PREFIX + '/beforeunload.html');
// We have to interact with a page so that 'beforeunload' handlers
// fire.
await page.click('body');
await browserContext.close();
await removeUserDataDir(userDataDir);
});
it.slow()('should close browser with beforeunload page', async({browserType, server}) => {
const userDataDir = await makeUserDataDir();
const browserContext = await browserType.launchPersistentContext(userDataDir, headfulOptions);
const page = await browserContext.newPage();
await page.goto(server.PREFIX + '/beforeunload.html');
// We have to interact with a page so that 'beforeunload' handlers
// fire.
await page.click('body');
await browserContext.close();
await removeUserDataDir(userDataDir);
});
};
});

View file

@ -15,13 +15,20 @@
* limitations under the License.
*/
/**
* @type {BrowserTestSuite}
*/
module.exports.describe = function({defaultBrowserOptions, playwright, FFOX, CHROMIUM, WEBKIT}) {
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
describe('ignoreHTTPSErrors', function() {
it('should work', async({browser, httpsServer}) => {
describe('ignoreHTTPSErrors', function() {
it('should work', async({browser, httpsServer}) => {
let error = null;
const context = await browser.newContext({ ignoreHTTPSErrors: true });
const page = await context.newPage();
const response = await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e);
expect(error).toBe(null);
expect(response.ok()).toBe(true);
await context.close();
});
it('should isolate contexts', async({browser, httpsServer}) => {
{
let error = null;
const context = await browser.newContext({ ignoreHTTPSErrors: true });
const page = await context.newPage();
@ -29,39 +36,28 @@ module.exports.describe = function({defaultBrowserOptions, playwright, FFOX, CHR
expect(error).toBe(null);
expect(response.ok()).toBe(true);
await context.close();
});
it('should isolate contexts', async({browser, httpsServer}) => {
{
let error = null;
const context = await browser.newContext({ ignoreHTTPSErrors: true });
const page = await context.newPage();
const response = await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e);
expect(error).toBe(null);
expect(response.ok()).toBe(true);
await context.close();
}
{
let error = null;
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e);
expect(error).not.toBe(null);
await context.close();
}
});
it('should work with mixed content', async({browser, server, httpsServer}) => {
httpsServer.setRoute('/mixedcontent.html', (req, res) => {
res.end(`<iframe src=${server.EMPTY_PAGE}></iframe>`);
});
const context = await browser.newContext({ ignoreHTTPSErrors: true });
}
{
let error = null;
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(httpsServer.PREFIX + '/mixedcontent.html', {waitUntil: 'domcontentloaded'});
expect(page.frames().length).toBe(2);
// Make sure blocked iframe has functional execution context
// @see https://github.com/GoogleChrome/puppeteer/issues/2709
expect(await page.frames()[0].evaluate('1 + 2')).toBe(3);
expect(await page.frames()[1].evaluate('2 + 3')).toBe(5);
await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e);
expect(error).not.toBe(null);
await context.close();
});
}
});
};
it('should work with mixed content', async({browser, server, httpsServer}) => {
httpsServer.setRoute('/mixedcontent.html', (req, res) => {
res.end(`<iframe src=${server.EMPTY_PAGE}></iframe>`);
});
const context = await browser.newContext({ ignoreHTTPSErrors: true });
const page = await context.newPage();
await page.goto(httpsServer.PREFIX + '/mixedcontent.html', {waitUntil: 'domcontentloaded'});
expect(page.frames().length).toBe(2);
// Make sure blocked iframe has functional execution context
// @see https://github.com/GoogleChrome/puppeteer/issues/2709
expect(await page.frames()[0].evaluate('1 + 2')).toBe(3);
expect(await page.frames()[1].evaluate('2 + 3')).toBe(5);
await context.close();
});
});

View file

@ -20,227 +20,222 @@ const fs = require('fs');
const formidable = require('formidable');
const FILE_TO_UPLOAD = path.join(__dirname, '/assets/file-to-upload.txt');
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({playwright, FFOX, CHROMIUM, WEBKIT}) {
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('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 => {
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, server}) => {
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, server}) => {
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, server}) => {
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 [{ element }] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
await element.setInputFiles(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 callback;
const result = new Promise(f => callback = f);
server.setRoute('/upload', async (req, res) => {
const form = new formidable.IncomingForm();
form.parse(req, function(err, fields, { file1, file2 }) {
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()
);
callback();
});
});
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'));
page.click('input[type=submit]');
await result;
});
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(({element}) => element.setInputFiles(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(e.files[0]);
reader.readAsText(picker.files[0]);
return promise.then(() => reader.result);
}, input)).toBe('contents of the file');
});
}),
]);
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(({element}) => element.setInputFiles(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(({element}) => element.setInputFiles([])),
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 [{ element }] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
let error = null;
await element.setInputFiles([
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', 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, server}) => {
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, server}) => {
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, server}) => {
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 [{ element }] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
await element.setInputFiles(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 callback;
const result = new Promise(f => callback = f);
server.setRoute('/upload', async (req, res) => {
const form = new formidable.IncomingForm();
form.parse(req, function(err, fields, { file1, file2 }) {
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()
);
callback();
});
});
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'));
page.click('input[type=submit]');
await result;
});
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(({element}) => element.setInputFiles(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(({element}) => element.setInputFiles(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(({element}) => element.setInputFiles([])),
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 [{ element }] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
let error = null;
await element.setInputFiles([
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 [{ multiple }] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
expect(multiple).toBe(false);
});
describe('Page.waitForFileChooser isMultiple', () => {
it('should work for single file pick', async({page, server}) => {
await page.setContent(`<input type=file>`);
const [{ multiple }] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
expect(multiple).toBe(false);
});
it('should work for "multiple"', async({page, server}) => {
await page.setContent(`<input multiple type=file>`);
const [{ multiple }] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
expect(multiple).toBe(true);
});
it('should work for "webkitdirectory"', async({page, server}) => {
await page.setContent(`<input multiple webkitdirectory type=file>`);
const [{ multiple }] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
expect(multiple).toBe(true);
});
it('should work for "multiple"', async({page, server}) => {
await page.setContent(`<input multiple type=file>`);
const [{ multiple }] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
expect(multiple).toBe(true);
});
};
it('should work for "webkitdirectory"', async({page, server}) => {
await page.setContent(`<input multiple webkitdirectory type=file>`);
const [{ multiple }] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('input'),
]);
expect(multiple).toBe(true);
});
});

File diff suppressed because it is too large Load diff

View file

@ -15,263 +15,259 @@
* limitations under the License.
*/
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({CHROMIUM, FFOX, WEBKIT}) {
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
describe('Page.evaluateHandle', function() {
it('should work', async({page, server}) => {
const windowHandle = await page.evaluateHandle(() => window);
expect(windowHandle).toBeTruthy();
});
it('should accept object handle as an argument', async({page, server}) => {
const navigatorHandle = await page.evaluateHandle(() => navigator);
const text = await page.evaluate(e => e.userAgent, navigatorHandle);
expect(text).toContain('Mozilla');
});
it('should accept object handle to primitive types', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => 5);
const isFive = await page.evaluate(e => Object.is(e, 5), aHandle);
expect(isFive).toBeTruthy();
});
it('should accept nested handle', async({page, server}) => {
const foo = await page.evaluateHandle(() => ({ x: 1, y: 'foo' }));
const result = await page.evaluate(({ foo }) => {
return foo;
}, { foo });
expect(result).toEqual({ x: 1, y: 'foo' });
});
it('should accept nested window handle', async({page, server}) => {
const foo = await page.evaluateHandle(() => window);
const result = await page.evaluate(({ foo }) => {
return foo === window;
}, { foo });
expect(result).toBe(true);
});
it('should accept multiple nested handles', async({page, server}) => {
const foo = await page.evaluateHandle(() => ({ x: 1, y: 'foo' }));
const bar = await page.evaluateHandle(() => 5);
const baz = await page.evaluateHandle(() => (['baz']));
const result = await page.evaluate(x => {
return JSON.stringify(x);
}, { a1: { foo }, a2: { bar, arr: [{ baz }] } });
expect(JSON.parse(result)).toEqual({
a1: { foo: { x: 1, y: 'foo' } },
a2: { bar: 5, arr: [{ baz: ['baz'] }] }
});
});
it('should throw for deep objects', async({page, server}) => {
let a = { x: 1 };
for (let i = 0; i < 98; i++)
a = { x: a };
expect(await page.evaluate(x => x, a)).toEqual(a);
let error = await page.evaluate(x => x, {a}).catch(e => e);
expect(error.message).toBe('Argument nesting is too deep');
error = await page.evaluate(x => x, [a]).catch(e => e);
expect(error.message).toBe('Argument nesting is too deep');
});
it('should throw for circular objects', async({page, server}) => {
const a = { x: 1 };
a.y = a;
const error = await page.evaluate(x => x, a).catch(e => e);
expect(error.message).toBe('Argument is a circular structure');
});
it('should accept same handle multiple times', async({page, server}) => {
const foo = await page.evaluateHandle(() => 1);
expect(await page.evaluate(x => x, { foo, bar: [foo], baz: { foo }})).toEqual({ foo: 1, bar: [1], baz: { foo: 1 } });
});
it('should accept same nested object multiple times', async({page, server}) => {
const foo = { x: 1 };
expect(await page.evaluate(x => x, { foo, bar: [foo], baz: { foo }})).toEqual({ foo: { x: 1 }, bar: [{ x : 1 }], baz: { foo: { x : 1 } } });
});
it('should accept object handle to unserializable value', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => Infinity);
expect(await page.evaluate(e => Object.is(e, Infinity), aHandle)).toBe(true);
});
it('should pass configurable args', async({page, server}) => {
const result = await page.evaluate(arg => {
if (arg.foo !== 42)
throw new Error('Not a 42');
arg.foo = 17;
if (arg.foo !== 17)
throw new Error('Not 17');
delete arg.foo;
if (arg.foo === 17)
throw new Error('Still 17');
return arg;
}, { foo: 42 });
expect(result).toEqual({});
});
it('should use the same JS wrappers', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => {
window.FOO = 123;
return window;
});
expect(await page.evaluate(e => e.FOO, aHandle)).toBe(123);
});
it('should work with primitives', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => {
window.FOO = 123;
return window;
});
expect(await page.evaluate(e => e.FOO, aHandle)).toBe(123);
describe('Page.evaluateHandle', function() {
it('should work', async({page, server}) => {
const windowHandle = await page.evaluateHandle(() => window);
expect(windowHandle).toBeTruthy();
});
it('should accept object handle as an argument', async({page, server}) => {
const navigatorHandle = await page.evaluateHandle(() => navigator);
const text = await page.evaluate(e => e.userAgent, navigatorHandle);
expect(text).toContain('Mozilla');
});
it('should accept object handle to primitive types', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => 5);
const isFive = await page.evaluate(e => Object.is(e, 5), aHandle);
expect(isFive).toBeTruthy();
});
it('should accept nested handle', async({page, server}) => {
const foo = await page.evaluateHandle(() => ({ x: 1, y: 'foo' }));
const result = await page.evaluate(({ foo }) => {
return foo;
}, { foo });
expect(result).toEqual({ x: 1, y: 'foo' });
});
it('should accept nested window handle', async({page, server}) => {
const foo = await page.evaluateHandle(() => window);
const result = await page.evaluate(({ foo }) => {
return foo === window;
}, { foo });
expect(result).toBe(true);
});
it('should accept multiple nested handles', async({page, server}) => {
const foo = await page.evaluateHandle(() => ({ x: 1, y: 'foo' }));
const bar = await page.evaluateHandle(() => 5);
const baz = await page.evaluateHandle(() => (['baz']));
const result = await page.evaluate(x => {
return JSON.stringify(x);
}, { a1: { foo }, a2: { bar, arr: [{ baz }] } });
expect(JSON.parse(result)).toEqual({
a1: { foo: { x: 1, y: 'foo' } },
a2: { bar: 5, arr: [{ baz: ['baz'] }] }
});
});
describe('JSHandle.getProperty', function() {
it('should work', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => ({
one: 1,
two: 2,
three: 3
}));
const twoHandle = await aHandle.getProperty('two');
expect(await twoHandle.jsonValue()).toEqual(2);
});
it('should work with undefined, null, and empty', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => ({
undefined: undefined,
null: null,
}));
const undefinedHandle = await aHandle.getProperty('undefined');
expect(String(await undefinedHandle.jsonValue())).toEqual('undefined');
const nullHandle = await aHandle.getProperty('null');
expect(await nullHandle.jsonValue()).toEqual(null);
const emptyhandle = await aHandle.getProperty('empty');
expect(String(await emptyhandle.jsonValue())).toEqual('undefined');
})
it('should throw for deep objects', async({page, server}) => {
let a = { x: 1 };
for (let i = 0; i < 98; i++)
a = { x: a };
expect(await page.evaluate(x => x, a)).toEqual(a);
let error = await page.evaluate(x => x, {a}).catch(e => e);
expect(error.message).toBe('Argument nesting is too deep');
error = await page.evaluate(x => x, [a]).catch(e => e);
expect(error.message).toBe('Argument nesting is too deep');
});
describe('JSHandle.jsonValue', function() {
it('should work', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => ({foo: 'bar'}));
const json = await aHandle.jsonValue();
expect(json).toEqual({foo: 'bar'});
});
it('should not work with dates', async({page, server}) => {
const dateHandle = await page.evaluateHandle(() => new Date('2017-09-26T00:00:00.000Z'));
const json = await dateHandle.jsonValue();
expect(json).toEqual({});
});
it('should throw for circular objects', async({page, server}) => {
const windowHandle = await page.evaluateHandle('window');
let error = null;
await windowHandle.jsonValue().catch(e => error = e);
if (WEBKIT)
expect(error.message).toContain('Object has too long reference chain');
else if (CHROMIUM)
expect(error.message).toContain('Object reference chain is too long');
else if (FFOX)
expect(error.message).toContain('Object is not serializable');
});
it('should work with tricky values', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => ({a: 1}));
const json = await aHandle.jsonValue();
expect(json).toEqual({a: 1});
});
it('should throw for circular objects', async({page, server}) => {
const a = { x: 1 };
a.y = a;
const error = await page.evaluate(x => x, a).catch(e => e);
expect(error.message).toBe('Argument is a circular structure');
});
it('should accept same handle multiple times', async({page, server}) => {
const foo = await page.evaluateHandle(() => 1);
expect(await page.evaluate(x => x, { foo, bar: [foo], baz: { foo }})).toEqual({ foo: 1, bar: [1], baz: { foo: 1 } });
});
it('should accept same nested object multiple times', async({page, server}) => {
const foo = { x: 1 };
expect(await page.evaluate(x => x, { foo, bar: [foo], baz: { foo }})).toEqual({ foo: { x: 1 }, bar: [{ x : 1 }], baz: { foo: { x : 1 } } });
});
it('should accept object handle to unserializable value', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => Infinity);
expect(await page.evaluate(e => Object.is(e, Infinity), aHandle)).toBe(true);
});
it('should pass configurable args', async({page, server}) => {
const result = await page.evaluate(arg => {
if (arg.foo !== 42)
throw new Error('Not a 42');
arg.foo = 17;
if (arg.foo !== 17)
throw new Error('Not 17');
delete arg.foo;
if (arg.foo === 17)
throw new Error('Still 17');
return arg;
}, { foo: 42 });
expect(result).toEqual({});
});
it('should use the same JS wrappers', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => {
window.FOO = 123;
return window;
});
expect(await page.evaluate(e => e.FOO, aHandle)).toBe(123);
});
it('should work with primitives', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => {
window.FOO = 123;
return window;
});
expect(await page.evaluate(e => e.FOO, aHandle)).toBe(123);
});
});
describe('JSHandle.getProperties', function() {
it('should work', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => ({
foo: 'bar'
}));
const properties = await aHandle.getProperties();
const foo = properties.get('foo');
expect(foo).toBeTruthy();
expect(await foo.jsonValue()).toBe('bar');
});
it('should return empty map for non-objects', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => 123);
const properties = await aHandle.getProperties();
expect(properties.size).toBe(0);
});
it('should return even non-own properties', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => {
class A {
constructor() {
this.a = '1';
}
describe('JSHandle.getProperty', function() {
it('should work', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => ({
one: 1,
two: 2,
three: 3
}));
const twoHandle = await aHandle.getProperty('two');
expect(await twoHandle.jsonValue()).toEqual(2);
});
it('should work with undefined, null, and empty', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => ({
undefined: undefined,
null: null,
}));
const undefinedHandle = await aHandle.getProperty('undefined');
expect(String(await undefinedHandle.jsonValue())).toEqual('undefined');
const nullHandle = await aHandle.getProperty('null');
expect(await nullHandle.jsonValue()).toEqual(null);
const emptyhandle = await aHandle.getProperty('empty');
expect(String(await emptyhandle.jsonValue())).toEqual('undefined');
})
});
describe('JSHandle.jsonValue', function() {
it('should work', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => ({foo: 'bar'}));
const json = await aHandle.jsonValue();
expect(json).toEqual({foo: 'bar'});
});
it('should not work with dates', async({page, server}) => {
const dateHandle = await page.evaluateHandle(() => new Date('2017-09-26T00:00:00.000Z'));
const json = await dateHandle.jsonValue();
expect(json).toEqual({});
});
it('should throw for circular objects', async({page, server}) => {
const windowHandle = await page.evaluateHandle('window');
let error = null;
await windowHandle.jsonValue().catch(e => error = e);
if (WEBKIT)
expect(error.message).toContain('Object has too long reference chain');
else if (CHROMIUM)
expect(error.message).toContain('Object reference chain is too long');
else if (FFOX)
expect(error.message).toContain('Object is not serializable');
});
it('should work with tricky values', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => ({a: 1}));
const json = await aHandle.jsonValue();
expect(json).toEqual({a: 1});
});
});
describe('JSHandle.getProperties', function() {
it('should work', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => ({
foo: 'bar'
}));
const properties = await aHandle.getProperties();
const foo = properties.get('foo');
expect(foo).toBeTruthy();
expect(await foo.jsonValue()).toBe('bar');
});
it('should return empty map for non-objects', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => 123);
const properties = await aHandle.getProperties();
expect(properties.size).toBe(0);
});
it('should return even non-own properties', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => {
class A {
constructor() {
this.a = '1';
}
class B extends A {
constructor() {
super();
this.b = '2';
}
}
class B extends A {
constructor() {
super();
this.b = '2';
}
return new B();
});
const properties = await aHandle.getProperties();
expect(await properties.get('a').jsonValue()).toBe('1');
expect(await properties.get('b').jsonValue()).toBe('2');
}
return new B();
});
const properties = await aHandle.getProperties();
expect(await properties.get('a').jsonValue()).toBe('1');
expect(await properties.get('b').jsonValue()).toBe('2');
});
});
describe('JSHandle.asElement', function() {
it('should work', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => document.body);
const element = aHandle.asElement();
expect(element).toBeTruthy();
});
it('should return null for non-elements', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => 2);
const element = aHandle.asElement();
expect(element).toBeFalsy();
});
it('should return ElementHandle for TextNodes', async({page, server}) => {
await page.setContent('<div>ee!</div>');
const aHandle = await page.evaluateHandle(() => document.querySelector('div').firstChild);
const element = aHandle.asElement();
expect(element).toBeTruthy();
expect(await page.evaluate(e => e.nodeType === HTMLElement.TEXT_NODE, element)).toBeTruthy();
});
it('should work with nullified Node', async({page, server}) => {
await page.setContent('<section>test</section>');
await page.evaluate(() => delete Node);
const handle = await page.evaluateHandle(() => document.querySelector('section'));
const element = handle.asElement();
expect(element).not.toBe(null);
});
describe('JSHandle.asElement', function() {
it('should work', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => document.body);
const element = aHandle.asElement();
expect(element).toBeTruthy();
});
it('should return null for non-elements', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => 2);
const element = aHandle.asElement();
expect(element).toBeFalsy();
});
it('should return ElementHandle for TextNodes', async({page, server}) => {
await page.setContent('<div>ee!</div>');
const aHandle = await page.evaluateHandle(() => document.querySelector('div').firstChild);
const element = aHandle.asElement();
expect(element).toBeTruthy();
expect(await page.evaluate(e => e.nodeType === HTMLElement.TEXT_NODE, element)).toBeTruthy();
});
it('should work with nullified Node', async({page, server}) => {
await page.setContent('<section>test</section>');
await page.evaluate(() => delete Node);
const handle = await page.evaluateHandle(() => document.querySelector('section'));
const element = handle.asElement();
expect(element).not.toBe(null);
});
});
describe('JSHandle.toString', function() {
it('should work for primitives', async({page, server}) => {
const numberHandle = await page.evaluateHandle(() => 2);
expect(numberHandle.toString()).toBe('JSHandle:2');
const stringHandle = await page.evaluateHandle(() => 'a');
expect(stringHandle.toString()).toBe('JSHandle:a');
});
it('should work for complicated objects', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => window);
expect(aHandle.toString()).toBe('JSHandle@object');
});
it('should work for promises', async({page, server}) => {
// wrap the promise in an object, otherwise we will await.
const wrapperHandle = await page.evaluateHandle(() => ({b: Promise.resolve(123)}));
const bHandle = await wrapperHandle.getProperty('b');
expect(bHandle.toString()).toBe('JSHandle@promise');
});
it('should work with different subtypes', async({page, server}) => {
expect((await page.evaluateHandle('(function(){})')).toString()).toBe('JSHandle@function');
expect((await page.evaluateHandle('12')).toString()).toBe('JSHandle:12');
expect((await page.evaluateHandle('true')).toString()).toBe('JSHandle:true');
expect((await page.evaluateHandle('undefined')).toString()).toBe('JSHandle:undefined');
expect((await page.evaluateHandle('"foo"')).toString()).toBe('JSHandle:foo');
expect((await page.evaluateHandle('Symbol()')).toString()).toBe('JSHandle@symbol');
expect((await page.evaluateHandle('new Map()')).toString()).toBe('JSHandle@map');
expect((await page.evaluateHandle('new Set()')).toString()).toBe('JSHandle@set');
expect((await page.evaluateHandle('[]')).toString()).toBe('JSHandle@array');
expect((await page.evaluateHandle('null')).toString()).toBe('JSHandle:null');
expect((await page.evaluateHandle('/foo/')).toString()).toBe('JSHandle@regexp');
expect((await page.evaluateHandle('document.body')).toString()).toBe('JSHandle@node');
expect((await page.evaluateHandle('new Date()')).toString()).toBe('JSHandle@date');
expect((await page.evaluateHandle('new WeakMap()')).toString()).toBe('JSHandle@weakmap');
expect((await page.evaluateHandle('new WeakSet()')).toString()).toBe('JSHandle@weakset');
expect((await page.evaluateHandle('new Error()')).toString()).toBe('JSHandle@error');
// TODO(yurys): change subtype from array to typedarray in WebKit.
expect((await page.evaluateHandle('new Int32Array()')).toString()).toBe(WEBKIT ? 'JSHandle@array' : 'JSHandle@typedarray');
expect((await page.evaluateHandle('new Proxy({}, {})')).toString()).toBe('JSHandle@proxy');
});
describe('JSHandle.toString', function() {
it('should work for primitives', async({page, server}) => {
const numberHandle = await page.evaluateHandle(() => 2);
expect(numberHandle.toString()).toBe('JSHandle:2');
const stringHandle = await page.evaluateHandle(() => 'a');
expect(stringHandle.toString()).toBe('JSHandle:a');
});
};
it('should work for complicated objects', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => window);
expect(aHandle.toString()).toBe('JSHandle@object');
});
it('should work for promises', async({page, server}) => {
// wrap the promise in an object, otherwise we will await.
const wrapperHandle = await page.evaluateHandle(() => ({b: Promise.resolve(123)}));
const bHandle = await wrapperHandle.getProperty('b');
expect(bHandle.toString()).toBe('JSHandle@promise');
});
it('should work with different subtypes', async({page, server}) => {
expect((await page.evaluateHandle('(function(){})')).toString()).toBe('JSHandle@function');
expect((await page.evaluateHandle('12')).toString()).toBe('JSHandle:12');
expect((await page.evaluateHandle('true')).toString()).toBe('JSHandle:true');
expect((await page.evaluateHandle('undefined')).toString()).toBe('JSHandle:undefined');
expect((await page.evaluateHandle('"foo"')).toString()).toBe('JSHandle:foo');
expect((await page.evaluateHandle('Symbol()')).toString()).toBe('JSHandle@symbol');
expect((await page.evaluateHandle('new Map()')).toString()).toBe('JSHandle@map');
expect((await page.evaluateHandle('new Set()')).toString()).toBe('JSHandle@set');
expect((await page.evaluateHandle('[]')).toString()).toBe('JSHandle@array');
expect((await page.evaluateHandle('null')).toString()).toBe('JSHandle:null');
expect((await page.evaluateHandle('/foo/')).toString()).toBe('JSHandle@regexp');
expect((await page.evaluateHandle('document.body')).toString()).toBe('JSHandle@node');
expect((await page.evaluateHandle('new Date()')).toString()).toBe('JSHandle@date');
expect((await page.evaluateHandle('new WeakMap()')).toString()).toBe('JSHandle@weakmap');
expect((await page.evaluateHandle('new WeakSet()')).toString()).toBe('JSHandle@weakset');
expect((await page.evaluateHandle('new Error()')).toString()).toBe('JSHandle@error');
// TODO(yurys): change subtype from array to typedarray in WebKit.
expect((await page.evaluateHandle('new Int32Array()')).toString()).toBe(WEBKIT ? 'JSHandle@array' : 'JSHandle@typedarray');
expect((await page.evaluateHandle('new Proxy({}, {})')).toString()).toBe('JSHandle@proxy');
});
});

View file

@ -16,297 +16,291 @@
*/
const utils = require('./utils');
const os = require('os');
const {FFOX, CHROMIUM, WEBKIT, MAC} = require('./utils').testOptions(browserType);
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({FFOX, CHROMIUM, WEBKIT, MAC}) {
describe('Keyboard', function() {
it('should type into a textarea', async({page, server}) => {
await page.evaluate(() => {
const textarea = document.createElement('textarea');
document.body.appendChild(textarea);
textarea.focus();
});
const text = 'Hello world. I am the text that was typed!';
await page.keyboard.type(text);
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe(text);
describe('Keyboard', function() {
it('should type into a textarea', async({page, server}) => {
await page.evaluate(() => {
const textarea = document.createElement('textarea');
document.body.appendChild(textarea);
textarea.focus();
});
it('should move with the arrow keys', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.type('textarea', 'Hello World!');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!');
for (let i = 0; i < 'World!'.length; i++)
page.keyboard.press('ArrowLeft');
await page.keyboard.type('inserted ');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello inserted World!');
page.keyboard.down('Shift');
for (let i = 0; i < 'inserted '.length; i++)
page.keyboard.press('ArrowLeft');
page.keyboard.up('Shift');
await page.keyboard.press('Backspace');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!');
});
it('should send a character with ElementHandle.press', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const textarea = await page.$('textarea');
await textarea.press('a');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('a');
await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true));
await textarea.press('b');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('a');
});
it('should send a character with sendCharacter', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea');
await page.keyboard.insertText('嗨');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨');
await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true));
await page.keyboard.insertText('a');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨a');
});
it('insertText should only emit input event', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea');
page.on('console', m => console.log(m.text()));
await page.evaluate(() => {
window.events = [];
document.addEventListener('keydown', e => events.push(e.type));
document.addEventListener('keyup', e => events.push(e.type));
document.addEventListener('keypress', e => events.push(e.type));
document.addEventListener('input', e => events.push(e.type));
});
await page.keyboard.insertText('hello world');
expect(await page.evaluate('window.events')).toEqual(['input']);
});
it.fail(FFOX)('should report shiftKey', async({page, server}) => {
await page.goto(server.PREFIX + '/input/keyboard.html');
const keyboard = page.keyboard;
const codeForKey = {'Shift': 16, 'Alt': 18, 'Control': 17};
for (const modifierKey in codeForKey) {
await keyboard.down(modifierKey);
expect(await page.evaluate(() => getResult())).toBe('Keydown: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' [' + modifierKey + ']');
await keyboard.down('!');
// Shift+! will generate a keypress
if (modifierKey === 'Shift')
expect(await page.evaluate(() => getResult())).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']\nKeypress: ! Digit1 33 33 [' + modifierKey + ']');
else
expect(await page.evaluate(() => getResult())).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']');
await keyboard.up('!');
expect(await page.evaluate(() => getResult())).toBe('Keyup: ! Digit1 49 [' + modifierKey + ']');
await keyboard.up(modifierKey);
expect(await page.evaluate(() => getResult())).toBe('Keyup: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' []');
}
});
it('should report multiple modifiers', async({page, server}) => {
await page.goto(server.PREFIX + '/input/keyboard.html');
const keyboard = page.keyboard;
await keyboard.down('Control');
expect(await page.evaluate(() => getResult())).toBe('Keydown: Control ControlLeft 17 [Control]');
await keyboard.down('Alt');
expect(await page.evaluate(() => getResult())).toBe('Keydown: Alt AltLeft 18 [Alt Control]');
await keyboard.down(';');
expect(await page.evaluate(() => getResult())).toBe('Keydown: ; Semicolon 186 [Alt Control]');
await keyboard.up(';');
expect(await page.evaluate(() => getResult())).toBe('Keyup: ; Semicolon 186 [Alt Control]');
await keyboard.up('Control');
expect(await page.evaluate(() => getResult())).toBe('Keyup: Control ControlLeft 17 [Alt]');
await keyboard.up('Alt');
expect(await page.evaluate(() => getResult())).toBe('Keyup: Alt AltLeft 18 []');
});
it('should send proper codes while typing', async({page, server}) => {
await page.goto(server.PREFIX + '/input/keyboard.html');
await page.keyboard.type('!');
expect(await page.evaluate(() => getResult())).toBe(
[ 'Keydown: ! Digit1 49 []',
'Keypress: ! Digit1 33 33 []',
'Keyup: ! Digit1 49 []'].join('\n'));
await page.keyboard.type('^');
expect(await page.evaluate(() => getResult())).toBe(
[ 'Keydown: ^ Digit6 54 []',
'Keypress: ^ Digit6 94 94 []',
'Keyup: ^ Digit6 54 []'].join('\n'));
});
it('should send proper codes while typing with shift', async({page, server}) => {
await page.goto(server.PREFIX + '/input/keyboard.html');
const keyboard = page.keyboard;
await keyboard.down('Shift');
await page.keyboard.type('~');
expect(await page.evaluate(() => getResult())).toBe(
[ 'Keydown: Shift ShiftLeft 16 [Shift]',
'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode
'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode
'Keyup: ~ Backquote 192 [Shift]'].join('\n'));
await keyboard.up('Shift');
});
it('should not type canceled events', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea');
await page.evaluate(() => {
window.addEventListener('keydown', event => {
event.stopPropagation();
event.stopImmediatePropagation();
if (event.key === 'l')
event.preventDefault();
if (event.key === 'o')
event.preventDefault();
}, false);
});
await page.keyboard.type('Hello World!');
expect(await page.evaluate(() => textarea.value)).toBe('He Wrd!');
});
it('should specify repeat property', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea');
await page.evaluate(() => document.querySelector('textarea').addEventListener('keydown', e => window.lastEvent = e, true));
await page.keyboard.down('a');
expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false);
await page.keyboard.press('a');
expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(true);
await page.keyboard.down('b');
expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false);
await page.keyboard.down('b');
expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(true);
await page.keyboard.up('a');
await page.keyboard.down('a');
expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false);
});
it('should type all kinds of characters', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea');
const text = 'This text goes onto two lines.\nThis character is 嗨.';
await page.keyboard.type(text);
expect(await page.evaluate('result')).toBe(text);
});
it('should specify location', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.evaluate(() => {
window.addEventListener('keydown', event => window.keyLocation = event.location, true);
});
const textarea = await page.$('textarea');
await textarea.press('Digit5');
expect(await page.evaluate('keyLocation')).toBe(0);
await textarea.press('ControlLeft');
expect(await page.evaluate('keyLocation')).toBe(1);
await textarea.press('ControlRight');
expect(await page.evaluate('keyLocation')).toBe(2);
await textarea.press('NumpadSubtract');
expect(await page.evaluate('keyLocation')).toBe(3);
});
it('should press Enter', async({page, server}) => {
await page.setContent('<textarea></textarea>');
await page.focus('textarea');
await page.evaluate(() => window.addEventListener('keydown', e => window.lastEvent = {key: e.key, code:e.code}));
await testEnterKey('Enter', 'Enter', 'Enter');
await testEnterKey('NumpadEnter', 'Enter', 'NumpadEnter');
await testEnterKey('\n', 'Enter', 'Enter');
await testEnterKey('\r', 'Enter', 'Enter');
async function testEnterKey(key, expectedKey, expectedCode) {
await page.keyboard.press(key);
const lastEvent = await page.evaluate('lastEvent');
expect(lastEvent.key).toBe(expectedKey, `${JSON.stringify(key)} had the wrong key: ${lastEvent.key}`);
expect(lastEvent.code).toBe(expectedCode, `${JSON.stringify(key)} had the wrong code: ${lastEvent.code}`);
const value = await page.$eval('textarea', t => t.value);
expect(value).toBe('\n', `${JSON.stringify(key)} failed to create a newline: ${JSON.stringify(value)}`);
await page.$eval('textarea', t => t.value = '');
}
});
it('should throw on unknown keys', async({page, server}) => {
let error = await page.keyboard.press('NotARealKey').catch(e => e);
expect(error.message).toBe('Unknown key: "NotARealKey"');
error = await page.keyboard.press('ё').catch(e => e);
expect(error && error.message).toBe('Unknown key: "ё"');
error = await page.keyboard.press('😊').catch(e => e);
expect(error && error.message).toBe('Unknown key: "😊"');
});
it('should type emoji', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.type('textarea', '👹 Tokyo street Japan 🇯🇵');
expect(await page.$eval('textarea', textarea => textarea.value)).toBe('👹 Tokyo street Japan 🇯🇵');
});
it('should type emoji into an iframe', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'emoji-test', server.PREFIX + '/input/textarea.html');
const frame = page.frames()[1];
const textarea = await frame.$('textarea');
await textarea.type('👹 Tokyo street Japan 🇯🇵');
expect(await frame.$eval('textarea', textarea => textarea.value)).toBe('👹 Tokyo street Japan 🇯🇵');
});
it.fail(CHROMIUM && MAC)('should handle selectAll', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const textarea = await page.$('textarea');
await textarea.type('some text');
const modifier = MAC ? 'Meta' : 'Control';
await page.keyboard.down(modifier);
await page.keyboard.press('a');
await page.keyboard.up(modifier);
await page.keyboard.press('Backspace');
expect(await page.$eval('textarea', textarea => textarea.value)).toBe('');
});
it.fail(CHROMIUM && MAC)('should be able to prevent selectAll', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const textarea = await page.$('textarea');
await textarea.type('some text');
await page.$eval('textarea', textarea => {
textarea.addEventListener('keydown', event => {
if (event.key === 'a' && (event.metaKey || event.ctrlKey))
event.preventDefault();
}, false);
});
const modifier = MAC ? 'Meta' : 'Control';
await page.keyboard.down(modifier);
await page.keyboard.press('a');
await page.keyboard.up(modifier);
await page.keyboard.press('Backspace');
expect(await page.$eval('textarea', textarea => textarea.value)).toBe('some tex');
});
it('should press the meta key', async({page}) => {
await page.evaluate(() => {
window.result = null;
document.addEventListener('keydown', event => {
window.result = [event.key, event.code, event.metaKey];
});
});
await page.keyboard.press('Meta');
const [key, code, metaKey] = await page.evaluate('result');
if (FFOX && !MAC)
expect(key).toBe('OS');
else
expect(key).toBe('Meta');
if (FFOX)
expect(code).toBe('OSLeft');
else
expect(code).toBe('MetaLeft');
if (FFOX && !MAC)
expect(metaKey).toBe(false);
else
expect(metaKey).toBe(true);
});
it('should work after a cross origin navigation', async({page, server}) => {
await page.goto(server.PREFIX + '/empty.html');
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
await page.evaluate(() => {
document.addEventListener('keydown', event => window.lastKey = event);
})
await page.keyboard.press('a');
expect(await page.evaluate('lastKey.key')).toBe('a');
})
const text = 'Hello world. I am the text that was typed!';
await page.keyboard.type(text);
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe(text);
});
};
it('should move with the arrow keys', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.type('textarea', 'Hello World!');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!');
for (let i = 0; i < 'World!'.length; i++)
page.keyboard.press('ArrowLeft');
await page.keyboard.type('inserted ');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello inserted World!');
page.keyboard.down('Shift');
for (let i = 0; i < 'inserted '.length; i++)
page.keyboard.press('ArrowLeft');
page.keyboard.up('Shift');
await page.keyboard.press('Backspace');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!');
});
it('should send a character with ElementHandle.press', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const textarea = await page.$('textarea');
await textarea.press('a');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('a');
await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true));
await textarea.press('b');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('a');
});
it('should send a character with sendCharacter', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea');
await page.keyboard.insertText('嗨');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨');
await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true));
await page.keyboard.insertText('a');
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨a');
});
it('insertText should only emit input event', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea');
page.on('console', m => console.log(m.text()));
await page.evaluate(() => {
window.events = [];
document.addEventListener('keydown', e => events.push(e.type));
document.addEventListener('keyup', e => events.push(e.type));
document.addEventListener('keypress', e => events.push(e.type));
document.addEventListener('input', e => events.push(e.type));
});
await page.keyboard.insertText('hello world');
expect(await page.evaluate('window.events')).toEqual(['input']);
});
it.fail(FFOX)('should report shiftKey', async({page, server}) => {
await page.goto(server.PREFIX + '/input/keyboard.html');
const keyboard = page.keyboard;
const codeForKey = {'Shift': 16, 'Alt': 18, 'Control': 17};
for (const modifierKey in codeForKey) {
await keyboard.down(modifierKey);
expect(await page.evaluate(() => getResult())).toBe('Keydown: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' [' + modifierKey + ']');
await keyboard.down('!');
// Shift+! will generate a keypress
if (modifierKey === 'Shift')
expect(await page.evaluate(() => getResult())).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']\nKeypress: ! Digit1 33 33 [' + modifierKey + ']');
else
expect(await page.evaluate(() => getResult())).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']');
await keyboard.up('!');
expect(await page.evaluate(() => getResult())).toBe('Keyup: ! Digit1 49 [' + modifierKey + ']');
await keyboard.up(modifierKey);
expect(await page.evaluate(() => getResult())).toBe('Keyup: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' []');
}
});
it('should report multiple modifiers', async({page, server}) => {
await page.goto(server.PREFIX + '/input/keyboard.html');
const keyboard = page.keyboard;
await keyboard.down('Control');
expect(await page.evaluate(() => getResult())).toBe('Keydown: Control ControlLeft 17 [Control]');
await keyboard.down('Alt');
expect(await page.evaluate(() => getResult())).toBe('Keydown: Alt AltLeft 18 [Alt Control]');
await keyboard.down(';');
expect(await page.evaluate(() => getResult())).toBe('Keydown: ; Semicolon 186 [Alt Control]');
await keyboard.up(';');
expect(await page.evaluate(() => getResult())).toBe('Keyup: ; Semicolon 186 [Alt Control]');
await keyboard.up('Control');
expect(await page.evaluate(() => getResult())).toBe('Keyup: Control ControlLeft 17 [Alt]');
await keyboard.up('Alt');
expect(await page.evaluate(() => getResult())).toBe('Keyup: Alt AltLeft 18 []');
});
it('should send proper codes while typing', async({page, server}) => {
await page.goto(server.PREFIX + '/input/keyboard.html');
await page.keyboard.type('!');
expect(await page.evaluate(() => getResult())).toBe(
[ 'Keydown: ! Digit1 49 []',
'Keypress: ! Digit1 33 33 []',
'Keyup: ! Digit1 49 []'].join('\n'));
await page.keyboard.type('^');
expect(await page.evaluate(() => getResult())).toBe(
[ 'Keydown: ^ Digit6 54 []',
'Keypress: ^ Digit6 94 94 []',
'Keyup: ^ Digit6 54 []'].join('\n'));
});
it('should send proper codes while typing with shift', async({page, server}) => {
await page.goto(server.PREFIX + '/input/keyboard.html');
const keyboard = page.keyboard;
await keyboard.down('Shift');
await page.keyboard.type('~');
expect(await page.evaluate(() => getResult())).toBe(
[ 'Keydown: Shift ShiftLeft 16 [Shift]',
'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode
'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode
'Keyup: ~ Backquote 192 [Shift]'].join('\n'));
await keyboard.up('Shift');
});
it('should not type canceled events', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea');
await page.evaluate(() => {
window.addEventListener('keydown', event => {
event.stopPropagation();
event.stopImmediatePropagation();
if (event.key === 'l')
event.preventDefault();
if (event.key === 'o')
event.preventDefault();
}, false);
});
await page.keyboard.type('Hello World!');
expect(await page.evaluate(() => textarea.value)).toBe('He Wrd!');
});
it('should specify repeat property', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea');
await page.evaluate(() => document.querySelector('textarea').addEventListener('keydown', e => window.lastEvent = e, true));
await page.keyboard.down('a');
expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false);
await page.keyboard.press('a');
expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(true);
await page.keyboard.down('b');
expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false);
await page.keyboard.down('b');
expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(true);
await page.keyboard.up('a');
await page.keyboard.down('a');
expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false);
});
it('should type all kinds of characters', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea');
const text = 'This text goes onto two lines.\nThis character is 嗨.';
await page.keyboard.type(text);
expect(await page.evaluate('result')).toBe(text);
});
it('should specify location', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.evaluate(() => {
window.addEventListener('keydown', event => window.keyLocation = event.location, true);
});
const textarea = await page.$('textarea');
await textarea.press('Digit5');
expect(await page.evaluate('keyLocation')).toBe(0);
await textarea.press('ControlLeft');
expect(await page.evaluate('keyLocation')).toBe(1);
await textarea.press('ControlRight');
expect(await page.evaluate('keyLocation')).toBe(2);
await textarea.press('NumpadSubtract');
expect(await page.evaluate('keyLocation')).toBe(3);
});
it('should press Enter', async({page, server}) => {
await page.setContent('<textarea></textarea>');
await page.focus('textarea');
await page.evaluate(() => window.addEventListener('keydown', e => window.lastEvent = {key: e.key, code:e.code}));
await testEnterKey('Enter', 'Enter', 'Enter');
await testEnterKey('NumpadEnter', 'Enter', 'NumpadEnter');
await testEnterKey('\n', 'Enter', 'Enter');
await testEnterKey('\r', 'Enter', 'Enter');
async function testEnterKey(key, expectedKey, expectedCode) {
await page.keyboard.press(key);
const lastEvent = await page.evaluate('lastEvent');
expect(lastEvent.key).toBe(expectedKey, `${JSON.stringify(key)} had the wrong key: ${lastEvent.key}`);
expect(lastEvent.code).toBe(expectedCode, `${JSON.stringify(key)} had the wrong code: ${lastEvent.code}`);
const value = await page.$eval('textarea', t => t.value);
expect(value).toBe('\n', `${JSON.stringify(key)} failed to create a newline: ${JSON.stringify(value)}`);
await page.$eval('textarea', t => t.value = '');
}
});
it('should throw on unknown keys', async({page, server}) => {
let error = await page.keyboard.press('NotARealKey').catch(e => e);
expect(error.message).toBe('Unknown key: "NotARealKey"');
error = await page.keyboard.press('ё').catch(e => e);
expect(error && error.message).toBe('Unknown key: "ё"');
error = await page.keyboard.press('😊').catch(e => e);
expect(error && error.message).toBe('Unknown key: "😊"');
});
it('should type emoji', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.type('textarea', '👹 Tokyo street Japan 🇯🇵');
expect(await page.$eval('textarea', textarea => textarea.value)).toBe('👹 Tokyo street Japan 🇯🇵');
});
it('should type emoji into an iframe', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'emoji-test', server.PREFIX + '/input/textarea.html');
const frame = page.frames()[1];
const textarea = await frame.$('textarea');
await textarea.type('👹 Tokyo street Japan 🇯🇵');
expect(await frame.$eval('textarea', textarea => textarea.value)).toBe('👹 Tokyo street Japan 🇯🇵');
});
it.fail(CHROMIUM && MAC)('should handle selectAll', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const textarea = await page.$('textarea');
await textarea.type('some text');
const modifier = MAC ? 'Meta' : 'Control';
await page.keyboard.down(modifier);
await page.keyboard.press('a');
await page.keyboard.up(modifier);
await page.keyboard.press('Backspace');
expect(await page.$eval('textarea', textarea => textarea.value)).toBe('');
});
it.fail(CHROMIUM && MAC)('should be able to prevent selectAll', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const textarea = await page.$('textarea');
await textarea.type('some text');
await page.$eval('textarea', textarea => {
textarea.addEventListener('keydown', event => {
if (event.key === 'a' && (event.metaKey || event.ctrlKey))
event.preventDefault();
}, false);
});
const modifier = MAC ? 'Meta' : 'Control';
await page.keyboard.down(modifier);
await page.keyboard.press('a');
await page.keyboard.up(modifier);
await page.keyboard.press('Backspace');
expect(await page.$eval('textarea', textarea => textarea.value)).toBe('some tex');
});
it('should press the meta key', async({page}) => {
await page.evaluate(() => {
window.result = null;
document.addEventListener('keydown', event => {
window.result = [event.key, event.code, event.metaKey];
});
});
await page.keyboard.press('Meta');
const [key, code, metaKey] = await page.evaluate('result');
if (FFOX && !MAC)
expect(key).toBe('OS');
else
expect(key).toBe('Meta');
if (FFOX)
expect(code).toBe('OSLeft');
else
expect(code).toBe('MetaLeft');
if (FFOX && !MAC)
expect(metaKey).toBe(false);
else
expect(metaKey).toBe(true);
});
it('should work after a cross origin navigation', async({page, server}) => {
await page.goto(server.PREFIX + '/empty.html');
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
await page.evaluate(() => {
document.addEventListener('keydown', event => window.lastKey = event);
})
await page.keyboard.press('a');
expect(await page.evaluate('lastKey.key')).toBe('a');
})
});

View file

@ -18,333 +18,328 @@
const path = require('path');
const fs = require('fs');
const utils = require('./utils');
const { makeUserDataDir, removeUserDataDir } = require('./utils');
const { makeUserDataDir, removeUserDataDir } = utils;
const {FFOX, CHROMIUM, WEBKIT, WIN, defaultBrowserOptions, playwrightPath} = utils.testOptions(browserType);
/**
* @type {TestSuite}
*/
module.exports.describe = function({defaultBrowserOptions, playwright, browserType, playwrightPath, CHROMIUM, FFOX, WEBKIT, WIN}) {
describe('Playwright', function() {
describe('browserType.launch', function() {
it('should reject all promises when browser is closed', async() => {
const browser = await browserType.launch(defaultBrowserOptions);
const page = await (await browser.newContext()).newPage();
let error = null;
const neverResolves = page.evaluate(() => new Promise(r => {})).catch(e => error = e);
await browser.close();
await neverResolves;
expect(error.message).toContain('Protocol error');
});
it('should throw if userDataDir option is passed', async() => {
let waitError = null;
const options = Object.assign({}, defaultBrowserOptions, {userDataDir: 'random-path'});
await browserType.launch(options).catch(e => waitError = e);
expect(waitError.message).toContain('launchPersistentContext');
});
it('should throw if page argument is passed', async() => {
let waitError = null;
const options = Object.assign({}, defaultBrowserOptions, { args: ['http://example.com'] });
await browserType.launch(options).catch(e => waitError = e);
expect(waitError.message).toContain('can not specify page');
});
it('should reject if executable path is invalid', async({server}) => {
let waitError = null;
const options = Object.assign({}, defaultBrowserOptions, {executablePath: 'random-invalid-path'});
await browserType.launch(options).catch(e => waitError = e);
expect(waitError.message).toContain('Failed to launch');
});
});
describe('browserType.launchPersistentContext', function() {
it('should have default URL when launching browser', async function() {
const userDataDir = await makeUserDataDir();
const browserContext = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions);
const urls = browserContext.pages().map(page => page.url());
expect(urls).toEqual(['about:blank']);
await browserContext.close();
await removeUserDataDir(userDataDir);
});
it('should have custom URL when launching browser', async function({server}) {
const userDataDir = await makeUserDataDir();
const options = Object.assign({}, defaultBrowserOptions);
options.args = [server.EMPTY_PAGE].concat(options.args || []);
const browserContext = await browserType.launchPersistentContext(userDataDir, options);
const pages = browserContext.pages();
expect(pages.length).toBe(1);
const page = pages[0];
if (page.url() !== server.EMPTY_PAGE) {
await page.waitForNavigation();
}
expect(page.url()).toBe(server.EMPTY_PAGE);
await browserContext.close();
await removeUserDataDir(userDataDir);
});
});
describe('browserType.launchServer', function() {
it('should return child_process instance', async () => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
expect(browserServer.process().pid).toBeGreaterThan(0);
await browserServer.close();
});
it('should fire close event', async () => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
await Promise.all([
new Promise(f => browserServer.on('close', f)),
browserServer.close(),
]);
});
});
describe('browserType.executablePath', function() {
it('should work', async({server}) => {
const executablePath = browserType.executablePath();
expect(fs.existsSync(executablePath)).toBe(true);
expect(fs.realpathSync(executablePath)).toBe(executablePath);
});
});
describe('browserType.name', function() {
it('should work', async({server}) => {
if (WEBKIT)
expect(browserType.name()).toBe('webkit');
else if (FFOX)
expect(browserType.name()).toBe('firefox');
else if (CHROMIUM)
expect(browserType.name()).toBe('chromium');
else
throw new Error('Unknown browser');
});
});
});
describe('Top-level requires', function() {
it('should require top-level Errors', async() => {
const Errors = require(path.join(utils.projectRoot(), '/lib/errors.js'));
expect(Errors.TimeoutError).toBe(playwright.errors.TimeoutError);
});
it('should require top-level DeviceDescriptors', async() => {
const Devices = require(path.join(utils.projectRoot(), '/lib/deviceDescriptors.js')).DeviceDescriptors;
expect(Devices['iPhone 6']).toBeTruthy();
expect(Devices['iPhone 6']).toBe(playwright.devices['iPhone 6']);
expect(Devices['iPhone 6']).toBe(require(playwrightPath).devices['iPhone 6']);
});
});
describe('Browser.isConnected', () => {
it('should set the browser connected state', async () => {
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
expect(remote.isConnected()).toBe(true);
await remote.close();
expect(remote.isConnected()).toBe(false);
await browserServer._checkLeaks();
await browserServer.close();
});
it('should throw when used after isConnected returns false', async({server}) => {
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
await Promise.all([
browserServer.close(),
new Promise(f => remote.once('disconnected', f)),
]);
expect(remote.isConnected()).toBe(false);
const error = await page.evaluate('1 + 1').catch(e => e);
expect(error.message).toContain('has been closed');
});
});
describe('Browser.disconnect', function() {
it('should reject navigation when browser closes', async({server}) => {
server.setRoute('/one-style.css', () => {});
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(e => e);
await server.waitForRequest('/one-style.css');
await remote.close();
const error = await navigationPromise;
expect(error.message).toContain('Navigation failed because browser has disconnected!');
await browserServer._checkLeaks();
await browserServer.close();
});
it('should reject waitForSelector when browser closes', async({server}) => {
server.setRoute('/empty.html', () => {});
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e);
// Make sure the previous waitForSelector has time to make it to the browser before we disconnect.
await page.waitForSelector('body');
await remote.close();
const error = await watchdog;
expect(error.message).toContain('Protocol error');
await browserServer._checkLeaks();
await browserServer.close();
});
it('should throw if used after disconnect', async({server}) => {
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
await remote.close();
const error = await page.evaluate('1 + 1').catch(e => e);
expect(error.message).toContain('has been closed');
await browserServer._checkLeaks();
await browserServer.close();
});
it('should emit close events on pages and contexts', async({server}) => {
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const context = await remote.newContext();
const page = await context.newPage();
let pageClosed = false;
page.on('close', e => pageClosed = true);
await Promise.all([
new Promise(f => context.on('close', f)),
browserServer.close()
]);
expect(pageClosed).toBeTruthy();
});
});
describe('Browser.close', function() {
it('should terminate network waiters', async({server}) => {
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const newPage = await remote.newPage();
const results = await Promise.all([
newPage.waitForRequest(server.EMPTY_PAGE).catch(e => e),
newPage.waitForResponse(server.EMPTY_PAGE).catch(e => e),
browserServer.close()
]);
for (let i = 0; i < 2; i++) {
const message = results[i].message;
expect(message).toContain('Target closed');
expect(message).not.toContain('Timeout');
}
});
it('should fire close event for all contexts', async(state, test) => {
describe('Playwright', function() {
describe('browserType.launch', function() {
it('should reject all promises when browser is closed', async({browserType}) => {
const browser = await browserType.launch(defaultBrowserOptions);
const context = await browser.newContext();
let closed = false;
context.on('close', () => closed = true);
const page = await (await browser.newContext()).newPage();
let error = null;
const neverResolves = page.evaluate(() => new Promise(r => {})).catch(e => error = e);
await browser.close();
expect(closed).toBe(true);
await neverResolves;
expect(error.message).toContain('Protocol error');
});
});
describe('browserType.launch |webSocket| option', function() {
it('should support the webSocket option', async() => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const browserContext = await browser.newContext();
expect(browserContext.pages().length).toBe(0);
expect(browserServer.wsEndpoint()).not.toBe(null);
const page = await browserContext.newPage();
expect(await page.evaluate('11 * 11')).toBe(121);
await page.close();
await browser.close();
await browserServer._checkLeaks();
await browserServer.close();
it('should throw if userDataDir option is passed', async({browserType}) => {
let waitError = null;
const options = Object.assign({}, defaultBrowserOptions, {userDataDir: 'random-path'});
await browserType.launch(options).catch(e => waitError = e);
expect(waitError.message).toContain('launchPersistentContext');
});
it('should fire "disconnected" when closing with webSocket', async() => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve));
browserServer.kill();
await disconnectedEventPromise;
it('should throw if page argument is passed', async({browserType}) => {
let waitError = null;
const options = Object.assign({}, defaultBrowserOptions, { args: ['http://example.com'] });
await browserType.launch(options).catch(e => waitError = e);
expect(waitError.message).toContain('can not specify page');
});
});
describe('browserType.connect', function() {
it.slow()('should be able to reconnect to a browser', async({server}) => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
{
const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const browserContext = await browser.newContext();
const page = await browserContext.newPage();
await page.goto(server.EMPTY_PAGE);
await browser.close();
}
{
const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const browserContext = await browser.newContext();
const page = await browserContext.newPage();
await page.goto(server.EMPTY_PAGE);
await browser.close();
}
await browserServer._checkLeaks();
await browserServer.close();
it('should reject if executable path is invalid', async({browserType}) => {
let waitError = null;
const options = Object.assign({}, defaultBrowserOptions, {executablePath: 'random-invalid-path'});
await browserType.launch(options).catch(e => waitError = e);
expect(waitError.message).toContain('Failed to launch');
});
});
describe('browserType.launchPersistentContext', function() {
it('userDataDir option', async({server}) => {
it('should have default URL when launching browser', async ({browserType}) => {
const userDataDir = await makeUserDataDir();
const options = Object.assign(defaultBrowserOptions);
const browserContext = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions);
const urls = browserContext.pages().map(page => page.url());
expect(urls).toEqual(['about:blank']);
await browserContext.close();
await removeUserDataDir(userDataDir);
});
it('should have custom URL when launching browser', async ({browserType, server}) => {
const userDataDir = await makeUserDataDir();
const options = Object.assign({}, defaultBrowserOptions);
options.args = [server.EMPTY_PAGE].concat(options.args || []);
const browserContext = await browserType.launchPersistentContext(userDataDir, options);
// Open a page to make sure its functional.
await browserContext.newPage();
expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
const pages = browserContext.pages();
expect(pages.length).toBe(1);
const page = pages[0];
if (page.url() !== server.EMPTY_PAGE) {
await page.waitForNavigation();
}
expect(page.url()).toBe(server.EMPTY_PAGE);
await browserContext.close();
expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
// This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
await removeUserDataDir(userDataDir);
});
it.slow()('userDataDir option should restore state', async({server}) => {
const userDataDir = await makeUserDataDir();
const browserContext = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions);
const page = await browserContext.newPage();
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => localStorage.hey = 'hello');
await browserContext.close();
const browserContext2 = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions);
const page2 = await browserContext2.newPage();
await page2.goto(server.EMPTY_PAGE);
expect(await page2.evaluate(() => localStorage.hey)).toBe('hello');
await browserContext2.close();
const userDataDir2 = await makeUserDataDir();
const browserContext3 = await browserType.launchPersistentContext(userDataDir2, defaultBrowserOptions);
const page3 = await browserContext3.newPage();
await page3.goto(server.EMPTY_PAGE);
expect(await page3.evaluate(() => localStorage.hey)).not.toBe('hello');
await browserContext3.close();
// This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
await removeUserDataDir(userDataDir);
await removeUserDataDir(userDataDir2);
});
// See https://github.com/microsoft/playwright/issues/717
it.slow().fail(WIN && CHROMIUM)('userDataDir option should restore cookies', async({server}) => {
const userDataDir = await makeUserDataDir();
const browserContext = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions);
const page = await browserContext.newPage();
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => document.cookie = 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT');
await browserContext.close();
const browserContext2 = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions);
const page2 = await browserContext2.newPage();
await page2.goto(server.EMPTY_PAGE);
expect(await page2.evaluate(() => document.cookie)).toBe('doSomethingOnlyOnce=true');
await browserContext2.close();
const userDataDir2 = await makeUserDataDir();
const browserContext3 = await browserType.launchPersistentContext(userDataDir2, defaultBrowserOptions);
const page3 = await browserContext3.newPage();
await page3.goto(server.EMPTY_PAGE);
expect(await page3.evaluate(() => localStorage.hey)).not.toBe('doSomethingOnlyOnce=true');
await browserContext3.close();
// This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
await removeUserDataDir(userDataDir);
await removeUserDataDir(userDataDir2);
});
});
};
describe('browserType.launchServer', function() {
it('should return child_process instance', async ({browserType}) => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
expect(browserServer.process().pid).toBeGreaterThan(0);
await browserServer.close();
});
it('should fire close event', async ({browserType}) => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
await Promise.all([
new Promise(f => browserServer.on('close', f)),
browserServer.close(),
]);
});
});
describe('browserType.executablePath', function() {
it('should work', async({browserType}) => {
const executablePath = browserType.executablePath();
expect(fs.existsSync(executablePath)).toBe(true);
expect(fs.realpathSync(executablePath)).toBe(executablePath);
});
});
describe('browserType.name', function() {
it('should work', async({browserType}) => {
if (WEBKIT)
expect(browserType.name()).toBe('webkit');
else if (FFOX)
expect(browserType.name()).toBe('firefox');
else if (CHROMIUM)
expect(browserType.name()).toBe('chromium');
else
throw new Error('Unknown browser');
});
});
});
describe('Top-level requires', function() {
it('should require top-level Errors', async() => {
const Errors = require(path.join(utils.projectRoot(), '/lib/errors.js'));
expect(Errors.TimeoutError).toBe(playwright.errors.TimeoutError);
});
it('should require top-level DeviceDescriptors', async() => {
const Devices = require(path.join(utils.projectRoot(), '/lib/deviceDescriptors.js')).DeviceDescriptors;
expect(Devices['iPhone 6']).toBeTruthy();
expect(Devices['iPhone 6']).toBe(playwright.devices['iPhone 6']);
expect(Devices['iPhone 6']).toBe(require(playwrightPath).devices['iPhone 6']);
});
});
describe('Browser.isConnected', () => {
it('should set the browser connected state', async ({browserType}) => {
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
expect(remote.isConnected()).toBe(true);
await remote.close();
expect(remote.isConnected()).toBe(false);
await browserServer._checkLeaks();
await browserServer.close();
});
it('should throw when used after isConnected returns false', async({browserType}) => {
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
await Promise.all([
browserServer.close(),
new Promise(f => remote.once('disconnected', f)),
]);
expect(remote.isConnected()).toBe(false);
const error = await page.evaluate('1 + 1').catch(e => e);
expect(error.message).toContain('has been closed');
});
});
describe('Browser.disconnect', function() {
it('should reject navigation when browser closes', async({browserType, server}) => {
server.setRoute('/one-style.css', () => {});
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(e => e);
await server.waitForRequest('/one-style.css');
await remote.close();
const error = await navigationPromise;
expect(error.message).toContain('Navigation failed because browser has disconnected!');
await browserServer._checkLeaks();
await browserServer.close();
});
it('should reject waitForSelector when browser closes', async({browserType, server}) => {
server.setRoute('/empty.html', () => {});
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e);
// Make sure the previous waitForSelector has time to make it to the browser before we disconnect.
await page.waitForSelector('body');
await remote.close();
const error = await watchdog;
expect(error.message).toContain('Protocol error');
await browserServer._checkLeaks();
await browserServer.close();
});
it('should throw if used after disconnect', async({browserType}) => {
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
await remote.close();
const error = await page.evaluate('1 + 1').catch(e => e);
expect(error.message).toContain('has been closed');
await browserServer._checkLeaks();
await browserServer.close();
});
it('should emit close events on pages and contexts', async({browserType}) => {
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const context = await remote.newContext();
const page = await context.newPage();
let pageClosed = false;
page.on('close', e => pageClosed = true);
await Promise.all([
new Promise(f => context.on('close', f)),
browserServer.close()
]);
expect(pageClosed).toBeTruthy();
});
});
describe('Browser.close', function() {
it('should terminate network waiters', async({browserType, server}) => {
const browserServer = await browserType.launchServer({...defaultBrowserOptions });
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const newPage = await remote.newPage();
const results = await Promise.all([
newPage.waitForRequest(server.EMPTY_PAGE).catch(e => e),
newPage.waitForResponse(server.EMPTY_PAGE).catch(e => e),
browserServer.close()
]);
for (let i = 0; i < 2; i++) {
const message = results[i].message;
expect(message).toContain('Target closed');
expect(message).not.toContain('Timeout');
}
});
it('should fire close event for all contexts', async({browserType}) => {
const browser = await browserType.launch(defaultBrowserOptions);
const context = await browser.newContext();
let closed = false;
context.on('close', () => closed = true);
await browser.close();
expect(closed).toBe(true);
});
});
describe('browserType.launch |webSocket| option', function() {
it('should support the webSocket option', async({browserType}) => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const browserContext = await browser.newContext();
expect(browserContext.pages().length).toBe(0);
expect(browserServer.wsEndpoint()).not.toBe(null);
const page = await browserContext.newPage();
expect(await page.evaluate('11 * 11')).toBe(121);
await page.close();
await browser.close();
await browserServer._checkLeaks();
await browserServer.close();
});
it('should fire "disconnected" when closing with webSocket', async({browserType}) => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve));
browserServer.kill();
await disconnectedEventPromise;
});
});
describe('browserType.connect', function() {
it.slow()('should be able to reconnect to a browser', async({browserType, server}) => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
{
const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const browserContext = await browser.newContext();
const page = await browserContext.newPage();
await page.goto(server.EMPTY_PAGE);
await browser.close();
}
{
const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const browserContext = await browser.newContext();
const page = await browserContext.newPage();
await page.goto(server.EMPTY_PAGE);
await browser.close();
}
await browserServer._checkLeaks();
await browserServer.close();
});
});
describe('browserType.launchPersistentContext', function() {
it('userDataDir option', async({browserType}) => {
const userDataDir = await makeUserDataDir();
const options = Object.assign(defaultBrowserOptions);
const browserContext = await browserType.launchPersistentContext(userDataDir, options);
// Open a page to make sure its functional.
await browserContext.newPage();
expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
await browserContext.close();
expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
// This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
await removeUserDataDir(userDataDir);
});
it.slow()('userDataDir option should restore state', async({browserType, server}) => {
const userDataDir = await makeUserDataDir();
const browserContext = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions);
const page = await browserContext.newPage();
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => localStorage.hey = 'hello');
await browserContext.close();
const browserContext2 = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions);
const page2 = await browserContext2.newPage();
await page2.goto(server.EMPTY_PAGE);
expect(await page2.evaluate(() => localStorage.hey)).toBe('hello');
await browserContext2.close();
const userDataDir2 = await makeUserDataDir();
const browserContext3 = await browserType.launchPersistentContext(userDataDir2, defaultBrowserOptions);
const page3 = await browserContext3.newPage();
await page3.goto(server.EMPTY_PAGE);
expect(await page3.evaluate(() => localStorage.hey)).not.toBe('hello');
await browserContext3.close();
// This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
await removeUserDataDir(userDataDir);
await removeUserDataDir(userDataDir2);
});
// See https://github.com/microsoft/playwright/issues/717
it.slow().fail(WIN && CHROMIUM)('userDataDir option should restore cookies', async({browserType, server}) => {
const userDataDir = await makeUserDataDir();
const browserContext = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions);
const page = await browserContext.newPage();
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => document.cookie = 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT');
await browserContext.close();
const browserContext2 = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions);
const page2 = await browserContext2.newPage();
await page2.goto(server.EMPTY_PAGE);
expect(await page2.evaluate(() => document.cookie)).toBe('doSomethingOnlyOnce=true');
await browserContext2.close();
const userDataDir2 = await makeUserDataDir();
const browserContext3 = await browserType.launchPersistentContext(userDataDir2, defaultBrowserOptions);
const page3 = await browserContext3.newPage();
await page3.goto(server.EMPTY_PAGE);
expect(await page3.evaluate(() => localStorage.hey)).not.toBe('doSomethingOnlyOnce=true');
await browserContext3.close();
// This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
await removeUserDataDir(userDataDir);
await removeUserDataDir(userDataDir2);
});
});

View file

@ -15,6 +15,8 @@
* limitations under the License.
*/
const {FFOX, CHROMIUM, WEBKIT, MAC} = require('./utils').testOptions(browserType);
function dimensions() {
const rect = document.querySelector('textarea').getBoundingClientRect();
return {
@ -25,135 +27,129 @@ function dimensions() {
};
}
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({FFOX, CHROMIUM, WEBKIT, MAC}) {
describe('Mouse', function() {
it('should click the document', async({page, server}) => {
await page.evaluate(() => {
window.clickPromise = new Promise(resolve => {
document.addEventListener('click', event => {
resolve({
type: event.type,
detail: event.detail,
clientX: event.clientX,
clientY: event.clientY,
isTrusted: event.isTrusted,
button: event.button
});
describe('Mouse', function() {
it('should click the document', async({page, server}) => {
await page.evaluate(() => {
window.clickPromise = new Promise(resolve => {
document.addEventListener('click', event => {
resolve({
type: event.type,
detail: event.detail,
clientX: event.clientX,
clientY: event.clientY,
isTrusted: event.isTrusted,
button: event.button
});
});
});
await page.mouse.click(50, 60);
const event = await page.evaluate(() => window.clickPromise);
expect(event.type).toBe('click');
expect(event.detail).toBe(1);
expect(event.clientX).toBe(50);
expect(event.clientY).toBe(60);
expect(event.isTrusted).toBe(true);
expect(event.button).toBe(0);
});
it('should select the text with mouse', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea');
const text = 'This is the text that we are going to try to select. Let\'s see how it goes.';
await page.keyboard.type(text);
// Firefox needs an extra frame here after typing or it will fail to set the scrollTop
await page.evaluate(() => new Promise(requestAnimationFrame));
await page.evaluate(() => document.querySelector('textarea').scrollTop = 0);
const {x, y} = await page.evaluate(dimensions);
await page.mouse.move(x + 2,y + 2);
await page.mouse.down();
await page.mouse.move(200,200);
await page.mouse.up();
expect(await page.evaluate(() => {
const textarea = document.querySelector('textarea');
return textarea.value.substring(textarea.selectionStart, textarea.selectionEnd);
})).toBe(text);
});
it('should trigger hover state', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.hover('#button-6');
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
await page.hover('#button-2');
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-2');
await page.hover('#button-91');
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-91');
});
it('should trigger hover state with removed window.Node', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() => delete window.Node);
await page.hover('#button-6');
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
});
it('should set modifier keys on click', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() => document.querySelector('#button-3').addEventListener('mousedown', e => window.lastEvent = e, true));
const modifiers = {'Shift': 'shiftKey', 'Control': 'ctrlKey', 'Alt': 'altKey', 'Meta': 'metaKey'};
// In Firefox, the Meta modifier only exists on Mac
if (FFOX && !MAC)
delete modifiers['Meta'];
for (const modifier in modifiers) {
await page.keyboard.down(modifier);
await page.click('#button-3');
if (!(await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier])))
throw new Error(modifiers[modifier] + ' should be true');
await page.keyboard.up(modifier);
}
await page.click('#button-3');
for (const modifier in modifiers) {
if ((await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier])))
throw new Error(modifiers[modifier] + ' should be false');
}
});
it('should tween mouse movement', async({page, server}) => {
// The test becomes flaky on WebKit without next line.
if (WEBKIT)
await page.evaluate(() => new Promise(requestAnimationFrame));
await page.mouse.move(100, 100);
await page.evaluate(() => {
window.result = [];
document.addEventListener('mousemove', event => {
window.result.push([event.clientX, event.clientY]);
});
});
await page.mouse.move(200, 300, {steps: 5});
expect(await page.evaluate('result')).toEqual([
[120, 140],
[140, 180],
[160, 220],
[180, 260],
[200, 300]
]);
});
it.skip(FFOX)('should work with mobile viewports and cross process navigations', async({browser, server}) => {
// @see https://crbug.com/929806
const context = await browser.newContext({ viewport: {width: 360, height: 640, isMobile: true} });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.goto(server.CROSS_PROCESS_PREFIX + '/mobile.html');
await page.evaluate(() => {
document.addEventListener('click', event => {
window.result = {x: event.clientX, y: event.clientY};
});
});
await page.mouse.click(30, 40);
expect(await page.evaluate('result')).toEqual({x: 30, y: 40});
await context.close();
});
xdescribe('Drag and Drop', function() {
it('should work', async({server, page}) => {
await page.goto(server.PREFIX + '/drag-n-drop.html');
await page.hover('#source');
await page.mouse.down();
await page.hover('#target');
await page.mouse.up();
expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true, 'could not find source in target');
})
});
await page.mouse.click(50, 60);
const event = await page.evaluate(() => window.clickPromise);
expect(event.type).toBe('click');
expect(event.detail).toBe(1);
expect(event.clientX).toBe(50);
expect(event.clientY).toBe(60);
expect(event.isTrusted).toBe(true);
expect(event.button).toBe(0);
});
};
it('should select the text with mouse', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.focus('textarea');
const text = 'This is the text that we are going to try to select. Let\'s see how it goes.';
await page.keyboard.type(text);
// Firefox needs an extra frame here after typing or it will fail to set the scrollTop
await page.evaluate(() => new Promise(requestAnimationFrame));
await page.evaluate(() => document.querySelector('textarea').scrollTop = 0);
const {x, y} = await page.evaluate(dimensions);
await page.mouse.move(x + 2,y + 2);
await page.mouse.down();
await page.mouse.move(200,200);
await page.mouse.up();
expect(await page.evaluate(() => {
const textarea = document.querySelector('textarea');
return textarea.value.substring(textarea.selectionStart, textarea.selectionEnd);
})).toBe(text);
});
it('should trigger hover state', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.hover('#button-6');
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
await page.hover('#button-2');
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-2');
await page.hover('#button-91');
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-91');
});
it('should trigger hover state with removed window.Node', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() => delete window.Node);
await page.hover('#button-6');
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
});
it('should set modifier keys on click', async({page, server}) => {
await page.goto(server.PREFIX + '/input/scrollable.html');
await page.evaluate(() => document.querySelector('#button-3').addEventListener('mousedown', e => window.lastEvent = e, true));
const modifiers = {'Shift': 'shiftKey', 'Control': 'ctrlKey', 'Alt': 'altKey', 'Meta': 'metaKey'};
// In Firefox, the Meta modifier only exists on Mac
if (FFOX && !MAC)
delete modifiers['Meta'];
for (const modifier in modifiers) {
await page.keyboard.down(modifier);
await page.click('#button-3');
if (!(await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier])))
throw new Error(modifiers[modifier] + ' should be true');
await page.keyboard.up(modifier);
}
await page.click('#button-3');
for (const modifier in modifiers) {
if ((await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier])))
throw new Error(modifiers[modifier] + ' should be false');
}
});
it('should tween mouse movement', async({page, server}) => {
// The test becomes flaky on WebKit without next line.
if (WEBKIT)
await page.evaluate(() => new Promise(requestAnimationFrame));
await page.mouse.move(100, 100);
await page.evaluate(() => {
window.result = [];
document.addEventListener('mousemove', event => {
window.result.push([event.clientX, event.clientY]);
});
});
await page.mouse.move(200, 300, {steps: 5});
expect(await page.evaluate('result')).toEqual([
[120, 140],
[140, 180],
[160, 220],
[180, 260],
[200, 300]
]);
});
it.skip(FFOX)('should work with mobile viewports and cross process navigations', async({browser, server}) => {
// @see https://crbug.com/929806
const context = await browser.newContext({ viewport: {width: 360, height: 640, isMobile: true} });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.goto(server.CROSS_PROCESS_PREFIX + '/mobile.html');
await page.evaluate(() => {
document.addEventListener('click', event => {
window.result = {x: event.clientX, y: event.clientY};
});
});
await page.mouse.click(30, 40);
expect(await page.evaluate('result')).toEqual({x: 30, y: 40});
await context.close();
});
xdescribe('Drag and Drop', function() {
it('should work', async({server, page}) => {
await page.goto(server.PREFIX + '/drag-n-drop.html');
await page.hover('#source');
await page.mouse.down();
await page.hover('#target');
await page.mouse.up();
expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true, 'could not find source in target');
})
});
});

View file

@ -15,101 +15,95 @@
* limitations under the License.
*/
const utils = require('./utils');
const {FFOX, CHROMIUM, WEBKIT, defaultBrowserOptions} = require('./utils').testOptions(browserType);
/**
* @type {TestSuite}
*/
module.exports.describe = function({defaultBrowserOptions, browserType, FFOX, CHROMIUM, WEBKIT}) {
describe('BrowserContext', function() {
it('should work across sessions', async ({browserType}) => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
const browser1 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
expect(browser1.contexts().length).toBe(0);
await browser1.newContext();
expect(browser1.contexts().length).toBe(1);
describe('BrowserContext', function() {
it('should work across sessions', async () => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
const browser1 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
expect(browser1.contexts().length).toBe(0);
await browser1.newContext();
expect(browser1.contexts().length).toBe(1);
const browser2 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
expect(browser2.contexts().length).toBe(0);
await browser2.newContext();
expect(browser2.contexts().length).toBe(1);
const browser2 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
expect(browser2.contexts().length).toBe(0);
await browser2.newContext();
expect(browser2.contexts().length).toBe(1);
expect(browser1.contexts().length).toBe(1);
expect(browser1.contexts().length).toBe(1);
await browser1.close();
await browser2.close();
await browser1.close();
await browser2.close();
await browserServer._checkLeaks();
await browserServer.close();
});
await browserServer._checkLeaks();
await browserServer.close();
});
});
describe('Browser.Events.disconnected', function() {
it.slow()('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async () => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
const originalBrowser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const wsEndpoint = browserServer.wsEndpoint();
const remoteBrowser1 = await browserType.connect({ wsEndpoint });
const remoteBrowser2 = await browserType.connect({ wsEndpoint });
describe('Browser.Events.disconnected', function() {
it.slow()('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async ({browserType}) => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
const originalBrowser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const wsEndpoint = browserServer.wsEndpoint();
const remoteBrowser1 = await browserType.connect({ wsEndpoint });
const remoteBrowser2 = await browserType.connect({ wsEndpoint });
let disconnectedOriginal = 0;
let disconnectedRemote1 = 0;
let disconnectedRemote2 = 0;
originalBrowser.on('disconnected', () => ++disconnectedOriginal);
remoteBrowser1.on('disconnected', () => ++disconnectedRemote1);
remoteBrowser2.on('disconnected', () => ++disconnectedRemote2);
let disconnectedOriginal = 0;
let disconnectedRemote1 = 0;
let disconnectedRemote2 = 0;
originalBrowser.on('disconnected', () => ++disconnectedOriginal);
remoteBrowser1.on('disconnected', () => ++disconnectedRemote1);
remoteBrowser2.on('disconnected', () => ++disconnectedRemote2);
await Promise.all([
new Promise(f => remoteBrowser2.on('disconnected', f)),
remoteBrowser2.close(),
]);
await Promise.all([
new Promise(f => remoteBrowser2.on('disconnected', f)),
remoteBrowser2.close(),
]);
expect(disconnectedOriginal).toBe(0);
expect(disconnectedRemote1).toBe(0);
expect(disconnectedRemote2).toBe(1);
expect(disconnectedOriginal).toBe(0);
expect(disconnectedRemote1).toBe(0);
expect(disconnectedRemote2).toBe(1);
await Promise.all([
new Promise(f => remoteBrowser1.on('disconnected', f)),
new Promise(f => originalBrowser.on('disconnected', f)),
browserServer.close(),
]);
await Promise.all([
new Promise(f => remoteBrowser1.on('disconnected', f)),
new Promise(f => originalBrowser.on('disconnected', f)),
browserServer.close(),
]);
expect(disconnectedOriginal).toBe(1);
expect(disconnectedRemote1).toBe(1);
expect(disconnectedRemote2).toBe(1);
});
expect(disconnectedOriginal).toBe(1);
expect(disconnectedRemote1).toBe(1);
expect(disconnectedRemote2).toBe(1);
});
});
describe('browserType.connect', function() {
it('should be able to connect multiple times to the same browser', async({server}) => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
const browser1 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const browser2 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page1 = await browser1.newPage();
expect(await page1.evaluate(() => 7 * 8)).toBe(56);
browser1.close();
describe('browserType.connect', function() {
it('should be able to connect multiple times to the same browser', async({browserType}) => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
const browser1 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const browser2 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page1 = await browser1.newPage();
expect(await page1.evaluate(() => 7 * 8)).toBe(56);
browser1.close();
const page2 = await browser2.newPage();
expect(await page2.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work');
await browser2.close();
await browserServer._checkLeaks();
await browserServer.close();
});
it('should not be able to close remote browser', async() => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
{
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
await remote.newContext();
await remote.close();
}
{
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
await remote.newContext();
await remote.close();
}
await browserServer._checkLeaks();
await browserServer.close();
});
const page2 = await browser2.newPage();
expect(await page2.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work');
await browser2.close();
await browserServer._checkLeaks();
await browserServer.close();
});
};
it('should not be able to close remote browser', async({browserType}) => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
{
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
await remote.newContext();
await remote.close();
}
{
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
await remote.newContext();
await remote.close();
}
await browserServer._checkLeaks();
await browserServer.close();
});
});

File diff suppressed because it is too large Load diff

View file

@ -18,355 +18,350 @@
const fs = require('fs');
const path = require('path');
const utils = require('./utils');
const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = utils.testOptions(browserType);
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({MAC, WIN, FFOX, CHROMIUM, WEBKIT}) {
describe('Page.Events.Request', function() {
it('should fire for navigation requests', async({page, server}) => {
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1);
});
it('should fire for iframes', async({page, server}) => {
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(requests.length).toBe(2);
});
it('should fire for fetches', async({page, server}) => {
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => fetch('/empty.html'));
expect(requests.length).toBe(2);
});
});
describe('Page.Events.Request', function() {
it('should fire for navigation requests', async({page, server}) => {
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1);
describe('Request.frame', function() {
it('should work for main frame navigation request', async({page, server}) => {
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.mainFrame());
});
it('should work for subframe navigation request', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const requests = [];
page.on('request', request => requests.push(request));
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.frames()[1]);
});
it('should work for fetch requests', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
let requests = [];
page.on('request', request => requests.push(request));
await page.evaluate(() => fetch('/digits/1.png'));
requests = requests.filter(request => !request.url().includes('favicon'));
expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.mainFrame());
});
});
describe('Request.headers', function() {
it('should work', async({page, server}) => {
const response = await page.goto(server.EMPTY_PAGE);
if (CHROMIUM)
expect(response.request().headers()['user-agent']).toContain('Chrome');
else if (FFOX)
expect(response.request().headers()['user-agent']).toContain('Firefox');
else if (WEBKIT)
expect(response.request().headers()['user-agent']).toContain('WebKit');
});
});
describe('Response.headers', function() {
it('should work', async({page, server}) => {
server.setRoute('/empty.html', (req, res) => {
res.setHeader('foo', 'bar');
res.end();
});
it('should fire for iframes', async({page, server}) => {
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(requests.length).toBe(2);
const response = await page.goto(server.EMPTY_PAGE);
expect(response.headers()['foo']).toBe('bar');
});
});
describe('Request.postData', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
server.setRoute('/post', (req, res) => res.end());
let request = null;
page.on('request', r => request = r);
await page.evaluate(() => fetch('./post', { method: 'POST', body: JSON.stringify({foo: 'bar'})}));
expect(request).toBeTruthy();
expect(request.postData()).toBe('{"foo":"bar"}');
});
it('should be |undefined| when there is no post data', async({page, server}) => {
const response = await page.goto(server.EMPTY_PAGE);
expect(response.request().postData()).toBe(null);
});
});
describe('Response.text', function() {
it('should work', async({page, server}) => {
const response = await page.goto(server.PREFIX + '/simple.json');
expect(await response.text()).toBe('{"foo": "bar"}\n');
});
it('should return uncompressed text', async({page, server}) => {
server.enableGzip('/simple.json');
const response = await page.goto(server.PREFIX + '/simple.json');
expect(response.headers()['content-encoding']).toBe('gzip');
expect(await response.text()).toBe('{"foo": "bar"}\n');
});
it('should throw when requesting body of redirected response', async({page, server}) => {
server.setRedirect('/foo.html', '/empty.html');
const response = await page.goto(server.PREFIX + '/foo.html');
const redirectedFrom = response.request().redirectedFrom();
expect(redirectedFrom).toBeTruthy();
const redirected = await redirectedFrom.response();
expect(redirected.status()).toBe(302);
let error = null;
await redirected.text().catch(e => error = e);
expect(error.message).toContain('Response body is unavailable for redirect responses');
});
it('should wait until response completes', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
// Setup server to trap request.
let serverResponse = null;
server.setRoute('/get', (req, res) => {
serverResponse = res;
// In Firefox, |fetch| will be hanging until it receives |Content-Type| header
// from server.
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.write('hello ');
});
it('should fire for fetches', async({page, server}) => {
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => fetch('/empty.html'));
expect(requests.length).toBe(2);
// Setup page to trap response.
let requestFinished = false;
page.on('requestfinished', r => requestFinished = requestFinished || r.url().includes('/get'));
// send request and wait for server response
const [pageResponse] = await Promise.all([
page.waitForEvent('response'),
page.evaluate(() => fetch('./get', { method: 'GET'})),
server.waitForRequest('/get'),
]);
expect(serverResponse).toBeTruthy();
expect(pageResponse).toBeTruthy();
expect(pageResponse.status()).toBe(200);
expect(requestFinished).toBe(false);
const responseText = pageResponse.text();
// Write part of the response and wait for it to be flushed.
await new Promise(x => serverResponse.write('wor', x));
// Finish response.
await new Promise(x => serverResponse.end('ld!', x));
expect(await responseText).toBe('hello world!');
});
});
describe('Response.json', function() {
it('should work', async({page, server}) => {
const response = await page.goto(server.PREFIX + '/simple.json');
expect(await response.json()).toEqual({foo: 'bar'});
});
});
describe('Response.body', function() {
it('should work', async({page, server}) => {
const response = await page.goto(server.PREFIX + '/pptr.png');
const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png'));
const responseBuffer = await response.body();
expect(responseBuffer.equals(imageBuffer)).toBe(true);
});
it('should work with compression', async({page, server}) => {
server.enableGzip('/pptr.png');
const response = await page.goto(server.PREFIX + '/pptr.png');
const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png'));
const responseBuffer = await response.body();
expect(responseBuffer.equals(imageBuffer)).toBe(true);
});
});
describe('Response.statusText', function() {
it('should work', async({page, server}) => {
server.setRoute('/cool', (req, res) => {
res.writeHead(200, 'cool!');
res.end();
});
const response = await page.goto(server.PREFIX + '/cool');
expect(response.statusText()).toBe('cool!');
});
});
describe('Network Events', function() {
it('Page.Events.Request', async({page, server}) => {
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE);
expect(requests[0].resourceType()).toBe('document');
expect(requests[0].method()).toBe('GET');
expect(await requests[0].response()).toBeTruthy();
expect(requests[0].frame() === page.mainFrame()).toBe(true);
expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE);
});
it('Page.Events.Response', async({page, server}) => {
const responses = [];
page.on('response', response => responses.push(response));
await page.goto(server.EMPTY_PAGE);
expect(responses.length).toBe(1);
expect(responses[0].url()).toBe(server.EMPTY_PAGE);
expect(responses[0].status()).toBe(200);
expect(responses[0].ok()).toBe(true);
expect(responses[0].request()).toBeTruthy();
});
describe('Request.frame', function() {
it('should work for main frame navigation request', async({page, server}) => {
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.mainFrame());
});
it('should work for subframe navigation request', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const requests = [];
page.on('request', request => requests.push(request));
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.frames()[1]);
});
it('should work for fetch requests', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
let requests = [];
page.on('request', request => requests.push(request));
await page.evaluate(() => fetch('/digits/1.png'));
requests = requests.filter(request => !request.url().includes('favicon'));
expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.mainFrame());
it.fail(FFOX)('Page.Events.RequestFailed', async({page, server}) => {
server.setRoute('/one-style.css', (req, res) => {
req.socket.write('deadbeef');
req.socket.end();
});
const failedRequests = [];
page.on('requestfailed', request => failedRequests.push(request));
await page.goto(server.PREFIX + '/one-style.html');
expect(failedRequests.length).toBe(1);
expect(failedRequests[0].url()).toContain('one-style.css');
expect(await failedRequests[0].response()).toBe(null);
expect(failedRequests[0].resourceType()).toBe('stylesheet');
if (CHROMIUM) {
expect(failedRequests[0].failure().errorText).toBe('net::ERR_INVALID_HTTP_RESPONSE');
} else if (WEBKIT) {
if (MAC)
expect(failedRequests[0].failure().errorText).toBe('The network connection was lost.');
else if (WIN)
expect(failedRequests[0].failure().errorText).toBe('Unsupported protocol');
else
expect(failedRequests[0].failure().errorText).toBe('Message Corrupt');
} else {
expect(failedRequests[0].failure().errorText).toBe('NS_ERROR_FAILURE');
}
expect(failedRequests[0].frame()).toBeTruthy();
});
describe('Request.headers', function() {
it('should work', async({page, server}) => {
const response = await page.goto(server.EMPTY_PAGE);
if (CHROMIUM)
expect(response.request().headers()['user-agent']).toContain('Chrome');
else if (FFOX)
expect(response.request().headers()['user-agent']).toContain('Firefox');
else if (WEBKIT)
expect(response.request().headers()['user-agent']).toContain('WebKit');
});
it('Page.Events.RequestFinished', async({page, server}) => {
const [response] = await Promise.all([
page.goto(server.EMPTY_PAGE),
page.waitForEvent('requestfinished')
]);
const request = response.request();
expect(request.url()).toBe(server.EMPTY_PAGE);
expect(await request.response()).toBeTruthy();
expect(request.frame() === page.mainFrame()).toBe(true);
expect(request.frame().url()).toBe(server.EMPTY_PAGE);
});
describe('Response.headers', function() {
it('should work', async({page, server}) => {
server.setRoute('/empty.html', (req, res) => {
res.setHeader('foo', 'bar');
res.end();
});
const response = await page.goto(server.EMPTY_PAGE);
expect(response.headers()['foo']).toBe('bar');
});
it('should fire events in proper order', async({page, server}) => {
const events = [];
page.on('request', request => events.push('request'));
page.on('response', response => events.push('response'));
const response = await page.goto(server.EMPTY_PAGE);
await response.finished();
events.push('requestfinished')
expect(events).toEqual(['request', 'response', 'requestfinished']);
});
describe('Request.postData', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
server.setRoute('/post', (req, res) => res.end());
let request = null;
page.on('request', r => request = r);
await page.evaluate(() => fetch('./post', { method: 'POST', body: JSON.stringify({foo: 'bar'})}));
expect(request).toBeTruthy();
expect(request.postData()).toBe('{"foo":"bar"}');
});
it('should be |undefined| when there is no post data', async({page, server}) => {
const response = await page.goto(server.EMPTY_PAGE);
expect(response.request().postData()).toBe(null);
});
it('should support redirects', async({page, server}) => {
const events = [];
page.on('request', request => events.push(`${request.method()} ${request.url()}`));
page.on('response', response => events.push(`${response.status()} ${response.url()}`));
page.on('requestfinished', request => events.push(`DONE ${request.url()}`));
page.on('requestfailed', request => events.push(`FAIL ${request.url()}`));
server.setRedirect('/foo.html', '/empty.html');
const FOO_URL = server.PREFIX + '/foo.html';
const response = await page.goto(FOO_URL);
await response.finished();
expect(events).toEqual([
`GET ${FOO_URL}`,
`302 ${FOO_URL}`,
`DONE ${FOO_URL}`,
`GET ${server.EMPTY_PAGE}`,
`200 ${server.EMPTY_PAGE}`,
`DONE ${server.EMPTY_PAGE}`
]);
const redirectedFrom = response.request().redirectedFrom();
expect(redirectedFrom.url()).toContain('/foo.html');
expect(redirectedFrom.redirectedFrom()).toBe(null);
expect(redirectedFrom.redirectedTo()).toBe(response.request());
});
});
describe('Response.text', function() {
it('should work', async({page, server}) => {
const response = await page.goto(server.PREFIX + '/simple.json');
expect(await response.text()).toBe('{"foo": "bar"}\n');
});
it('should return uncompressed text', async({page, server}) => {
server.enableGzip('/simple.json');
const response = await page.goto(server.PREFIX + '/simple.json');
expect(response.headers()['content-encoding']).toBe('gzip');
expect(await response.text()).toBe('{"foo": "bar"}\n');
});
it('should throw when requesting body of redirected response', async({page, server}) => {
server.setRedirect('/foo.html', '/empty.html');
const response = await page.goto(server.PREFIX + '/foo.html');
const redirectedFrom = response.request().redirectedFrom();
expect(redirectedFrom).toBeTruthy();
const redirected = await redirectedFrom.response();
expect(redirected.status()).toBe(302);
let error = null;
await redirected.text().catch(e => error = e);
expect(error.message).toContain('Response body is unavailable for redirect responses');
});
it('should wait until response completes', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
// Setup server to trap request.
let serverResponse = null;
server.setRoute('/get', (req, res) => {
serverResponse = res;
// In Firefox, |fetch| will be hanging until it receives |Content-Type| header
// from server.
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.write('hello ');
});
// Setup page to trap response.
let requestFinished = false;
page.on('requestfinished', r => requestFinished = requestFinished || r.url().includes('/get'));
// send request and wait for server response
const [pageResponse] = await Promise.all([
page.waitForEvent('response'),
page.evaluate(() => fetch('./get', { method: 'GET'})),
server.waitForRequest('/get'),
]);
expect(serverResponse).toBeTruthy();
expect(pageResponse).toBeTruthy();
expect(pageResponse.status()).toBe(200);
expect(requestFinished).toBe(false);
const responseText = pageResponse.text();
// Write part of the response and wait for it to be flushed.
await new Promise(x => serverResponse.write('wor', x));
// Finish response.
await new Promise(x => serverResponse.end('ld!', x));
expect(await responseText).toBe('hello world!');
});
describe('Request.isNavigationRequest', () => {
it('should work', async({page, server}) => {
const requests = new Map();
page.on('request', request => requests.set(request.url().split('/').pop(), request));
server.setRedirect('/rrredirect', '/frames/one-frame.html');
await page.goto(server.PREFIX + '/rrredirect');
expect(requests.get('rrredirect').isNavigationRequest()).toBe(true);
expect(requests.get('one-frame.html').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('Response.json', function() {
it('should work', async({page, server}) => {
const response = await page.goto(server.PREFIX + '/simple.json');
expect(await response.json()).toEqual({foo: 'bar'});
});
it('should work when navigating to image', async({page, server}) => {
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.PREFIX + '/pptr.png');
expect(requests[0].isNavigationRequest()).toBe(true);
});
});
describe('Response.body', function() {
it('should work', async({page, server}) => {
const response = await page.goto(server.PREFIX + '/pptr.png');
const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png'));
const responseBuffer = await response.body();
expect(responseBuffer.equals(imageBuffer)).toBe(true);
});
it('should work with compression', async({page, server}) => {
server.enableGzip('/pptr.png');
const response = await page.goto(server.PREFIX + '/pptr.png');
const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png'));
const responseBuffer = await response.body();
expect(responseBuffer.equals(imageBuffer)).toBe(true);
describe('Page.setExtraHTTPHeaders', function() {
it('should work', async({page, server}) => {
await page.setExtraHTTPHeaders({
foo: 'bar'
});
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
expect(request.headers['foo']).toBe('bar');
});
describe('Response.statusText', function() {
it('should work', async({page, server}) => {
server.setRoute('/cool', (req, res) => {
res.writeHead(200, 'cool!');
res.end();
});
const response = await page.goto(server.PREFIX + '/cool');
expect(response.statusText()).toBe('cool!');
it('should work with extra headers from browser context', async({browser, server}) => {
const context = await browser.newContext();
await context.setExtraHTTPHeaders({
'foo': 'bar',
});
const page = await context.newPage();
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
await context.close();
expect(request.headers['foo']).toBe('bar');
});
describe('Network Events', function() {
it('Page.Events.Request', async({page, server}) => {
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE);
expect(requests[0].resourceType()).toBe('document');
expect(requests[0].method()).toBe('GET');
expect(await requests[0].response()).toBeTruthy();
expect(requests[0].frame() === page.mainFrame()).toBe(true);
expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE);
it('should override extra headers from browser context', async({browser, server}) => {
const context = await browser.newContext({
extraHTTPHeaders: { 'fOo': 'bAr', 'baR': 'foO' },
});
it('Page.Events.Response', async({page, server}) => {
const responses = [];
page.on('response', response => responses.push(response));
await page.goto(server.EMPTY_PAGE);
expect(responses.length).toBe(1);
expect(responses[0].url()).toBe(server.EMPTY_PAGE);
expect(responses[0].status()).toBe(200);
expect(responses[0].ok()).toBe(true);
expect(responses[0].request()).toBeTruthy();
});
it.fail(FFOX)('Page.Events.RequestFailed', async({page, server}) => {
server.setRoute('/one-style.css', (req, res) => {
req.socket.write('deadbeef');
req.socket.end();
});
const failedRequests = [];
page.on('requestfailed', request => failedRequests.push(request));
await page.goto(server.PREFIX + '/one-style.html');
expect(failedRequests.length).toBe(1);
expect(failedRequests[0].url()).toContain('one-style.css');
expect(await failedRequests[0].response()).toBe(null);
expect(failedRequests[0].resourceType()).toBe('stylesheet');
if (CHROMIUM) {
expect(failedRequests[0].failure().errorText).toBe('net::ERR_INVALID_HTTP_RESPONSE');
} else if (WEBKIT) {
if (MAC)
expect(failedRequests[0].failure().errorText).toBe('The network connection was lost.');
else if (WIN)
expect(failedRequests[0].failure().errorText).toBe('Unsupported protocol');
else
expect(failedRequests[0].failure().errorText).toBe('Message Corrupt');
} else {
expect(failedRequests[0].failure().errorText).toBe('NS_ERROR_FAILURE');
}
expect(failedRequests[0].frame()).toBeTruthy();
});
it('Page.Events.RequestFinished', async({page, server}) => {
const [response] = await Promise.all([
page.goto(server.EMPTY_PAGE),
page.waitForEvent('requestfinished')
]);
const request = response.request();
expect(request.url()).toBe(server.EMPTY_PAGE);
expect(await request.response()).toBeTruthy();
expect(request.frame() === page.mainFrame()).toBe(true);
expect(request.frame().url()).toBe(server.EMPTY_PAGE);
});
it('should fire events in proper order', async({page, server}) => {
const events = [];
page.on('request', request => events.push('request'));
page.on('response', response => events.push('response'));
const response = await page.goto(server.EMPTY_PAGE);
await response.finished();
events.push('requestfinished')
expect(events).toEqual(['request', 'response', 'requestfinished']);
});
it('should support redirects', async({page, server}) => {
const events = [];
page.on('request', request => events.push(`${request.method()} ${request.url()}`));
page.on('response', response => events.push(`${response.status()} ${response.url()}`));
page.on('requestfinished', request => events.push(`DONE ${request.url()}`));
page.on('requestfailed', request => events.push(`FAIL ${request.url()}`));
server.setRedirect('/foo.html', '/empty.html');
const FOO_URL = server.PREFIX + '/foo.html';
const response = await page.goto(FOO_URL);
await response.finished();
expect(events).toEqual([
`GET ${FOO_URL}`,
`302 ${FOO_URL}`,
`DONE ${FOO_URL}`,
`GET ${server.EMPTY_PAGE}`,
`200 ${server.EMPTY_PAGE}`,
`DONE ${server.EMPTY_PAGE}`
]);
const redirectedFrom = response.request().redirectedFrom();
expect(redirectedFrom.url()).toContain('/foo.html');
expect(redirectedFrom.redirectedFrom()).toBe(null);
expect(redirectedFrom.redirectedTo()).toBe(response.request());
const page = await context.newPage();
await page.setExtraHTTPHeaders({
'Foo': 'Bar'
});
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
await context.close();
expect(request.headers['foo']).toBe('Bar');
expect(request.headers['bar']).toBe('foO');
});
describe('Request.isNavigationRequest', () => {
it('should work', async({page, server}) => {
const requests = new Map();
page.on('request', request => requests.set(request.url().split('/').pop(), request));
server.setRedirect('/rrredirect', '/frames/one-frame.html');
await page.goto(server.PREFIX + '/rrredirect');
expect(requests.get('rrredirect').isNavigationRequest()).toBe(true);
expect(requests.get('one-frame.html').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 when navigating to image', async({page, server}) => {
const requests = [];
page.on('request', request => requests.push(request));
await page.goto(server.PREFIX + '/pptr.png');
expect(requests[0].isNavigationRequest()).toBe(true);
});
it('should throw for non-string header values', async({page, server}) => {
let error = null;
try {
await page.setExtraHTTPHeaders({ 'foo': 1 });
} catch (e) {
error = e;
}
expect(error.message).toBe('Expected value of header "foo" to be String, but "number" is found.');
});
describe('Page.setExtraHTTPHeaders', function() {
it('should work', async({page, server}) => {
await page.setExtraHTTPHeaders({
foo: 'bar'
});
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
expect(request.headers['foo']).toBe('bar');
});
it('should work with extra headers from browser context', async({browser, server}) => {
const context = await browser.newContext();
await context.setExtraHTTPHeaders({
'foo': 'bar',
});
const page = await context.newPage();
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
await context.close();
expect(request.headers['foo']).toBe('bar');
});
it('should override extra headers from browser context', async({browser, server}) => {
const context = await browser.newContext({
extraHTTPHeaders: { 'fOo': 'bAr', 'baR': 'foO' },
});
const page = await context.newPage();
await page.setExtraHTTPHeaders({
'Foo': 'Bar'
});
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
await context.close();
expect(request.headers['foo']).toBe('Bar');
expect(request.headers['bar']).toBe('foO');
});
it('should throw for non-string header values', async({page, server}) => {
let error = null;
try {
await page.setExtraHTTPHeaders({ 'foo': 1 });
} catch (e) {
error = e;
}
expect(error.message).toBe('Expected value of header "foo" to be String, but "number" is found.');
});
});
};
});

File diff suppressed because it is too large Load diff

View file

@ -15,112 +15,108 @@
* limitations under the License.
*/
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({WEBKIT, FFOX}) {
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
// Permissions API is not implemented in WebKit (see https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API)
describe.skip(WEBKIT)('Permissions', function() {
function getPermission(page, name) {
return page.evaluate(name => navigator.permissions.query({name}).then(result => result.state), name);
}
// Permissions API is not implemented in WebKit (see https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API)
describe.skip(WEBKIT)('Permissions', function() {
function getPermission(page, name) {
return page.evaluate(name => navigator.permissions.query({name}).then(result => result.state), name);
}
it('should be prompt by default', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
expect(await getPermission(page, 'geolocation')).toBe('prompt');
});
it('should deny permission when not listed', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions([], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('denied');
});
it('should fail when bad permission is given', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
let error = {};
await context.grantPermissions(['foo'], { origin: server.EMPTY_PAGE }).catch(e => error = e);
expect(error.message).toBe('Unknown permission: foo');
});
it('should grant geolocation permission when listed', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('granted');
});
it('should grant notifications permission when listed', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['notifications'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'notifications')).toBe('granted');
});
it('should accumulate when adding', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']);
await context.grantPermissions(['notifications']);
expect(await getPermission(page, 'geolocation')).toBe('granted');
expect(await getPermission(page, 'notifications')).toBe('granted');
});
it('should clear permissions', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']);
await context.clearPermissions();
await context.grantPermissions(['notifications']);
expect(await getPermission(page, 'geolocation')).not.toBe('granted');
expect(await getPermission(page, 'notifications')).toBe('granted');
});
it('should grant permission when listed for all domains', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']);
expect(await getPermission(page, 'geolocation')).toBe('granted');
});
it('should grant permission when creating context', async({server, browser}) => {
const context = await browser.newContext({ permissions: ['geolocation'] });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(await getPermission(page, 'geolocation')).toBe('granted');
await context.close();
});
it('should reset permissions', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('granted');
await context.clearPermissions();
expect(await getPermission(page, 'geolocation')).toBe('prompt');
});
it('should trigger permission onchange', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
window['events'] = [];
return navigator.permissions.query({name: 'geolocation'}).then(function(result) {
window['events'].push(result.state);
result.onchange = function() {
window['events'].push(result.state);
};
});
});
expect(await page.evaluate(() => window['events'])).toEqual(['prompt']);
await context.grantPermissions([], { origin: server.EMPTY_PAGE });
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied']);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted']);
await context.clearPermissions();
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted', 'prompt']);
});
it('should isolate permissions between browser contexs', async({page, server, context, browser}) => {
await page.goto(server.EMPTY_PAGE);
const otherContext = await browser.newContext();
const otherPage = await otherContext.newPage();
await otherPage.goto(server.EMPTY_PAGE);
expect(await getPermission(page, 'geolocation')).toBe('prompt');
expect(await getPermission(otherPage, 'geolocation')).toBe('prompt');
await context.grantPermissions([], { origin: server.EMPTY_PAGE });
await otherContext.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('denied');
expect(await getPermission(otherPage, 'geolocation')).toBe('granted');
await context.clearPermissions();
expect(await getPermission(page, 'geolocation')).toBe('prompt');
expect(await getPermission(otherPage, 'geolocation')).toBe('granted');
await otherContext.close();
});
it('should be prompt by default', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
expect(await getPermission(page, 'geolocation')).toBe('prompt');
});
};
it('should deny permission when not listed', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions([], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('denied');
});
it('should fail when bad permission is given', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
let error = {};
await context.grantPermissions(['foo'], { origin: server.EMPTY_PAGE }).catch(e => error = e);
expect(error.message).toBe('Unknown permission: foo');
});
it('should grant geolocation permission when listed', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('granted');
});
it('should grant notifications permission when listed', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['notifications'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'notifications')).toBe('granted');
});
it('should accumulate when adding', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']);
await context.grantPermissions(['notifications']);
expect(await getPermission(page, 'geolocation')).toBe('granted');
expect(await getPermission(page, 'notifications')).toBe('granted');
});
it('should clear permissions', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']);
await context.clearPermissions();
await context.grantPermissions(['notifications']);
expect(await getPermission(page, 'geolocation')).not.toBe('granted');
expect(await getPermission(page, 'notifications')).toBe('granted');
});
it('should grant permission when listed for all domains', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']);
expect(await getPermission(page, 'geolocation')).toBe('granted');
});
it('should grant permission when creating context', async({server, browser}) => {
const context = await browser.newContext({ permissions: ['geolocation'] });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(await getPermission(page, 'geolocation')).toBe('granted');
await context.close();
});
it('should reset permissions', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('granted');
await context.clearPermissions();
expect(await getPermission(page, 'geolocation')).toBe('prompt');
});
it('should trigger permission onchange', async({page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
window['events'] = [];
return navigator.permissions.query({name: 'geolocation'}).then(function(result) {
window['events'].push(result.state);
result.onchange = function() {
window['events'].push(result.state);
};
});
});
expect(await page.evaluate(() => window['events'])).toEqual(['prompt']);
await context.grantPermissions([], { origin: server.EMPTY_PAGE });
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied']);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted']);
await context.clearPermissions();
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted', 'prompt']);
});
it('should isolate permissions between browser contexs', async({page, server, context, browser}) => {
await page.goto(server.EMPTY_PAGE);
const otherContext = await browser.newContext();
const otherPage = await otherContext.newPage();
await otherPage.goto(server.EMPTY_PAGE);
expect(await getPermission(page, 'geolocation')).toBe('prompt');
expect(await getPermission(otherPage, 'geolocation')).toBe('prompt');
await context.grantPermissions([], { origin: server.EMPTY_PAGE });
await otherContext.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('denied');
expect(await getPermission(otherPage, 'geolocation')).toBe('granted');
await context.clearPermissions();
expect(await getPermission(page, 'geolocation')).toBe('prompt');
expect(await getPermission(otherPage, 'geolocation')).toBe('granted');
await otherContext.close();
});
});

View file

@ -80,9 +80,11 @@ module.exports.addPlaywrightTests = ({testRunner, products}) => {
const playwrightEnvironment = new Environment('Playwright');
playwrightEnvironment.beforeAll(async state => {
state.playwright = playwright;
global.playwright = playwright;
});
playwrightEnvironment.afterAll(async state => {
delete state.playwright;
delete global.playwright;
});
for (const product of products) {
@ -143,21 +145,6 @@ module.exports.addPlaywrightTests = ({testRunner, products}) => {
state.page = null;
});
function loadTests(modulePath) {
const testOptions = {
...require('./utils').testOptions(global.browserType),
playwright: global.playwright,
browserType: global.browserType,
};
const module = require(modulePath);
if (typeof module.describe === 'function')
describe('', module.describe, testOptions);
if (typeof module.fdescribe === 'function')
fdescribe('', module.fdescribe, testOptions);
if (typeof module.xdescribe === 'function')
xdescribe('', module.xdescribe, testOptions);
}
testRunner.collector().useEnvironment(serverEnvironment); // Custom global environment.
testRunner.collector().useEnvironment(playwrightEnvironment);
@ -177,71 +164,66 @@ module.exports.addPlaywrightTests = ({testRunner, products}) => {
// Page-level tests that are given a browser, a context and a page.
// Each test is launched in a new browser context.
describe('[Accessibility]', () => loadTests('./accessibility.spec.js'));
describe('[Driver]', () => {
loadTests('./autowaiting.spec.js');
loadTests('./click.spec.js');
loadTests('./cookies.spec.js');
loadTests('./dialog.spec.js');
loadTests('./download.spec.js');
loadTests('./elementhandle.spec.js');
loadTests('./emulation.spec.js');
loadTests('./evaluation.spec.js');
loadTests('./frame.spec.js');
loadTests('./focus.spec.js');
loadTests('./input.spec.js');
loadTests('./jshandle.spec.js');
loadTests('./keyboard.spec.js');
loadTests('./mouse.spec.js');
loadTests('./navigation.spec.js');
loadTests('./network.spec.js');
loadTests('./page.spec.js');
loadTests('./queryselector.spec.js');
loadTests('./screenshot.spec.js');
loadTests('./waittask.spec.js');
loadTests('./interception.spec.js');
loadTests('./geolocation.spec.js');
loadTests('./workers.spec.js');
loadTests('./capabilities.spec.js');
});
describe('[Permissions]', () => {
loadTests('./permissions.spec.js');
});
require('./accessibility.spec.js');
require('./autowaiting.spec.js');
require('./click.spec.js');
require('./cookies.spec.js');
require('./dialog.spec.js');
require('./download.spec.js');
require('./elementhandle.spec.js');
require('./emulation.spec.js');
require('./evaluation.spec.js');
require('./frame.spec.js');
require('./focus.spec.js');
require('./input.spec.js');
require('./jshandle.spec.js');
require('./keyboard.spec.js');
require('./mouse.spec.js');
require('./navigation.spec.js');
require('./network.spec.js');
require('./page.spec.js');
require('./queryselector.spec.js');
require('./screenshot.spec.js');
require('./waittask.spec.js');
require('./interception.spec.js');
require('./geolocation.spec.js');
require('./workers.spec.js');
require('./capabilities.spec.js');
require('./permissions.spec.js');
describe.skip(product !== 'Chromium')('[Chromium]', () => {
loadTests('./chromium/chromium.spec.js');
loadTests('./chromium/coverage.spec.js');
loadTests('./chromium/pdf.spec.js');
loadTests('./chromium/session.spec.js');
require('./chromium/chromium.spec.js');
require('./chromium/coverage.spec.js');
require('./chromium/pdf.spec.js');
require('./chromium/session.spec.js');
});
});
// Browser-level tests that are given a browser.
describe('[Driver]', () => {
loadTests('./browser.spec.js');
loadTests('./browsercontext.spec.js');
loadTests('./ignorehttpserrors.spec.js');
loadTests('./popup.spec.js');
require('./browser.spec.js');
require('./browsercontext.spec.js');
require('./ignorehttpserrors.spec.js');
require('./popup.spec.js');
});
});
// Top-level tests that launch Browser themselves.
describe('[Driver]', () => {
loadTests('./defaultbrowsercontext.spec.js');
loadTests('./fixtures.spec.js');
loadTests('./launcher.spec.js');
loadTests('./headful.spec.js');
loadTests('./multiclient.spec.js');
require('./defaultbrowsercontext.spec.js');
require('./fixtures.spec.js');
require('./launcher.spec.js');
require('./headful.spec.js');
require('./multiclient.spec.js');
});
describe.skip(product !== 'Chromium')('[Chromium]', () => {
loadTests('./chromium/launcher.spec.js');
loadTests('./chromium/oopif.spec.js');
loadTests('./chromium/tracing.spec.js');
require('./chromium/launcher.spec.js');
require('./chromium/oopif.spec.js');
require('./chromium/tracing.spec.js');
});
if (process.env.COVERAGE)
loadTests('./apicoverage.spec.js');
require('./apicoverage.spec.js');
delete global.browserType;
delete global.playwright;

View file

@ -14,324 +14,322 @@
* limitations under the License.
*/
module.exports.describe = function({playwright, CHROMIUM, WEBKIT, FFOX}) {
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
describe('Link navigation', function() {
it('should inherit user agent from browser context', async function({browser, server}) {
const context = await browser.newContext({
userAgent: 'hey'
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/popup/popup.html">link</a>');
const requestPromise = server.waitForRequest('/popup/popup.html');
const [popup] = await Promise.all([
context.waitForEvent('page'),
page.click('a'),
]);
await popup.waitForLoadState('domcontentloaded');
const userAgent = await popup.evaluate(() => window.initialUserAgent);
const request = await requestPromise;
await context.close();
expect(userAgent).toBe('hey');
expect(request.headers['user-agent']).toBe('hey');
});
it('should respect routes from browser context', async function({browser, server}) {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="empty.html">link</a>');
let intercepted = false;
await context.route('**/empty.html', route => {
route.continue();
intercepted = true;
});
await Promise.all([
context.waitForEvent('page'),
page.click('a'),
]);
await context.close();
expect(intercepted).toBe(true);
describe('Link navigation', function() {
it('should inherit user agent from browser context', async function({browser, server}) {
const context = await browser.newContext({
userAgent: 'hey'
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/popup/popup.html">link</a>');
const requestPromise = server.waitForRequest('/popup/popup.html');
const [popup] = await Promise.all([
context.waitForEvent('page'),
page.click('a'),
]);
await popup.waitForLoadState('domcontentloaded');
const userAgent = await popup.evaluate(() => window.initialUserAgent);
const request = await requestPromise;
await context.close();
expect(userAgent).toBe('hey');
expect(request.headers['user-agent']).toBe('hey');
});
describe('window.open', function() {
it('should inherit user agent from browser context', async function({browser, server}) {
const context = await browser.newContext({
userAgent: 'hey'
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const requestPromise = server.waitForRequest('/dummy.html');
const userAgent = await page.evaluate(url => {
const win = window.open(url);
return win.navigator.userAgent;
}, server.PREFIX + '/dummy.html');
const request = await requestPromise;
await context.close();
expect(userAgent).toBe('hey');
expect(request.headers['user-agent']).toBe('hey');
});
it('should inherit extra headers from browser context', async function({browser, server}) {
const context = await browser.newContext({
extraHTTPHeaders: { 'foo': 'bar' },
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const requestPromise = server.waitForRequest('/dummy.html');
await page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/dummy.html');
const request = await requestPromise;
await context.close();
expect(request.headers['foo']).toBe('bar');
});
it('should inherit offline from browser context', async function({browser, server}) {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await context.setOffline(true);
const online = await page.evaluate(url => {
const win = window.open(url);
return win.navigator.onLine;
}, server.PREFIX + '/dummy.html');
await context.close();
expect(online).toBe(false);
});
it('should inherit http credentials from browser context', async function({browser, server}) {
server.setAuth('/title.html', 'user', 'pass');
const context = await browser.newContext({
httpCredentials: { username: 'user', password: 'pass' }
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/title.html'),
]);
await popup.waitForLoadState('domcontentloaded');
expect(await popup.title()).toBe('Woof-Woof');
await context.close();
});
it('should inherit touch support from browser context', async function({browser, server}) {
const context = await browser.newContext({
viewport: { width: 400, height: 500 },
hasTouch: true
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const hasTouch = await page.evaluate(() => {
const win = window.open('');
return 'ontouchstart' in win;
});
await context.close();
expect(hasTouch).toBe(true);
});
it('should inherit viewport size from browser context', async function({browser, server}) {
const context = await browser.newContext({
viewport: { width: 400, height: 500 }
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const size = await page.evaluate(() => {
const win = window.open('about:blank');
return { width: win.innerWidth, height: win.innerHeight };
});
await context.close();
expect(size).toEqual({width: 400, height: 500});
});
it('should respect routes from browser context', async function({browser, server}) {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
let intercepted = false;
await context.route('**/empty.html', route => {
route.continue();
intercepted = true;
});
await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE),
]);
expect(intercepted).toBe(true);
await context.close();
});
it('should apply addInitScript from browser context', async function({browser, server}) {
const context = await browser.newContext();
await context.addInitScript(() => window.injected = 123);
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const injected = await page.evaluate(() => {
const win = window.open('about:blank');
return win.injected;
});
await context.close();
expect(injected).toBe(123);
});
it('should expose function from browser context', async function({browser, server}) {
const context = await browser.newContext();
await context.exposeFunction('add', (a, b) => a + b);
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const added = await page.evaluate(async () => {
const win = window.open('about:blank');
return win.add(9, 4);
});
await context.close();
expect(added).toBe(13);
it('should respect routes from browser context', async function({browser, server}) {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="empty.html">link</a>');
let intercepted = false;
await context.route('**/empty.html', route => {
route.continue();
intercepted = true;
});
await Promise.all([
context.waitForEvent('page'),
page.click('a'),
]);
await context.close();
expect(intercepted).toBe(true);
});
});
describe('Page.Events.Popup', function() {
it('should work', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => window.__popup = window.open('about:blank')),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(true);
await context.close();
});
it('should work with window features', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => window.__popup = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=200,top=0,left=0')),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(true);
await context.close();
});
it('should emit for immediately closed popups', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => {
const win = window.open('about:blank');
win.close();
}),
]);
expect(popup).toBeTruthy();
await context.close();
});
it('should be able to capture alert', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const evaluatePromise = page.evaluate(() => {
const win = window.open('');
win.alert('hello');
});
const popup = await page.waitForEvent('popup');
const dialog = await popup.waitForEvent('dialog');
expect(dialog.message()).toBe('hello');
await dialog.dismiss();
await evaluatePromise;
await context.close();
});
it('should work with empty url', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => window.__popup = window.open('')),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(true);
await context.close();
});
it('should work with noopener and no url', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => window.__popup = window.open(undefined, null, 'noopener')),
]);
expect(popup.url()).toBe('about:blank');
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
await context.close();
});
it('should work with noopener and about:blank', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => window.__popup = window.open('about:blank', null, 'noopener')),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
await context.close();
});
it('should work with noopener and url', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window.__popup = window.open(url, null, 'noopener'), server.EMPTY_PAGE),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
await context.close();
});
it('should work with clicking target=_blank', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
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'),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(true);
await context.close();
});
it('should work with fake-clicking target=_blank and rel=noopener', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
// TODO: FFOX sends events for "one-style.html" request to both pages.
await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.$eval('a', a => a.click()),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
await context.close();
});
it('should work with clicking target=_blank and rel=noopener', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('a'),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
await context.close();
});
it('should not treat navigations as new popups', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('a'),
]);
let badSecondPopup = false;
page.on('popup', () => badSecondPopup = true);
await popup.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
await context.close();
expect(badSecondPopup).toBe(false);
describe('window.open', function() {
it('should inherit user agent from browser context', async function({browser, server}) {
const context = await browser.newContext({
userAgent: 'hey'
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const requestPromise = server.waitForRequest('/dummy.html');
const userAgent = await page.evaluate(url => {
const win = window.open(url);
return win.navigator.userAgent;
}, server.PREFIX + '/dummy.html');
const request = await requestPromise;
await context.close();
expect(userAgent).toBe('hey');
expect(request.headers['user-agent']).toBe('hey');
});
it('should inherit extra headers from browser context', async function({browser, server}) {
const context = await browser.newContext({
extraHTTPHeaders: { 'foo': 'bar' },
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const requestPromise = server.waitForRequest('/dummy.html');
await page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/dummy.html');
const request = await requestPromise;
await context.close();
expect(request.headers['foo']).toBe('bar');
});
it('should inherit offline from browser context', async function({browser, server}) {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await context.setOffline(true);
const online = await page.evaluate(url => {
const win = window.open(url);
return win.navigator.onLine;
}, server.PREFIX + '/dummy.html');
await context.close();
expect(online).toBe(false);
});
it('should inherit http credentials from browser context', async function({browser, server}) {
server.setAuth('/title.html', 'user', 'pass');
const context = await browser.newContext({
httpCredentials: { username: 'user', password: 'pass' }
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/title.html'),
]);
await popup.waitForLoadState('domcontentloaded');
expect(await popup.title()).toBe('Woof-Woof');
await context.close();
});
it('should inherit touch support from browser context', async function({browser, server}) {
const context = await browser.newContext({
viewport: { width: 400, height: 500 },
hasTouch: true
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const hasTouch = await page.evaluate(() => {
const win = window.open('');
return 'ontouchstart' in win;
});
await context.close();
expect(hasTouch).toBe(true);
});
it('should inherit viewport size from browser context', async function({browser, server}) {
const context = await browser.newContext({
viewport: { width: 400, height: 500 }
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const size = await page.evaluate(() => {
const win = window.open('about:blank');
return { width: win.innerWidth, height: win.innerHeight };
});
await context.close();
expect(size).toEqual({width: 400, height: 500});
});
it('should respect routes from browser context', async function({browser, server}) {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
let intercepted = false;
await context.route('**/empty.html', route => {
route.continue();
intercepted = true;
});
await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE),
]);
expect(intercepted).toBe(true);
await context.close();
});
it('should apply addInitScript from browser context', async function({browser, server}) {
const context = await browser.newContext();
await context.addInitScript(() => window.injected = 123);
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const injected = await page.evaluate(() => {
const win = window.open('about:blank');
return win.injected;
});
await context.close();
expect(injected).toBe(123);
});
it('should expose function from browser context', async function({browser, server}) {
const context = await browser.newContext();
await context.exposeFunction('add', (a, b) => a + b);
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const added = await page.evaluate(async () => {
const win = window.open('about:blank');
return win.add(9, 4);
});
await context.close();
expect(added).toBe(13);
});
});
};
describe('Page.Events.Popup', function() {
it('should work', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => window.__popup = window.open('about:blank')),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(true);
await context.close();
});
it('should work with window features', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => window.__popup = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=200,top=0,left=0')),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(true);
await context.close();
});
it('should emit for immediately closed popups', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => {
const win = window.open('about:blank');
win.close();
}),
]);
expect(popup).toBeTruthy();
await context.close();
});
it('should be able to capture alert', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const evaluatePromise = page.evaluate(() => {
const win = window.open('');
win.alert('hello');
});
const popup = await page.waitForEvent('popup');
const dialog = await popup.waitForEvent('dialog');
expect(dialog.message()).toBe('hello');
await dialog.dismiss();
await evaluatePromise;
await context.close();
});
it('should work with empty url', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => window.__popup = window.open('')),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(true);
await context.close();
});
it('should work with noopener and no url', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => window.__popup = window.open(undefined, null, 'noopener')),
]);
expect(popup.url()).toBe('about:blank');
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
await context.close();
});
it('should work with noopener and about:blank', async({browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(() => window.__popup = window.open('about:blank', null, 'noopener')),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
await context.close();
});
it('should work with noopener and url', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.evaluate(url => window.__popup = window.open(url, null, 'noopener'), server.EMPTY_PAGE),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
await context.close();
});
it('should work with clicking target=_blank', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
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'),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(true);
await context.close();
});
it('should work with fake-clicking target=_blank and rel=noopener', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
// TODO: FFOX sends events for "one-style.html" request to both pages.
await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.$eval('a', a => a.click()),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
await context.close();
});
it('should work with clicking target=_blank and rel=noopener', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('a'),
]);
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
await context.close();
});
it('should not treat navigations as new popups', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('a'),
]);
let badSecondPopup = false;
page.on('popup', () => badSecondPopup = true);
await popup.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
await context.close();
expect(badSecondPopup).toBe(false);
});
});

File diff suppressed because it is too large Load diff

View file

@ -15,469 +15,465 @@
* limitations under the License.
*/
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({FFOX, CHROMIUM, WEBKIT, LINUX }) {
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
describe('Page.screenshot', function() {
it('should work', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-sanity.png');
});
it('should clip rect', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot({
clip: {
x: 50,
y: 100,
width: 150,
height: 100
}
});
expect(screenshot).toBeGolden('screenshot-clip-rect.png');
});
it('should clip rect with fullPage', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(150, 200));
const screenshot = await page.screenshot({
fullPage: true,
clip: {
x: 50,
y: 100,
width: 150,
height: 100,
},
});
expect(screenshot).toBeGolden('screenshot-clip-rect.png');
});
it('should clip elements to the viewport', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot({
clip: {
x: 50,
y: 450,
width: 1000,
height: 100
}
});
expect(screenshot).toBeGolden('screenshot-offscreen-clip.png');
});
it('should throw on clip outside the viewport', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const screenshotError = await page.screenshot({
clip: {
x: 50,
y: 650,
width: 100,
height: 100
}
}).catch(error => error);
expect(screenshotError.message).toBe('Clipped area is either empty or outside the resulting image');
});
it('should run in parallel', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const promises = [];
for (let i = 0; i < 3; ++i) {
promises.push(page.screenshot({
clip: {
x: 50 * i,
y: 0,
width: 50,
height: 50
}
}));
describe('Page.screenshot', function() {
it('should work', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-sanity.png');
});
it('should clip rect', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot({
clip: {
x: 50,
y: 100,
width: 150,
height: 100
}
const screenshots = await Promise.all(promises);
expect(screenshots[1]).toBeGolden('grid-cell-1.png');
});
it('should take fullPage screenshots', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot({
fullPage: true
});
expect(screenshot).toBeGolden('screenshot-grid-fullpage.png');
expect(screenshot).toBeGolden('screenshot-clip-rect.png');
});
it('should clip rect with fullPage', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(150, 200));
const screenshot = await page.screenshot({
fullPage: true,
clip: {
x: 50,
y: 100,
width: 150,
height: 100,
},
});
it('should restore viewport after fullPage screenshot', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot({ fullPage: true });
expect(screenshot).toBeInstanceOf(Buffer);
expect(page.viewportSize().width).toBe(500);
expect(page.viewportSize().height).toBe(500);
expect(screenshot).toBeGolden('screenshot-clip-rect.png');
});
it('should clip elements to the viewport', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot({
clip: {
x: 50,
y: 450,
width: 1000,
height: 100
}
});
it('should run in parallel in multiple pages', async({page, server, context}) => {
const N = 2;
const pages = await Promise.all(Array(N).fill(0).map(async() => {
const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html');
return page;
}));
const promises = [];
for (let i = 0; i < N; ++i)
promises.push(pages[i].screenshot({ clip: { x: 50 * i, y: 0, width: 50, height: 50 } }));
const screenshots = await Promise.all(promises);
for (let i = 0; i < N; ++i)
expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`);
await Promise.all(pages.map(page => page.close()));
});
it.fail(FFOX)('should allow transparency', async({page, server}) => {
await page.setViewportSize({ width: 50, height: 150 });
await page.setContent(`
<style>
body { margin: 0 }
div { width: 50px; height: 50px; }
</style>
<div style="background:black"></div>
<div style="background:white"></div>
<div style="background:transparent"></div>
`);
const screenshot = await page.screenshot({omitBackground: true});
expect(screenshot).toBeGolden('transparent.png');
});
it('should render white background on jpeg file', async({page, server}) => {
await page.setViewportSize({ width: 100, height: 100 });
await page.goto(server.EMPTY_PAGE);
const screenshot = await page.screenshot({omitBackground: true, type: 'jpeg'});
expect(screenshot).toBeGolden('white.jpg');
});
it('should work with odd clip size on Retina displays', async({page, server}) => {
const screenshot = await page.screenshot({
expect(screenshot).toBeGolden('screenshot-offscreen-clip.png');
});
it('should throw on clip outside the viewport', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const screenshotError = await page.screenshot({
clip: {
x: 50,
y: 650,
width: 100,
height: 100
}
}).catch(error => error);
expect(screenshotError.message).toBe('Clipped area is either empty or outside the resulting image');
});
it('should run in parallel', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const promises = [];
for (let i = 0; i < 3; ++i) {
promises.push(page.screenshot({
clip: {
x: 0,
x: 50 * i,
y: 0,
width: 11,
height: 11,
width: 50,
height: 50
}
});
expect(screenshot).toBeGolden('screenshot-clip-odd-size.png');
}));
}
const screenshots = await Promise.all(promises);
expect(screenshots[1]).toBeGolden('grid-cell-1.png');
});
it('should take fullPage screenshots', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot({
fullPage: true
});
it.skip(FFOX)('should work with a mobile viewport', async({browser, server}) => {
const context = await browser.newContext({ viewport: { width: 320, height: 480 }, isMobile: true });
expect(screenshot).toBeGolden('screenshot-grid-fullpage.png');
});
it('should restore viewport after fullPage screenshot', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot({ fullPage: true });
expect(screenshot).toBeInstanceOf(Buffer);
expect(page.viewportSize().width).toBe(500);
expect(page.viewportSize().height).toBe(500);
});
it('should run in parallel in multiple pages', async({page, server, context}) => {
const N = 2;
const pages = await Promise.all(Array(N).fill(0).map(async() => {
const page = await context.newPage();
await page.goto(server.PREFIX + '/overflow.html');
const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-mobile.png');
await context.close();
});
it.skip(FFOX)('should work with a mobile viewport and clip', async({browser, server}) => {
const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true});
const page = await context.newPage();
await page.goto(server.PREFIX + '/overflow.html');
const screenshot = await page.screenshot({ clip: { x: 10, y: 10, width: 100, height: 150 } });
expect(screenshot).toBeGolden('screenshot-mobile-clip.png');
await context.close();
});
it.skip(FFOX)('should work with a mobile viewport and fullPage', async({browser, server}) => {
const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true});
const page = await context.newPage();
await page.goto(server.PREFIX + '/overflow-large.html');
const screenshot = await page.screenshot({ fullPage: true });
expect(screenshot).toBeGolden('screenshot-mobile-fullpage.png');
await context.close();
});
it('should work for canvas', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/screenshots/canvas.html');
const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-canvas.png');
});
it('should work for translateZ', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/screenshots/translateZ.html');
const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-translateZ.png');
});
it.fail(FFOX || WEBKIT)('should work for webgl', async({page, server}) => {
await page.setViewportSize({width: 640, height: 480});
await page.goto(server.PREFIX + '/screenshots/webgl.html');
const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-webgl.png');
});
it('should work while navigating', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/redirectloop1.html');
for (let i = 0; i < 10; i++) {
const screenshot = await page.screenshot({ fullPage: true }).catch(e => {
if (e.message.includes('Cannot take a screenshot while page is navigating'))
return Buffer.from('');
throw e;
});
expect(screenshot).toBeInstanceOf(Buffer);
await page.goto(server.PREFIX + '/grid.html');
return page;
}));
const promises = [];
for (let i = 0; i < N; ++i)
promises.push(pages[i].screenshot({ clip: { x: 50 * i, y: 0, width: 50, height: 50 } }));
const screenshots = await Promise.all(promises);
for (let i = 0; i < N; ++i)
expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`);
await Promise.all(pages.map(page => page.close()));
});
it.fail(FFOX)('should allow transparency', async({page, server}) => {
await page.setViewportSize({ width: 50, height: 150 });
await page.setContent(`
<style>
body { margin: 0 }
div { width: 50px; height: 50px; }
</style>
<div style="background:black"></div>
<div style="background:white"></div>
<div style="background:transparent"></div>
`);
const screenshot = await page.screenshot({omitBackground: true});
expect(screenshot).toBeGolden('transparent.png');
});
it('should render white background on jpeg file', async({page, server}) => {
await page.setViewportSize({ width: 100, height: 100 });
await page.goto(server.EMPTY_PAGE);
const screenshot = await page.screenshot({omitBackground: true, type: 'jpeg'});
expect(screenshot).toBeGolden('white.jpg');
});
it('should work with odd clip size on Retina displays', async({page, server}) => {
const screenshot = await page.screenshot({
clip: {
x: 0,
y: 0,
width: 11,
height: 11,
}
});
it('should work with device scale factor', async({browser, server}) => {
const context = await browser.newContext({ viewport: { width: 320, height: 480 }, deviceScaleFactor: 2 });
const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-device-scale-factor.png');
await context.close();
});
expect(screenshot).toBeGolden('screenshot-clip-odd-size.png');
});
describe('ElementHandle.screenshot', function() {
it('should work', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(50, 100));
const elementHandle = await page.$('.box:nth-of-type(3)');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-bounding-box.png');
});
it('should take into account padding and border', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.setContent(`
<div style="height: 14px">oooo</div>
<style>div {
border: 2px solid blue;
background: green;
width: 50px;
height: 50px;
}
</style>
<div id="d"></div>
`);
const elementHandle = await page.$('div#d');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-padding-border.png');
});
it('should capture full element when larger than viewport in parallel', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.setContent(`
<div style="height: 14px">oooo</div>
<style>
div.to-screenshot {
border: 1px solid blue;
width: 600px;
height: 600px;
margin-left: 50px;
}
::-webkit-scrollbar{
display: none;
}
</style>
<div class="to-screenshot"></div>
<div class="to-screenshot"></div>
<div class="to-screenshot"></div>
`);
const elementHandles = await page.$$('div.to-screenshot');
const promises = elementHandles.map(handle => handle.screenshot());
const screenshots = await Promise.all(promises);
expect(screenshots[2]).toBeGolden('screenshot-element-larger-than-viewport.png');
expect(await page.evaluate(() => ({ w: window.innerWidth, h: window.innerHeight }))).toEqual({ w: 500, h: 500 });
});
it('should capture full element when larger than viewport', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.setContent(`
<div style="height: 14px">oooo</div>
<style>
div.to-screenshot {
border: 1px solid blue;
width: 600px;
height: 600px;
margin-left: 50px;
}
::-webkit-scrollbar{
display: none;
}
</style>
<div class="to-screenshot"></div>
<div class="to-screenshot"></div>
<div class="to-screenshot"></div>
`);
const elementHandle = await page.$('div.to-screenshot');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-larger-than-viewport.png');
expect(await page.evaluate(() => ({ w: window.innerWidth, h: window.innerHeight }))).toEqual({ w: 500, h: 500 });
});
it('should scroll element into view', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.setContent(`
<div style="height: 14px">oooo</div>
<style>div.above {
border: 2px solid blue;
background: red;
height: 1500px;
}
div.to-screenshot {
border: 2px solid blue;
background: green;
width: 50px;
height: 50px;
}
</style>
<div class="above"></div>
<div class="to-screenshot"></div>
`);
const elementHandle = await page.$('div.to-screenshot');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-scrolled-into-view.png');
});
it('should scroll 15000px into view', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.setContent(`
<div style="height: 14px">oooo</div>
<style>div.above {
border: 2px solid blue;
background: red;
height: 15000px;
}
div.to-screenshot {
border: 2px solid blue;
background: green;
width: 50px;
height: 50px;
}
</style>
<div class="above"></div>
<div class="to-screenshot"></div>
`);
const elementHandle = await page.$('div.to-screenshot');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-scrolled-into-view.png');
});
it('should work with a rotated element', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.setContent(`<div style="position:absolute;
top: 100px;
left: 100px;
width: 100px;
height: 100px;
background: green;
transform: rotateZ(200deg);">&nbsp;</div>`);
const elementHandle = await page.$('div');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-rotate.png');
});
it('should fail to screenshot a detached element', async({page, server}) => {
await page.setContent('<h1>remove this</h1>');
const elementHandle = await page.$('h1');
await page.evaluate(element => element.remove(), elementHandle);
const screenshotError = await elementHandle.screenshot().catch(error => error);
expect(screenshotError.message).toContain('Node is detached');
});
it('should not hang with zero width/height element', async({page, server}) => {
await page.setContent('<div style="width: 50px; height: 0"></div>');
const div = await page.$('div');
const error = await div.screenshot().catch(e => e);
expect(error.message).toBe('Node has 0 height.');
});
it('should work for an element with fractional dimensions', async({page}) => {
await page.setContent('<div style="width:48.51px;height:19.8px;border:1px solid black;"></div>');
const elementHandle = await page.$('div');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-fractional.png');
});
it.skip(FFOX)('should work with a mobile viewport', async({browser, server}) => {
const context = await browser.newContext({viewport: { width: 320, height: 480, isMobile: true }});
const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(50, 100));
const elementHandle = await page.$('.box:nth-of-type(3)');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-mobile.png');
await context.close();
});
it.skip(FFOX)('should work with device scale factor', async({browser, server}) => {
const context = await browser.newContext({ viewport: { width: 320, height: 480 }, deviceScaleFactor: 2 });
const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(50, 100));
const elementHandle = await page.$('.box:nth-of-type(3)');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-mobile-dsf.png');
await context.close();
});
it('should work for an element with an offset', async({page}) => {
await page.setContent('<div style="position:absolute; top: 10.3px; left: 20.4px;width:50.3px;height:20.2px;border:1px solid black;"></div>');
const elementHandle = await page.$('div');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-fractional-offset.png');
});
it('should take screenshots when default viewport is null', async({server, browser}) => {
const context = await browser.newContext({ viewport: null });
const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html');
const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
const screenshot = await page.screenshot();
expect(screenshot).toBeInstanceOf(Buffer);
const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
expect(sizeBefore.width).toBe(sizeAfter.width);
expect(sizeBefore.height).toBe(sizeAfter.height);
await context.close();
});
it('should take fullPage screenshots when default viewport is null', async({server, browser}) => {
const context = await browser.newContext({ viewport: null });
const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html');
const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
const screenshot = await page.screenshot({
fullPage: true
it.skip(FFOX)('should work with a mobile viewport', async({browser, server}) => {
const context = await browser.newContext({ viewport: { width: 320, height: 480 }, isMobile: true });
const page = await context.newPage();
await page.goto(server.PREFIX + '/overflow.html');
const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-mobile.png');
await context.close();
});
it.skip(FFOX)('should work with a mobile viewport and clip', async({browser, server}) => {
const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true});
const page = await context.newPage();
await page.goto(server.PREFIX + '/overflow.html');
const screenshot = await page.screenshot({ clip: { x: 10, y: 10, width: 100, height: 150 } });
expect(screenshot).toBeGolden('screenshot-mobile-clip.png');
await context.close();
});
it.skip(FFOX)('should work with a mobile viewport and fullPage', async({browser, server}) => {
const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true});
const page = await context.newPage();
await page.goto(server.PREFIX + '/overflow-large.html');
const screenshot = await page.screenshot({ fullPage: true });
expect(screenshot).toBeGolden('screenshot-mobile-fullpage.png');
await context.close();
});
it('should work for canvas', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/screenshots/canvas.html');
const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-canvas.png');
});
it('should work for translateZ', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/screenshots/translateZ.html');
const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-translateZ.png');
});
it.fail(FFOX || WEBKIT)('should work for webgl', async({page, server}) => {
await page.setViewportSize({width: 640, height: 480});
await page.goto(server.PREFIX + '/screenshots/webgl.html');
const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-webgl.png');
});
it('should work while navigating', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/redirectloop1.html');
for (let i = 0; i < 10; i++) {
const screenshot = await page.screenshot({ fullPage: true }).catch(e => {
if (e.message.includes('Cannot take a screenshot while page is navigating'))
return Buffer.from('');
throw e;
});
expect(screenshot).toBeInstanceOf(Buffer);
const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
expect(sizeBefore.width).toBe(sizeAfter.width);
expect(sizeBefore.height).toBe(sizeAfter.height);
await context.close();
});
it('should restore default viewport after fullPage screenshot', async({ browser }) => {
const context = await browser.newContext({ viewport: { width: 456, height: 789 } });
const page = await context.newPage();
expect(page.viewportSize().width).toBe(456);
expect(page.viewportSize().height).toBe(789);
expect(await page.evaluate('window.innerWidth')).toBe(456);
expect(await page.evaluate('window.innerHeight')).toBe(789);
const screenshot = await page.screenshot({ fullPage: true });
expect(screenshot).toBeInstanceOf(Buffer);
expect(page.viewportSize().width).toBe(456);
expect(page.viewportSize().height).toBe(789);
expect(await page.evaluate('window.innerWidth')).toBe(456);
expect(await page.evaluate('window.innerHeight')).toBe(789);
await context.close();
});
it('should take element screenshot when default viewport is null and restore back', async({server, browser}) => {
const context = await browser.newContext({viewport: null});
const page = await context.newPage({ viewport: null });
await page.setContent(`
<div style="height: 14px">oooo</div>
<style>
div.to-screenshot {
border: 1px solid blue;
width: 600px;
height: 600px;
margin-left: 50px;
}
::-webkit-scrollbar{
display: none;
}
</style>
<div class="to-screenshot"></div>
<div class="to-screenshot"></div>
<div class="to-screenshot"></div>
`);
const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
const elementHandle = await page.$('div.to-screenshot');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeInstanceOf(Buffer);
const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
expect(sizeBefore.width).toBe(sizeAfter.width);
expect(sizeBefore.height).toBe(sizeAfter.height);
await context.close();
});
}
});
};
it('should work with device scale factor', async({browser, server}) => {
const context = await browser.newContext({ viewport: { width: 320, height: 480 }, deviceScaleFactor: 2 });
const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html');
const screenshot = await page.screenshot();
expect(screenshot).toBeGolden('screenshot-device-scale-factor.png');
await context.close();
});
});
describe('ElementHandle.screenshot', function() {
it('should work', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(50, 100));
const elementHandle = await page.$('.box:nth-of-type(3)');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-bounding-box.png');
});
it('should take into account padding and border', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.setContent(`
<div style="height: 14px">oooo</div>
<style>div {
border: 2px solid blue;
background: green;
width: 50px;
height: 50px;
}
</style>
<div id="d"></div>
`);
const elementHandle = await page.$('div#d');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-padding-border.png');
});
it('should capture full element when larger than viewport in parallel', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.setContent(`
<div style="height: 14px">oooo</div>
<style>
div.to-screenshot {
border: 1px solid blue;
width: 600px;
height: 600px;
margin-left: 50px;
}
::-webkit-scrollbar{
display: none;
}
</style>
<div class="to-screenshot"></div>
<div class="to-screenshot"></div>
<div class="to-screenshot"></div>
`);
const elementHandles = await page.$$('div.to-screenshot');
const promises = elementHandles.map(handle => handle.screenshot());
const screenshots = await Promise.all(promises);
expect(screenshots[2]).toBeGolden('screenshot-element-larger-than-viewport.png');
expect(await page.evaluate(() => ({ w: window.innerWidth, h: window.innerHeight }))).toEqual({ w: 500, h: 500 });
});
it('should capture full element when larger than viewport', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.setContent(`
<div style="height: 14px">oooo</div>
<style>
div.to-screenshot {
border: 1px solid blue;
width: 600px;
height: 600px;
margin-left: 50px;
}
::-webkit-scrollbar{
display: none;
}
</style>
<div class="to-screenshot"></div>
<div class="to-screenshot"></div>
<div class="to-screenshot"></div>
`);
const elementHandle = await page.$('div.to-screenshot');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-larger-than-viewport.png');
expect(await page.evaluate(() => ({ w: window.innerWidth, h: window.innerHeight }))).toEqual({ w: 500, h: 500 });
});
it('should scroll element into view', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.setContent(`
<div style="height: 14px">oooo</div>
<style>div.above {
border: 2px solid blue;
background: red;
height: 1500px;
}
div.to-screenshot {
border: 2px solid blue;
background: green;
width: 50px;
height: 50px;
}
</style>
<div class="above"></div>
<div class="to-screenshot"></div>
`);
const elementHandle = await page.$('div.to-screenshot');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-scrolled-into-view.png');
});
it('should scroll 15000px into view', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.setContent(`
<div style="height: 14px">oooo</div>
<style>div.above {
border: 2px solid blue;
background: red;
height: 15000px;
}
div.to-screenshot {
border: 2px solid blue;
background: green;
width: 50px;
height: 50px;
}
</style>
<div class="above"></div>
<div class="to-screenshot"></div>
`);
const elementHandle = await page.$('div.to-screenshot');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-scrolled-into-view.png');
});
it('should work with a rotated element', async({page, server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.setContent(`<div style="position:absolute;
top: 100px;
left: 100px;
width: 100px;
height: 100px;
background: green;
transform: rotateZ(200deg);">&nbsp;</div>`);
const elementHandle = await page.$('div');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-rotate.png');
});
it('should fail to screenshot a detached element', async({page, server}) => {
await page.setContent('<h1>remove this</h1>');
const elementHandle = await page.$('h1');
await page.evaluate(element => element.remove(), elementHandle);
const screenshotError = await elementHandle.screenshot().catch(error => error);
expect(screenshotError.message).toContain('Node is detached');
});
it('should not hang with zero width/height element', async({page, server}) => {
await page.setContent('<div style="width: 50px; height: 0"></div>');
const div = await page.$('div');
const error = await div.screenshot().catch(e => e);
expect(error.message).toBe('Node has 0 height.');
});
it('should work for an element with fractional dimensions', async({page}) => {
await page.setContent('<div style="width:48.51px;height:19.8px;border:1px solid black;"></div>');
const elementHandle = await page.$('div');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-fractional.png');
});
it.skip(FFOX)('should work with a mobile viewport', async({browser, server}) => {
const context = await browser.newContext({viewport: { width: 320, height: 480, isMobile: true }});
const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(50, 100));
const elementHandle = await page.$('.box:nth-of-type(3)');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-mobile.png');
await context.close();
});
it.skip(FFOX)('should work with device scale factor', async({browser, server}) => {
const context = await browser.newContext({ viewport: { width: 320, height: 480 }, deviceScaleFactor: 2 });
const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(50, 100));
const elementHandle = await page.$('.box:nth-of-type(3)');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-mobile-dsf.png');
await context.close();
});
it('should work for an element with an offset', async({page}) => {
await page.setContent('<div style="position:absolute; top: 10.3px; left: 20.4px;width:50.3px;height:20.2px;border:1px solid black;"></div>');
const elementHandle = await page.$('div');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-fractional-offset.png');
});
it('should take screenshots when default viewport is null', async({server, browser}) => {
const context = await browser.newContext({ viewport: null });
const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html');
const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
const screenshot = await page.screenshot();
expect(screenshot).toBeInstanceOf(Buffer);
const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
expect(sizeBefore.width).toBe(sizeAfter.width);
expect(sizeBefore.height).toBe(sizeAfter.height);
await context.close();
});
it('should take fullPage screenshots when default viewport is null', async({server, browser}) => {
const context = await browser.newContext({ viewport: null });
const page = await context.newPage();
await page.goto(server.PREFIX + '/grid.html');
const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
const screenshot = await page.screenshot({
fullPage: true
});
expect(screenshot).toBeInstanceOf(Buffer);
const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
expect(sizeBefore.width).toBe(sizeAfter.width);
expect(sizeBefore.height).toBe(sizeAfter.height);
await context.close();
});
it('should restore default viewport after fullPage screenshot', async({ browser }) => {
const context = await browser.newContext({ viewport: { width: 456, height: 789 } });
const page = await context.newPage();
expect(page.viewportSize().width).toBe(456);
expect(page.viewportSize().height).toBe(789);
expect(await page.evaluate('window.innerWidth')).toBe(456);
expect(await page.evaluate('window.innerHeight')).toBe(789);
const screenshot = await page.screenshot({ fullPage: true });
expect(screenshot).toBeInstanceOf(Buffer);
expect(page.viewportSize().width).toBe(456);
expect(page.viewportSize().height).toBe(789);
expect(await page.evaluate('window.innerWidth')).toBe(456);
expect(await page.evaluate('window.innerHeight')).toBe(789);
await context.close();
});
it('should take element screenshot when default viewport is null and restore back', async({server, browser}) => {
const context = await browser.newContext({viewport: null});
const page = await context.newPage({ viewport: null });
await page.setContent(`
<div style="height: 14px">oooo</div>
<style>
div.to-screenshot {
border: 1px solid blue;
width: 600px;
height: 600px;
margin-left: 50px;
}
::-webkit-scrollbar{
display: none;
}
</style>
<div class="to-screenshot"></div>
<div class="to-screenshot"></div>
<div class="to-screenshot"></div>
`);
const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
const elementHandle = await page.$('div.to-screenshot');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeInstanceOf(Buffer);
const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
expect(sizeBefore.width).toBe(sizeAfter.width);
expect(sizeBefore.height).toBe(sizeAfter.height);
await context.close();
});
});

View file

@ -16,452 +16,446 @@
*/
const utils = require('./utils');
const {FFOX, CHROMIUM, WEBKIT} = utils.testOptions(browserType);
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({playwright, FFOX, CHROMIUM, WEBKIT}) {
describe('Page.waitFor', function() {
it('should wait for selector', async({page, server}) => {
let found = false;
const waitFor = page.waitFor('div').then(() => found = true);
await page.goto(server.EMPTY_PAGE);
expect(found).toBe(false);
await page.goto(server.PREFIX + '/grid.html');
await waitFor;
expect(found).toBe(true);
});
it('should wait for an xpath', async({page, server}) => {
let found = false;
const waitFor = page.waitFor('//div').then(() => found = true);
await page.goto(server.EMPTY_PAGE);
expect(found).toBe(false);
await page.goto(server.PREFIX + '/grid.html');
await waitFor;
expect(found).toBe(true);
});
it('should not allow you to select an element with single slash xpath', async({page, server}) => {
await page.setContent(`<div>some text</div>`);
let error = null;
await page.waitFor('/html/body/div').catch(e => error = e);
expect(error).toBeTruthy();
});
it('should timeout', async({page, server}) => {
const startTime = Date.now();
const timeout = 42;
await page.waitFor(timeout);
expect(Date.now() - startTime).not.toBeLessThan(timeout / 2);
});
it('should work with multiline body', async({page, server}) => {
const result = await page.waitForFunction(`
(() => true)()
`);
expect(await result.jsonValue()).toBe(true);
});
it('should wait for predicate', async({page, server}) => {
await Promise.all([
page.waitFor(() => window.innerWidth < 130), // Windows doesn't like windows below 120px wide
page.setViewportSize({width: 10, height: 10}),
]);
});
it('should throw when unknown type', async({page, server}) => {
let error = null;
await page.waitFor({foo: 'bar'}).catch(e => error = e);
expect(error.message).toContain('Unsupported target type');
});
it('should wait for predicate with arguments', async({page, server}) => {
await page.waitFor(({arg1, arg2}) => arg1 + arg2 === 3, {}, { arg1: 1, arg2: 2});
});
});
describe('Page.waitFor', function() {
it('should wait for selector', async({page, server}) => {
let found = false;
const waitFor = page.waitFor('div').then(() => found = true);
await page.goto(server.EMPTY_PAGE);
expect(found).toBe(false);
await page.goto(server.PREFIX + '/grid.html');
await waitFor;
expect(found).toBe(true);
});
it('should wait for an xpath', async({page, server}) => {
let found = false;
const waitFor = page.waitFor('//div').then(() => found = true);
await page.goto(server.EMPTY_PAGE);
expect(found).toBe(false);
await page.goto(server.PREFIX + '/grid.html');
await waitFor;
expect(found).toBe(true);
});
it('should not allow you to select an element with single slash xpath', async({page, server}) => {
await page.setContent(`<div>some text</div>`);
let error = null;
await page.waitFor('/html/body/div').catch(e => error = e);
expect(error).toBeTruthy();
});
it('should timeout', async({page, server}) => {
const startTime = Date.now();
const timeout = 42;
await page.waitFor(timeout);
expect(Date.now() - startTime).not.toBeLessThan(timeout / 2);
});
it('should work with multiline body', async({page, server}) => {
const result = await page.waitForFunction(`
(() => true)()
`);
expect(await result.jsonValue()).toBe(true);
});
it('should wait for predicate', async({page, server}) => {
await Promise.all([
page.waitFor(() => window.innerWidth < 130), // Windows doesn't like windows below 120px wide
page.setViewportSize({width: 10, height: 10}),
]);
});
it('should throw when unknown type', async({page, server}) => {
let error = null;
await page.waitFor({foo: 'bar'}).catch(e => error = e);
expect(error.message).toContain('Unsupported target type');
});
it('should wait for predicate with arguments', async({page, server}) => {
await page.waitFor(({arg1, arg2}) => arg1 + arg2 === 3, {}, { arg1: 1, arg2: 2});
describe('Frame.waitForFunction', function() {
it('should accept a string', async({page, server}) => {
const watchdog = page.waitForFunction('window.__FOO === 1');
await page.evaluate(() => window.__FOO = 1);
await watchdog;
});
it('should work when resolved right before execution context disposal', async({page, server}) => {
await page.addInitScript(() => window.__RELOADED = true);
await page.waitForFunction(() => {
if (!window.__RELOADED)
window.location.reload();
return true;
});
});
describe('Frame.waitForFunction', function() {
it('should accept a string', async({page, server}) => {
const watchdog = page.waitForFunction('window.__FOO === 1');
await page.evaluate(() => window.__FOO = 1);
await watchdog;
});
it('should work when resolved right before execution context disposal', async({page, server}) => {
await page.addInitScript(() => window.__RELOADED = true);
await page.waitForFunction(() => {
if (!window.__RELOADED)
window.location.reload();
return true;
});
});
it('should poll on interval', async({page, server}) => {
const polling = 100;
const timeDelta = await page.waitForFunction(() => {
if (!window.__startTime) {
window.__startTime = Date.now();
return false;
}
return Date.now() - window.__startTime;
}, {}, {polling});
expect(timeDelta).not.toBeLessThan(polling);
});
it('should poll on mutation', async({page, server}) => {
let success = false;
const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'mutation'})
.then(() => success = true);
await page.evaluate(() => window.__FOO = 'hit');
expect(success).toBe(false);
await page.evaluate(() => document.body.appendChild(document.createElement('div')));
await watchdog;
});
it('should poll on raf', async({page, server}) => {
const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'raf'});
await page.evaluate(() => window.__FOO = 'hit');
await watchdog;
});
it('should work with strict CSP policy', async({page, server}) => {
server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
await page.goto(server.EMPTY_PAGE);
let error = null;
await Promise.all([
page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'raf'}).catch(e => error = e),
page.evaluate(() => window.__FOO = 'hit')
]);
expect(error).toBe(null);
});
it('should throw on bad polling value', async({page, server}) => {
let error = null;
try {
await page.waitForFunction(() => !!document.body, {}, {polling: 'unknown'});
} catch (e) {
error = e;
it('should poll on interval', async({page, server}) => {
const polling = 100;
const timeDelta = await page.waitForFunction(() => {
if (!window.__startTime) {
window.__startTime = Date.now();
return false;
}
expect(error).toBeTruthy();
expect(error.message).toContain('polling');
});
it('should throw negative polling interval', async({page, server}) => {
let error = null;
try {
await page.waitForFunction(() => !!document.body, {}, {polling: -10});
} catch (e) {
error = e;
}
expect(error).toBeTruthy();
expect(error.message).toContain('Cannot poll with non-positive interval');
});
it('should return the success value as a JSHandle', async({page}) => {
expect(await (await page.waitForFunction(() => 5)).jsonValue()).toBe(5);
});
it('should return the window as a success value', async({ page }) => {
expect(await page.waitForFunction(() => window)).toBeTruthy();
});
it('should accept ElementHandle arguments', async({page}) => {
await page.setContent('<div></div>');
const div = await page.$('div');
let resolved = false;
const waitForFunction = page.waitForFunction(element => !element.parentElement, div).then(() => resolved = true);
expect(resolved).toBe(false);
await page.evaluate(element => element.remove(), div);
await waitForFunction;
});
it('should respect timeout', async({page}) => {
let error = null;
await page.waitForFunction('false', {}, {timeout: 10}).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('waiting for function failed: timeout');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});
it('should respect default timeout', async({page}) => {
page.setDefaultTimeout(1);
let error = null;
await page.waitForFunction('false').catch(e => error = e);
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
expect(error.message).toContain('waiting for function failed: timeout');
});
it('should disable timeout when its set to 0', async({page}) => {
const watchdog = page.waitForFunction(() => {
window.__counter = (window.__counter || 0) + 1;
return window.__injected;
}, {}, {timeout: 0, polling: 10});
await page.waitForFunction(() => window.__counter > 10);
await page.evaluate(() => window.__injected = true);
await watchdog;
});
it('should survive cross-process navigation', async({page, server}) => {
let fooFound = false;
const waitForFunction = page.waitForFunction('window.__FOO === 1').then(() => fooFound = true);
await page.goto(server.EMPTY_PAGE);
expect(fooFound).toBe(false);
await page.reload();
expect(fooFound).toBe(false);
await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
expect(fooFound).toBe(false);
await page.evaluate(() => window.__FOO = 1);
await waitForFunction;
expect(fooFound).toBe(true);
});
it('should survive navigations', async({page, server}) => {
const watchdog = page.waitForFunction(() => window.__done);
await page.goto(server.EMPTY_PAGE);
await page.goto(server.PREFIX + '/consolelog.html');
await page.evaluate(() => window.__done = true);
await watchdog;
});
return Date.now() - window.__startTime;
}, {}, {polling});
expect(timeDelta).not.toBeLessThan(polling);
});
describe('Frame.waitForSelector', function() {
const addElement = tag => document.body.appendChild(document.createElement(tag));
it('should immediately resolve promise if node exists', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame();
await frame.waitForSelector('*');
await frame.evaluate(addElement, 'div');
await frame.waitForSelector('div');
});
it('should work with removed MutationObserver', async({page, server}) => {
await page.evaluate(() => delete window.MutationObserver);
const [handle] = await Promise.all([
page.waitForSelector('.zombo'),
page.setContent(`<div class='zombo'>anything</div>`),
]);
expect(await page.evaluate(x => x.textContent, handle)).toBe('anything');
});
it('should resolve promise when node is added', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame();
const watchdog = frame.waitForSelector('div');
await frame.evaluate(addElement, 'br');
await frame.evaluate(addElement, 'div');
const eHandle = await watchdog;
const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
expect(tagName).toBe('DIV');
});
it('should work when node is added through innerHTML', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const watchdog = page.waitForSelector('h3 div');
await page.evaluate(addElement, 'span');
await page.evaluate(() => document.querySelector('span').innerHTML = '<h3><div></div></h3>');
await watchdog;
});
it('Page.$ waitFor is shortcut for main frame', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const otherFrame = page.frames()[1];
const watchdog = page.waitForSelector('div');
await otherFrame.evaluate(addElement, 'div');
await page.evaluate(addElement, 'div');
const eHandle = await watchdog;
expect(await eHandle.ownerFrame()).toBe(page.mainFrame());
});
it('should run in specified frame', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1];
const frame2 = page.frames()[2];
const waitForSelectorPromise = frame2.waitForSelector('div');
await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div');
const eHandle = await waitForSelectorPromise;
expect(await eHandle.ownerFrame()).toBe(frame2);
});
it('should throw when frame is detached', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
let waitError = null;
const waitPromise = frame.waitForSelector('.box').catch(e => waitError = e);
await utils.detachFrame(page, 'frame1');
await waitPromise;
expect(waitError).toBeTruthy();
expect(waitError.message).toContain('waitForFunction failed: frame got detached.');
});
it('should survive cross-process navigation', async({page, server}) => {
let boxFound = false;
const waitForSelector = page.waitForSelector('.box').then(() => boxFound = true);
await page.goto(server.EMPTY_PAGE);
expect(boxFound).toBe(false);
await page.reload();
expect(boxFound).toBe(false);
await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
await waitForSelector;
expect(boxFound).toBe(true);
});
it('should wait for visible', async({page, server}) => {
let divFound = false;
const waitForSelector = page.waitForSelector('div', { waitFor: 'visible' }).then(() => divFound = true);
await page.setContent(`<div style='display: none; visibility: hidden;'>1</div>`);
expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
expect(await waitForSelector).toBe(true);
expect(divFound).toBe(true);
});
it('should wait for visible recursively', async({page, server}) => {
let divVisible = false;
const waitForSelector = page.waitForSelector('div#inner', { waitFor: 'visible' }).then(() => divVisible = true);
await page.setContent(`<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>`);
expect(divVisible).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
expect(divVisible).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
expect(await waitForSelector).toBe(true);
expect(divVisible).toBe(true);
});
it('hidden should wait for hidden', async({page, server}) => {
let divHidden = false;
await page.setContent(`<div style='display: block;'></div>`);
const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divHidden = true);
await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden'));
expect(await waitForSelector).toBe(true);
expect(divHidden).toBe(true);
});
it('hidden should wait for display: none', async({page, server}) => {
let divHidden = false;
await page.setContent(`<div style='display: block;'></div>`);
const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divHidden = true);
await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none'));
expect(await waitForSelector).toBe(true);
expect(divHidden).toBe(true);
});
it('hidden should wait for removal', async({page, server}) => {
await page.setContent(`<div></div>`);
let divRemoved = false;
const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divRemoved = true);
await page.waitForSelector('div'); // do a round trip
expect(divRemoved).toBe(false);
await page.evaluate(() => document.querySelector('div').remove());
expect(await waitForSelector).toBe(true);
expect(divRemoved).toBe(true);
});
it('should return null if waiting to hide non-existing element', async({page, server}) => {
const handle = await page.waitForSelector('non-existing', { waitFor: 'hidden' });
expect(handle).toBe(null);
});
it('should respect timeout', async({page, server}) => {
let error = null;
await page.waitForSelector('div', { timeout: 10 }).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "div" failed: timeout');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});
it('should have an error message specifically for awaiting an element to be hidden', async({page, server}) => {
await page.setContent(`<div></div>`);
let error = null;
await page.waitForSelector('div', { waitFor: 'hidden', timeout: 10 }).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "[hidden] div" failed: timeout');
});
it('should respond to node attribute mutation', async({page, server}) => {
let divFound = false;
const waitForSelector = page.waitForSelector('.zombo').then(() => divFound = true);
await page.setContent(`<div class='notZombo'></div>`);
expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').className = 'zombo');
expect(await waitForSelector).toBe(true);
});
it('should return the element handle', async({page, server}) => {
const waitForSelector = page.waitForSelector('.zombo');
await page.setContent(`<div class='zombo'>anything</div>`);
expect(await page.evaluate(x => x.textContent, await waitForSelector)).toBe('anything');
});
it('should have correct stack trace for timeout', async({page, server}) => {
let error;
await page.waitForSelector('.zombo', { timeout: 10 }).catch(e => error = e);
expect(error.stack).toContain('waittask.spec.js');
});
it('should throw for unknown waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { waitFor: 'foo' }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor option');
});
it('should throw for visibility option', async({page, server}) => {
await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { visibility: 'hidden' }).catch(e => e);
expect(error.message).toBe('options.visibility is not supported, did you mean options.waitFor?');
});
it('should throw for true waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { waitFor: true }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor option');
});
it('should throw for false waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { waitFor: false }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor option');
});
it('should support >> selector syntax', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame();
const watchdog = frame.waitForSelector('css=div >> css=span');
await frame.evaluate(addElement, 'br');
await frame.evaluate(addElement, 'div');
await frame.evaluate(() => document.querySelector('div').appendChild(document.createElement('span')));
const eHandle = await watchdog;
const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
expect(tagName).toBe('SPAN');
});
it('should wait for detached if already detached', async({page, server}) => {
await page.setContent('<section id="testAttribute">43543</section>');
expect(await page.waitForSelector('css=div', { waitFor: 'detached'})).toBe(null);
});
it('should wait for detached', async({page, server}) => {
await page.setContent('<section id="testAttribute"><div>43543</div></section>');
let done = false;
const waitFor = page.waitForSelector('css=div', { waitFor: 'detached'}).then(() => done = true);
expect(done).toBe(false);
await page.waitForSelector('css=section');
expect(done).toBe(false);
await page.$eval('div', div => div.remove());
expect(await waitFor).toBe(true);
expect(done).toBe(true);
});
it('should poll on mutation', async({page, server}) => {
let success = false;
const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'mutation'})
.then(() => success = true);
await page.evaluate(() => window.__FOO = 'hit');
expect(success).toBe(false);
await page.evaluate(() => document.body.appendChild(document.createElement('div')));
await watchdog;
});
describe('Frame.waitForSelector xpath', function() {
const addElement = tag => document.body.appendChild(document.createElement(tag));
it('should support some fancy xpath', async({page, server}) => {
await page.setContent(`<p>red herring</p><p>hello world </p>`);
const waitForXPath = page.waitForSelector('//p[normalize-space(.)="hello world"]');
expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('hello world ');
});
it('should respect timeout', async({page}) => {
let error = null;
await page.waitForSelector('//div', { timeout: 10 }).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "//div" failed: timeout');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});
it('should run in specified frame', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1];
const frame2 = page.frames()[2];
const waitForXPathPromise = frame2.waitForSelector('//div');
await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div');
const eHandle = await waitForXPathPromise;
expect(await eHandle.ownerFrame()).toBe(frame2);
});
it('should throw when frame is detached', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
let waitError = null;
const waitPromise = frame.waitForSelector('//*[@class="box"]').catch(e => waitError = e);
await utils.detachFrame(page, 'frame1');
await waitPromise;
expect(waitError).toBeTruthy();
expect(waitError.message).toContain('waitForFunction failed: frame got detached.');
});
it('should return the element handle', async({page, server}) => {
const waitForXPath = page.waitForSelector('//*[@class="zombo"]');
await page.setContent(`<div class='zombo'>anything</div>`);
expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('anything');
});
it('should allow you to select an element with single slash', async({page, server}) => {
await page.setContent(`<div>some text</div>`);
const waitForXPath = page.waitForSelector('//html/body/div');
expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('some text');
});
it('should poll on raf', async({page, server}) => {
const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'raf'});
await page.evaluate(() => window.__FOO = 'hit');
await watchdog;
});
it('should work with strict CSP policy', async({page, server}) => {
server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
await page.goto(server.EMPTY_PAGE);
let error = null;
await Promise.all([
page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'raf'}).catch(e => error = e),
page.evaluate(() => window.__FOO = 'hit')
]);
expect(error).toBe(null);
});
it('should throw on bad polling value', async({page, server}) => {
let error = null;
try {
await page.waitForFunction(() => !!document.body, {}, {polling: 'unknown'});
} catch (e) {
error = e;
}
expect(error).toBeTruthy();
expect(error.message).toContain('polling');
});
it('should throw negative polling interval', async({page, server}) => {
let error = null;
try {
await page.waitForFunction(() => !!document.body, {}, {polling: -10});
} catch (e) {
error = e;
}
expect(error).toBeTruthy();
expect(error.message).toContain('Cannot poll with non-positive interval');
});
it('should return the success value as a JSHandle', async({page}) => {
expect(await (await page.waitForFunction(() => 5)).jsonValue()).toBe(5);
});
it('should return the window as a success value', async({ page }) => {
expect(await page.waitForFunction(() => window)).toBeTruthy();
});
it('should accept ElementHandle arguments', async({page}) => {
await page.setContent('<div></div>');
const div = await page.$('div');
let resolved = false;
const waitForFunction = page.waitForFunction(element => !element.parentElement, div).then(() => resolved = true);
expect(resolved).toBe(false);
await page.evaluate(element => element.remove(), div);
await waitForFunction;
});
it('should respect timeout', async({page}) => {
let error = null;
await page.waitForFunction('false', {}, {timeout: 10}).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('waiting for function failed: timeout');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});
it('should respect default timeout', async({page}) => {
page.setDefaultTimeout(1);
let error = null;
await page.waitForFunction('false').catch(e => error = e);
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
expect(error.message).toContain('waiting for function failed: timeout');
});
it('should disable timeout when its set to 0', async({page}) => {
const watchdog = page.waitForFunction(() => {
window.__counter = (window.__counter || 0) + 1;
return window.__injected;
}, {}, {timeout: 0, polling: 10});
await page.waitForFunction(() => window.__counter > 10);
await page.evaluate(() => window.__injected = true);
await watchdog;
});
it('should survive cross-process navigation', async({page, server}) => {
let fooFound = false;
const waitForFunction = page.waitForFunction('window.__FOO === 1').then(() => fooFound = true);
await page.goto(server.EMPTY_PAGE);
expect(fooFound).toBe(false);
await page.reload();
expect(fooFound).toBe(false);
await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
expect(fooFound).toBe(false);
await page.evaluate(() => window.__FOO = 1);
await waitForFunction;
expect(fooFound).toBe(true);
});
it('should survive navigations', async({page, server}) => {
const watchdog = page.waitForFunction(() => window.__done);
await page.goto(server.EMPTY_PAGE);
await page.goto(server.PREFIX + '/consolelog.html');
await page.evaluate(() => window.__done = true);
await watchdog;
});
});
};
describe('Frame.waitForSelector', function() {
const addElement = tag => document.body.appendChild(document.createElement(tag));
it('should immediately resolve promise if node exists', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame();
await frame.waitForSelector('*');
await frame.evaluate(addElement, 'div');
await frame.waitForSelector('div');
});
it('should work with removed MutationObserver', async({page, server}) => {
await page.evaluate(() => delete window.MutationObserver);
const [handle] = await Promise.all([
page.waitForSelector('.zombo'),
page.setContent(`<div class='zombo'>anything</div>`),
]);
expect(await page.evaluate(x => x.textContent, handle)).toBe('anything');
});
it('should resolve promise when node is added', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame();
const watchdog = frame.waitForSelector('div');
await frame.evaluate(addElement, 'br');
await frame.evaluate(addElement, 'div');
const eHandle = await watchdog;
const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
expect(tagName).toBe('DIV');
});
it('should work when node is added through innerHTML', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const watchdog = page.waitForSelector('h3 div');
await page.evaluate(addElement, 'span');
await page.evaluate(() => document.querySelector('span').innerHTML = '<h3><div></div></h3>');
await watchdog;
});
it('Page.$ waitFor is shortcut for main frame', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const otherFrame = page.frames()[1];
const watchdog = page.waitForSelector('div');
await otherFrame.evaluate(addElement, 'div');
await page.evaluate(addElement, 'div');
const eHandle = await watchdog;
expect(await eHandle.ownerFrame()).toBe(page.mainFrame());
});
it('should run in specified frame', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1];
const frame2 = page.frames()[2];
const waitForSelectorPromise = frame2.waitForSelector('div');
await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div');
const eHandle = await waitForSelectorPromise;
expect(await eHandle.ownerFrame()).toBe(frame2);
});
it('should throw when frame is detached', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
let waitError = null;
const waitPromise = frame.waitForSelector('.box').catch(e => waitError = e);
await utils.detachFrame(page, 'frame1');
await waitPromise;
expect(waitError).toBeTruthy();
expect(waitError.message).toContain('waitForFunction failed: frame got detached.');
});
it('should survive cross-process navigation', async({page, server}) => {
let boxFound = false;
const waitForSelector = page.waitForSelector('.box').then(() => boxFound = true);
await page.goto(server.EMPTY_PAGE);
expect(boxFound).toBe(false);
await page.reload();
expect(boxFound).toBe(false);
await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
await waitForSelector;
expect(boxFound).toBe(true);
});
it('should wait for visible', async({page, server}) => {
let divFound = false;
const waitForSelector = page.waitForSelector('div', { waitFor: 'visible' }).then(() => divFound = true);
await page.setContent(`<div style='display: none; visibility: hidden;'>1</div>`);
expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
expect(await waitForSelector).toBe(true);
expect(divFound).toBe(true);
});
it('should wait for visible recursively', async({page, server}) => {
let divVisible = false;
const waitForSelector = page.waitForSelector('div#inner', { waitFor: 'visible' }).then(() => divVisible = true);
await page.setContent(`<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>`);
expect(divVisible).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
expect(divVisible).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
expect(await waitForSelector).toBe(true);
expect(divVisible).toBe(true);
});
it('hidden should wait for hidden', async({page, server}) => {
let divHidden = false;
await page.setContent(`<div style='display: block;'></div>`);
const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divHidden = true);
await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden'));
expect(await waitForSelector).toBe(true);
expect(divHidden).toBe(true);
});
it('hidden should wait for display: none', async({page, server}) => {
let divHidden = false;
await page.setContent(`<div style='display: block;'></div>`);
const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divHidden = true);
await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none'));
expect(await waitForSelector).toBe(true);
expect(divHidden).toBe(true);
});
it('hidden should wait for removal', async({page, server}) => {
await page.setContent(`<div></div>`);
let divRemoved = false;
const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divRemoved = true);
await page.waitForSelector('div'); // do a round trip
expect(divRemoved).toBe(false);
await page.evaluate(() => document.querySelector('div').remove());
expect(await waitForSelector).toBe(true);
expect(divRemoved).toBe(true);
});
it('should return null if waiting to hide non-existing element', async({page, server}) => {
const handle = await page.waitForSelector('non-existing', { waitFor: 'hidden' });
expect(handle).toBe(null);
});
it('should respect timeout', async({page, server}) => {
let error = null;
await page.waitForSelector('div', { timeout: 10 }).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "div" failed: timeout');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});
it('should have an error message specifically for awaiting an element to be hidden', async({page, server}) => {
await page.setContent(`<div></div>`);
let error = null;
await page.waitForSelector('div', { waitFor: 'hidden', timeout: 10 }).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "[hidden] div" failed: timeout');
});
it('should respond to node attribute mutation', async({page, server}) => {
let divFound = false;
const waitForSelector = page.waitForSelector('.zombo').then(() => divFound = true);
await page.setContent(`<div class='notZombo'></div>`);
expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').className = 'zombo');
expect(await waitForSelector).toBe(true);
});
it('should return the element handle', async({page, server}) => {
const waitForSelector = page.waitForSelector('.zombo');
await page.setContent(`<div class='zombo'>anything</div>`);
expect(await page.evaluate(x => x.textContent, await waitForSelector)).toBe('anything');
});
it('should have correct stack trace for timeout', async({page, server}) => {
let error;
await page.waitForSelector('.zombo', { timeout: 10 }).catch(e => error = e);
expect(error.stack).toContain('waittask.spec.js');
});
it('should throw for unknown waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { waitFor: 'foo' }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor option');
});
it('should throw for visibility option', async({page, server}) => {
await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { visibility: 'hidden' }).catch(e => e);
expect(error.message).toBe('options.visibility is not supported, did you mean options.waitFor?');
});
it('should throw for true waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { waitFor: true }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor option');
});
it('should throw for false waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { waitFor: false }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor option');
});
it('should support >> selector syntax', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame();
const watchdog = frame.waitForSelector('css=div >> css=span');
await frame.evaluate(addElement, 'br');
await frame.evaluate(addElement, 'div');
await frame.evaluate(() => document.querySelector('div').appendChild(document.createElement('span')));
const eHandle = await watchdog;
const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
expect(tagName).toBe('SPAN');
});
it('should wait for detached if already detached', async({page, server}) => {
await page.setContent('<section id="testAttribute">43543</section>');
expect(await page.waitForSelector('css=div', { waitFor: 'detached'})).toBe(null);
});
it('should wait for detached', async({page, server}) => {
await page.setContent('<section id="testAttribute"><div>43543</div></section>');
let done = false;
const waitFor = page.waitForSelector('css=div', { waitFor: 'detached'}).then(() => done = true);
expect(done).toBe(false);
await page.waitForSelector('css=section');
expect(done).toBe(false);
await page.$eval('div', div => div.remove());
expect(await waitFor).toBe(true);
expect(done).toBe(true);
});
});
describe('Frame.waitForSelector xpath', function() {
const addElement = tag => document.body.appendChild(document.createElement(tag));
it('should support some fancy xpath', async({page, server}) => {
await page.setContent(`<p>red herring</p><p>hello world </p>`);
const waitForXPath = page.waitForSelector('//p[normalize-space(.)="hello world"]');
expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('hello world ');
});
it('should respect timeout', async({page}) => {
let error = null;
await page.waitForSelector('//div', { timeout: 10 }).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "//div" failed: timeout');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});
it('should run in specified frame', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1];
const frame2 = page.frames()[2];
const waitForXPathPromise = frame2.waitForSelector('//div');
await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div');
const eHandle = await waitForXPathPromise;
expect(await eHandle.ownerFrame()).toBe(frame2);
});
it('should throw when frame is detached', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
let waitError = null;
const waitPromise = frame.waitForSelector('//*[@class="box"]').catch(e => waitError = e);
await utils.detachFrame(page, 'frame1');
await waitPromise;
expect(waitError).toBeTruthy();
expect(waitError.message).toContain('waitForFunction failed: frame got detached.');
});
it('should return the element handle', async({page, server}) => {
const waitForXPath = page.waitForSelector('//*[@class="zombo"]');
await page.setContent(`<div class='zombo'>anything</div>`);
expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('anything');
});
it('should allow you to select an element with single slash', async({page, server}) => {
await page.setContent(`<div>some text</div>`);
const waitForXPath = page.waitForSelector('//html/body/div');
expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('some text');
});
});

View file

@ -15,126 +15,122 @@
* limitations under the License.
*/
/**
* @type {PageTestSuite}
*/
module.exports.describe = function({FFOX, CHROMIUM, WEBKIT, LINUX}) {
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
describe('Workers', function() {
it('Page.workers', async function({page, server}) {
await Promise.all([
page.waitForEvent('worker'),
page.goto(server.PREFIX + '/worker/worker.html')]);
const worker = page.workers()[0];
expect(worker.url()).toContain('worker.js');
describe('Workers', function() {
it('Page.workers', async function({page, server}) {
await Promise.all([
page.waitForEvent('worker'),
page.goto(server.PREFIX + '/worker/worker.html')]);
const worker = page.workers()[0];
expect(worker.url()).toContain('worker.js');
expect(await worker.evaluate(() => self['workerFunction']())).toBe('worker function result');
expect(await worker.evaluate(() => self['workerFunction']())).toBe('worker function result');
await page.goto(server.EMPTY_PAGE);
expect(page.workers().length).toBe(0);
});
it('should emit created and destroyed events', async function({page}) {
const workerCreatedPromise = page.waitForEvent('worker');
const workerObj = await page.evaluateHandle(() => new Worker(URL.createObjectURL(new Blob(['1'], {type: 'application/javascript'}))));
const worker = await workerCreatedPromise;
const workerThisObj = await worker.evaluateHandle(() => this);
const workerDestroyedPromise = new Promise(x => worker.once('close', x));
await page.evaluate(workerObj => workerObj.terminate(), workerObj);
expect(await workerDestroyedPromise).toBe(worker);
const error = await workerThisObj.getProperty('self').catch(error => error);
expect(error.message).toContain('Most likely the worker has been closed.');
});
it('should report console logs', async function({page}) {
const [message] = await Promise.all([
page.waitForEvent('console'),
page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))),
]);
expect(message.text()).toBe('1');
});
it('should have JSHandles for console logs', async function({page}) {
const logPromise = new Promise(x => page.on('console', x));
await page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1,2,3,this)'], {type: 'application/javascript'}))));
const log = await logPromise;
expect(log.text()).toBe('1 2 3 JSHandle@object');
expect(log.args().length).toBe(4);
expect(await (await log.args()[3].getProperty('origin')).jsonValue()).toBe('null');
});
it('should evaluate', async function({page}) {
const workerCreatedPromise = page.waitForEvent('worker');
page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
const worker = await workerCreatedPromise;
expect(await worker.evaluate('1+1')).toBe(2);
});
it('should report errors', async function({page}) {
const errorPromise = new Promise(x => page.on('pageerror', x));
page.evaluate(() => new Worker(URL.createObjectURL(new Blob([`setTimeout(() => { throw new Error('this is my error'); })`], {type: 'application/javascript'}))));
const errorLog = await errorPromise;
expect(errorLog.message).toContain('this is my error');
});
it('should clear upon navigation', async function({server, page}) {
await page.goto(server.EMPTY_PAGE);
const workerCreatedPromise = page.waitForEvent('worker');
page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
const worker = await workerCreatedPromise;
expect(page.workers().length).toBe(1);
let destroyed = false;
worker.once('close', () => destroyed = true);
await page.goto(server.PREFIX + '/one-style.html');
expect(destroyed).toBe(true);
expect(page.workers().length).toBe(0);
});
it('should clear upon cross-process navigation', async function({server, page}) {
await page.goto(server.EMPTY_PAGE);
const workerCreatedPromise = page.waitForEvent('worker');
page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
const worker = await workerCreatedPromise;
expect(page.workers().length).toBe(1);
let destroyed = false;
worker.once('close', () => destroyed = true);
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
expect(destroyed).toBe(true);
expect(page.workers().length).toBe(0);
});
it('should report network activity', async function({page, server}) {
const [worker] = await Promise.all([
page.waitForEvent('worker'),
page.goto(server.PREFIX + '/worker/worker.html'),
]);
const url = server.PREFIX + '/one-style.css';
const requestPromise = page.waitForRequest(url);
const responsePromise = page.waitForResponse(url);
await worker.evaluate(url => fetch(url).then(response => response.text()).then(console.log), url);
const request = await requestPromise;
const response = await responsePromise;
expect(request.url()).toBe(url);
expect(response.request()).toBe(request);
expect(response.ok()).toBe(true);
});
it('should report network activity on worker creation', async function({page, server}) {
// Chromium needs waitForDebugger enabled for this one.
await page.goto(server.EMPTY_PAGE);
const url = server.PREFIX + '/one-style.css';
const requestPromise = page.waitForRequest(url);
const responsePromise = page.waitForResponse(url);
await page.evaluate(url => new Worker(URL.createObjectURL(new Blob([`
fetch("${url}").then(response => response.text()).then(console.log);
`], {type: 'application/javascript'}))), url);
const request = await requestPromise;
const response = await responsePromise;
expect(request.url()).toBe(url);
expect(response.request()).toBe(request);
expect(response.ok()).toBe(true);
});
it('should format number using context locale', async({browser, server}) => {
const context = await browser.newContext({ locale: 'ru-RU' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [worker] = await Promise.all([
page.waitForEvent('worker'),
page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))),
]);
expect(await worker.evaluate(() => (10000.20).toLocaleString())).toBe('10\u00A0000,2');
await context.close();
});
await page.goto(server.EMPTY_PAGE);
expect(page.workers().length).toBe(0);
});
};
it('should emit created and destroyed events', async function({page}) {
const workerCreatedPromise = page.waitForEvent('worker');
const workerObj = await page.evaluateHandle(() => new Worker(URL.createObjectURL(new Blob(['1'], {type: 'application/javascript'}))));
const worker = await workerCreatedPromise;
const workerThisObj = await worker.evaluateHandle(() => this);
const workerDestroyedPromise = new Promise(x => worker.once('close', x));
await page.evaluate(workerObj => workerObj.terminate(), workerObj);
expect(await workerDestroyedPromise).toBe(worker);
const error = await workerThisObj.getProperty('self').catch(error => error);
expect(error.message).toContain('Most likely the worker has been closed.');
});
it('should report console logs', async function({page}) {
const [message] = await Promise.all([
page.waitForEvent('console'),
page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))),
]);
expect(message.text()).toBe('1');
});
it('should have JSHandles for console logs', async function({page}) {
const logPromise = new Promise(x => page.on('console', x));
await page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1,2,3,this)'], {type: 'application/javascript'}))));
const log = await logPromise;
expect(log.text()).toBe('1 2 3 JSHandle@object');
expect(log.args().length).toBe(4);
expect(await (await log.args()[3].getProperty('origin')).jsonValue()).toBe('null');
});
it('should evaluate', async function({page}) {
const workerCreatedPromise = page.waitForEvent('worker');
page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
const worker = await workerCreatedPromise;
expect(await worker.evaluate('1+1')).toBe(2);
});
it('should report errors', async function({page}) {
const errorPromise = new Promise(x => page.on('pageerror', x));
page.evaluate(() => new Worker(URL.createObjectURL(new Blob([`setTimeout(() => { throw new Error('this is my error'); })`], {type: 'application/javascript'}))));
const errorLog = await errorPromise;
expect(errorLog.message).toContain('this is my error');
});
it('should clear upon navigation', async function({server, page}) {
await page.goto(server.EMPTY_PAGE);
const workerCreatedPromise = page.waitForEvent('worker');
page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
const worker = await workerCreatedPromise;
expect(page.workers().length).toBe(1);
let destroyed = false;
worker.once('close', () => destroyed = true);
await page.goto(server.PREFIX + '/one-style.html');
expect(destroyed).toBe(true);
expect(page.workers().length).toBe(0);
});
it('should clear upon cross-process navigation', async function({server, page}) {
await page.goto(server.EMPTY_PAGE);
const workerCreatedPromise = page.waitForEvent('worker');
page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
const worker = await workerCreatedPromise;
expect(page.workers().length).toBe(1);
let destroyed = false;
worker.once('close', () => destroyed = true);
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
expect(destroyed).toBe(true);
expect(page.workers().length).toBe(0);
});
it('should report network activity', async function({page, server}) {
const [worker] = await Promise.all([
page.waitForEvent('worker'),
page.goto(server.PREFIX + '/worker/worker.html'),
]);
const url = server.PREFIX + '/one-style.css';
const requestPromise = page.waitForRequest(url);
const responsePromise = page.waitForResponse(url);
await worker.evaluate(url => fetch(url).then(response => response.text()).then(console.log), url);
const request = await requestPromise;
const response = await responsePromise;
expect(request.url()).toBe(url);
expect(response.request()).toBe(request);
expect(response.ok()).toBe(true);
});
it('should report network activity on worker creation', async function({page, server}) {
// Chromium needs waitForDebugger enabled for this one.
await page.goto(server.EMPTY_PAGE);
const url = server.PREFIX + '/one-style.css';
const requestPromise = page.waitForRequest(url);
const responsePromise = page.waitForResponse(url);
await page.evaluate(url => new Worker(URL.createObjectURL(new Blob([`
fetch("${url}").then(response => response.text()).then(console.log);
`], {type: 'application/javascript'}))), url);
const request = await requestPromise;
const response = await responsePromise;
expect(request.url()).toBe(url);
expect(response.request()).toBe(request);
expect(response.ok()).toBe(true);
});
it('should format number using context locale', async({browser, server}) => {
const context = await browser.newContext({ locale: 'ru-RU' });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [worker] = await Promise.all([
page.waitForEvent('worker'),
page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))),
]);
expect(await worker.evaluate(() => (10000.20).toLocaleString())).toBe('10\u00A0000,2');
await context.close();
});
});