diff --git a/test/accessibility.spec.js b/test/accessibility.spec.js index 723f5f843f..9556793a46 100644 --- a/test/accessibility.spec.js +++ b/test/accessibility.spec.js @@ -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(` + + Accessibility Test + + +

Inputs

+ + + + + + + + + `); + // autofocus happens after a delay in chrome these days + await page.waitForFunction(() => document.activeElement.hasAttribute('autofocus')); + + const golden = FFOX ? { + role: 'document', + name: 'Accessibility Test', + children: [ + {role: 'heading', name: 'Inputs', level: 1}, + {role: 'textbox', name: 'Empty input', focused: true}, + {role: 'textbox', name: 'readonly input', readonly: true}, + {role: 'textbox', name: 'disabled input', disabled: true}, + {role: 'textbox', name: 'Input with whitespace', value: ' '}, + {role: 'textbox', name: '', value: 'value only'}, + {role: 'textbox', name: '', value: 'and a value'}, // firefox doesn't use aria-placeholder for the name + {role: 'textbox', name: '', value: 'and a value', description: 'This is a description!'}, // and here + ] + } : CHROMIUM ? { + role: 'WebArea', + name: 'Accessibility Test', + children: [ + {role: 'heading', name: 'Inputs', level: 1}, + {role: 'textbox', name: 'Empty input', focused: true}, + {role: 'textbox', name: 'readonly input', readonly: true}, + {role: 'textbox', name: 'disabled input', disabled: true}, + {role: 'textbox', name: 'Input with whitespace', value: ' '}, + {role: 'textbox', name: '', value: 'value only'}, + {role: 'textbox', name: 'placeholder', value: 'and a value'}, + {role: 'textbox', name: 'placeholder', value: 'and a value', description: 'This is a description!'}, + ] + } : { + role: 'WebArea', + name: 'Accessibility Test', + children: [ + {role: 'heading', name: 'Inputs', level: 1}, + {role: 'textbox', name: 'Empty input', focused: true}, + {role: 'textbox', name: 'readonly input', readonly: true}, + {role: 'textbox', name: 'disabled input', disabled: true}, + {role: 'textbox', name: 'Input with whitespace', value: ' ' }, + {role: 'textbox', name: '', value: 'value only' }, + {role: 'textbox', name: 'placeholder', value: 'and a value'}, + {role: 'textbox', name: 'This is a description!',value: 'and a value'}, // webkit uses the description over placeholder for the name + ] + }; + expect(await page.accessibility.snapshot()).toEqual(golden); + }); + it('should work with regular text', async({page}) => { + await page.setContent(`
Hello World
`); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual({ + role: FFOX ? 'text leaf' : 'text', + name: 'Hello World', + }); + }); + it('roledescription', async({page}) => { + await page.setContent('
Hi
'); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0].roledescription).toEqual('foo'); + }); + it('orientation', async({page}) => { + await page.setContent('11'); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0].orientation).toEqual('vertical'); + }); + it('autocomplete', async({page}) => { + await page.setContent('
hi
'); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0].autocomplete).toEqual('list'); + }); + it('multiselectable', async({page}) => { + await page.setContent('
hey
'); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0].multiselectable).toEqual(true); + }); + it('keyshortcuts', async({page}) => { + await page.setContent('
hey
'); + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0].keyshortcuts).toEqual('foo'); + }); + describe('filtering children of leaf nodes', function() { + it('should not report text nodes inside controls', async function({page}) { await page.setContent(` - - Accessibility Test - - -

Inputs

- - - - - - - - - `); - // autofocus happens after a delay in chrome these days - await page.waitForFunction(() => document.activeElement.hasAttribute('autofocus')); - - const golden = FFOX ? { - role: 'document', - name: 'Accessibility Test', - children: [ - {role: 'heading', name: 'Inputs', level: 1}, - {role: 'textbox', name: 'Empty input', focused: true}, - {role: 'textbox', name: 'readonly input', readonly: true}, - {role: 'textbox', name: 'disabled input', disabled: true}, - {role: 'textbox', name: 'Input with whitespace', value: ' '}, - {role: 'textbox', name: '', value: 'value only'}, - {role: 'textbox', name: '', value: 'and a value'}, // firefox doesn't use aria-placeholder for the name - {role: 'textbox', name: '', value: 'and a value', description: 'This is a description!'}, // and here - ] - } : CHROMIUM ? { - role: 'WebArea', - name: 'Accessibility Test', - children: [ - {role: 'heading', name: 'Inputs', level: 1}, - {role: 'textbox', name: 'Empty input', focused: true}, - {role: 'textbox', name: 'readonly input', readonly: true}, - {role: 'textbox', name: 'disabled input', disabled: true}, - {role: 'textbox', name: 'Input with whitespace', value: ' '}, - {role: 'textbox', name: '', value: 'value only'}, - {role: 'textbox', name: 'placeholder', value: 'and a value'}, - {role: 'textbox', name: 'placeholder', value: 'and a value', description: 'This is a description!'}, - ] - } : { - role: 'WebArea', - name: 'Accessibility Test', - children: [ - {role: 'heading', name: 'Inputs', level: 1}, - {role: 'textbox', name: 'Empty input', focused: true}, - {role: 'textbox', name: 'readonly input', readonly: true}, - {role: 'textbox', name: 'disabled input', disabled: true}, - {role: 'textbox', name: 'Input with whitespace', value: ' ' }, - {role: 'textbox', name: '', value: 'value only' }, - {role: 'textbox', name: 'placeholder', value: 'and a value'}, - {role: 'textbox', name: 'This is a description!',value: 'and a value'}, // webkit uses the description over placeholder for the name - ] +
+
Tab1
+
Tab2
+
`); + const golden = { + role: FFOX ? 'document' : 'WebArea', + name: '', + children: [{ + role: 'tab', + name: 'Tab1', + selected: true + }, { + role: 'tab', + name: 'Tab2' + }] }; expect(await page.accessibility.snapshot()).toEqual(golden); }); - it('should work with regular text', async({page}) => { - await page.setContent(`
Hello World
`); + // WebKit rich text accessibility is iffy + it.skip(WEBKIT)('rich text editable fields should have children', async function({page}) { + await page.setContent(` +
+ Edit this image: my fake image +
`); + const golden = FFOX ? { + role: 'section', + name: '', + children: [{ + role: 'text leaf', + name: 'Edit this image: ' + }, { + role: 'text', + name: 'my fake image' + }] + } : { + role: 'generic', + name: '', + value: 'Edit this image: ', + children: [{ + role: 'text', + name: 'Edit this image:' + }, { + role: 'img', + name: 'my fake image' + }] + }; const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual({ - role: FFOX ? 'text leaf' : 'text', - name: 'Hello World', - }); + expect(snapshot.children[0]).toEqual(golden); }); - it('roledescription', async({page}) => { - await page.setContent('
Hi
'); + // WebKit rich text accessibility is iffy + it.skip(WEBKIT)('rich text editable fields with role should have children', async function({page}) { + await page.setContent(` +
+ Edit this image: my fake image +
`); + const golden = FFOX ? { + role: 'textbox', + name: '', + value: 'Edit this image: my fake image', + children: [{ + role: 'text', + name: 'my fake image' + }] + } : { + role: 'textbox', + name: '', + value: 'Edit this image: ', + children: [{ + role: 'text', + name: 'Edit this image:' + }, { + role: 'img', + name: 'my fake image' + }] + }; const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0].roledescription).toEqual('foo'); + expect(snapshot.children[0]).toEqual(golden); }); - it('orientation', async({page}) => { - await page.setContent('11'); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0].orientation).toEqual('vertical'); - }); - it('autocomplete', async({page}) => { - await page.setContent('
hi
'); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0].autocomplete).toEqual('list'); - }); - it('multiselectable', async({page}) => { - await page.setContent('
hey
'); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0].multiselectable).toEqual(true); - }); - it('keyshortcuts', async({page}) => { - await page.setContent('
hey
'); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0].keyshortcuts).toEqual('foo'); - }); - describe('filtering children of leaf nodes', function() { - it('should not report text nodes inside controls', async function({page}) { + // 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(` -
-
Tab1
-
Tab2
-
`); - const golden = { - role: FFOX ? 'document' : 'WebArea', +
Edit this image:my fake image
`); + 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(` -
- Edit this image: my fake image -
`); - const golden = FFOX ? { - role: 'section', - name: '', - children: [{ - role: 'text leaf', - name: 'Edit this image: ' - }, { - role: 'text', - name: 'my fake image' - }] - } : { +
Edit this image:my fake image
`); + 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(` -
- Edit this image: my fake image -
`); - const golden = FFOX ? { - role: 'textbox', - name: '', - value: 'Edit this image: my fake image', - children: [{ - role: 'text', - name: 'my fake image' - }] - } : { - role: 'textbox', - name: '', - value: 'Edit this image: ', - children: [{ - role: 'text', - name: 'Edit this image:' - }, { - role: 'img', - name: 'my fake image' - }] - }; +
Edit this image:my fake image
`); const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual(golden); - }); - // Firefox does not support contenteditable="plaintext-only". - // WebKit rich text accessibility is iffy - describe.skip(FFOX || WEBKIT)('plaintext contenteditable', function() { - it('plain text field with role should not have children', async function({page}) { - await page.setContent(` -
Edit this image:my fake image
`); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual({ - role: 'textbox', - name: '', - value: 'Edit this image:' - }); - }); - it('plain text field without role should not have content', async function({page}) { - await page.setContent(` -
Edit this image:my fake image
`); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual({ - role: 'generic', - name: '' - }); - }); - it('plain text field with tabindex and without role should not have content', async function({page}) { - await page.setContent(` -
Edit this image:my fake image
`); - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual({ - role: 'generic', - name: '' - }); + 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(` +
+ this is the inner content + yo +
`); + const golden = FFOX ? { + role: 'textbox', + name: 'my favorite textbox', + value: 'this is the inner content yo' + } : CHROMIUM ? { + role: 'textbox', + name: 'my favorite textbox', + value: 'this is the inner content ' + } : { + role: 'textbox', + name: 'my favorite textbox', + value: 'this is the inner content ', + }; + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual(golden); + }); + it('checkbox with and tabIndex and label should not have children', async function({page}) { + await page.setContent(` +
+ this is the inner content + yo +
`); + const golden = { + role: 'checkbox', + name: 'my favorite checkbox', + checked: true + }; + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual(golden); + }); + it('checkbox without label should not have children', async function({page}) { + await page.setContent(` +
+ this is the inner content + yo +
`); + const golden = FFOX ? { + role: 'checkbox', + name: 'this is the inner content yo', + checked: true + } : { + role: 'checkbox', + name: 'this is the inner content yo', + checked: true + }; + const snapshot = await page.accessibility.snapshot(); + expect(snapshot.children[0]).toEqual(golden); + }); + + describe('root option', function() { + it('should work a button', async({page}) => { + await page.setContent(``); + + const button = await page.$('button'); + expect(await page.accessibility.snapshot({root: button})).toEqual({ + role: 'button', + name: 'My Button' + }); + }); + it('should work an input', async({page}) => { + await page.setContent(``); + + const input = await page.$('input'); + expect(await page.accessibility.snapshot({root: input})).toEqual({ + role: 'textbox', + name: 'My Input', + value: 'My Value' + }); + }); + it('should work on a menu', async({page}) => { await page.setContent(` -
- this is the inner content - yo -
`); - const golden = FFOX ? { - role: 'textbox', - name: 'my favorite textbox', - value: 'this is the inner content yo' - } : CHROMIUM ? { - role: 'textbox', - name: 'my favorite textbox', - value: 'this is the inner content ' - } : { - role: 'textbox', - name: 'my favorite textbox', - value: 'this is the inner content ', - }; - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual(golden); +
+
First Item
+
Second Item
+
Third Item
+
+ `); + + const menu = await page.$('div[role="menu"]'); + expect(await page.accessibility.snapshot({root: menu})).toEqual({ + role: 'menu', + name: 'My Menu', + children: + [ { role: 'menuitem', name: 'First Item' }, + { role: 'menuitem', name: 'Second Item' }, + { role: 'menuitem', name: 'Third Item' } ], + orientation: WEBKIT ? 'vertical' : undefined + }); }); - it('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(``); + 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(` -
- this is the inner content - yo -
`); - const golden = { - role: 'checkbox', - name: 'my favorite checkbox', - checked: true - }; - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual(golden); - }); - it('checkbox without label should not have children', async function({page}) { - await page.setContent(` -
- this is the inner content - yo -
`); - const golden = FFOX ? { - role: 'checkbox', - name: 'this is the inner content yo', - checked: true - } : { - role: 'checkbox', - name: 'this is the inner content yo', - checked: true - }; - const snapshot = await page.accessibility.snapshot(); - expect(snapshot.children[0]).toEqual(golden); - }); - - describe('root option', function() { - it('should work a button', async({page}) => { - await page.setContent(``); - - const button = await page.$('button'); - expect(await page.accessibility.snapshot({root: button})).toEqual({ - role: 'button', - name: 'My Button' - }); - }); - it('should work an input', async({page}) => { - await page.setContent(``); - - const input = await page.$('input'); - expect(await page.accessibility.snapshot({root: input})).toEqual({ - role: 'textbox', - name: 'My Input', - value: 'My Value' - }); - }); - it('should work on a menu', async({page}) => { - await page.setContent(` -
-
First Item
-
Second Item
-
Third Item
-
- `); - - const menu = await page.$('div[role="menu"]'); - expect(await page.accessibility.snapshot({root: menu})).toEqual({ - role: 'menu', - name: 'My Menu', - children: - [ { role: 'menuitem', name: 'First Item' }, - { role: 'menuitem', name: 'Second Item' }, - { role: 'menuitem', name: 'Third Item' } ], - orientation: WEBKIT ? 'vertical' : undefined - }); - }); - it('should return null when the element is no longer in DOM', async({page}) => { - await page.setContent(``); - const button = await page.$('button'); - await page.$eval('button', button => button.remove()); - expect(await page.accessibility.snapshot({root: button})).toEqual(null); - }); - it('should show uninteresting nodes', async({page}) => { - await page.setContent(` -
+
+
+ hello
- hello -
- world -
+ world
- `); +
+ `); - const root = await page.$('#root'); - const snapshot = await page.accessibility.snapshot({root, interestingOnly: false}); - expect(snapshot.role).toBe('textbox'); - expect(snapshot.value).toContain('hello'); - expect(snapshot.value).toContain('world'); - expect(!!snapshot.children).toBe(true); - }); + 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); }); }); }); -}; +}); diff --git a/test/apicoverage.spec.js b/test/apicoverage.spec.js index 71708d6c51..e70f13cc68 100644 --- a/test/apicoverage.spec.js +++ b/test/apicoverage.spec.js @@ -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(', ')); + }); +}); diff --git a/test/autowaiting.spec.js b/test/autowaiting.spec.js index abe307347b..3ba2a4ed4a 100644 --- a/test/autowaiting.spec.js +++ b/test/autowaiting.spec.js @@ -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(``); - }); - - await page.setContent(`empty.html`); - - 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(``); }); - it('should await cross-process navigation when clicking anchor', async({page, server}) => { - const messages = []; - server.setRoute('/empty.html', async (req, res) => { - messages.push('route'); - res.setHeader('Content-Type', 'text/html'); - res.end(``); - }); - await page.setContent(`empty.html`); + await page.setContent(`empty.html`); - 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(``); }); - it('should await form-get on click', async({page, server}) => { - const messages = []; - server.setRoute('/empty.html?foo=bar', async (req, res) => { - messages.push('route'); - res.setHeader('Content-Type', 'text/html'); - res.end(``); - }); - await page.setContent(` -
- - -
`); + await page.setContent(`empty.html`); - 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(``); }); - it('should await form-post on click', async({page, server}) => { - const messages = []; - server.setRoute('/empty.html', async (req, res) => { - messages.push('route'); - res.setHeader('Content-Type', 'text/html'); - res.end(``); - }); - await page.setContent(` -
- - -
`); + await page.setContent(` +
+ + +
`); - 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(``); }); - it('should await navigation when assigning location', async({page, server}) => { - const messages = []; - server.setRoute('/empty.html', async (req, res) => { - messages.push('route'); - res.setHeader('Content-Type', 'text/html'); - res.end(``); - }); - await Promise.all([ - page.evaluate(`window.location.href = "${server.EMPTY_PAGE}"`).then(() => messages.push('evaluate')), - page.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(``); - }); - 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(``); - }); + await page.setContent(` +
+ + +
`); - await page.setContent(` - empty.html - - `); - 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(``); }); - 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(``); - }); - - await page.setContent(`empty.html`); - 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(``); }); - 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(``); - }); - await page.setContent(`empty.html`); - 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(``); + }); + + await page.setContent(` + empty.html + + `); + 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(``); + }); + + await page.setContent(`empty.html`); + 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(``); + }); + + await page.setContent(`empty.html`); + 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(`foobar`); + await page.click('a'); + }); + it('calling window.stop async', async({page, server, httpsServer}) => { + server.setRoute('/empty.html', async (req, res) => {}); + await page.evaluate((url) => { + window.location.href = url; + setTimeout(() => window.stop(), 100); + }, server.EMPTY_PAGE); + }); + it.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(`foobar`); - await page.click('a'); - }); - it('calling window.stop async', async({page, server, httpsServer}) => { - server.setRoute('/empty.html', async (req, res) => {}); - await page.evaluate((url) => { - window.location.href = url; - setTimeout(() => window.stop(), 100); - }, server.EMPTY_PAGE); - }); - it.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(); - }); - }); - }); -}; +}); diff --git a/test/browser.spec.js b/test/browser.spec.js index fbbd64dd26..3047c71621 100644 --- a/test/browser.spec.js +++ b/test/browser.spec.js @@ -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()'); + }); +}); diff --git a/test/browsercontext.spec.js b/test/browsercontext.spec.js index 7e36459df4..31bd882836 100644 --- a/test/browsercontext.spec.js +++ b/test/browsercontext.spec.js @@ -16,166 +16,115 @@ */ const utils = require('./utils'); +const {FFOX, CHROMIUM, WEBKIT} = utils.testOptions(browserType); -/** - * @type {BrowserTestSuite} - */ -module.exports.describe = function({playwright, CHROMIUM, FFOX, WEBKIT, LINUX}) { - - describe('BrowserContext', function() { - it('should create new context', async function({browser}) { - expect(browser.contexts().length).toBe(0); - const context = await browser.newContext(); - expect(browser.contexts().length).toBe(1); - expect(browser.contexts().indexOf(context) !== -1).toBe(true); - await context.close(); - expect(browser.contexts().length).toBe(0); - }); - it('window.open should use parent tab context', async function({browser, server}) { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window.open(url), server.EMPTY_PAGE) - ]); - expect(popup.context()).toBe(context); - await context.close(); - }); - it('should isolate localStorage and cookies', async function({browser, server}) { - // Create two incognito contexts. - const context1 = await browser.newContext(); - const context2 = await browser.newContext(); - expect(context1.pages().length).toBe(0); - expect(context2.pages().length).toBe(0); - - // Create a page in first incognito context. - const page1 = await context1.newPage(); - await page1.goto(server.EMPTY_PAGE); - await page1.evaluate(() => { - localStorage.setItem('name', 'page1'); - document.cookie = 'name=page1'; - }); - - expect(context1.pages().length).toBe(1); - expect(context2.pages().length).toBe(0); - - // Create a page in second incognito context. - const page2 = await context2.newPage(); - await page2.goto(server.EMPTY_PAGE); - await page2.evaluate(() => { - localStorage.setItem('name', 'page2'); - document.cookie = 'name=page2'; - }); - - expect(context1.pages().length).toBe(1); - expect(context2.pages().length).toBe(1); - expect(context1.pages()[0]).toBe(page1); - expect(context2.pages()[0]).toBe(page2); - - // Make sure pages don't share localstorage or cookies. - expect(await page1.evaluate(() => localStorage.getItem('name'))).toBe('page1'); - expect(await page1.evaluate(() => document.cookie)).toBe('name=page1'); - expect(await page2.evaluate(() => localStorage.getItem('name'))).toBe('page2'); - expect(await page2.evaluate(() => document.cookie)).toBe('name=page2'); - - // Cleanup contexts. - await Promise.all([ - context1.close(), - context2.close() - ]); - expect(browser.contexts().length).toBe(0); - }); - it('should propagate default viewport to the page', async({ browser }) => { - const context = await browser.newContext({ viewport: { width: 456, height: 789 } }); - const page = await context.newPage(); - 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 make a copy of default viewport', async({ browser }) => { - const viewport = { width: 456, height: 789 }; - const context = await browser.newContext({ viewport }); - viewport.width = 567; - const page = await context.newPage(); - 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('close() should work for empty context', async({ browser }) => { - const context = await browser.newContext(); - await context.close(); - }); - it('close() should abort waitForEvent', async({ browser }) => { - const context = await browser.newContext(); - const promise = context.waitForEvent('page').catch(e => e); - await context.close(); - let error = await promise; - expect(error.message).toContain('Context closed'); - }); +describe('BrowserContext', function() { + it('should create new context', async function({browser}) { + expect(browser.contexts().length).toBe(0); + const context = await browser.newContext(); + expect(browser.contexts().length).toBe(1); + expect(browser.contexts().indexOf(context) !== -1).toBe(true); + await context.close(); + expect(browser.contexts().length).toBe(0); }); + it('window.open should use parent tab context', async function({browser, server}) { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window.open(url), server.EMPTY_PAGE) + ]); + expect(popup.context()).toBe(context); + await context.close(); + }); + it('should isolate localStorage and cookies', async function({browser, server}) { + // Create two incognito contexts. + const context1 = await browser.newContext(); + const context2 = await browser.newContext(); + expect(context1.pages().length).toBe(0); + expect(context2.pages().length).toBe(0); - describe('BrowserContext({userAgent})', function() { - it('should work', async({browser, server}) => { - { - const context = await browser.newContext(); - const page = await context.newPage(); - expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla'); - await context.close(); - } - { - const context = await browser.newContext({ userAgent: 'foobar' }); - const page = await context.newPage(); - const [request] = await Promise.all([ - server.waitForRequest('/empty.html'), - page.goto(server.EMPTY_PAGE), - ]); - expect(request.headers['user-agent']).toBe('foobar'); - await context.close(); - } + // Create a page in first incognito context. + const page1 = await context1.newPage(); + await page1.goto(server.EMPTY_PAGE); + await page1.evaluate(() => { + localStorage.setItem('name', 'page1'); + document.cookie = 'name=page1'; }); - it('should work for subframes', async({browser, server}) => { - { - const context = await browser.newContext(); - const page = await context.newPage(); - expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla'); - await context.close(); - } - { - const context = await browser.newContext({ userAgent: 'foobar' }); - const page = await context.newPage(); - const [request] = await Promise.all([ - server.waitForRequest('/empty.html'), - utils.attachFrame(page, 'frame1', server.EMPTY_PAGE), - ]); - expect(request.headers['user-agent']).toBe('foobar'); - await context.close(); - } + + expect(context1.pages().length).toBe(1); + expect(context2.pages().length).toBe(0); + + // Create a page in second incognito context. + const page2 = await context2.newPage(); + await page2.goto(server.EMPTY_PAGE); + await page2.evaluate(() => { + localStorage.setItem('name', 'page2'); + document.cookie = 'name=page2'; }); - it('should emulate device user-agent', async({browser, server}) => { - { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => navigator.userAgent)).not.toContain('iPhone'); - await context.close(); - } - { - const context = await browser.newContext({ userAgent: playwright.devices['iPhone 6'].userAgent }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone'); - await context.close(); - } - }); - it('should make a copy of default options', async({browser, server}) => { - const options = { userAgent: 'foobar' }; - const context = await browser.newContext(options); - options.userAgent = 'wrong'; + + expect(context1.pages().length).toBe(1); + expect(context2.pages().length).toBe(1); + expect(context1.pages()[0]).toBe(page1); + expect(context2.pages()[0]).toBe(page2); + + // Make sure pages don't share localstorage or cookies. + expect(await page1.evaluate(() => localStorage.getItem('name'))).toBe('page1'); + expect(await page1.evaluate(() => document.cookie)).toBe('name=page1'); + expect(await page2.evaluate(() => localStorage.getItem('name'))).toBe('page2'); + expect(await page2.evaluate(() => document.cookie)).toBe('name=page2'); + + // Cleanup contexts. + await Promise.all([ + context1.close(), + context2.close() + ]); + expect(browser.contexts().length).toBe(0); + }); + it('should propagate default viewport to the page', async({ browser }) => { + const context = await browser.newContext({ viewport: { width: 456, height: 789 } }); + const page = await context.newPage(); + 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 make a copy of default viewport', async({ browser }) => { + const viewport = { width: 456, height: 789 }; + const context = await browser.newContext({ viewport }); + viewport.width = 567; + const page = await context.newPage(); + 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('close() should work for empty context', async({ browser }) => { + const context = await browser.newContext(); + await context.close(); + }); + it('close() should abort waitForEvent', async({ browser }) => { + const context = await browser.newContext(); + const promise = context.waitForEvent('page').catch(e => e); + await context.close(); + let error = await promise; + expect(error.message).toContain('Context closed'); + }); +}); + +describe('BrowserContext({userAgent})', function() { + it('should work', async({browser, server}) => { + { + const context = await browser.newContext(); + const page = await context.newPage(); + expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla'); + await context.close(); + } + { + const context = await browser.newContext({ userAgent: 'foobar' }); const page = await context.newPage(); const [request] = await Promise.all([ server.waitForRequest('/empty.html'), @@ -183,441 +132,487 @@ module.exports.describe = function({playwright, CHROMIUM, FFOX, WEBKIT, LINUX}) ]); expect(request.headers['user-agent']).toBe('foobar'); await context.close(); - }); + } }); + it('should work for subframes', async({browser, server}) => { + { + const context = await browser.newContext(); + const page = await context.newPage(); + expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla'); + await context.close(); + } + { + const context = await browser.newContext({ userAgent: 'foobar' }); + const page = await context.newPage(); + const [request] = await Promise.all([ + server.waitForRequest('/empty.html'), + utils.attachFrame(page, 'frame1', server.EMPTY_PAGE), + ]); + expect(request.headers['user-agent']).toBe('foobar'); + await context.close(); + } + }); + it('should emulate device user-agent', async({browser, server}) => { + { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/mobile.html'); + expect(await page.evaluate(() => navigator.userAgent)).not.toContain('iPhone'); + await context.close(); + } + { + const context = await browser.newContext({ userAgent: playwright.devices['iPhone 6'].userAgent }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/mobile.html'); + expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone'); + await context.close(); + } + }); + it('should make a copy of default options', async({browser, server}) => { + const options = { userAgent: 'foobar' }; + const context = await browser.newContext(options); + options.userAgent = 'wrong'; + const page = await context.newPage(); + const [request] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.goto(server.EMPTY_PAGE), + ]); + expect(request.headers['user-agent']).toBe('foobar'); + await context.close(); + }); +}); - describe('BrowserContext({bypassCSP})', function() { - it('should bypass CSP meta tag', async({browser, server}) => { - // Make sure CSP prohibits addScriptTag. - { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/csp.html'); - await page.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); - expect(await page.evaluate(() => window.__injected)).toBe(undefined); - await context.close(); - } +describe('BrowserContext({bypassCSP})', function() { + it('should bypass CSP meta tag', async({browser, server}) => { + // Make sure CSP prohibits addScriptTag. + { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/csp.html'); + await page.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); + expect(await page.evaluate(() => window.__injected)).toBe(undefined); + await context.close(); + } - // By-pass CSP and try one more time. - { - const context = await browser.newContext({ bypassCSP: true }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/csp.html'); - await page.addScriptTag({content: 'window.__injected = 42;'}); - expect(await page.evaluate(() => window.__injected)).toBe(42); - await context.close(); - } - }); - - it('should bypass CSP header', async({browser, server}) => { - // Make sure CSP prohibits addScriptTag. - server.setCSP('/empty.html', 'default-src "self"'); - - { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); - expect(await page.evaluate(() => window.__injected)).toBe(undefined); - await context.close(); - } - - // By-pass CSP and try one more time. - { - const context = await browser.newContext({ bypassCSP: true }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({content: 'window.__injected = 42;'}); - expect(await page.evaluate(() => window.__injected)).toBe(42); - await context.close(); - } - }); - - it('should bypass after cross-process navigation', async({browser, server}) => { + // By-pass CSP and try one more time. + { const context = await browser.newContext({ bypassCSP: true }); const page = await context.newPage(); await page.goto(server.PREFIX + '/csp.html'); await page.addScriptTag({content: 'window.__injected = 42;'}); expect(await page.evaluate(() => window.__injected)).toBe(42); + await context.close(); + } + }); - await page.goto(server.CROSS_PROCESS_PREFIX + '/csp.html'); + it('should bypass CSP header', async({browser, server}) => { + // Make sure CSP prohibits addScriptTag. + server.setCSP('/empty.html', 'default-src "self"'); + + { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); + expect(await page.evaluate(() => window.__injected)).toBe(undefined); + await context.close(); + } + + // By-pass CSP and try one more time. + { + const context = await browser.newContext({ bypassCSP: true }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); await page.addScriptTag({content: 'window.__injected = 42;'}); expect(await page.evaluate(() => window.__injected)).toBe(42); await context.close(); - }); - it('should bypass CSP in iframes as well', async({browser, server}) => { - // Make sure CSP prohibits addScriptTag in an iframe. - { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); - await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); - expect(await frame.evaluate(() => window.__injected)).toBe(undefined); - await context.close(); - } - - // By-pass CSP and try one more time. - { - const context = await browser.newContext({ bypassCSP: true }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); - await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); - expect(await frame.evaluate(() => window.__injected)).toBe(42); - await context.close(); - } - }); + } }); - describe('BrowserContext({javaScriptEnabled})', function() { - it('should work', async({browser}) => { - { - const context = await browser.newContext({ javaScriptEnabled: false }); - const page = await context.newPage(); - await page.goto('data:text/html, '); - let error = null; - await page.evaluate('something').catch(e => error = e); - if (WEBKIT) - expect(error.message).toContain('Can\'t find variable: something'); - else - expect(error.message).toContain('something is not defined'); - await context.close(); - } + it('should bypass after cross-process navigation', async({browser, server}) => { + const context = await browser.newContext({ bypassCSP: true }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/csp.html'); + await page.addScriptTag({content: 'window.__injected = 42;'}); + expect(await page.evaluate(() => window.__injected)).toBe(42); - { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto('data:text/html, '); - expect(await page.evaluate('something')).toBe('forbidden'); - await context.close(); - } - }); - it('should be able to navigate after disabling javascript', async({browser, server}) => { + await page.goto(server.CROSS_PROCESS_PREFIX + '/csp.html'); + await page.addScriptTag({content: 'window.__injected = 42;'}); + expect(await page.evaluate(() => window.__injected)).toBe(42); + await context.close(); + }); + it('should bypass CSP in iframes as well', async({browser, server}) => { + // Make sure CSP prohibits addScriptTag in an iframe. + { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); + await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); + expect(await frame.evaluate(() => window.__injected)).toBe(undefined); + await context.close(); + } + + // By-pass CSP and try one more time. + { + const context = await browser.newContext({ bypassCSP: true }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); + await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e); + expect(await frame.evaluate(() => window.__injected)).toBe(42); + await context.close(); + } + }); +}); + +describe('BrowserContext({javaScriptEnabled})', function() { + it('should work', async({browser}) => { + { const context = await browser.newContext({ javaScriptEnabled: false }); const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await context.close(); - }); - }); - - describe('BrowserContext.pages()', function() { - it('should return all of the pages', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const second = await context.newPage(); - const allPages = context.pages(); - expect(allPages.length).toBe(2); - expect(allPages).toContain(page); - expect(allPages).toContain(second); - await context.close(); - }); - it('should close all belonging pages once closing context', async function({browser}) { - const context = await browser.newContext(); - await context.newPage(); - expect(context.pages().length).toBe(1); - - await context.close(); - expect(context.pages().length).toBe(0); - }); - }); - - describe('BrowserContext.exposeFunction', () => { - it('should work', async({browser, server}) => { - const context = await browser.newContext(); - await context.exposeFunction('add', (a, b) => a + b); - const page = await context.newPage(); - await page.exposeFunction('mul', (a, b) => a * b); - const result = await page.evaluate(async function() { - return { mul: await mul(9, 4), add: await add(9, 4) }; - }); - expect(result).toEqual({ mul: 36, add: 13 }); - await context.close(); - }); - it('should throw for duplicate registrations', async({browser, server}) => { - const context = await browser.newContext(); - await context.exposeFunction('foo', () => {}); - await context.exposeFunction('bar', () => {}); - let error = await context.exposeFunction('foo', () => {}).catch(e => e); - expect(error.message).toBe('Function "foo" has been already registered'); - const page = await context.newPage(); - error = await page.exposeFunction('foo', () => {}).catch(e => e); - expect(error.message).toBe('Function "foo" has been already registered in the browser context'); - await page.exposeFunction('baz', () => {}); - error = await context.exposeFunction('baz', () => {}).catch(e => e); - expect(error.message).toBe('Function "baz" has been already registered in one of the pages'); - await context.close(); - }); - it('should be callable from-inside addInitScript', async({browser, server}) => { - const context = await browser.newContext(); - let args = []; - await context.exposeFunction('woof', function(arg) { - args.push(arg); - }); - await context.addInitScript(() => woof('context')); - const page = await context.newPage(); - await page.addInitScript(() => woof('page')); - args = []; - await page.reload(); - expect(args).toEqual(['context', 'page']); - await context.close(); - }); - }); - - describe('BrowserContext.route', () => { - it('should intercept', async({browser, server}) => { - const context = await browser.newContext(); - let intercepted = false; - await context.route('**/empty.html', route => { - intercepted = true; - const request = route.request(); - expect(request.url()).toContain('empty.html'); - expect(request.headers()['user-agent']).toBeTruthy(); - expect(request.method()).toBe('GET'); - expect(request.postData()).toBe(null); - expect(request.isNavigationRequest()).toBe(true); - expect(request.resourceType()).toBe('document'); - expect(request.frame() === page.mainFrame()).toBe(true); - expect(request.frame().url()).toBe('about:blank'); - route.continue(); - }); - const page = await context.newPage(); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - expect(intercepted).toBe(true); - await context.close(); - }); - it('should yield to page.route', async({browser, server}) => { - const context = await browser.newContext(); - await context.route('**/empty.html', route => { - route.fulfill({ status: 200, body: 'context' }); - }); - const page = await context.newPage(); - await page.route('**/empty.html', route => { - route.fulfill({ status: 200, body: 'page' }); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - expect(await response.text()).toBe('page'); - await context.close(); - }); - }); - - describe('BrowserContext.setHTTPCredentials', function() { - it('should work', async({browser, server}) => { - server.setAuth('/empty.html', 'user', 'pass'); - const context = await browser.newContext(); - const page = await context.newPage(); - let response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(401); - await context.setHTTPCredentials({ - username: 'user', - password: 'pass' - }); - response = await page.reload(); - expect(response.status()).toBe(200); - await context.close(); - }); - it('should fail if wrong credentials', async({browser, server}) => { - server.setAuth('/empty.html', 'user', 'pass'); - const context = await browser.newContext({ - httpCredentials: { username: 'foo', password: 'bar' } - }); - const page = await context.newPage(); - let response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(401); - await context.setHTTPCredentials({ - username: 'user', - password: 'pass' - }); - response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(200); - await context.close(); - }); - it('should allow disable authentication', async({browser, server}) => { - server.setAuth('/empty.html', 'user', 'pass'); - const context = await browser.newContext({ - httpCredentials: { username: 'user', password: 'pass' } - }); - const page = await context.newPage(); - let response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(200); - await context.setHTTPCredentials(null); - // Navigate to a different origin to bust Chromium's credential caching. - response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); - expect(response.status()).toBe(401); - await context.close(); - }); - it('should return resource body', async({browser, server}) => { - server.setAuth('/playground.html', 'user', 'pass'); - const context = await browser.newContext({ - httpCredentials: { username: 'user', password: 'pass' } - }); - const page = await context.newPage(); - let response = await page.goto(server.PREFIX + '/playground.html'); - expect(response.status()).toBe(200); - expect(await page.title()).toBe("Playground"); - expect((await response.body()).toString()).toContain("Playground"); - await context.close(); - }); - }); - - describe('BrowserContext.setOffline', function() { - it('should work with initial option', async({browser, server}) => { - const context = await browser.newContext({offline: true}); - const page = await context.newPage(); + await page.goto('data:text/html, '); let error = null; - await page.goto(server.EMPTY_PAGE).catch(e => error = e); - expect(error).toBeTruthy(); - await context.setOffline(false); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(200); + await page.evaluate('something').catch(e => error = e); + if (WEBKIT) + expect(error.message).toContain('Can\'t find variable: something'); + else + expect(error.message).toContain('something is not defined'); await context.close(); - }); - it('should emulate navigator.onLine', async({browser, server}) => { + } + + { const context = await browser.newContext(); const page = await context.newPage(); - expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); - await context.setOffline(true); - expect(await page.evaluate(() => window.navigator.onLine)).toBe(false); - await context.setOffline(false); - expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); + await page.goto('data:text/html, '); + expect(await page.evaluate('something')).toBe('forbidden'); await context.close(); - }); + } }); - - describe('Events.BrowserContext.Page', function() { - it('should have url', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [otherPage] = await Promise.all([ - context.waitForEvent('page'), - page.evaluate(url => window.open(url), server.EMPTY_PAGE) - ]); - expect(otherPage.url()).toBe(server.EMPTY_PAGE); - await context.close(); - }); - it('should have url after domcontentloaded', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [otherPage] = await Promise.all([ - context.waitForEvent('page'), - page.evaluate(url => window.open(url), server.EMPTY_PAGE) - ]); - await otherPage.waitForLoadState('domcontentloaded'); - expect(otherPage.url()).toBe(server.EMPTY_PAGE); - await context.close(); - }); - it('should have about:blank url with domcontentloaded', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [otherPage] = await Promise.all([ - context.waitForEvent('page'), - page.evaluate(url => window.open(url), 'about:blank') - ]); - await otherPage.waitForLoadState('domcontentloaded'); - expect(otherPage.url()).toBe('about:blank'); - await context.close(); - }); - it('should have about:blank for empty url with domcontentloaded', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [otherPage] = await Promise.all([ - context.waitForEvent('page'), - page.evaluate(() => window.open()) - ]); - await otherPage.waitForLoadState('domcontentloaded'); - expect(otherPage.url()).toBe('about:blank'); - await context.close(); - }); - it('should report when a new page is created and closed', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [otherPage] = await Promise.all([ - context.waitForEvent('page'), - page.evaluate(url => window.open(url), server.CROSS_PROCESS_PREFIX + '/empty.html'), - ]); - // The url is about:blank in FF when 'page' event is fired. - expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX); - expect(await otherPage.evaluate(() => ['Hello', 'world'].join(' '))).toBe('Hello world'); - expect(await otherPage.$('body')).toBeTruthy(); - - let allPages = context.pages(); - expect(allPages).toContain(page); - expect(allPages).toContain(otherPage); - - let closeEventReceived; - otherPage.once('close', () => closeEventReceived = true); - await otherPage.close(); - expect(closeEventReceived).toBeTruthy(); - - allPages = context.pages(); - expect(allPages).toContain(page); - expect(allPages).not.toContain(otherPage); - await context.close(); - }); - it('should report initialized pages', async({browser, server}) => { - const context = await browser.newContext(); - const pagePromise = context.waitForEvent('page'); - context.newPage(); - const newPage = await pagePromise; - expect(newPage.url()).toBe('about:blank'); - - const popupPromise = context.waitForEvent('page'); - const evaluatePromise = newPage.evaluate(() => window.open('about:blank')); - const popup = await popupPromise; - expect(popup.url()).toBe('about:blank'); - await evaluatePromise; - await context.close(); - }); - it('should not crash while redirecting of original request was missed', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - let serverResponse = null; - server.setRoute('/one-style.css', (req, res) => serverResponse = res); - // Open a new page. Use window.open to connect to the page later. - const [newPage] = await Promise.all([ - context.waitForEvent('page'), - page.evaluate(url => window.open(url), server.PREFIX + '/one-style.html'), - server.waitForRequest('/one-style.css') - ]); - // Issue a redirect. - serverResponse.writeHead(302, { location: '/injectedstyle.css' }); - serverResponse.end(); - await newPage.waitForLoadState('domcontentloaded'); - expect(newPage.url()).toBe(server.PREFIX + '/one-style.html'); - // Cleanup. - await context.close(); - }); - it('should have an opener', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - context.waitForEvent('page'), - page.goto(server.PREFIX + '/popup/window-open.html') - ]); - // The url is still about:blank in FF when 'page' event is fired. - expect(popup.url()).toBe(server.PREFIX + '/popup/popup.html'); - expect(await popup.opener()).toBe(page); - expect(await page.opener()).toBe(null); - await context.close(); - }); - it('should fire page lifecycle events', async function({browser, server}) { - const context = await browser.newContext(); - const events = []; - context.on('page', async page => { - events.push('CREATED: ' + page.url()); - page.on('close', () => events.push('DESTROYED: ' + page.url())); - }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.close(); - expect(events).toEqual([ - 'CREATED: about:blank', - `DESTROYED: ${server.EMPTY_PAGE}` - ]); - await context.close(); - }); + it('should be able to navigate after disabling javascript', async({browser, server}) => { + const context = await browser.newContext({ javaScriptEnabled: false }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await context.close(); }); -}; +}); + +describe('BrowserContext.pages()', function() { + it('should return all of the pages', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const second = await context.newPage(); + const allPages = context.pages(); + expect(allPages.length).toBe(2); + expect(allPages).toContain(page); + expect(allPages).toContain(second); + await context.close(); + }); + it('should close all belonging pages once closing context', async function({browser}) { + const context = await browser.newContext(); + await context.newPage(); + expect(context.pages().length).toBe(1); + + await context.close(); + expect(context.pages().length).toBe(0); + }); +}); + +describe('BrowserContext.exposeFunction', () => { + it('should work', async({browser, server}) => { + const context = await browser.newContext(); + await context.exposeFunction('add', (a, b) => a + b); + const page = await context.newPage(); + await page.exposeFunction('mul', (a, b) => a * b); + const result = await page.evaluate(async function() { + return { mul: await mul(9, 4), add: await add(9, 4) }; + }); + expect(result).toEqual({ mul: 36, add: 13 }); + await context.close(); + }); + it('should throw for duplicate registrations', async({browser, server}) => { + const context = await browser.newContext(); + await context.exposeFunction('foo', () => {}); + await context.exposeFunction('bar', () => {}); + let error = await context.exposeFunction('foo', () => {}).catch(e => e); + expect(error.message).toBe('Function "foo" has been already registered'); + const page = await context.newPage(); + error = await page.exposeFunction('foo', () => {}).catch(e => e); + expect(error.message).toBe('Function "foo" has been already registered in the browser context'); + await page.exposeFunction('baz', () => {}); + error = await context.exposeFunction('baz', () => {}).catch(e => e); + expect(error.message).toBe('Function "baz" has been already registered in one of the pages'); + await context.close(); + }); + it('should be callable from-inside addInitScript', async({browser, server}) => { + const context = await browser.newContext(); + let args = []; + await context.exposeFunction('woof', function(arg) { + args.push(arg); + }); + await context.addInitScript(() => woof('context')); + const page = await context.newPage(); + await page.addInitScript(() => woof('page')); + args = []; + await page.reload(); + expect(args).toEqual(['context', 'page']); + await context.close(); + }); +}); + +describe('BrowserContext.route', () => { + it('should intercept', async({browser, server}) => { + const context = await browser.newContext(); + let intercepted = false; + await context.route('**/empty.html', route => { + intercepted = true; + const request = route.request(); + expect(request.url()).toContain('empty.html'); + expect(request.headers()['user-agent']).toBeTruthy(); + expect(request.method()).toBe('GET'); + expect(request.postData()).toBe(null); + expect(request.isNavigationRequest()).toBe(true); + expect(request.resourceType()).toBe('document'); + expect(request.frame() === page.mainFrame()).toBe(true); + expect(request.frame().url()).toBe('about:blank'); + route.continue(); + }); + const page = await context.newPage(); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + expect(intercepted).toBe(true); + await context.close(); + }); + it('should yield to page.route', async({browser, server}) => { + const context = await browser.newContext(); + await context.route('**/empty.html', route => { + route.fulfill({ status: 200, body: 'context' }); + }); + const page = await context.newPage(); + await page.route('**/empty.html', route => { + route.fulfill({ status: 200, body: 'page' }); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + expect(await response.text()).toBe('page'); + await context.close(); + }); +}); + +describe('BrowserContext.setHTTPCredentials', function() { + it('should work', async({browser, server}) => { + server.setAuth('/empty.html', 'user', 'pass'); + const context = await browser.newContext(); + const page = await context.newPage(); + let response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(401); + await context.setHTTPCredentials({ + username: 'user', + password: 'pass' + }); + response = await page.reload(); + expect(response.status()).toBe(200); + await context.close(); + }); + it('should fail if wrong credentials', async({browser, server}) => { + server.setAuth('/empty.html', 'user', 'pass'); + const context = await browser.newContext({ + httpCredentials: { username: 'foo', password: 'bar' } + }); + const page = await context.newPage(); + let response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(401); + await context.setHTTPCredentials({ + username: 'user', + password: 'pass' + }); + response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(200); + await context.close(); + }); + it('should allow disable authentication', async({browser, server}) => { + server.setAuth('/empty.html', 'user', 'pass'); + const context = await browser.newContext({ + httpCredentials: { username: 'user', password: 'pass' } + }); + const page = await context.newPage(); + let response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(200); + await context.setHTTPCredentials(null); + // Navigate to a different origin to bust Chromium's credential caching. + response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + expect(response.status()).toBe(401); + await context.close(); + }); + it('should return resource body', async({browser, server}) => { + server.setAuth('/playground.html', 'user', 'pass'); + const context = await browser.newContext({ + httpCredentials: { username: 'user', password: 'pass' } + }); + const page = await context.newPage(); + let response = await page.goto(server.PREFIX + '/playground.html'); + expect(response.status()).toBe(200); + expect(await page.title()).toBe("Playground"); + expect((await response.body()).toString()).toContain("Playground"); + await context.close(); + }); +}); + +describe('BrowserContext.setOffline', function() { + it('should work with initial option', async({browser, server}) => { + const context = await browser.newContext({offline: true}); + const page = await context.newPage(); + let error = null; + await page.goto(server.EMPTY_PAGE).catch(e => error = e); + expect(error).toBeTruthy(); + await context.setOffline(false); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(200); + await context.close(); + }); + it('should emulate navigator.onLine', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); + await context.setOffline(true); + expect(await page.evaluate(() => window.navigator.onLine)).toBe(false); + await context.setOffline(false); + expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); + await context.close(); + }); +}); + +describe('Events.BrowserContext.Page', function() { + it('should have url', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [otherPage] = await Promise.all([ + context.waitForEvent('page'), + page.evaluate(url => window.open(url), server.EMPTY_PAGE) + ]); + expect(otherPage.url()).toBe(server.EMPTY_PAGE); + await context.close(); + }); + it('should have url after domcontentloaded', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [otherPage] = await Promise.all([ + context.waitForEvent('page'), + page.evaluate(url => window.open(url), server.EMPTY_PAGE) + ]); + await otherPage.waitForLoadState('domcontentloaded'); + expect(otherPage.url()).toBe(server.EMPTY_PAGE); + await context.close(); + }); + it('should have about:blank url with domcontentloaded', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [otherPage] = await Promise.all([ + context.waitForEvent('page'), + page.evaluate(url => window.open(url), 'about:blank') + ]); + await otherPage.waitForLoadState('domcontentloaded'); + expect(otherPage.url()).toBe('about:blank'); + await context.close(); + }); + it('should have about:blank for empty url with domcontentloaded', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [otherPage] = await Promise.all([ + context.waitForEvent('page'), + page.evaluate(() => window.open()) + ]); + await otherPage.waitForLoadState('domcontentloaded'); + expect(otherPage.url()).toBe('about:blank'); + await context.close(); + }); + it('should report when a new page is created and closed', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [otherPage] = await Promise.all([ + context.waitForEvent('page'), + page.evaluate(url => window.open(url), server.CROSS_PROCESS_PREFIX + '/empty.html'), + ]); + // The url is about:blank in FF when 'page' event is fired. + expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX); + expect(await otherPage.evaluate(() => ['Hello', 'world'].join(' '))).toBe('Hello world'); + expect(await otherPage.$('body')).toBeTruthy(); + + let allPages = context.pages(); + expect(allPages).toContain(page); + expect(allPages).toContain(otherPage); + + let closeEventReceived; + otherPage.once('close', () => closeEventReceived = true); + await otherPage.close(); + expect(closeEventReceived).toBeTruthy(); + + allPages = context.pages(); + expect(allPages).toContain(page); + expect(allPages).not.toContain(otherPage); + await context.close(); + }); + it('should report initialized pages', async({browser, server}) => { + const context = await browser.newContext(); + const pagePromise = context.waitForEvent('page'); + context.newPage(); + const newPage = await pagePromise; + expect(newPage.url()).toBe('about:blank'); + + const popupPromise = context.waitForEvent('page'); + const evaluatePromise = newPage.evaluate(() => window.open('about:blank')); + const popup = await popupPromise; + expect(popup.url()).toBe('about:blank'); + await evaluatePromise; + await context.close(); + }); + it('should not crash while redirecting of original request was missed', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + let serverResponse = null; + server.setRoute('/one-style.css', (req, res) => serverResponse = res); + // Open a new page. Use window.open to connect to the page later. + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + page.evaluate(url => window.open(url), server.PREFIX + '/one-style.html'), + server.waitForRequest('/one-style.css') + ]); + // Issue a redirect. + serverResponse.writeHead(302, { location: '/injectedstyle.css' }); + serverResponse.end(); + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toBe(server.PREFIX + '/one-style.html'); + // Cleanup. + await context.close(); + }); + it('should have an opener', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + context.waitForEvent('page'), + page.goto(server.PREFIX + '/popup/window-open.html') + ]); + // The url is still about:blank in FF when 'page' event is fired. + expect(popup.url()).toBe(server.PREFIX + '/popup/popup.html'); + expect(await popup.opener()).toBe(page); + expect(await page.opener()).toBe(null); + await context.close(); + }); + it('should fire page lifecycle events', async function({browser, server}) { + const context = await browser.newContext(); + const events = []; + context.on('page', async page => { + events.push('CREATED: ' + page.url()); + page.on('close', () => events.push('DESTROYED: ' + page.url())); + }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.close(); + expect(events).toEqual([ + 'CREATED: about:blank', + `DESTROYED: ${server.EMPTY_PAGE}` + ]); + await context.close(); + }); +}); diff --git a/test/capabilities.spec.js b/test/capabilities.spec.js index c11c5d1cb0..1b6fae9d86 100644 --- a/test/capabilities.spec.js +++ b/test/capabilities.spec.js @@ -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'); }); -}; +}); diff --git a/test/chromium/chromium.spec.js b/test/chromium/chromium.spec.js index 75aa0fe145..a6c83eab4f 100644 --- a/test/chromium/chromium.spec.js +++ b/test/chromium/chromium.spec.js @@ -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(` - - `)); - 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(` + + `)); + 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'); + }); +}); diff --git a/test/chromium/coverage.spec.js b/test/chromium/coverage.spec.js index fa3b4aa6d2..bf45fb056b 100644 --- a/test/chromium/coverage.spec.js +++ b/test/chromium/coverage.spec.js @@ -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, ':/')).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, ':/')).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); + }); +}); diff --git a/test/chromium/launcher.spec.js b/test/chromium/launcher.spec.js index c1472fc820..62afe3fad4 100644 --- a/test/chromium/launcher.spec.js +++ b/test/chromium/launcher.spec.js @@ -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); - }); - }); -}; +}); diff --git a/test/chromium/oopif.spec.js b/test/chromium/oopif.spec.js index ef99e8269a..d1d0daaee1 100644 --- a/test/chromium/oopif.spec.js +++ b/test/chromium/oopif.spec.js @@ -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(); diff --git a/test/chromium/pdf.spec.js b/test/chromium/pdf.spec.js index 907288030a..e901a398ed 100644 --- a/test/chromium/pdf.spec.js +++ b/test/chromium/pdf.spec.js @@ -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); }); -}; +}); diff --git a/test/chromium/session.spec.js b/test/chromium/session.spec.js index 83dac604f8..2c4c2e686f 100644 --- a/test/chromium/session.spec.js +++ b/test/chromium/session.spec.js @@ -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(); + }); +}); diff --git a/test/chromium/tracing.spec.js b/test/chromium/tracing.spec.js index dec98ef7bd..26f760c319 100644 --- a/test/chromium/tracing.spec.js +++ b/test/chromium/tracing.spec.js @@ -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'); + }); +}); diff --git a/test/click.spec.js b/test/click.spec.js index 88d138be99..de2708a0a5 100644 --- a/test/click.spec.js +++ b/test/click.spec.js @@ -16,601 +16,596 @@ */ const utils = require('./utils'); +const {FFOX, CHROMIUM, WEBKIT} = utils.testOptions(browserType); -/** - * @type {PageTestSuite} - */ - module.exports.describe = function({playwright, FFOX, CHROMIUM, WEBKIT}) { - - describe('Page.click', function() { - it('should click the button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should click svg', async({page, server}) => { - await page.setContent(` - - - - `); - await page.click('circle'); - expect(await page.evaluate(() => window.__CLICKED)).toBe(42); - }); - it('should click the button if window.Node is removed', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.evaluate(() => delete window.Node); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/4281 - it('should click on a span with an inline element inside', async({page, server}) => { - await page.setContent(` - - - `); - await page.click('span'); - expect(await page.evaluate(() => window.CLICKED)).toBe(42); - }); - it('should not throw UnhandledPromiseRejection when page closes', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await Promise.all([ - page.close(), - page.mouse.click(1, 2), - ]).catch(e => {}); - await context.close(); - }); - it('should click the button after navigation ', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button'); - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should click the button after a cross origin navigation ', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button'); - await page.goto(server.CROSS_PROCESS_PREFIX + '/input/button.html'); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should click with disabled javascript', async({browser, server}) => { - const context = await browser.newContext({ javaScriptEnabled: false }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/wrappedlink.html'); - await Promise.all([ - page.click('a'), - page.waitForNavigation() - ]); - expect(page.url()).toBe(server.PREFIX + '/wrappedlink.html#clicked'); - await context.close(); - }); - it('should click when one of inline box children is outside of viewport', async({page, server}) => { - await page.setContent(` - - woofdoggo - `); - await page.click('span'); - expect(await page.evaluate(() => window.CLICKED)).toBe(42); - }); - it('should select the text by triple clicking', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - const text = 'This is the text that we are going to try to select. Let\'s see how it goes.'; - await page.fill('textarea', text); - await page.click('textarea', { clickCount: 3 }); - expect(await page.evaluate(() => { - const textarea = document.querySelector('textarea'); - return textarea.value.substring(textarea.selectionStart, textarea.selectionEnd); - })).toBe(text); - }); - it('should click offscreen buttons', async({page, server}) => { - await page.goto(server.PREFIX + '/offscreenbuttons.html'); - const messages = []; - page.on('console', msg => messages.push(msg.text())); - for (let i = 0; i < 11; ++i) { - // We might've scrolled to click a button - reset to (0, 0). - await page.evaluate(() => window.scrollTo(0, 0)); - await page.click(`#btn${i}`); +describe('Page.click', function() { + it('should click the button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); + }); + it('should click svg', async({page, server}) => { + await page.setContent(` + + + + `); + await page.click('circle'); + expect(await page.evaluate(() => window.__CLICKED)).toBe(42); + }); + it('should click the button if window.Node is removed', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.evaluate(() => delete window.Node); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); + }); + // @see https://github.com/GoogleChrome/puppeteer/issues/4281 + it('should click on a span with an inline element inside', async({page, server}) => { + await page.setContent(` + + + `); + await page.click('span'); + expect(await page.evaluate(() => window.CLICKED)).toBe(42); + }); + it('should not throw UnhandledPromiseRejection when page closes', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await Promise.all([ + page.close(), + page.mouse.click(1, 2), + ]).catch(e => {}); + await context.close(); + }); + it('should click the button after navigation ', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); + }); + it('should click the button after a cross origin navigation ', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + await page.goto(server.CROSS_PROCESS_PREFIX + '/input/button.html'); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); + }); + it('should click with disabled javascript', async({browser, server}) => { + const context = await browser.newContext({ javaScriptEnabled: false }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/wrappedlink.html'); + await Promise.all([ + page.click('a'), + page.waitForNavigation() + ]); + expect(page.url()).toBe(server.PREFIX + '/wrappedlink.html#clicked'); + await context.close(); + }); + it('should click when one of inline box children is outside of viewport', async({page, server}) => { + await page.setContent(` + + woofdoggo + `); + await page.click('span'); + expect(await page.evaluate(() => window.CLICKED)).toBe(42); + }); + it('should select the text by triple clicking', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + const text = 'This is the text that we are going to try to select. Let\'s see how it goes.'; + await page.fill('textarea', text); + await page.click('textarea', { clickCount: 3 }); + expect(await page.evaluate(() => { + const textarea = document.querySelector('textarea'); + return textarea.value.substring(textarea.selectionStart, textarea.selectionEnd); + })).toBe(text); + }); + it('should click offscreen buttons', async({page, server}) => { + await page.goto(server.PREFIX + '/offscreenbuttons.html'); + const messages = []; + page.on('console', msg => messages.push(msg.text())); + for (let i = 0; i < 11; ++i) { + // We might've scrolled to click a button - reset to (0, 0). + await page.evaluate(() => window.scrollTo(0, 0)); + await page.click(`#btn${i}`); + } + expect(messages).toEqual([ + 'button #0 clicked', + 'button #1 clicked', + 'button #2 clicked', + 'button #3 clicked', + 'button #4 clicked', + 'button #5 clicked', + 'button #6 clicked', + 'button #7 clicked', + 'button #8 clicked', + 'button #9 clicked', + 'button #10 clicked' + ]); }); - describe('Page.check', function() { - it('should check the box', async({page}) => { - await page.setContent(``); - await page.check('input'); - expect(await page.evaluate(() => checkbox.checked)).toBe(true); - }); - it('should not check the checked box', async({page}) => { - await page.setContent(``); - await page.check('input'); - expect(await page.evaluate(() => checkbox.checked)).toBe(true); - }); - it('should uncheck the box', async({page}) => { - await page.setContent(``); - await page.uncheck('input'); - expect(await page.evaluate(() => checkbox.checked)).toBe(false); - }); - it('should not uncheck the unchecked box', async({page}) => { - await page.setContent(``); - await page.uncheck('input'); - expect(await page.evaluate(() => checkbox.checked)).toBe(false); - }); - it('should check the box by label', async({page}) => { - await page.setContent(``); - await page.check('label'); - expect(await page.evaluate(() => checkbox.checked)).toBe(true); - }); - it('should check the box outside label', async({page}) => { - await page.setContent(`
`); - await page.check('label'); - expect(await page.evaluate(() => checkbox.checked)).toBe(true); - }); - it('should check the box inside label w/o id', async({page}) => { - await page.setContent(``); - await page.check('label'); - expect(await page.evaluate(() => checkbox.checked)).toBe(true); - }); - it('should check radio', async({page}) => { - await page.setContent(` - one - two - three`); - await page.check('#two'); - expect(await page.evaluate(() => two.checked)).toBe(true); - }); - it('should check the box by aria role', async({page}) => { - await page.setContent(` - `); - await page.check('div'); - expect(await page.evaluate(() => checkbox.getAttribute('aria-checked'))).toBe('true'); - }); + it('should waitFor visible when already visible', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); }); -}; + it('should not wait with false waitFor', async({page, server}) => { + let error = null; + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', b => b.style.display = 'none'); + await page.click('button', { force: true }).catch(e => error = e); + expect(error.message).toBe('Node is either not visible or not an HTMLElement'); + expect(await page.evaluate(() => result)).toBe('Was not clicked'); + }); + it('should waitFor visible', async({page, server}) => { + let done = false; + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', b => b.style.display = 'none'); + const clicked = page.click('button', { timeout: 0 }).then(() => done = true); + for (let i = 0; i < 10; i++) { + // Do enough double rafs to check for possible races. + await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)))); + } + expect(done).toBe(false); + await page.$eval('button', b => b.style.display = 'block'); + await clicked; + expect(done).toBe(true); + expect(await page.evaluate(() => result)).toBe('Clicked'); + }); + it('should timeout waiting for visible', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', b => b.style.display = 'none'); + const error = await page.click('button', { timeout: 100 }).catch(e => e); + expect(error.message).toContain('timeout exceeded'); + }); + it('should waitFor visible when parent is hidden', async({page, server}) => { + let done = false; + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', b => b.parentElement.style.display = 'none'); + const clicked = page.click('button', { timeout: 0 }).then(() => done = true); + for (let i = 0; i < 10; i++) { + // Do enough double rafs to check for possible races. + await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)))); + } + expect(done).toBe(false); + await page.$eval('button', b => b.parentElement.style.display = 'block'); + await clicked; + expect(done).toBe(true); + expect(await page.evaluate(() => result)).toBe('Clicked'); + }); + + it('should click wrapped links', async({page, server}) => { + await page.goto(server.PREFIX + '/wrappedlink.html'); + await page.click('a'); + expect(await page.evaluate(() => window.__clicked)).toBe(true); + }); + + it('should click on checkbox input and toggle', async({page, server}) => { + await page.goto(server.PREFIX + '/input/checkbox.html'); + expect(await page.evaluate(() => result.check)).toBe(null); + await page.click('input#agree'); + expect(await page.evaluate(() => result.check)).toBe(true); + expect(await page.evaluate(() => result.events)).toEqual([ + 'mouseover', + 'mouseenter', + 'mousemove', + 'mousedown', + 'mouseup', + 'click', + 'input', + 'change', + ]); + await page.click('input#agree'); + expect(await page.evaluate(() => result.check)).toBe(false); + }); + + it('should click on checkbox label and toggle', async({page, server}) => { + await page.goto(server.PREFIX + '/input/checkbox.html'); + expect(await page.evaluate(() => result.check)).toBe(null); + await page.click('label[for="agree"]'); + expect(await page.evaluate(() => result.check)).toBe(true); + expect(await page.evaluate(() => result.events)).toEqual([ + 'click', + 'input', + 'change', + ]); + await page.click('label[for="agree"]'); + expect(await page.evaluate(() => result.check)).toBe(false); + }); + it('should not hang with touch-enabled viewports', async({server, browser}) => { + // @see https://github.com/GoogleChrome/puppeteer/issues/161 + const { viewport, hasTouch } = playwright.devices['iPhone 6']; + const context = await browser.newContext({ viewport, hasTouch }); + const page = await context.newPage(); + await page.mouse.down(); + await page.mouse.move(100, 10); + await page.mouse.up(); + await context.close(); + }); + it('should scroll and click the button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.click('#button-5'); + expect(await page.evaluate(() => document.querySelector('#button-5').textContent)).toBe('clicked'); + await page.click('#button-80'); + expect(await page.evaluate(() => document.querySelector('#button-80').textContent)).toBe('clicked'); + }); + it('should double click the button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.evaluate(() => { + window.double = false; + const button = document.querySelector('button'); + button.addEventListener('dblclick', event => { + window.double = true; + }); + }); + await page.dblclick('button'); + expect(await page.evaluate('double')).toBe(true); + expect(await page.evaluate('result')).toBe('Clicked'); + }); + it('should click a partially obscured button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.evaluate(() => { + const button = document.querySelector('button'); + button.textContent = 'Some really long text that will go offscreen'; + button.style.position = 'absolute'; + button.style.left = '368px'; + }); + await page.click('button'); + expect(await page.evaluate(() => window.result)).toBe('Clicked'); + }); + it('should click a rotated button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/rotatedButton.html'); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); + }); + it('should fire contextmenu event on right click', async({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.click('#button-8', {button: 'right'}); + expect(await page.evaluate(() => document.querySelector('#button-8').textContent)).toBe('context menu'); + }); + // @see https://github.com/GoogleChrome/puppeteer/issues/206 + it('should click links which cause navigation', async({page, server}) => { + await page.setContent(`empty.html`); + // This await should not hang. + await page.click('a'); + }); + it('should click the button inside an iframe', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent('
spacer
'); + await utils.attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); + const frame = page.frames()[1]; + const button = await frame.$('button'); + await button.click(); + expect(await frame.evaluate(() => window.result)).toBe('Clicked'); + }); + // @see https://github.com/GoogleChrome/puppeteer/issues/4110 + // @see https://bugs.chromium.org/p/chromium/issues/detail?id=986390 + // @see https://chromium-review.googlesource.com/c/chromium/src/+/1742784 + it.fail(true)('should click the button with fixed position inside an iframe', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setViewportSize({width: 500, height: 500}); + await page.setContent('
spacer
'); + await utils.attachFrame(page, 'button-test', server.CROSS_PROCESS_PREFIX + '/input/button.html'); + const frame = page.frames()[1]; + await frame.$eval('button', button => button.style.setProperty('position', 'fixed')); + await frame.click('button'); + expect(await frame.evaluate(() => window.result)).toBe('Clicked'); + }); + it('should click the button with deviceScaleFactor set', async({browser, server}) => { + const context = await browser.newContext({ viewport: { width: 400, height: 400 }, deviceScaleFactor: 5 }); + const page = await context.newPage(); + expect(await page.evaluate(() => window.devicePixelRatio)).toBe(5); + await page.setContent('
spacer
'); + await utils.attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); + const frame = page.frames()[1]; + const button = await frame.$('button'); + await button.click(); + expect(await frame.evaluate(() => window.result)).toBe('Clicked'); + await context.close(); + }); + it('should click the button with px border with offset', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => button.style.borderWidth = '8px'); + await page.click('button', { position: { x: 20, y: 10 } }); + expect(await page.evaluate(() => result)).toBe('Clicked'); + // Safari reports border-relative offsetX/offsetY. + expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 20 + 8 : 20); + expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 10 + 8 : 10); + }); + it('should click the button with em border with offset', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => button.style.borderWidth = '2em'); + await page.$eval('button', button => button.style.fontSize = '12px'); + await page.click('button', { position: { x: 20, y: 10 } }); + expect(await page.evaluate(() => result)).toBe('Clicked'); + // Safari reports border-relative offsetX/offsetY. + expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 12 * 2 + 20 : 20); + expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 12 * 2 + 10 : 10); + }); + it.fail(FFOX)('should click a very large button with offset', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => button.style.borderWidth = '8px'); + await page.$eval('button', button => button.style.height = button.style.width = '2000px'); + await page.click('button', { position: { x: 1900, y: 1910 } }); + expect(await page.evaluate(() => window.result)).toBe('Clicked'); + // Safari reports border-relative offsetX/offsetY. + expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 1900 + 8 : 1900); + expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 1910 + 8 : 1910); + }); + it.fail(FFOX)('should click a button in scrolling container with offset', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => { + const container = document.createElement('div'); + container.style.overflow = 'auto'; + container.style.width = '200px'; + container.style.height = '200px'; + button.parentElement.insertBefore(container, button); + container.appendChild(button); + button.style.height = '2000px'; + button.style.width = '2000px'; + button.style.borderWidth = '8px'; + }); + await page.click('button', { position: { x: 1900, y: 1910 } }); + expect(await page.evaluate(() => window.result)).toBe('Clicked'); + // Safari reports border-relative offsetX/offsetY. + expect(await page.evaluate(() => offsetX)).toBe(WEBKIT ? 1900 + 8 : 1900); + expect(await page.evaluate(() => offsetY)).toBe(WEBKIT ? 1910 + 8 : 1910); + }); + it.skip(FFOX)('should click the button with offset with page scale', async({browser, server}) => { + const context = await browser.newContext({ viewport: { width: 400, height: 400 }, isMobile: true }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => { + button.style.borderWidth = '8px'; + document.body.style.margin = '0'; + }); + await page.click('button', { position: { x: 20, y: 10 } }); + expect(await page.evaluate(() => result)).toBe('Clicked'); + let expected = { x: 28, y: 18 }; // 20;10 + 8px of border in each direction + if (WEBKIT) { + // WebKit rounds up during css -> dip -> css conversion. + expected = { x: 29, y: 19 }; + } else if (CHROMIUM) { + // Chromium rounds down during css -> dip -> css conversion. + expected = { x: 27, y: 18 }; + } + expect(await page.evaluate(() => pageX)).toBe(expected.x); + expect(await page.evaluate(() => pageY)).toBe(expected.y); + await context.close(); + }); + + it('should wait for stable position', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => { + button.style.transition = 'margin 500ms linear 0s'; + button.style.marginLeft = '200px'; + button.style.borderWidth = '0'; + button.style.width = '200px'; + button.style.height = '20px'; + // Set display to "block" - otherwise Firefox layouts with non-even + // values on Linux. + button.style.display = 'block'; + document.body.style.margin = '0'; + }); + await page.click('button'); + expect(await page.evaluate(() => window.result)).toBe('Clicked'); + expect(await page.evaluate(() => pageX)).toBe(300); + expect(await page.evaluate(() => pageY)).toBe(10); + }); + it('should timeout waiting for stable position', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await button.evaluate(button => { + button.style.transition = 'margin 5s linear 0s'; + button.style.marginLeft = '200px'; + }); + const error = await button.click({ timeout: 100 }).catch(e => e); + expect(error.message).toContain('timeout exceeded'); + }); + it('should wait for becoming hit target', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.$eval('button', button => { + button.style.borderWidth = '0'; + button.style.width = '200px'; + button.style.height = '20px'; + document.body.style.margin = '0'; + document.body.style.position = 'relative'; + const flyOver = document.createElement('div'); + flyOver.className = 'flyover'; + flyOver.style.position = 'absolute'; + flyOver.style.width = '400px'; + flyOver.style.height = '20px'; + flyOver.style.left = '-200px'; + flyOver.style.top = '0'; + flyOver.style.background = 'red'; + document.body.appendChild(flyOver); + }); + let clicked = false; + const clickPromise = page.click('button').then(() => clicked = true); + expect(clicked).toBe(false); + + await page.$eval('.flyover', flyOver => flyOver.style.left = '0'); + await page.evaluate(() => new Promise(requestAnimationFrame)); + await page.evaluate(() => new Promise(requestAnimationFrame)); + expect(clicked).toBe(false); + + await page.$eval('.flyover', flyOver => flyOver.style.left = '200px'); + await clickPromise; + expect(clicked).toBe(true); + expect(await page.evaluate(() => window.result)).toBe('Clicked'); + }); + it('should timeout waiting for hit target', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(() => { + document.body.style.position = 'relative'; + const blocker = document.createElement('div'); + blocker.style.position = 'absolute'; + blocker.style.width = '400px'; + blocker.style.height = '20px'; + blocker.style.left = '0'; + blocker.style.top = '0'; + document.body.appendChild(blocker); + }); + const error = await button.click({ timeout: 100 }).catch(e => e); + expect(error.message).toContain('timeout exceeded'); + }); + it('should fail when obscured and not waiting for hit target', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(() => { + document.body.style.position = 'relative'; + const blocker = document.createElement('div'); + blocker.style.position = 'absolute'; + blocker.style.width = '400px'; + blocker.style.height = '20px'; + blocker.style.left = '0'; + blocker.style.top = '0'; + document.body.appendChild(blocker); + }); + await button.click({ force: true }); + expect(await page.evaluate(() => window.result)).toBe('Was not clicked'); + }); + + it('should climb dom for pointer-events:none targets', async({page, server}) => { + await page.setContent('') + await page.click('text=Click target'); + }); + it('should update modifiers correctly', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button', { modifiers: ['Shift'] }); + expect(await page.evaluate(() => shiftKey)).toBe(true); + await page.click('button', { modifiers: [] }); + expect(await page.evaluate(() => shiftKey)).toBe(false); + + await page.keyboard.down('Shift'); + await page.click('button', { modifiers: [] }); + expect(await page.evaluate(() => shiftKey)).toBe(false); + await page.click('button'); + expect(await page.evaluate(() => shiftKey)).toBe(true); + await page.keyboard.up('Shift'); + await page.click('button'); + expect(await page.evaluate(() => shiftKey)).toBe(false); + }); + it('should click an offscreen element when scroll-behavior is smooth', async({page}) => { + await page.setContent(` +
+ +
+ `); + await page.click('button'); + expect(await page.evaluate('window.clicked')).toBe(true); + }); + it('should fail to click a button animated via CSS animations and setInterval', async({page}) => { + // This test has a setInterval that consistently animates a button. + const buttonSize = 10; + const containerWidth = 500; + const transition = 100; + await page.setContent(` + + +
+ +
+ + + + `); + await page.evaluate(transition => { + window.setInterval(animateLeft, transition); + animateLeft(); + }, transition); + + // Ideally, we we detect the button to be continuously animating, and timeout waiting for it to stop. + // That does not happen though: + // - Chromium headless does not issue rafs between first and second animateLeft() calls. + // - Chromium and WebKit keep element bounds the same when for 2 frames when changing left to a new value. + // This test currently documents our flaky behavior, because it's unclear whether we could + // guarantee timeout. + const error1 = await page.click('button', { timeout: 250 }).catch(e => e); + if (error1) + expect(error1.message).toContain('timeout exceeded'); + const error2 = await page.click('button', { timeout: 250 }).catch(e => e); + if (error2) + expect(error2.message).toContain('timeout exceeded'); + }); +}); + +describe('Page.check', function() { + it('should check the box', async({page}) => { + await page.setContent(``); + await page.check('input'); + expect(await page.evaluate(() => checkbox.checked)).toBe(true); + }); + it('should not check the checked box', async({page}) => { + await page.setContent(``); + await page.check('input'); + expect(await page.evaluate(() => checkbox.checked)).toBe(true); + }); + it('should uncheck the box', async({page}) => { + await page.setContent(``); + await page.uncheck('input'); + expect(await page.evaluate(() => checkbox.checked)).toBe(false); + }); + it('should not uncheck the unchecked box', async({page}) => { + await page.setContent(``); + await page.uncheck('input'); + expect(await page.evaluate(() => checkbox.checked)).toBe(false); + }); + it('should check the box by label', async({page}) => { + await page.setContent(``); + await page.check('label'); + expect(await page.evaluate(() => checkbox.checked)).toBe(true); + }); + it('should check the box outside label', async({page}) => { + await page.setContent(`
`); + await page.check('label'); + expect(await page.evaluate(() => checkbox.checked)).toBe(true); + }); + it('should check the box inside label w/o id', async({page}) => { + await page.setContent(``); + await page.check('label'); + expect(await page.evaluate(() => checkbox.checked)).toBe(true); + }); + it('should check radio', async({page}) => { + await page.setContent(` + one + two + three`); + await page.check('#two'); + expect(await page.evaluate(() => two.checked)).toBe(true); + }); + it('should check the box by aria role', async({page}) => { + await page.setContent(` + `); + await page.check('div'); + expect(await page.evaluate(() => checkbox.getAttribute('aria-checked'))).toBe('true'); + }); +}); diff --git a/test/cookies.spec.js b/test/cookies.spec.js index 69ecf331a7..2fab64b538 100644 --- a/test/cookies.spec.js +++ b/test/cookies.spec.js @@ -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(); + }); +}); diff --git a/test/defaultbrowsercontext.spec.js b/test/defaultbrowsercontext.spec.js index 02c49cf118..c578cd6591 100644 --- a/test/defaultbrowsercontext.spec.js +++ b/test/defaultbrowsercontext.spec.js @@ -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(''); + }); +}); diff --git a/test/dialog.spec.js b/test/dialog.spec.js index 8765f5b144..89b71a4b79 100644 --- a/test/dialog.spec.js +++ b/test/dialog.spec.js @@ -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); + }); +}); diff --git a/test/download.spec.js b/test/download.spec.js index 14dfbe82c4..2969e73965 100644 --- a/test/download.spec.js +++ b/test/download.spec.js @@ -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(`download`); - 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(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path = await download.path(); - expect(fs.existsSync(path)).toBeTruthy(); - expect(fs.readFileSync(path).toString()).toBe('Hello world'); - await page.close(); - }); - it('should delete file', async({browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path = await download.path(); - expect(fs.existsSync(path)).toBeTruthy(); - await download.delete(); - expect(fs.existsSync(path)).toBeFalsy(); - }); - it('should expose stream', async({browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const stream = await download.createReadStream(); - let content = ''; - stream.on('data', data => content += data.toString()); - await new Promise(f => stream.on('end', f)); - expect(content).toBe('Hello world'); - stream.close(); - }); - it('should delete downloads on context destruction', async({browser, server}) => { - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download1 ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const [ download2 ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path1 = await download1.path(); - const path2 = await download2.path(); - expect(fs.existsSync(path1)).toBeTruthy(); - expect(fs.existsSync(path2)).toBeTruthy(); - await page.context().close(); - expect(fs.existsSync(path1)).toBeFalsy(); - expect(fs.existsSync(path2)).toBeFalsy(); - }); - it('should delete downloads on browser gone', async ({ server }) => { - const browser = await browserType.launch(defaultBrowserOptions); - const page = await browser.newPage({ acceptDownloads: true }); - await page.setContent(`download`); - const [ download1 ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const [ download2 ] = await Promise.all([ - page.waitForEvent('download'), - page.click('a') - ]); - const path1 = await download1.path(); - const path2 = await download2.path(); - expect(fs.existsSync(path1)).toBeTruthy(); - expect(fs.existsSync(path2)).toBeTruthy(); - await browser.close(); - expect(fs.existsSync(path1)).toBeFalsy(); - expect(fs.existsSync(path2)).toBeFalsy(); - expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy(); +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(`download`); + 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(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path = await download.path(); + expect(fs.existsSync(path)).toBeTruthy(); + expect(fs.readFileSync(path).toString()).toBe('Hello world'); + await page.close(); + }); + it('should delete file', async({browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path = await download.path(); + expect(fs.existsSync(path)).toBeTruthy(); + await download.delete(); + expect(fs.existsSync(path)).toBeFalsy(); + }); + it('should expose stream', async({browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const stream = await download.createReadStream(); + let content = ''; + stream.on('data', data => content += data.toString()); + await new Promise(f => stream.on('end', f)); + expect(content).toBe('Hello world'); + stream.close(); + }); + it('should delete downloads on context destruction', async({browser, server}) => { + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download1 ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const [ download2 ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path1 = await download1.path(); + const path2 = await download2.path(); + expect(fs.existsSync(path1)).toBeTruthy(); + expect(fs.existsSync(path2)).toBeTruthy(); + await page.context().close(); + expect(fs.existsSync(path1)).toBeFalsy(); + expect(fs.existsSync(path2)).toBeFalsy(); + }); + it('should delete downloads on browser gone', async ({ server, browserType }) => { + const browser = await browserType.launch(defaultBrowserOptions); + const page = await browser.newPage({ acceptDownloads: true }); + await page.setContent(`download`); + const [ download1 ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const [ download2 ] = await Promise.all([ + page.waitForEvent('download'), + page.click('a') + ]); + const path1 = await download1.path(); + const path2 = await download2.path(); + expect(fs.existsSync(path1)).toBeTruthy(); + expect(fs.existsSync(path2)).toBeTruthy(); + await browser.close(); + expect(fs.existsSync(path1)).toBeFalsy(); + expect(fs.existsSync(path2)).toBeFalsy(); + expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy(); + }); +}); diff --git a/test/elementhandle.spec.js b/test/elementhandle.spec.js index 0eaf5e31f4..dd3b631166 100644 --- a/test/elementhandle.spec.js +++ b/test/elementhandle.spec.js @@ -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('
hi
'); - 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('
hello
'); - 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(` - - - - `); - 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(` - - woofdoggo - `); - 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('
hi
'); + 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
elements', async({page, server}) => { - await page.setContent('hello
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('
hello
'); + 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(` + + + + `); + 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(` + + woofdoggo + `); + 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
elements', async({page, server}) => { + await page.setContent('hello
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'); + }); +}); diff --git a/test/emulation.spec.js b/test/emulation.spec.js index ae04cf7fa9..9e20033cbc 100644 --- a/test/emulation.spec.js +++ b/test/emulation.spec.js @@ -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(); + }); +}); diff --git a/test/evaluation.spec.js b/test/evaluation.spec.js index 480f95ee79..ddd6ef7730 100644 --- a/test/evaluation.spec.js +++ b/test/evaluation.spec.js @@ -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('
42
'); + 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('
39
'); + 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('
42
'); - 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('
39
'); - 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('
Hi, I\'m frame
'); - }); - 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('
Hi, I\'m frame
'); + }); + 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'); + }); +}); diff --git a/test/fixtures.spec.js b/test/fixtures.spec.js index 188774cb15..9eaed2af28 100644 --- a/test/fixtures.spec.js +++ b/test/fixtures.spec.js @@ -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. }); }); -}; +}); diff --git a/test/focus.spec.js b/test/focus.spec.js index 8272755578..27d89f053a 100644 --- a/test/focus.spec.js +++ b/test/focus.spec.js @@ -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(`
`); - 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(`
`); - 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(`
DIV1
DIV2
`); - 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(`
`); + 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(`
`); + 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(`
DIV1
DIV2
`); + 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); + }); +}); diff --git a/test/frame.spec.js b/test/frame.spec.js index 5c5c2b3d18..7aa9efd4eb 100644 --- a/test/frame.spec.js +++ b/test/frame.spec.js @@ -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:/frames/nested-frames.html', - ' http://localhost:/frames/frame.html (aframe)', - ' http://localhost:/frames/two-frames.html (2frames)', - ' http://localhost:/frames/frame.html (dos)', - ' http://localhost:/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:/frames/nested-frames.html', + ' http://localhost:/frames/frame.html (aframe)', + ' http://localhost:/frames/two-frames.html (2frames)', + ' http://localhost:/frames/frame.html (dos)', + ' http://localhost:/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); + }); +}); diff --git a/test/geolocation.spec.js b/test/geolocation.spec.js index 17d4b9ffa2..ffca54fcd2 100644 --- a/test/geolocation.spec.js +++ b/test/geolocation.spec.js @@ -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 }); + }); +}); diff --git a/test/headful.spec.js b/test/headful.spec.js index 2228b7c928..146fdb38ff 100644 --- a/test/headful.spec.js +++ b/test/headful.spec.js @@ -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); }); -}; +}); diff --git a/test/ignorehttpserrors.spec.js b/test/ignorehttpserrors.spec.js index 8d8cd1b67a..04438bc5b7 100644 --- a/test/ignorehttpserrors.spec.js +++ b/test/ignorehttpserrors.spec.js @@ -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(``); - }); - 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(``); + }); + 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(); + }); +}); diff --git a/test/input.spec.js b/test/input.spec.js index d047d17a97..3eed30a7be 100644 --- a/test/input.spec.js +++ b/test/input.spec.js @@ -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(``); + const [chooser] = await Promise.all([ + new Promise(f => page.once('filechooser', f)), + page.click('input'), + ]); + expect(chooser).toBeTruthy(); + }); + it('should work when file input is attached to DOM', async({page, server}) => { + await page.setContent(``); + const [chooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(chooser).toBeTruthy(); + }); + it('should work when file input is not attached to DOM', async({page, server}) => { + const [chooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.evaluate(() => { + const el = document.createElement('input'); + el.type = 'file'; + el.click(); + }), + ]); + expect(chooser).toBeTruthy(); + }); + it('should respect timeout', async({page, 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(``); + const [fileChooser1, fileChooser2] = await Promise.all([ + page.waitForEvent('filechooser'), + page.waitForEvent('filechooser'), + page.$eval('input', input => input.click()), + ]); + expect(fileChooser1 === fileChooser2).toBe(true); + }); + it('should accept single file', async({page, server}) => { + await page.setContent(``); + const [{ 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(` +
+ + + +
`) + 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(``); + 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(``); + 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(``); + 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(` + + `); + 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(``); - const [chooser] = await Promise.all([ - new Promise(f => page.once('filechooser', f)), - page.click('input'), - ]); - expect(chooser).toBeTruthy(); - }); - it('should work when file input is attached to DOM', async({page, server}) => { - await page.setContent(``); - const [chooser] = await Promise.all([ - page.waitForEvent('filechooser'), - page.click('input'), - ]); - expect(chooser).toBeTruthy(); - }); - it('should work when file input is not attached to DOM', async({page, server}) => { - const [chooser] = await Promise.all([ - page.waitForEvent('filechooser'), - page.evaluate(() => { - const el = document.createElement('input'); - el.type = 'file'; - el.click(); - }), - ]); - expect(chooser).toBeTruthy(); - }); - it('should respect timeout', async({page, 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(``); - const [fileChooser1, fileChooser2] = await Promise.all([ - page.waitForEvent('filechooser'), - page.waitForEvent('filechooser'), - page.$eval('input', input => input.click()), - ]); - expect(fileChooser1 === fileChooser2).toBe(true); - }); - it('should accept single file', async({page, server}) => { - await page.setContent(``); - const [{ 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(` -
- - - -
`) - 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(``); - 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(``); - 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(``); - 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(` - - `); - await (await page.$('input')).setInputFiles(FILE_TO_UPLOAD); - expect(events.length).toBe(2); - expect(events[0].type).toBe('input'); - expect(events[1].type).toBe('change'); - }); +describe('Page.waitForFileChooser isMultiple', () => { + it('should work for single file pick', async({page, server}) => { + await page.setContent(``); + const [{ 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(``); - 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(``); - 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(``); - 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(``); + 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(``); + const [{ multiple }] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(multiple).toBe(true); + }); +}); diff --git a/test/interception.spec.js b/test/interception.spec.js index 83a101c00e..92fd7898b1 100644 --- a/test/interception.spec.js +++ b/test/interception.spec.js @@ -18,554 +18,548 @@ const fs = require('fs'); const path = require('path'); const { helper } = require('../lib/helper'); -const utils = require('./utils'); const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType); -/** - * @type {PageTestSuite} - */ -module.exports.describe = function({defaultBrowserOptions, playwright, FFOX, CHROMIUM, WEBKIT}) { - - describe('Page.route', function() { - it('should intercept', async({page, server}) => { - let intercepted = false; - await page.route('**/empty.html', (route, request) => { - expect(route.request()).toBe(request); - expect(request.url()).toContain('empty.html'); - expect(request.headers()['user-agent']).toBeTruthy(); - expect(request.method()).toBe('GET'); - expect(request.postData()).toBe(null); - expect(request.isNavigationRequest()).toBe(true); - expect(request.resourceType()).toBe('document'); - expect(request.frame() === page.mainFrame()).toBe(true); - expect(request.frame().url()).toBe('about:blank'); - route.continue(); - intercepted = true; - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - expect(intercepted).toBe(true); +describe('Page.route', function() { + it('should intercept', async({page, server}) => { + let intercepted = false; + await page.route('**/empty.html', (route, request) => { + expect(route.request()).toBe(request); + expect(request.url()).toContain('empty.html'); + expect(request.headers()['user-agent']).toBeTruthy(); + expect(request.method()).toBe('GET'); + expect(request.postData()).toBe(null); + expect(request.isNavigationRequest()).toBe(true); + expect(request.resourceType()).toBe('document'); + expect(request.frame() === page.mainFrame()).toBe(true); + expect(request.frame().url()).toBe('about:blank'); + route.continue(); + intercepted = true; }); - it('should work when POST is redirected with 302', async({page, server}) => { - server.setRedirect('/rredirect', '/empty.html'); - await page.goto(server.EMPTY_PAGE); - await page.route('**/*', route => route.continue()); - await page.setContent(` -
- -
- `); - await Promise.all([ - page.$eval('form', form => form.submit()), - page.waitForNavigation() - ]); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/3973 - it('should work when header manipulation headers with redirect', async({page, server}) => { - server.setRedirect('/rrredirect', '/empty.html'); - await page.route('**/*', route => { - const headers = Object.assign({}, route.request().headers(), { - foo: 'bar' - }); - route.continue({ headers }); - }); - await page.goto(server.PREFIX + '/rrredirect'); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/4743 - it('should be able to remove headers', async({page, server}) => { - await page.route('**/*', route => { - const headers = Object.assign({}, route.request().headers(), { - foo: 'bar', - origin: undefined, // remove "origin" header - }); - route.continue({ headers }); - }); - - const [serverRequest] = await Promise.all([ - server.waitForRequest('/empty.html'), - page.goto(server.PREFIX + '/empty.html') - ]); - - expect(serverRequest.headers.origin).toBe(undefined); - }); - it('should contain referer header', async({page, server}) => { - const requests = []; - await page.route('**/*', route => { - requests.push(route.request()); - route.continue(); - }); - await page.goto(server.PREFIX + '/one-style.html'); - expect(requests[1].url()).toContain('/one-style.css'); - expect(requests[1].headers().referer).toContain('/one-style.html'); - }); - it('should properly return navigation response when URL has cookies', async({context, page, server}) => { - // Setup cookie. - await page.goto(server.EMPTY_PAGE); - await context.addCookies([{ url: server.EMPTY_PAGE, name: 'foo', value: 'bar'}]); - - // Setup request interception. - await page.route('**/*', route => route.continue()); - const response = await page.reload(); - expect(response.status()).toBe(200); - }); - it('should show custom HTTP headers', async({page, server}) => { - await page.setExtraHTTPHeaders({ + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + expect(intercepted).toBe(true); + }); + it('should work when POST is redirected with 302', async({page, server}) => { + server.setRedirect('/rredirect', '/empty.html'); + await page.goto(server.EMPTY_PAGE); + await page.route('**/*', route => route.continue()); + await page.setContent(` +
+ +
+ `); + await Promise.all([ + page.$eval('form', form => form.submit()), + page.waitForNavigation() + ]); + }); + // @see https://github.com/GoogleChrome/puppeteer/issues/3973 + it('should work when header manipulation headers with redirect', async({page, server}) => { + server.setRedirect('/rrredirect', '/empty.html'); + await page.route('**/*', route => { + const headers = Object.assign({}, route.request().headers(), { foo: 'bar' }); - await page.route('**/*', route => { - expect(route.request().headers()['foo']).toBe('bar'); - route.continue(); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/4337 - it('should work with redirect inside sync XHR', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - server.setRedirect('/logo.png', '/pptr.png'); - await page.route('**/*', route => route.continue()); - const status = await page.evaluate(async() => { - const request = new XMLHttpRequest(); - request.open('GET', '/logo.png', false); // `false` makes the request synchronous - request.send(null); - return request.status; - }); - expect(status).toBe(200); - }); - it('should work with custom referer headers', async({page, server}) => { - await page.setExtraHTTPHeaders({ 'referer': server.EMPTY_PAGE }); - await page.route('**/*', route => { - expect(route.request().headers()['referer']).toBe(server.EMPTY_PAGE); - route.continue(); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - }); - it('should be abortable', async({page, server}) => { - await page.route(/\.css$/, route => route.abort()); - let failedRequests = 0; - page.on('requestfailed', event => ++failedRequests); - const response = await page.goto(server.PREFIX + '/one-style.html'); - expect(response.ok()).toBe(true); - expect(response.request().failure()).toBe(null); - expect(failedRequests).toBe(1); - }); - it('should be abortable with custom error codes', async({page, server}) => { - await page.route('**/*', route => route.abort('internetdisconnected')); - let failedRequest = null; - page.on('requestfailed', request => failedRequest = request); - await page.goto(server.EMPTY_PAGE).catch(e => {}); - expect(failedRequest).toBeTruthy(); - if (WEBKIT) - expect(failedRequest.failure().errorText).toBe('Request intercepted'); - else if (FFOX) - expect(failedRequest.failure().errorText).toBe('NS_ERROR_OFFLINE'); - else - expect(failedRequest.failure().errorText).toBe('net::ERR_INTERNET_DISCONNECTED'); - }); - it('should send referer', async({page, server}) => { - await page.setExtraHTTPHeaders({ - referer: 'http://google.com/' - }); - await page.route('**/*', route => route.continue()); - const [request] = await Promise.all([ - server.waitForRequest('/grid.html'), - page.goto(server.PREFIX + '/grid.html'), - ]); - expect(request.headers['referer']).toBe('http://google.com/'); - }); - it('should fail navigation when aborting main resource', async({page, server}) => { - await page.route('**/*', route => route.abort()); - let error = null; - await page.goto(server.EMPTY_PAGE).catch(e => error = e); - expect(error).toBeTruthy(); - if (WEBKIT) - expect(error.message).toContain('Request intercepted'); - else if (FFOX) - expect(error.message).toContain('NS_ERROR_FAILURE'); - else - expect(error.message).toContain('net::ERR_FAILED'); - }); - it('should work with redirects', async({page, server}) => { - const requests = []; - await page.route('**/*', route => { - route.continue(); - requests.push(route.request()); - }); - server.setRedirect('/non-existing-page.html', '/non-existing-page-2.html'); - server.setRedirect('/non-existing-page-2.html', '/non-existing-page-3.html'); - server.setRedirect('/non-existing-page-3.html', '/non-existing-page-4.html'); - server.setRedirect('/non-existing-page-4.html', '/empty.html'); - const response = await page.goto(server.PREFIX + '/non-existing-page.html'); - expect(response.status()).toBe(200); - expect(response.url()).toContain('empty.html'); - expect(requests.length).toBe(5); - expect(requests[2].resourceType()).toBe('document'); - const chain = []; - for (let r = response.request(); r; r = r.redirectedFrom()) { - chain.push(r); - expect(r.isNavigationRequest()).toBe(true); - } - expect(chain.length).toBe(5); - expect(chain[0].url()).toContain('/empty.html'); - expect(chain[1].url()).toContain('/non-existing-page-4.html'); - expect(chain[2].url()).toContain('/non-existing-page-3.html'); - expect(chain[3].url()).toContain('/non-existing-page-2.html'); - expect(chain[4].url()).toContain('/non-existing-page.html'); - for (let i = 0; i < chain.length; i++) - expect(chain[i].redirectedTo()).toBe(i ? chain[i - 1] : null); - }); - it('should work with redirects for subresources', async({page, server}) => { - const requests = []; - await page.route('**/*', route => { - route.continue(); - requests.push(route.request()); - }); - server.setRedirect('/one-style.css', '/two-style.css'); - server.setRedirect('/two-style.css', '/three-style.css'); - server.setRedirect('/three-style.css', '/four-style.css'); - server.setRoute('/four-style.css', (req, res) => res.end('body {box-sizing: border-box; }')); - - const response = await page.goto(server.PREFIX + '/one-style.html'); - expect(response.status()).toBe(200); - expect(response.url()).toContain('one-style.html'); - expect(requests.length).toBe(5); - expect(requests[0].resourceType()).toBe('document'); - - let r = requests.find(r => r.url().includes('/four-style.css')); - for (const url of ['/four-style.css', '/three-style.css', '/two-style.css', '/one-style.css']) { - expect(r.resourceType()).toBe('stylesheet'); - expect(r.url()).toContain(url); - r = r.redirectedFrom(); - } - expect(r).toBe(null); - }); - it('should work with equal requests', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - let responseCount = 1; - server.setRoute('/zzz', (req, res) => res.end((responseCount++) * 11 + '')); - - let spinner = false; - // Cancel 2nd request. - await page.route('**/*', route => { - spinner ? route.abort() : route.continue(); - spinner = !spinner; - }); - const results = []; - for (let i = 0; i < 3; i++) - results.push(await page.evaluate(() => fetch('/zzz').then(response => response.text()).catch(e => 'FAILED'))); - expect(results).toEqual(['11', 'FAILED', '22']); - }); - it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => { - const requests = []; - await page.route('**/*', route => { - requests.push(route.request()); - route.continue(); - }); - const dataURL = 'data:text/html,
yo
'; - const response = await page.goto(dataURL); - expect(response).toBe(null); - expect(requests.length).toBe(0); - }); - it('should be able to fetch dataURL and not fire dataURL requests', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const requests = []; - await page.route('**/*', route => { - requests.push(route.request()); - route.continue(); - }); - const dataURL = 'data:text/html,
yo
'; - const text = await page.evaluate(url => fetch(url).then(r => r.text()), dataURL); - expect(text).toBe('
yo
'); - expect(requests.length).toBe(0); - }); - it('should navigate to URL with hash and and fire requests without hash', async({page, server}) => { - const requests = []; - await page.route('**/*', route => { - requests.push(route.request()); - route.continue(); - }); - const response = await page.goto(server.EMPTY_PAGE + '#hash'); - expect(response.status()).toBe(200); - expect(response.url()).toBe(server.EMPTY_PAGE); - expect(requests.length).toBe(1); - expect(requests[0].url()).toBe(server.EMPTY_PAGE); - }); - it('should work with encoded server', async({page, server}) => { - // The requestWillBeSent will report encoded URL, whereas interception will - // report URL as-is. @see crbug.com/759388 - await page.route('**/*', route => route.continue()); - const response = await page.goto(server.PREFIX + '/some nonexisting page'); - expect(response.status()).toBe(404); - }); - it('should work with badly encoded server', async({page, server}) => { - server.setRoute('/malformed?rnd=%911', (req, res) => res.end()); - await page.route('**/*', route => route.continue()); - const response = await page.goto(server.PREFIX + '/malformed?rnd=%911'); - expect(response.status()).toBe(200); - }); - it('should work with encoded server - 2', async({page, server}) => { - // The requestWillBeSent will report URL as-is, whereas interception will - // report encoded URL for stylesheet. @see crbug.com/759388 - const requests = []; - await page.route('**/*', route => { - route.continue(); - requests.push(route.request()); - }); - const response = await page.goto(`data:text/html,`); - expect(response).toBe(null); - expect(requests.length).toBe(1); - expect((await requests[0].response()).status()).toBe(404); - }); - it('should not throw "Invalid Interception Id" if the request was cancelled', async({page, server}) => { - await page.setContent(''); - let route = null; - await page.route('**/*', async r => route = r); - page.$eval('iframe', (frame, url) => frame.src = url, server.EMPTY_PAGE), - // Wait for request interception. - await page.waitForEvent('request'); - // Delete frame to cause request to be canceled. - await page.$eval('iframe', frame => frame.remove()); - let error = null; - await route.continue().catch(e => error = e); - expect(error).toBe(null); - }); - it('should intercept main resource during cross-process navigation', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - let intercepted = false; - await page.route(server.CROSS_PROCESS_PREFIX + '/empty.html', route => { - intercepted = true; - route.continue(); - }); - const response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); - expect(response.ok()).toBe(true); - expect(intercepted).toBe(true); + route.continue({ headers }); }); + await page.goto(server.PREFIX + '/rrredirect'); }); + // @see https://github.com/GoogleChrome/puppeteer/issues/4743 + it('should be able to remove headers', async({page, server}) => { + await page.route('**/*', route => { + const headers = Object.assign({}, route.request().headers(), { + foo: 'bar', + origin: undefined, // remove "origin" header + }); + route.continue({ headers }); + }); - describe('Request.continue', function() { - it('should work', async({page, server}) => { - await page.route('**/*', route => route.continue()); - await page.goto(server.EMPTY_PAGE); - }); - it('should amend HTTP headers', async({page, server}) => { - await page.route('**/*', route => { - const headers = Object.assign({}, route.request().headers()); - headers['FOO'] = 'bar'; - route.continue({ headers }); - }); - await page.goto(server.EMPTY_PAGE); - const [request] = await Promise.all([ - server.waitForRequest('/sleep.zzz'), - page.evaluate(() => fetch('/sleep.zzz')) - ]); - expect(request.headers['foo']).toBe('bar'); - }); - it('should amend method', async({page, server}) => { - const sRequest = server.waitForRequest('/sleep.zzz'); - await page.goto(server.EMPTY_PAGE); - await page.route('**/*', route => route.continue({ method: 'POST' })); - const [request] = await Promise.all([ - server.waitForRequest('/sleep.zzz'), - page.evaluate(() => fetch('/sleep.zzz')) - ]); - expect(request.method).toBe('POST'); - expect((await sRequest).method).toBe('POST'); - }); - it('should amend method on main request', async({page, server}) => { - const request = server.waitForRequest('/empty.html'); - await page.route('**/*', route => route.continue({ method: 'POST' })); - await page.goto(server.EMPTY_PAGE); - expect((await request).method).toBe('POST'); - }); - it('should amend post data', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.route('**/*', route => { - route.continue({ postData: 'doggo' }); - }); - const [serverRequest] = await Promise.all([ - server.waitForRequest('/sleep.zzz'), - page.evaluate(() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })) - ]); - expect(await serverRequest.postBody).toBe('doggo'); - }); + const [serverRequest] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.goto(server.PREFIX + '/empty.html') + ]); + + expect(serverRequest.headers.origin).toBe(undefined); }); - - describe('Request.fulfill', function() { - it('should work', async({page, server}) => { - await page.route('**/*', route => { - route.fulfill({ - status: 201, - headers: { - foo: 'bar' - }, - contentType: 'text/html', - body: 'Yo, page!' - }); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(201); - expect(response.headers().foo).toBe('bar'); - expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); - }); - it('should work with status code 422', async({page, server}) => { - await page.route('**/*', route => { - route.fulfill({ - status: 422, - body: 'Yo, page!' - }); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(422); - expect(response.statusText()).toBe('Unprocessable Entity'); - expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); - }); - it('should allow mocking binary responses', async({page, server}) => { - await page.route('**/*', route => { - const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png')); - route.fulfill({ - contentType: 'image/png', - body: imageBuffer - }); - }); - await page.evaluate(PREFIX => { - const img = document.createElement('img'); - img.src = PREFIX + '/does-not-exist.png'; - document.body.appendChild(img); - return new Promise(fulfill => img.onload = fulfill); - }, server.PREFIX); - const img = await page.$('img'); - expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); - }); - it('should work with file path', async({page, server}) => { - await page.route('**/*', route => route.fulfill({ contentType: 'shouldBeIgnored', path: path.join(__dirname, 'assets', 'pptr.png') })); - await page.evaluate(PREFIX => { - const img = document.createElement('img'); - img.src = PREFIX + '/does-not-exist.png'; - document.body.appendChild(img); - return new Promise(fulfill => img.onload = fulfill); - }, server.PREFIX); - const img = await page.$('img'); - expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); - }); - it('should stringify intercepted request response headers', async({page, server}) => { - await page.route('**/*', route => { - route.fulfill({ - status: 200, - headers: { - 'foo': true - }, - body: 'Yo, page!' - }); - }); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(200); - const headers = response.headers(); - expect(headers.foo).toBe('true'); - expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); + it('should contain referer header', async({page, server}) => { + const requests = []; + await page.route('**/*', route => { + requests.push(route.request()); + route.continue(); }); + await page.goto(server.PREFIX + '/one-style.html'); + expect(requests[1].url()).toContain('/one-style.css'); + expect(requests[1].headers().referer).toContain('/one-style.html'); }); + it('should properly return navigation response when URL has cookies', async({context, page, server}) => { + // Setup cookie. + await page.goto(server.EMPTY_PAGE); + await context.addCookies([{ url: server.EMPTY_PAGE, name: 'foo', value: 'bar'}]); - describe('Interception vs isNavigationRequest', () => { - it('should work with request interception', async({page, server}) => { - const requests = new Map(); - await page.route('**/*', route => { - requests.set(route.request().url().split('/').pop(), route.request()); - route.continue(); + // Setup request interception. + await page.route('**/*', route => route.continue()); + const response = await page.reload(); + expect(response.status()).toBe(200); + }); + it('should show custom HTTP headers', async({page, server}) => { + await page.setExtraHTTPHeaders({ + foo: 'bar' + }); + await page.route('**/*', route => { + expect(route.request().headers()['foo']).toBe('bar'); + route.continue(); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + }); + // @see https://github.com/GoogleChrome/puppeteer/issues/4337 + it('should work with redirect inside sync XHR', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + server.setRedirect('/logo.png', '/pptr.png'); + await page.route('**/*', route => route.continue()); + const status = await page.evaluate(async() => { + const request = new XMLHttpRequest(); + request.open('GET', '/logo.png', false); // `false` makes the request synchronous + request.send(null); + return request.status; + }); + expect(status).toBe(200); + }); + it('should work with custom referer headers', async({page, server}) => { + await page.setExtraHTTPHeaders({ 'referer': server.EMPTY_PAGE }); + await page.route('**/*', route => { + expect(route.request().headers()['referer']).toBe(server.EMPTY_PAGE); + route.continue(); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + }); + it('should be abortable', async({page, server}) => { + await page.route(/\.css$/, route => route.abort()); + let failedRequests = 0; + page.on('requestfailed', event => ++failedRequests); + const response = await page.goto(server.PREFIX + '/one-style.html'); + expect(response.ok()).toBe(true); + expect(response.request().failure()).toBe(null); + expect(failedRequests).toBe(1); + }); + it('should be abortable with custom error codes', async({page, server}) => { + await page.route('**/*', route => route.abort('internetdisconnected')); + let failedRequest = null; + page.on('requestfailed', request => failedRequest = request); + await page.goto(server.EMPTY_PAGE).catch(e => {}); + expect(failedRequest).toBeTruthy(); + if (WEBKIT) + expect(failedRequest.failure().errorText).toBe('Request intercepted'); + else if (FFOX) + expect(failedRequest.failure().errorText).toBe('NS_ERROR_OFFLINE'); + else + expect(failedRequest.failure().errorText).toBe('net::ERR_INTERNET_DISCONNECTED'); + }); + it('should send referer', async({page, server}) => { + await page.setExtraHTTPHeaders({ + referer: 'http://google.com/' + }); + await page.route('**/*', route => route.continue()); + const [request] = await Promise.all([ + server.waitForRequest('/grid.html'), + page.goto(server.PREFIX + '/grid.html'), + ]); + expect(request.headers['referer']).toBe('http://google.com/'); + }); + it('should fail navigation when aborting main resource', async({page, server}) => { + await page.route('**/*', route => route.abort()); + let error = null; + await page.goto(server.EMPTY_PAGE).catch(e => error = e); + expect(error).toBeTruthy(); + if (WEBKIT) + expect(error.message).toContain('Request intercepted'); + else if (FFOX) + expect(error.message).toContain('NS_ERROR_FAILURE'); + else + expect(error.message).toContain('net::ERR_FAILED'); + }); + it('should work with redirects', async({page, server}) => { + const requests = []; + await page.route('**/*', route => { + route.continue(); + requests.push(route.request()); + }); + server.setRedirect('/non-existing-page.html', '/non-existing-page-2.html'); + server.setRedirect('/non-existing-page-2.html', '/non-existing-page-3.html'); + server.setRedirect('/non-existing-page-3.html', '/non-existing-page-4.html'); + server.setRedirect('/non-existing-page-4.html', '/empty.html'); + const response = await page.goto(server.PREFIX + '/non-existing-page.html'); + expect(response.status()).toBe(200); + expect(response.url()).toContain('empty.html'); + expect(requests.length).toBe(5); + expect(requests[2].resourceType()).toBe('document'); + const chain = []; + for (let r = response.request(); r; r = r.redirectedFrom()) { + chain.push(r); + expect(r.isNavigationRequest()).toBe(true); + } + expect(chain.length).toBe(5); + expect(chain[0].url()).toContain('/empty.html'); + expect(chain[1].url()).toContain('/non-existing-page-4.html'); + expect(chain[2].url()).toContain('/non-existing-page-3.html'); + expect(chain[3].url()).toContain('/non-existing-page-2.html'); + expect(chain[4].url()).toContain('/non-existing-page.html'); + for (let i = 0; i < chain.length; i++) + expect(chain[i].redirectedTo()).toBe(i ? chain[i - 1] : null); + }); + it('should work with redirects for subresources', async({page, server}) => { + const requests = []; + await page.route('**/*', route => { + route.continue(); + requests.push(route.request()); + }); + server.setRedirect('/one-style.css', '/two-style.css'); + server.setRedirect('/two-style.css', '/three-style.css'); + server.setRedirect('/three-style.css', '/four-style.css'); + server.setRoute('/four-style.css', (req, res) => res.end('body {box-sizing: border-box; }')); + + const response = await page.goto(server.PREFIX + '/one-style.html'); + expect(response.status()).toBe(200); + expect(response.url()).toContain('one-style.html'); + expect(requests.length).toBe(5); + expect(requests[0].resourceType()).toBe('document'); + + let r = requests.find(r => r.url().includes('/four-style.css')); + for (const url of ['/four-style.css', '/three-style.css', '/two-style.css', '/one-style.css']) { + expect(r.resourceType()).toBe('stylesheet'); + expect(r.url()).toContain(url); + r = r.redirectedFrom(); + } + expect(r).toBe(null); + }); + it('should work with equal requests', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + let responseCount = 1; + server.setRoute('/zzz', (req, res) => res.end((responseCount++) * 11 + '')); + + let spinner = false; + // Cancel 2nd request. + await page.route('**/*', route => { + spinner ? route.abort() : route.continue(); + spinner = !spinner; + }); + const results = []; + for (let i = 0; i < 3; i++) + results.push(await page.evaluate(() => fetch('/zzz').then(response => response.text()).catch(e => 'FAILED'))); + expect(results).toEqual(['11', 'FAILED', '22']); + }); + it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => { + const requests = []; + await page.route('**/*', route => { + requests.push(route.request()); + route.continue(); + }); + const dataURL = 'data:text/html,
yo
'; + const response = await page.goto(dataURL); + expect(response).toBe(null); + expect(requests.length).toBe(0); + }); + it('should be able to fetch dataURL and not fire dataURL requests', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const requests = []; + await page.route('**/*', route => { + requests.push(route.request()); + route.continue(); + }); + const dataURL = 'data:text/html,
yo
'; + const text = await page.evaluate(url => fetch(url).then(r => r.text()), dataURL); + expect(text).toBe('
yo
'); + expect(requests.length).toBe(0); + }); + it('should navigate to URL with hash and and fire requests without hash', async({page, server}) => { + const requests = []; + await page.route('**/*', route => { + requests.push(route.request()); + route.continue(); + }); + const response = await page.goto(server.EMPTY_PAGE + '#hash'); + expect(response.status()).toBe(200); + expect(response.url()).toBe(server.EMPTY_PAGE); + expect(requests.length).toBe(1); + expect(requests[0].url()).toBe(server.EMPTY_PAGE); + }); + it('should work with encoded server', async({page, server}) => { + // The requestWillBeSent will report encoded URL, whereas interception will + // report URL as-is. @see crbug.com/759388 + await page.route('**/*', route => route.continue()); + const response = await page.goto(server.PREFIX + '/some nonexisting page'); + expect(response.status()).toBe(404); + }); + it('should work with badly encoded server', async({page, server}) => { + server.setRoute('/malformed?rnd=%911', (req, res) => res.end()); + await page.route('**/*', route => route.continue()); + const response = await page.goto(server.PREFIX + '/malformed?rnd=%911'); + expect(response.status()).toBe(200); + }); + it('should work with encoded server - 2', async({page, server}) => { + // The requestWillBeSent will report URL as-is, whereas interception will + // report encoded URL for stylesheet. @see crbug.com/759388 + const requests = []; + await page.route('**/*', route => { + route.continue(); + requests.push(route.request()); + }); + const response = await page.goto(`data:text/html,`); + expect(response).toBe(null); + expect(requests.length).toBe(1); + expect((await requests[0].response()).status()).toBe(404); + }); + it('should not throw "Invalid Interception Id" if the request was cancelled', async({page, server}) => { + await page.setContent(''); + let route = null; + await page.route('**/*', async r => route = r); + page.$eval('iframe', (frame, url) => frame.src = url, server.EMPTY_PAGE), + // Wait for request interception. + await page.waitForEvent('request'); + // Delete frame to cause request to be canceled. + await page.$eval('iframe', frame => frame.remove()); + let error = null; + await route.continue().catch(e => error = e); + expect(error).toBe(null); + }); + it('should intercept main resource during cross-process navigation', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + let intercepted = false; + await page.route(server.CROSS_PROCESS_PREFIX + '/empty.html', route => { + intercepted = true; + route.continue(); + }); + const response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + expect(response.ok()).toBe(true); + expect(intercepted).toBe(true); + }); +}); + +describe('Request.continue', function() { + it('should work', async({page, server}) => { + await page.route('**/*', route => route.continue()); + await page.goto(server.EMPTY_PAGE); + }); + it('should amend HTTP headers', async({page, server}) => { + await page.route('**/*', route => { + const headers = Object.assign({}, route.request().headers()); + headers['FOO'] = 'bar'; + route.continue({ headers }); + }); + await page.goto(server.EMPTY_PAGE); + const [request] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz')) + ]); + expect(request.headers['foo']).toBe('bar'); + }); + it('should amend method', async({page, server}) => { + const sRequest = server.waitForRequest('/sleep.zzz'); + await page.goto(server.EMPTY_PAGE); + await page.route('**/*', route => route.continue({ method: 'POST' })); + const [request] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz')) + ]); + expect(request.method).toBe('POST'); + expect((await sRequest).method).toBe('POST'); + }); + it('should amend method on main request', async({page, server}) => { + const request = server.waitForRequest('/empty.html'); + await page.route('**/*', route => route.continue({ method: 'POST' })); + await page.goto(server.EMPTY_PAGE); + expect((await request).method).toBe('POST'); + }); + it('should amend post data', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.route('**/*', route => { + route.continue({ postData: 'doggo' }); + }); + const [serverRequest] = await Promise.all([ + server.waitForRequest('/sleep.zzz'), + page.evaluate(() => fetch('/sleep.zzz', { method: 'POST', body: 'birdy' })) + ]); + expect(await serverRequest.postBody).toBe('doggo'); + }); +}); + +describe('Request.fulfill', function() { + it('should work', async({page, server}) => { + await page.route('**/*', route => { + route.fulfill({ + status: 201, + headers: { + foo: 'bar' + }, + contentType: 'text/html', + body: 'Yo, page!' }); - 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); }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(201); + expect(response.headers().foo).toBe('bar'); + expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); }); - - describe('ignoreHTTPSErrors', function() { - it('should work with request interception', async({browser, httpsServer}) => { - const context = await browser.newContext({ ignoreHTTPSErrors: true }); - const page = await context.newPage(); - - await page.route('**/*', route => route.continue()); - const response = await page.goto(httpsServer.EMPTY_PAGE); - expect(response.status()).toBe(200); - await context.close(); - }); - }); - - describe('service worker', function() { - it('should intercept after a service worker', async({browser, page, server, context}) => { - await page.goto(server.PREFIX + '/serviceworkers/fetchdummy/sw.html'); - await page.evaluate(() => window.activationPromise); - - // Sanity check. - const swResponse = await page.evaluate(() => fetchDummy('foo')); - expect(swResponse).toBe('responseFromServiceWorker:foo'); - - await page.route('**/foo', route => { - const slash = route.request().url().lastIndexOf('/'); - const name = route.request().url().substring(slash + 1); - route.fulfill({ - status: 200, - contentType: 'text/css', - body: 'responseFromInterception:' + name - }); + it('should work with status code 422', async({page, server}) => { + await page.route('**/*', route => { + route.fulfill({ + status: 422, + body: 'Yo, page!' }); - - // Page route is applied after service worker fetch event. - const swResponse2 = await page.evaluate(() => fetchDummy('foo')); - expect(swResponse2).toBe('responseFromServiceWorker:foo'); - - // Page route is not applied to service worker initiated fetch. - const nonInterceptedResponse = await page.evaluate(() => fetchDummy('passthrough')); - expect(nonInterceptedResponse).toBe('FAILURE: Not Found'); }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(422); + expect(response.statusText()).toBe('Unprocessable Entity'); + expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); }); - - describe('glob', function() { - it('should work with glob', async({newPage, httpsServer}) => { - expect(helper.globToRegex('**/*.js').test('https://localhost:8080/foo.js')).toBeTruthy(); - expect(helper.globToRegex('**/*.css').test('https://localhost:8080/foo.js')).toBeFalsy(); - expect(helper.globToRegex('*.js').test('https://localhost:8080/foo.js')).toBeFalsy(); - expect(helper.globToRegex('https://**/*.js').test('https://localhost:8080/foo.js')).toBeTruthy(); - expect(helper.globToRegex('http://localhost:8080/simple/path.js').test('http://localhost:8080/simple/path.js')).toBeTruthy(); - expect(helper.globToRegex('http://localhost:8080/?imple/path.js').test('http://localhost:8080/Simple/path.js')).toBeTruthy(); - expect(helper.globToRegex('**/{a,b}.js').test('https://localhost:8080/a.js')).toBeTruthy(); - expect(helper.globToRegex('**/{a,b}.js').test('https://localhost:8080/b.js')).toBeTruthy(); - expect(helper.globToRegex('**/{a,b}.js').test('https://localhost:8080/c.js')).toBeFalsy(); - - expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.jpg')).toBeTruthy(); - expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.jpeg')).toBeTruthy(); - expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.png')).toBeTruthy(); - expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.css')).toBeFalsy(); - }); - }); - - describe('regexp', function() { - it('should work with regular expression passed from a different context', async({page, server}) => { - const ctx = vm.createContext(); - const regexp = vm.runInContext('new RegExp("empty\\.html")', ctx); - let intercepted = false; - - await page.route(regexp, (route, request) => { - expect(route.request()).toBe(request); - expect(request.url()).toContain('empty.html'); - expect(request.headers()['user-agent']).toBeTruthy(); - expect(request.method()).toBe('GET'); - expect(request.postData()).toBe(null); - expect(request.isNavigationRequest()).toBe(true); - expect(request.resourceType()).toBe('document'); - expect(request.frame() === page.mainFrame()).toBe(true); - expect(request.frame().url()).toBe('about:blank'); - route.continue(); - intercepted = true; + it('should allow mocking binary responses', async({page, server}) => { + await page.route('**/*', route => { + const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png')); + route.fulfill({ + contentType: 'image/png', + body: imageBuffer }); - - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - expect(intercepted).toBe(true); }); + await page.evaluate(PREFIX => { + const img = document.createElement('img'); + img.src = PREFIX + '/does-not-exist.png'; + document.body.appendChild(img); + return new Promise(fulfill => img.onload = fulfill); + }, server.PREFIX); + const img = await page.$('img'); + expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); }); -}; + it('should work with file path', async({page, server}) => { + await page.route('**/*', route => route.fulfill({ contentType: 'shouldBeIgnored', path: path.join(__dirname, 'assets', 'pptr.png') })); + await page.evaluate(PREFIX => { + const img = document.createElement('img'); + img.src = PREFIX + '/does-not-exist.png'; + document.body.appendChild(img); + return new Promise(fulfill => img.onload = fulfill); + }, server.PREFIX); + const img = await page.$('img'); + expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); + }); + it('should stringify intercepted request response headers', async({page, server}) => { + await page.route('**/*', route => { + route.fulfill({ + status: 200, + headers: { + 'foo': true + }, + body: 'Yo, page!' + }); + }); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(200); + const headers = response.headers(); + expect(headers.foo).toBe('true'); + expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); + }); +}); + +describe('Interception vs isNavigationRequest', () => { + it('should work with request interception', async({page, server}) => { + const requests = new Map(); + await page.route('**/*', route => { + requests.set(route.request().url().split('/').pop(), route.request()); + route.continue(); + }); + server.setRedirect('/rrredirect', '/frames/one-frame.html'); + await page.goto(server.PREFIX + '/rrredirect'); + expect(requests.get('rrredirect').isNavigationRequest()).toBe(true); + expect(requests.get('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('ignoreHTTPSErrors', function() { + it('should work with request interception', async({browser, httpsServer}) => { + const context = await browser.newContext({ ignoreHTTPSErrors: true }); + const page = await context.newPage(); + + await page.route('**/*', route => route.continue()); + const response = await page.goto(httpsServer.EMPTY_PAGE); + expect(response.status()).toBe(200); + await context.close(); + }); +}); + +describe('service worker', function() { + it('should intercept after a service worker', async({browser, page, server, context}) => { + await page.goto(server.PREFIX + '/serviceworkers/fetchdummy/sw.html'); + await page.evaluate(() => window.activationPromise); + + // Sanity check. + const swResponse = await page.evaluate(() => fetchDummy('foo')); + expect(swResponse).toBe('responseFromServiceWorker:foo'); + + await page.route('**/foo', route => { + const slash = route.request().url().lastIndexOf('/'); + const name = route.request().url().substring(slash + 1); + route.fulfill({ + status: 200, + contentType: 'text/css', + body: 'responseFromInterception:' + name + }); + }); + + // Page route is applied after service worker fetch event. + const swResponse2 = await page.evaluate(() => fetchDummy('foo')); + expect(swResponse2).toBe('responseFromServiceWorker:foo'); + + // Page route is not applied to service worker initiated fetch. + const nonInterceptedResponse = await page.evaluate(() => fetchDummy('passthrough')); + expect(nonInterceptedResponse).toBe('FAILURE: Not Found'); + }); +}); + +describe('glob', function() { + it('should work with glob', async({newPage, httpsServer}) => { + expect(helper.globToRegex('**/*.js').test('https://localhost:8080/foo.js')).toBeTruthy(); + expect(helper.globToRegex('**/*.css').test('https://localhost:8080/foo.js')).toBeFalsy(); + expect(helper.globToRegex('*.js').test('https://localhost:8080/foo.js')).toBeFalsy(); + expect(helper.globToRegex('https://**/*.js').test('https://localhost:8080/foo.js')).toBeTruthy(); + expect(helper.globToRegex('http://localhost:8080/simple/path.js').test('http://localhost:8080/simple/path.js')).toBeTruthy(); + expect(helper.globToRegex('http://localhost:8080/?imple/path.js').test('http://localhost:8080/Simple/path.js')).toBeTruthy(); + expect(helper.globToRegex('**/{a,b}.js').test('https://localhost:8080/a.js')).toBeTruthy(); + expect(helper.globToRegex('**/{a,b}.js').test('https://localhost:8080/b.js')).toBeTruthy(); + expect(helper.globToRegex('**/{a,b}.js').test('https://localhost:8080/c.js')).toBeFalsy(); + + expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.jpg')).toBeTruthy(); + expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.jpeg')).toBeTruthy(); + expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.png')).toBeTruthy(); + expect(helper.globToRegex('**/*.{png,jpg,jpeg}').test('https://localhost:8080/c.css')).toBeFalsy(); + }); +}); + +describe('regexp', function() { + it('should work with regular expression passed from a different context', async({page, server}) => { + const ctx = vm.createContext(); + const regexp = vm.runInContext('new RegExp("empty\\.html")', ctx); + let intercepted = false; + + await page.route(regexp, (route, request) => { + expect(route.request()).toBe(request); + expect(request.url()).toContain('empty.html'); + expect(request.headers()['user-agent']).toBeTruthy(); + expect(request.method()).toBe('GET'); + expect(request.postData()).toBe(null); + expect(request.isNavigationRequest()).toBe(true); + expect(request.resourceType()).toBe('document'); + expect(request.frame() === page.mainFrame()).toBe(true); + expect(request.frame().url()).toBe('about:blank'); + route.continue(); + intercepted = true; + }); + + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + expect(intercepted).toBe(true); + }); +}); diff --git a/test/jshandle.spec.js b/test/jshandle.spec.js index fd1c795ee6..4ba304143b 100644 --- a/test/jshandle.spec.js +++ b/test/jshandle.spec.js @@ -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('
ee!
'); - 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('
test
'); - 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('
ee!
'); + 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('
test
'); + 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'); + }); +}); diff --git a/test/keyboard.spec.js b/test/keyboard.spec.js index 9d1e0e2d9c..3653d8734b 100644 --- a/test/keyboard.spec.js +++ b/test/keyboard.spec.js @@ -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(''); - 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(''); + 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'); + }) +}); diff --git a/test/launcher.spec.js b/test/launcher.spec.js index e2fb7d9812..d993a602a6 100644 --- a/test/launcher.spec.js +++ b/test/launcher.spec.js @@ -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); + }); +}); diff --git a/test/mouse.spec.js b/test/mouse.spec.js index 66c818d32a..a6cb2ca30d 100644 --- a/test/mouse.spec.js +++ b/test/mouse.spec.js @@ -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'); + }) + }); +}); diff --git a/test/multiclient.spec.js b/test/multiclient.spec.js index 5a2222e800..18cba582b8 100644 --- a/test/multiclient.spec.js +++ b/test/multiclient.spec.js @@ -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(); + }); +}); diff --git a/test/navigation.spec.js b/test/navigation.spec.js index ebccac53f9..c8cef81847 100644 --- a/test/navigation.spec.js +++ b/test/navigation.spec.js @@ -18,1068 +18,1062 @@ const utils = require('./utils'); const path = require('path'); const url = require('url'); +const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = utils.testOptions(browserType); -/** - * @type {PageTestSuite} - */ -module.exports.describe = function({playwright, MAC, WIN, FFOX, CHROMIUM, WEBKIT}) { - - describe('Page.goto', function() { - it('should work', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - expect(page.url()).toBe(server.EMPTY_PAGE); - }); - it('should work with file URL', async({page, server}) => { - const fileurl = url.pathToFileURL(path.join(__dirname, 'assets', 'frames', 'two-frames.html')).href; - await page.goto(fileurl); - expect(page.url().toLowerCase()).toBe(fileurl.toLowerCase()); - expect(page.frames().length).toBe(3); - }); - it('should use http for no protocol', async({page, server}) => { - await page.goto(server.EMPTY_PAGE.substring('http://'.length)); - expect(page.url()).toBe(server.EMPTY_PAGE); - }); - it('should work cross-process', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - expect(page.url()).toBe(server.EMPTY_PAGE); - - const url = server.CROSS_PROCESS_PREFIX + '/empty.html'; - let requestFrame; - page.on('request', r => { - if (r.url() === url) - requestFrame = r.frame(); - }); - const response = await page.goto(url); - expect(page.url()).toBe(url); - expect(response.frame()).toBe(page.mainFrame()); - expect(requestFrame).toBe(page.mainFrame()); - expect(response.url()).toBe(url); - }); - it('should capture iframe navigation request', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - expect(page.url()).toBe(server.EMPTY_PAGE); - - let requestFrame; - page.on('request', r => { - if (r.url() === server.PREFIX + '/frames/frame.html') - requestFrame = r.frame(); - }); - const response = await page.goto(server.PREFIX + '/frames/one-frame.html'); - expect(page.url()).toBe(server.PREFIX + '/frames/one-frame.html'); - expect(response.frame()).toBe(page.mainFrame()); - expect(response.url()).toBe(server.PREFIX + '/frames/one-frame.html'); - - expect(page.frames().length).toBe(2); - expect(requestFrame).toBe(page.frames()[1]); - }); - it('should capture cross-process iframe navigation request', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - expect(page.url()).toBe(server.EMPTY_PAGE); - - let requestFrame; - page.on('request', r => { - if (r.url() === server.CROSS_PROCESS_PREFIX + '/frames/frame.html') - requestFrame = r.frame(); - }); - const response = await page.goto(server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'); - expect(page.url()).toBe(server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'); - expect(response.frame()).toBe(page.mainFrame()); - expect(response.url()).toBe(server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'); - - expect(page.frames().length).toBe(2); - expect(requestFrame).toBe(page.frames()[1]); - }); - it('should work with anchor navigation', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - expect(page.url()).toBe(server.EMPTY_PAGE); - await page.goto(server.EMPTY_PAGE + '#foo'); - expect(page.url()).toBe(server.EMPTY_PAGE + '#foo'); - await page.goto(server.EMPTY_PAGE + '#bar'); - expect(page.url()).toBe(server.EMPTY_PAGE + '#bar'); - }); - it('should work with redirects', async({page, server}) => { - server.setRedirect('/redirect/1.html', '/redirect/2.html'); - server.setRedirect('/redirect/2.html', '/empty.html'); - const response = await page.goto(server.PREFIX + '/redirect/1.html'); - expect(response.status()).toBe(200); - expect(page.url()).toBe(server.EMPTY_PAGE); - }); - it('should navigate to about:blank', async({page, server}) => { - const response = await page.goto('about:blank'); - expect(response).toBe(null); - }); - it('should return response when page changes its URL after load', async({page, server}) => { - const response = await page.goto(server.PREFIX + '/historyapi.html'); - expect(response.status()).toBe(200); - }); - it('should work with subframes return 204', async({page, server}) => { - server.setRoute('/frames/frame.html', (req, res) => { - res.statusCode = 204; - res.end(); - }); - await page.goto(server.PREFIX + '/frames/one-frame.html'); - }); - it('should fail when server returns 204', async({page, server}) => { - // Webkit just loads an empty page. - server.setRoute('/empty.html', (req, res) => { - res.statusCode = 204; - res.end(); - }); - let error = null; - await page.goto(server.EMPTY_PAGE).catch(e => error = e); - expect(error).not.toBe(null); - if (CHROMIUM) - expect(error.message).toContain('net::ERR_ABORTED'); - else if (WEBKIT) - expect(error.message).toContain('Aborted: 204 No Content'); - else - expect(error.message).toContain('NS_BINDING_ABORTED'); - }); - it('should navigate to empty page with domcontentloaded', async({page, server}) => { - const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'domcontentloaded'}); - expect(response.status()).toBe(200); - }); - it('should work when page calls history API in beforeunload', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => { - window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false); - }); - const response = await page.goto(server.PREFIX + '/grid.html'); - expect(response.status()).toBe(200); - }); - it('should fail when navigating to bad url', async({page, server}) => { - let error = null; - await page.goto('asdfasdf').catch(e => error = e); - if (CHROMIUM || WEBKIT) - expect(error.message).toContain('Cannot navigate to invalid URL'); - else - expect(error.message).toContain('Invalid url'); - }); - it('should fail when navigating to bad SSL', async({page, httpsServer}) => { - // Make sure that network events do not emit 'undefined'. - // @see https://crbug.com/750469 - page.on('request', request => expect(request).toBeTruthy()); - page.on('requestfinished', request => expect(request).toBeTruthy()); - page.on('requestfailed', request => expect(request).toBeTruthy()); - let error = null; - await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); - expectSSLError(error.message); - }); - it('should fail when navigating to bad SSL after redirects', async({page, server, httpsServer}) => { - server.setRedirect('/redirect/1.html', '/redirect/2.html'); - server.setRedirect('/redirect/2.html', '/empty.html'); - let error = null; - await page.goto(httpsServer.PREFIX + '/redirect/1.html').catch(e => error = e); - expectSSLError(error.message); - }); - it('should not crash when navigating to bad SSL after a cross origin navigation', async({page, server, httpsServer}) => { - await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); - await page.goto(httpsServer.EMPTY_PAGE).catch(e => void 0); - }); - it('should throw if networkidle is passed as an option', async({page, server}) => { - let error = null; - await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle'}).catch(err => error = err); - expect(error.message).toContain('Unsupported waitUntil option'); - }); - it('should fail when main resources failed to load', async({page, server}) => { - let error = null; - await page.goto('http://localhost:44123/non-existing-url').catch(e => error = e); - if (CHROMIUM) - expect(error.message).toContain('net::ERR_CONNECTION_REFUSED'); - else if (WEBKIT && WIN) - expect(error.message).toContain(`Couldn\'t connect to server`); - else if (WEBKIT) - expect(error.message).toContain('Could not connect'); - else - expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED'); - }); - it('should fail when exceeding maximum navigation timeout', async({page, server}) => { - // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); - let error = null; - await page.goto(server.PREFIX + '/empty.html', {timeout: 1}).catch(e => error = e); - const message = 'Navigation timeout exceeded'; - expect(error.message).toContain(message); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should fail when exceeding default maximum navigation timeout', async({page, server}) => { - // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); - let error = null; - page.context().setDefaultNavigationTimeout(2); - page.setDefaultNavigationTimeout(1); - await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - const message = 'Navigation timeout exceeded'; - expect(error.message).toContain(message); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should fail when exceeding browser context navigation timeout', async({page, server}) => { - // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); - let error = null; - page.context().setDefaultNavigationTimeout(2); - await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - const message = 'Navigation timeout exceeded'; - expect(error.message).toContain(message); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should fail when exceeding default maximum timeout', async({page, server}) => { - // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); - let error = null; - page.context().setDefaultTimeout(2); - page.setDefaultTimeout(1); - await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - const message = 'Navigation timeout exceeded'; - expect(error.message).toContain(message); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should fail when exceeding browser context timeout', async({page, server}) => { - // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); - let error = null; - page.context().setDefaultTimeout(2); - await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - const message = 'Navigation timeout exceeded'; - expect(error.message).toContain(message); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should prioritize default navigation timeout over default timeout', async({page, server}) => { - // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); - let error = null; - page.setDefaultTimeout(0); - page.setDefaultNavigationTimeout(1); - await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - const message = 'Navigation timeout exceeded'; - expect(error.message).toContain(message); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should disable timeout when its set to 0', async({page, server}) => { - let error = null; - let loaded = false; - page.once('load', () => loaded = true); - await page.goto(server.PREFIX + '/grid.html', {timeout: 0, waitUntil: 'load'}).catch(e => error = e); - expect(error).toBe(null); - expect(loaded).toBe(true); - }); - it('should work when navigating to valid url', async({page, server}) => { - const response = await page.goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - }); - it('should work when navigating to data url', async({page, server}) => { - const response = await page.goto('data:text/html,hello'); - expect(response).toBe(null); - }); - it('should work when navigating to 404', async({page, server}) => { - const response = await page.goto(server.PREFIX + '/not-found'); - expect(response.ok()).toBe(false); - expect(response.status()).toBe(404); - }); - it('should return last response in redirect chain', async({page, server}) => { - server.setRedirect('/redirect/1.html', '/redirect/2.html'); - server.setRedirect('/redirect/2.html', '/redirect/3.html'); - server.setRedirect('/redirect/3.html', server.EMPTY_PAGE); - const response = await page.goto(server.PREFIX + '/redirect/1.html'); - expect(response.ok()).toBe(true); - expect(response.url()).toBe(server.EMPTY_PAGE); - }); - it('should not leak listeners during navigation', async({page, server}) => { - let warning = null; - const warningHandler = w => warning = w; - process.on('warning', warningHandler); - for (let i = 0; i < 20; ++i) - await page.goto(server.EMPTY_PAGE); - process.removeListener('warning', warningHandler); - expect(warning).toBe(null); - }); - it('should not leak listeners during bad navigation', async({page, server}) => { - let warning = null; - const warningHandler = w => warning = w; - process.on('warning', warningHandler); - for (let i = 0; i < 20; ++i) - await page.goto('asdf').catch(e => {/* swallow navigation error */}); - process.removeListener('warning', warningHandler); - expect(warning).toBe(null); - }); - it('should not leak listeners during navigation of 11 pages', async({page, context, server}) => { - let warning = null; - const warningHandler = w => warning = w; - process.on('warning', warningHandler); - await Promise.all([...Array(20)].map(async() => { - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.close(); - })); - process.removeListener('warning', warningHandler); - expect(warning).toBe(null); - }); - it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => { - const requests = []; - page.on('request', request => requests.push(request)); - const dataURL = 'data:text/html,
yo
'; - const response = await page.goto(dataURL); - expect(response).toBe(null); - expect(requests.length).toBe(0); - }); - it('should navigate to URL with hash and fire requests without hash', async({page, server}) => { - const requests = []; - page.on('request', request => requests.push(request)); - const response = await page.goto(server.EMPTY_PAGE + '#hash'); - expect(response.status()).toBe(200); - expect(response.url()).toBe(server.EMPTY_PAGE); - expect(requests.length).toBe(1); - expect(requests[0].url()).toBe(server.EMPTY_PAGE); - }); - it('should work with self requesting page', async({page, server}) => { - const response = await page.goto(server.PREFIX + '/self-request.html'); - expect(response.status()).toBe(200); - expect(response.url()).toContain('self-request.html'); - }); - it('should fail when navigating and show the url at the error message', async function({page, server, httpsServer}) { - const url = httpsServer.PREFIX + '/redirect/1.html'; - let error = null; - try { - await page.goto(url); - } catch (e) { - error = e; - } - expect(error.message).toContain(url); - }); - it('should send referer', async({page, server}) => { - const [request1, request2] = await Promise.all([ - server.waitForRequest('/grid.html'), - server.waitForRequest('/digits/1.png'), - page.goto(server.PREFIX + '/grid.html', { - referer: 'http://google.com/', - }), - ]); - expect(request1.headers['referer']).toBe('http://google.com/'); - // Make sure subresources do not inherit referer. - expect(request2.headers['referer']).toBe(server.PREFIX + '/grid.html'); - expect(page.url()).toBe(server.PREFIX + '/grid.html'); - }); - it('should reject referer option when setExtraHTTPHeaders provides referer', async({page, server}) => { - await page.setExtraHTTPHeaders({ 'referer': 'http://microsoft.com/' }); - let error; - await page.goto(server.PREFIX + '/grid.html', { - referer: 'http://google.com/', - }).catch(e => error = e); - expect(error.message).toBe('"referer" is already specified as extra HTTP header'); - }); - it('should override referrer-policy', async({page, server}) => { - server.setRoute('/grid.html', (req, res) => { - res.setHeader('Referrer-Policy', 'no-referrer'); - server.serveFile(req, res, '/grid.html'); - }); - const [request1, request2] = await Promise.all([ - server.waitForRequest('/grid.html'), - server.waitForRequest('/digits/1.png'), - page.goto(server.PREFIX + '/grid.html', { - referer: 'http://microsoft.com/', - }), - ]); - expect(request1.headers['referer']).toBe('http://microsoft.com/'); - // Make sure subresources do not inherit referer. - expect(request2.headers['referer']).toBe(undefined); - expect(page.url()).toBe(server.PREFIX + '/grid.html'); - }); - it('should fail when canceled by another navigation', async({page, server}) => { - server.setRoute('/one-style.html', (req, res) => {}); - const failed = page.goto(server.PREFIX + '/one-style.html').catch(e => e); - await server.waitForRequest('/one-style.html'); - await page.goto(server.PREFIX + '/empty.html'); - const error = await failed; - expect(error.message).toBeTruthy(); - }); - it.skip(true)('extraHttpHeaders should be pushed to provisional page', async({page, server}) => { - // This test is flaky, because we cannot await page.setExtraHTTPHeaders. - // We need a way to test our implementation by more than just public api. - await page.goto(server.EMPTY_PAGE); - const pagePath = '/one-style.html'; - server.setRoute(pagePath, async (req, res) => { - page.setExtraHTTPHeaders({ foo: 'bar' }); - server.serveFile(req, res, pagePath); - }); - const [htmlReq, cssReq] = await Promise.all([ - server.waitForRequest(pagePath), - server.waitForRequest('/one-style.css'), - page.goto(server.CROSS_PROCESS_PREFIX + pagePath) - ]); - expect(htmlReq.headers['foo']).toBe(undefined); - expect(cssReq.headers['foo']).toBe('bar'); - }); - - describe('network idle', function() { - it('should navigate to empty page with networkidle0', async({page, server}) => { - const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle0' }); - expect(response.status()).toBe(200); - }); - it('should navigate to empty page with networkidle2', async({page, server}) => { - const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle2' }); - expect(response.status()).toBe(200); - }); - - /** - * @param {import('../src/frames').Frame} frame - * @param {TestServer} server - * @param {'networkidle0'|'networkidle2'} signal - * @param {() => Promise} action - * @param {boolean} isSetContent - */ - async function networkIdleTest(frame, server, signal, action, isSetContent) { - const finishResponse = response => { - response.statusCode = 404; - response.end(`File not found`); - }; - const waitForRequest = suffix => { - return Promise.all([ - server.waitForRequest(suffix), - frame._page.waitForRequest(server.PREFIX + suffix), - ]) - } - let responses = {}; - // Hold on to a bunch of requests without answering. - server.setRoute('/fetch-request-a.js', (req, res) => responses.a = res); - server.setRoute('/fetch-request-b.js', (req, res) => responses.b = res); - server.setRoute('/fetch-request-c.js', (req, res) => responses.c = res); - const initialFetchResourcesRequested = Promise.all([ - waitForRequest('/fetch-request-a.js'), - waitForRequest('/fetch-request-b.js'), - waitForRequest('/fetch-request-c.js') - ]); - - let secondFetchResourceRequested; - if (signal === 'networkidle0') { - server.setRoute('/fetch-request-d.js', (req, res) => responses.d = res); - secondFetchResourceRequested = waitForRequest('/fetch-request-d.js'); - } - - const waitForLoadPromise = isSetContent ? Promise.resolve() : frame.waitForNavigation({ waitUntil: 'load' }); - - // Navigate to a page which loads immediately and then does a bunch of - // requests via javascript's fetch method. - const actionPromise = action(); - - // Track when the action gets completed. - let actionFinished = false; - actionPromise.then(() => actionFinished = true); - - // Wait for the frame's 'load' event. - await waitForLoadPromise; - expect(actionFinished).toBe(false); - - // Wait for the initial three resources to be requested. - await initialFetchResourcesRequested; - expect(actionFinished).toBe(false); - - expect(responses.a).toBeTruthy(); - expect(responses.b).toBeTruthy(); - expect(responses.c).toBeTruthy(); - let timer; - let timerTriggered = false; - if (signal === 'networkidle0') { - // Finishing first response should leave 2 requests alive and trigger networkidle2. - finishResponse(responses.a); - // Finishing two more responses should trigger the second round. - finishResponse(responses.b); - finishResponse(responses.c); - - // Wait for the second round to be requested. - await secondFetchResourceRequested; - expect(actionFinished).toBe(false); - // Finishing the last response should trigger networkidle0. - timer = setTimeout(() => timerTriggered = true, 500); - finishResponse(responses.d); - } else { - timer = setTimeout(() => timerTriggered = true, 500); - // Finishing first response should leave 2 requests alive and trigger networkidle2. - finishResponse(responses.a); - } - const response = await actionPromise; - clearTimeout(timer); - expect(timerTriggered).toBe(true); - if (!isSetContent) - expect(response.ok()).toBe(true); - - if (signal === 'networkidle2') { - // Cleanup. - finishResponse(responses.b); - finishResponse(responses.c); - } - } - - it('should wait for networkidle0 to succeed navigation', async({page, server}) => { - await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { - return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle0' }); - }); - }); - it('should wait for networkidle2 to succeed navigation', async({page, server}) => { - await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { - return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle2' }); - }); - }); - it('should wait for networkidle0 to succeed navigation with request from previous navigation', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - server.setRoute('/foo.js', () => {}); - await page.setContent(``); - await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { - return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle0' }); - }); - }); - it('should wait for networkidle2 to succeed navigation with request from previous navigation', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - server.setRoute('/foo.js', () => {}); - await page.setContent(``); - await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { - return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle2' }); - }); - }); - it('should wait for networkidle0 in waitForNavigation', async({page, server}) => { - await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { - const promise = page.waitForNavigation({ waitUntil: 'networkidle0' }); - page.goto(server.PREFIX + '/networkidle.html'); - return promise; - }); - }); - it('should wait for networkidle2 in waitForNavigation', async({page, server}) => { - await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { - const promise = page.waitForNavigation({ waitUntil: 'networkidle2' }); - page.goto(server.PREFIX + '/networkidle.html'); - return promise; - }); - }); - it('should wait for networkidle0 in setContent', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { - return page.setContent(``, { waitUntil: 'networkidle0' }); - }, true); - }); - it('should wait for networkidle2 in setContent', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { - return page.setContent(``, { waitUntil: 'networkidle2' }); - }, true); - }); - it.fail(FFOX)('should wait for networkidle0 in setContent with request from previous navigation', async({page, server}) => { - // TODO: in Firefox window.stop() does not cancel outstanding requests, and we also lack 'init' lifecycle, - // therefore we don't clear inflight requests at the right time. - await page.goto(server.EMPTY_PAGE); - server.setRoute('/foo.js', () => {}); - await page.setContent(``); - await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { - return page.setContent(``, { waitUntil: 'networkidle0' }); - }, true); - }); - it.fail(FFOX)('should wait for networkidle2 in setContent with request from previous navigation', async({page, server}) => { - // TODO: in Firefox window.stop() does not cancel outstanding requests, and we also lack 'init' lifecycle, - // therefore we don't clear inflight requests at the right time. - await page.goto(server.EMPTY_PAGE); - server.setRoute('/foo.js', () => {}); - await page.setContent(``); - await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { - return page.setContent(``, { waitUntil: 'networkidle2' }); - }, true); - }); - it('should wait for networkidle0 when navigating iframe', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = page.mainFrame().childFrames()[0]; - await networkIdleTest(frame, server, 'networkidle0', () => frame.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle0' })); - }); - it('should wait for networkidle2 when navigating iframe', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = page.mainFrame().childFrames()[0]; - await networkIdleTest(frame, server, 'networkidle2', () => frame.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle2' })); - }); - it('should wait for networkidle0 in setContent from the child frame', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { - return page.setContent(``, { waitUntil: 'networkidle0' }); - }, true); - }); - it('should wait for networkidle2 in setContent from the child frame', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { - return page.setContent(``, { waitUntil: 'networkidle2' }); - }, true); - }); - it('should wait for networkidle0 from the child frame', async({page, server}) => { - await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { - return page.goto(server.PREFIX + '/networkidle-frame.html', { waitUntil: 'networkidle0' }); - }); - }); - it('should wait for networkidle2 from the child frame', async({page, server}) => { - await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { - return page.goto(server.PREFIX + '/networkidle-frame.html', { waitUntil: 'networkidle2' }); - }); - }); - }); +describe('Page.goto', function() { + it('should work', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + expect(page.url()).toBe(server.EMPTY_PAGE); }); - - describe('Page.waitForNavigation', function() { - it('should work', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [response] = await Promise.all([ - page.waitForNavigation(), - page.evaluate(url => window.location.href = url, server.PREFIX + '/grid.html') - ]); - expect(response.ok()).toBe(true); - expect(response.url()).toContain('grid.html'); - }); - it('should work with both domcontentloaded and load', async({page, server}) => { - let response = null; - server.setRoute('/one-style.css', (req, res) => response = res); - const navigationPromise = page.goto(server.PREFIX + '/one-style.html'); - const domContentLoadedPromise = page.waitForNavigation({ - waitUntil: 'domcontentloaded' - }); - - let bothFired = false; - const bothFiredPromise = Promise.all([ - page.waitForNavigation({ waitUntil: 'load' }), - domContentLoadedPromise - ]).then(() => bothFired = true); - - await server.waitForRequest('/one-style.css'); - await domContentLoadedPromise; - expect(bothFired).toBe(false); - response.end(); - await bothFiredPromise; - await navigationPromise; - }); - it('should work with clicking on anchor links', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent(`foobar`); - const [response] = await Promise.all([ - page.waitForNavigation(), - page.click('a'), - ]); - expect(response).toBe(null); - expect(page.url()).toBe(server.EMPTY_PAGE + '#foobar'); - }); - it('should work with clicking on links which do not commit navigation', async({page, server, httpsServer}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent(`foobar`); - const [error] = await Promise.all([ - page.waitForNavigation().catch(e => e), - page.click('a'), - ]); - expectSSLError(error.message); - }); - it('should work with history.pushState()', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent(` - SPA - - `); - const [response] = await Promise.all([ - page.waitForNavigation(), - page.click('a'), - ]); - expect(response).toBe(null); - expect(page.url()).toBe(server.PREFIX + '/wow.html'); - }); - it('should work with history.replaceState()', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent(` - SPA - - `); - const [response] = await Promise.all([ - page.waitForNavigation(), - page.click('a'), - ]); - expect(response).toBe(null); - expect(page.url()).toBe(server.PREFIX + '/replaced.html'); - }); - it('should work with DOM history.back()/history.forward()', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent(` - back - forward - - `); - expect(page.url()).toBe(server.PREFIX + '/second.html'); - const [backResponse] = await Promise.all([ - page.waitForNavigation(), - page.click('a#back'), - ]); - expect(backResponse).toBe(null); - expect(page.url()).toBe(server.PREFIX + '/first.html'); - const [forwardResponse] = await Promise.all([ - page.waitForNavigation(), - page.click('a#forward'), - ]); - expect(forwardResponse).toBe(null); - expect(page.url()).toBe(server.PREFIX + '/second.html'); - }); - it('should work when subframe issues window.stop()', async({page, server}) => { - server.setRoute('/frames/style.css', (req, res) => {}); - const navigationPromise = page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = await new Promise(f => page.once('frameattached', f)); - await new Promise(fulfill => page.on('framenavigated', f => { - if (f === frame) - fulfill(); - })); - await Promise.all([ - frame.evaluate(() => window.stop()), - navigationPromise - ]); - }); - it('should work with url match', async({page, server}) => { - let response1 = null; - const response1Promise = page.waitForNavigation({ url: /one-style\.html/ }).then(response => response1 = response); - let response2 = null; - const response2Promise = page.waitForNavigation({ url: /\/frame.html/ }).then(response => response2 = response); - let response3 = null; - const response3Promise = page.waitForNavigation({ url: url => url.searchParams.get('foo') === 'bar' }).then(response => response3 = response); - expect(response1).toBe(null); - expect(response2).toBe(null); - expect(response3).toBe(null); - await page.goto(server.EMPTY_PAGE); - expect(response1).toBe(null); - expect(response2).toBe(null); - expect(response3).toBe(null); - await page.goto(server.PREFIX + '/frame.html'); - expect(response1).toBe(null); - await response2Promise; - expect(response2).not.toBe(null); - expect(response3).toBe(null); - await page.goto(server.PREFIX + '/one-style.html'); - await response1Promise; - expect(response1).not.toBe(null); - expect(response2).not.toBe(null); - expect(response3).toBe(null); - await page.goto(server.PREFIX + '/frame.html?foo=bar'); - await response3Promise; - expect(response1).not.toBe(null); - expect(response2).not.toBe(null); - expect(response3).not.toBe(null); - await page.goto(server.PREFIX + '/empty.html'); - expect(response1.url()).toBe(server.PREFIX + '/one-style.html'); - expect(response2.url()).toBe(server.PREFIX + '/frame.html'); - expect(response3.url()).toBe(server.PREFIX + '/frame.html?foo=bar'); - }); - it('should work with url match for same document navigations', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - let resolved = false; - const waitPromise = page.waitForNavigation({ url: /third\.html/ }).then(() => resolved = true); - expect(resolved).toBe(false); - await page.evaluate(() => { - history.pushState({}, '', '/first.html'); - }); - expect(resolved).toBe(false); - await page.evaluate(() => { - history.pushState({}, '', '/second.html'); - }); - expect(resolved).toBe(false); - await page.evaluate(() => { - history.pushState({}, '', '/third.html'); - }); - await waitPromise; - expect(resolved).toBe(true); - }); - it('should work for cross-process navigations', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const waitPromise = page.waitForNavigation({waitUntil: 'domcontentloaded'}); - const url = server.CROSS_PROCESS_PREFIX + '/empty.html'; - const gotoPromise = page.goto(url); - const response = await waitPromise; - expect(response.url()).toBe(url); - expect(page.url()).toBe(url); - expect(await page.evaluate('document.location.href')).toBe(url); - await gotoPromise; - }); + it('should work with file URL', async({page, server}) => { + const fileurl = url.pathToFileURL(path.join(__dirname, 'assets', 'frames', 'two-frames.html')).href; + await page.goto(fileurl); + expect(page.url().toLowerCase()).toBe(fileurl.toLowerCase()); + expect(page.frames().length).toBe(3); }); + it('should use http for no protocol', async({page, server}) => { + await page.goto(server.EMPTY_PAGE.substring('http://'.length)); + expect(page.url()).toBe(server.EMPTY_PAGE); + }); + it('should work cross-process', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + expect(page.url()).toBe(server.EMPTY_PAGE); - describe('Page.waitForLoadState', () => { - it('should pick up ongoing navigation', async({page, server}) => { - let response = null; - server.setRoute('/one-style.css', (req, res) => response = res); - await Promise.all([ - server.waitForRequest('/one-style.css'), - page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}), - ]); - const waitPromise = page.waitForLoadState(); - response.statusCode = 404; - response.end('Not found'); - await waitPromise; + const url = server.CROSS_PROCESS_PREFIX + '/empty.html'; + let requestFrame; + page.on('request', r => { + if (r.url() === url) + requestFrame = r.frame(); }); - it('should respect timeout', async({page, server}) => { - server.setRoute('/one-style.css', (req, res) => response = res); - await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); - const error = await page.waitForLoadState('load', { timeout: 1 }).catch(e => e); - expect(error.message).toBe('Navigation timeout exceeded'); + const response = await page.goto(url); + expect(page.url()).toBe(url); + expect(response.frame()).toBe(page.mainFrame()); + expect(requestFrame).toBe(page.mainFrame()); + expect(response.url()).toBe(url); + }); + it('should capture iframe navigation request', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + expect(page.url()).toBe(server.EMPTY_PAGE); + + let requestFrame; + page.on('request', r => { + if (r.url() === server.PREFIX + '/frames/frame.html') + requestFrame = r.frame(); }); - it('should resolve immediately if loaded', async({page, server}) => { - await page.goto(server.PREFIX + '/one-style.html'); - await page.waitForLoadState(); + const response = await page.goto(server.PREFIX + '/frames/one-frame.html'); + expect(page.url()).toBe(server.PREFIX + '/frames/one-frame.html'); + expect(response.frame()).toBe(page.mainFrame()); + expect(response.url()).toBe(server.PREFIX + '/frames/one-frame.html'); + + expect(page.frames().length).toBe(2); + expect(requestFrame).toBe(page.frames()[1]); + }); + it('should capture cross-process iframe navigation request', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + expect(page.url()).toBe(server.EMPTY_PAGE); + + let requestFrame; + page.on('request', r => { + if (r.url() === server.CROSS_PROCESS_PREFIX + '/frames/frame.html') + requestFrame = r.frame(); }); - it('should resolve immediately if load state matches', async({page, server}) => { + const response = await page.goto(server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'); + expect(page.url()).toBe(server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'); + expect(response.frame()).toBe(page.mainFrame()); + expect(response.url()).toBe(server.CROSS_PROCESS_PREFIX + '/frames/one-frame.html'); + + expect(page.frames().length).toBe(2); + expect(requestFrame).toBe(page.frames()[1]); + }); + it('should work with anchor navigation', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + expect(page.url()).toBe(server.EMPTY_PAGE); + await page.goto(server.EMPTY_PAGE + '#foo'); + expect(page.url()).toBe(server.EMPTY_PAGE + '#foo'); + await page.goto(server.EMPTY_PAGE + '#bar'); + expect(page.url()).toBe(server.EMPTY_PAGE + '#bar'); + }); + it('should work with redirects', async({page, server}) => { + server.setRedirect('/redirect/1.html', '/redirect/2.html'); + server.setRedirect('/redirect/2.html', '/empty.html'); + const response = await page.goto(server.PREFIX + '/redirect/1.html'); + expect(response.status()).toBe(200); + expect(page.url()).toBe(server.EMPTY_PAGE); + }); + it('should navigate to about:blank', async({page, server}) => { + const response = await page.goto('about:blank'); + expect(response).toBe(null); + }); + it('should return response when page changes its URL after load', async({page, server}) => { + const response = await page.goto(server.PREFIX + '/historyapi.html'); + expect(response.status()).toBe(200); + }); + it('should work with subframes return 204', async({page, server}) => { + server.setRoute('/frames/frame.html', (req, res) => { + res.statusCode = 204; + res.end(); + }); + await page.goto(server.PREFIX + '/frames/one-frame.html'); + }); + it('should fail when server returns 204', async({page, server}) => { + // Webkit just loads an empty page. + server.setRoute('/empty.html', (req, res) => { + res.statusCode = 204; + res.end(); + }); + let error = null; + await page.goto(server.EMPTY_PAGE).catch(e => error = e); + expect(error).not.toBe(null); + if (CHROMIUM) + expect(error.message).toContain('net::ERR_ABORTED'); + else if (WEBKIT) + expect(error.message).toContain('Aborted: 204 No Content'); + else + expect(error.message).toContain('NS_BINDING_ABORTED'); + }); + it('should navigate to empty page with domcontentloaded', async({page, server}) => { + const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'domcontentloaded'}); + expect(response.status()).toBe(200); + }); + it('should work when page calls history API in beforeunload', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => { + window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false); + }); + const response = await page.goto(server.PREFIX + '/grid.html'); + expect(response.status()).toBe(200); + }); + it('should fail when navigating to bad url', async({page, server}) => { + let error = null; + await page.goto('asdfasdf').catch(e => error = e); + if (CHROMIUM || WEBKIT) + expect(error.message).toContain('Cannot navigate to invalid URL'); + else + expect(error.message).toContain('Invalid url'); + }); + it('should fail when navigating to bad SSL', async({page, httpsServer}) => { + // Make sure that network events do not emit 'undefined'. + // @see https://crbug.com/750469 + page.on('request', request => expect(request).toBeTruthy()); + page.on('requestfinished', request => expect(request).toBeTruthy()); + page.on('requestfailed', request => expect(request).toBeTruthy()); + let error = null; + await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); + expectSSLError(error.message); + }); + it('should fail when navigating to bad SSL after redirects', async({page, server, httpsServer}) => { + server.setRedirect('/redirect/1.html', '/redirect/2.html'); + server.setRedirect('/redirect/2.html', '/empty.html'); + let error = null; + await page.goto(httpsServer.PREFIX + '/redirect/1.html').catch(e => error = e); + expectSSLError(error.message); + }); + it('should not crash when navigating to bad SSL after a cross origin navigation', async({page, server, httpsServer}) => { + await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + await page.goto(httpsServer.EMPTY_PAGE).catch(e => void 0); + }); + it('should throw if networkidle is passed as an option', async({page, server}) => { + let error = null; + await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle'}).catch(err => error = err); + expect(error.message).toContain('Unsupported waitUntil option'); + }); + it('should fail when main resources failed to load', async({page, server}) => { + let error = null; + await page.goto('http://localhost:44123/non-existing-url').catch(e => error = e); + if (CHROMIUM) + expect(error.message).toContain('net::ERR_CONNECTION_REFUSED'); + else if (WEBKIT && WIN) + expect(error.message).toContain(`Couldn\'t connect to server`); + else if (WEBKIT) + expect(error.message).toContain('Could not connect'); + else + expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED'); + }); + it('should fail when exceeding maximum navigation timeout', async({page, server}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + await page.goto(server.PREFIX + '/empty.html', {timeout: 1}).catch(e => error = e); + const message = 'Navigation timeout exceeded'; + expect(error.message).toContain(message); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); + }); + it('should fail when exceeding default maximum navigation timeout', async({page, server}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + page.context().setDefaultNavigationTimeout(2); + page.setDefaultNavigationTimeout(1); + await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); + const message = 'Navigation timeout exceeded'; + expect(error.message).toContain(message); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); + }); + it('should fail when exceeding browser context navigation timeout', async({page, server}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + page.context().setDefaultNavigationTimeout(2); + await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); + const message = 'Navigation timeout exceeded'; + expect(error.message).toContain(message); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); + }); + it('should fail when exceeding default maximum timeout', async({page, server}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + page.context().setDefaultTimeout(2); + page.setDefaultTimeout(1); + await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); + const message = 'Navigation timeout exceeded'; + expect(error.message).toContain(message); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); + }); + it('should fail when exceeding browser context timeout', async({page, server}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + page.context().setDefaultTimeout(2); + await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); + const message = 'Navigation timeout exceeded'; + expect(error.message).toContain(message); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); + }); + it('should prioritize default navigation timeout over default timeout', async({page, server}) => { + // Hang for request to the empty.html + server.setRoute('/empty.html', (req, res) => { }); + let error = null; + page.setDefaultTimeout(0); + page.setDefaultNavigationTimeout(1); + await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); + const message = 'Navigation timeout exceeded'; + expect(error.message).toContain(message); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); + }); + it('should disable timeout when its set to 0', async({page, server}) => { + let error = null; + let loaded = false; + page.once('load', () => loaded = true); + await page.goto(server.PREFIX + '/grid.html', {timeout: 0, waitUntil: 'load'}).catch(e => error = e); + expect(error).toBe(null); + expect(loaded).toBe(true); + }); + it('should work when navigating to valid url', async({page, server}) => { + const response = await page.goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + }); + it('should work when navigating to data url', async({page, server}) => { + const response = await page.goto('data:text/html,hello'); + expect(response).toBe(null); + }); + it('should work when navigating to 404', async({page, server}) => { + const response = await page.goto(server.PREFIX + '/not-found'); + expect(response.ok()).toBe(false); + expect(response.status()).toBe(404); + }); + it('should return last response in redirect chain', async({page, server}) => { + server.setRedirect('/redirect/1.html', '/redirect/2.html'); + server.setRedirect('/redirect/2.html', '/redirect/3.html'); + server.setRedirect('/redirect/3.html', server.EMPTY_PAGE); + const response = await page.goto(server.PREFIX + '/redirect/1.html'); + expect(response.ok()).toBe(true); + expect(response.url()).toBe(server.EMPTY_PAGE); + }); + it('should not leak listeners during navigation', async({page, server}) => { + let warning = null; + const warningHandler = w => warning = w; + process.on('warning', warningHandler); + for (let i = 0; i < 20; ++i) await page.goto(server.EMPTY_PAGE); - server.setRoute('/one-style.css', (req, res) => response = res); - await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); - await page.waitForLoadState('domcontentloaded'); - }); - it('should work with pages that have loaded before being connected to', async({page, context, server}) => { - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window._popup = window.open(document.location.href)), - ]); - // The url is about:blank in FF. - // expect(popup.url()).toBe(server.EMPTY_PAGE); - await popup.waitForLoadState(); - expect(popup.url()).toBe(server.EMPTY_PAGE); - }); - it('should wait for load state of empty url popup', async({browser, page}) => { - const [popup, readyState] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => { - const popup = window.open(''); - return popup.document.readyState; - }), - ]); - await popup.waitForLoadState(); - expect(readyState).toBe(FFOX ? 'uninitialized' : 'complete'); - expect(await popup.evaluate(() => document.readyState)).toBe(FFOX ? 'uninitialized' : 'complete'); - }); - it('should wait for load state of about:blank popup ', async({browser, page}) => { - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.open('about:blank') && 1), - ]); - await popup.waitForLoadState(); - expect(await popup.evaluate(() => document.readyState)).toBe('complete'); - }); - it('should wait for load state of about:blank popup with noopener ', async({browser, page}) => { - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.open('about:blank', null, 'noopener') && 1), - ]); - await popup.waitForLoadState(); - expect(await popup.evaluate(() => document.readyState)).toBe('complete'); - }); - it('should wait for load state of popup with network url ', async({browser, page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window.open(url) && 1, server.EMPTY_PAGE), - ]); - await popup.waitForLoadState(); - expect(await popup.evaluate(() => document.readyState)).toBe('complete'); - }); - it('should wait for load state of popup with network url and noopener ', async({browser, page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window.open(url, null, 'noopener') && 1, server.EMPTY_PAGE), - ]); - await popup.waitForLoadState(); - expect(await popup.evaluate(() => document.readyState)).toBe('complete'); - }); - it('should work with clicking target=_blank', async({browser, page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.click('a'), - ]); - await popup.waitForLoadState(); - expect(await popup.evaluate(() => document.readyState)).toBe('complete'); - }); - it('should wait for load state of newPage', async({browser, context, page, server}) => { - const [newPage] = await Promise.all([ - context.waitForEvent('page'), - context.newPage(), - ]); - await newPage.waitForLoadState(); - expect(await newPage.evaluate(() => document.readyState)).toBe('complete'); - }); - it('should resolve after popup load', async({browser, server}) => { - const context = await browser.newContext(); + process.removeListener('warning', warningHandler); + expect(warning).toBe(null); + }); + it('should not leak listeners during bad navigation', async({page, server}) => { + let warning = null; + const warningHandler = w => warning = w; + process.on('warning', warningHandler); + for (let i = 0; i < 20; ++i) + await page.goto('asdf').catch(e => {/* swallow navigation error */}); + process.removeListener('warning', warningHandler); + expect(warning).toBe(null); + }); + it('should not leak listeners during navigation of 11 pages', async({page, context, server}) => { + let warning = null; + const warningHandler = w => warning = w; + process.on('warning', warningHandler); + await Promise.all([...Array(20)].map(async() => { const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); - // Stall the 'load' by delaying css. - let cssResponse; - server.setRoute('/one-style.css', (req, res) => cssResponse = res); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - server.waitForRequest('/one-style.css'), - page.evaluate(url => window.popup = window.open(url), server.PREFIX + '/one-style.html'), - ]); - let resolved = false; - const loadSatePromise = popup.waitForLoadState().then(() => resolved = true); - // Round trips! - for (let i = 0; i < 5; i++) - await page.evaluate('window'); - expect(resolved).toBe(false); - cssResponse.end(''); - await loadSatePromise; - expect(resolved).toBe(true); - expect(popup.url()).toBe(server.PREFIX + '/one-style.html'); - await context.close(); + await page.close(); + })); + process.removeListener('warning', warningHandler); + expect(warning).toBe(null); + }); + it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => { + const requests = []; + page.on('request', request => requests.push(request)); + const dataURL = 'data:text/html,
yo
'; + const response = await page.goto(dataURL); + expect(response).toBe(null); + expect(requests.length).toBe(0); + }); + it('should navigate to URL with hash and fire requests without hash', async({page, server}) => { + const requests = []; + page.on('request', request => requests.push(request)); + const response = await page.goto(server.EMPTY_PAGE + '#hash'); + expect(response.status()).toBe(200); + expect(response.url()).toBe(server.EMPTY_PAGE); + expect(requests.length).toBe(1); + expect(requests[0].url()).toBe(server.EMPTY_PAGE); + }); + it('should work with self requesting page', async({page, server}) => { + const response = await page.goto(server.PREFIX + '/self-request.html'); + expect(response.status()).toBe(200); + expect(response.url()).toContain('self-request.html'); + }); + it('should fail when navigating and show the url at the error message', async function({page, server, httpsServer}) { + const url = httpsServer.PREFIX + '/redirect/1.html'; + let error = null; + try { + await page.goto(url); + } catch (e) { + error = e; + } + expect(error.message).toContain(url); + }); + it('should send referer', async({page, server}) => { + const [request1, request2] = await Promise.all([ + server.waitForRequest('/grid.html'), + server.waitForRequest('/digits/1.png'), + page.goto(server.PREFIX + '/grid.html', { + referer: 'http://google.com/', + }), + ]); + expect(request1.headers['referer']).toBe('http://google.com/'); + // Make sure subresources do not inherit referer. + expect(request2.headers['referer']).toBe(server.PREFIX + '/grid.html'); + expect(page.url()).toBe(server.PREFIX + '/grid.html'); + }); + it('should reject referer option when setExtraHTTPHeaders provides referer', async({page, server}) => { + await page.setExtraHTTPHeaders({ 'referer': 'http://microsoft.com/' }); + let error; + await page.goto(server.PREFIX + '/grid.html', { + referer: 'http://google.com/', + }).catch(e => error = e); + expect(error.message).toBe('"referer" is already specified as extra HTTP header'); + }); + it('should override referrer-policy', async({page, server}) => { + server.setRoute('/grid.html', (req, res) => { + res.setHeader('Referrer-Policy', 'no-referrer'); + server.serveFile(req, res, '/grid.html'); }); + const [request1, request2] = await Promise.all([ + server.waitForRequest('/grid.html'), + server.waitForRequest('/digits/1.png'), + page.goto(server.PREFIX + '/grid.html', { + referer: 'http://microsoft.com/', + }), + ]); + expect(request1.headers['referer']).toBe('http://microsoft.com/'); + // Make sure subresources do not inherit referer. + expect(request2.headers['referer']).toBe(undefined); + expect(page.url()).toBe(server.PREFIX + '/grid.html'); + }); + it('should fail when canceled by another navigation', async({page, server}) => { + server.setRoute('/one-style.html', (req, res) => {}); + const failed = page.goto(server.PREFIX + '/one-style.html').catch(e => e); + await server.waitForRequest('/one-style.html'); + await page.goto(server.PREFIX + '/empty.html'); + const error = await failed; + expect(error.message).toBeTruthy(); + }); + it.skip(true)('extraHttpHeaders should be pushed to provisional page', async({page, server}) => { + // This test is flaky, because we cannot await page.setExtraHTTPHeaders. + // We need a way to test our implementation by more than just public api. + await page.goto(server.EMPTY_PAGE); + const pagePath = '/one-style.html'; + server.setRoute(pagePath, async (req, res) => { + page.setExtraHTTPHeaders({ foo: 'bar' }); + server.serveFile(req, res, pagePath); + }); + const [htmlReq, cssReq] = await Promise.all([ + server.waitForRequest(pagePath), + server.waitForRequest('/one-style.css'), + page.goto(server.CROSS_PROCESS_PREFIX + pagePath) + ]); + expect(htmlReq.headers['foo']).toBe(undefined); + expect(cssReq.headers['foo']).toBe('bar'); + }); + + describe('network idle', function() { + it('should navigate to empty page with networkidle0', async({page, server}) => { + const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle0' }); + expect(response.status()).toBe(200); + }); + it('should navigate to empty page with networkidle2', async({page, server}) => { + const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle2' }); + expect(response.status()).toBe(200); + }); + + /** + * @param {import('../src/frames').Frame} frame + * @param {TestServer} server + * @param {'networkidle0'|'networkidle2'} signal + * @param {() => Promise} action + * @param {boolean} isSetContent + */ + async function networkIdleTest(frame, server, signal, action, isSetContent) { + const finishResponse = response => { + response.statusCode = 404; + response.end(`File not found`); + }; + const waitForRequest = suffix => { + return Promise.all([ + server.waitForRequest(suffix), + frame._page.waitForRequest(server.PREFIX + suffix), + ]) + } + let responses = {}; + // Hold on to a bunch of requests without answering. + server.setRoute('/fetch-request-a.js', (req, res) => responses.a = res); + server.setRoute('/fetch-request-b.js', (req, res) => responses.b = res); + server.setRoute('/fetch-request-c.js', (req, res) => responses.c = res); + const initialFetchResourcesRequested = Promise.all([ + waitForRequest('/fetch-request-a.js'), + waitForRequest('/fetch-request-b.js'), + waitForRequest('/fetch-request-c.js') + ]); + + let secondFetchResourceRequested; + if (signal === 'networkidle0') { + server.setRoute('/fetch-request-d.js', (req, res) => responses.d = res); + secondFetchResourceRequested = waitForRequest('/fetch-request-d.js'); + } + + const waitForLoadPromise = isSetContent ? Promise.resolve() : frame.waitForNavigation({ waitUntil: 'load' }); + + // Navigate to a page which loads immediately and then does a bunch of + // requests via javascript's fetch method. + const actionPromise = action(); + + // Track when the action gets completed. + let actionFinished = false; + actionPromise.then(() => actionFinished = true); + + // Wait for the frame's 'load' event. + await waitForLoadPromise; + expect(actionFinished).toBe(false); + + // Wait for the initial three resources to be requested. + await initialFetchResourcesRequested; + expect(actionFinished).toBe(false); + + expect(responses.a).toBeTruthy(); + expect(responses.b).toBeTruthy(); + expect(responses.c).toBeTruthy(); + let timer; + let timerTriggered = false; + if (signal === 'networkidle0') { + // Finishing first response should leave 2 requests alive and trigger networkidle2. + finishResponse(responses.a); + // Finishing two more responses should trigger the second round. + finishResponse(responses.b); + finishResponse(responses.c); + + // Wait for the second round to be requested. + await secondFetchResourceRequested; + expect(actionFinished).toBe(false); + // Finishing the last response should trigger networkidle0. + timer = setTimeout(() => timerTriggered = true, 500); + finishResponse(responses.d); + } else { + timer = setTimeout(() => timerTriggered = true, 500); + // Finishing first response should leave 2 requests alive and trigger networkidle2. + finishResponse(responses.a); + } + const response = await actionPromise; + clearTimeout(timer); + expect(timerTriggered).toBe(true); + if (!isSetContent) + expect(response.ok()).toBe(true); + + if (signal === 'networkidle2') { + // Cleanup. + finishResponse(responses.b); + finishResponse(responses.c); + } + } + + it('should wait for networkidle0 to succeed navigation', async({page, server}) => { + await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { + return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle0' }); + }); + }); + it('should wait for networkidle2 to succeed navigation', async({page, server}) => { + await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { + return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle2' }); + }); + }); + it('should wait for networkidle0 to succeed navigation with request from previous navigation', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + server.setRoute('/foo.js', () => {}); + await page.setContent(``); + await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { + return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle0' }); + }); + }); + it('should wait for networkidle2 to succeed navigation with request from previous navigation', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + server.setRoute('/foo.js', () => {}); + await page.setContent(``); + await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { + return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle2' }); + }); + }); + it('should wait for networkidle0 in waitForNavigation', async({page, server}) => { + await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { + const promise = page.waitForNavigation({ waitUntil: 'networkidle0' }); + page.goto(server.PREFIX + '/networkidle.html'); + return promise; + }); + }); + it('should wait for networkidle2 in waitForNavigation', async({page, server}) => { + await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { + const promise = page.waitForNavigation({ waitUntil: 'networkidle2' }); + page.goto(server.PREFIX + '/networkidle.html'); + return promise; + }); + }); + it('should wait for networkidle0 in setContent', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { + return page.setContent(``, { waitUntil: 'networkidle0' }); + }, true); + }); + it('should wait for networkidle2 in setContent', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { + return page.setContent(``, { waitUntil: 'networkidle2' }); + }, true); + }); + it.fail(FFOX)('should wait for networkidle0 in setContent with request from previous navigation', async({page, server}) => { + // TODO: in Firefox window.stop() does not cancel outstanding requests, and we also lack 'init' lifecycle, + // therefore we don't clear inflight requests at the right time. + await page.goto(server.EMPTY_PAGE); + server.setRoute('/foo.js', () => {}); + await page.setContent(``); + await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { + return page.setContent(``, { waitUntil: 'networkidle0' }); + }, true); + }); + it.fail(FFOX)('should wait for networkidle2 in setContent with request from previous navigation', async({page, server}) => { + // TODO: in Firefox window.stop() does not cancel outstanding requests, and we also lack 'init' lifecycle, + // therefore we don't clear inflight requests at the right time. + await page.goto(server.EMPTY_PAGE); + server.setRoute('/foo.js', () => {}); + await page.setContent(``); + await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { + return page.setContent(``, { waitUntil: 'networkidle2' }); + }, true); + }); + it('should wait for networkidle0 when navigating iframe', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = page.mainFrame().childFrames()[0]; + await networkIdleTest(frame, server, 'networkidle0', () => frame.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle0' })); + }); + it('should wait for networkidle2 when navigating iframe', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = page.mainFrame().childFrames()[0]; + await networkIdleTest(frame, server, 'networkidle2', () => frame.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle2' })); + }); + it('should wait for networkidle0 in setContent from the child frame', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { + return page.setContent(``, { waitUntil: 'networkidle0' }); + }, true); + }); + it('should wait for networkidle2 in setContent from the child frame', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { + return page.setContent(``, { waitUntil: 'networkidle2' }); + }, true); + }); + it('should wait for networkidle0 from the child frame', async({page, server}) => { + await networkIdleTest(page.mainFrame(), server, 'networkidle0', () => { + return page.goto(server.PREFIX + '/networkidle-frame.html', { waitUntil: 'networkidle0' }); + }); + }); + it('should wait for networkidle2 from the child frame', async({page, server}) => { + await networkIdleTest(page.mainFrame(), server, 'networkidle2', () => { + return page.goto(server.PREFIX + '/networkidle-frame.html', { waitUntil: 'networkidle2' }); + }); + }); + }); }); - describe('Page.goBack', function() { - it('should work', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.goto(server.PREFIX + '/grid.html'); - - let response = await page.goBack(); - expect(response.ok()).toBe(true); - expect(response.url()).toContain(server.EMPTY_PAGE); - - response = await page.goForward(); - expect(response.ok()).toBe(true); - expect(response.url()).toContain('/grid.html'); - - response = await page.goForward(); - expect(response).toBe(null); +describe('Page.waitForNavigation', function() { + it('should work', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [response] = await Promise.all([ + page.waitForNavigation(), + page.evaluate(url => window.location.href = url, server.PREFIX + '/grid.html') + ]); + expect(response.ok()).toBe(true); + expect(response.url()).toContain('grid.html'); + }); + it('should work with both domcontentloaded and load', async({page, server}) => { + let response = null; + server.setRoute('/one-style.css', (req, res) => response = res); + const navigationPromise = page.goto(server.PREFIX + '/one-style.html'); + const domContentLoadedPromise = page.waitForNavigation({ + waitUntil: 'domcontentloaded' }); - it('should work with HistoryAPI', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => { + + let bothFired = false; + const bothFiredPromise = Promise.all([ + page.waitForNavigation({ waitUntil: 'load' }), + domContentLoadedPromise + ]).then(() => bothFired = true); + + await server.waitForRequest('/one-style.css'); + await domContentLoadedPromise; + expect(bothFired).toBe(false); + response.end(); + await bothFiredPromise; + await navigationPromise; + }); + it('should work with clicking on anchor links', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(`foobar`); + const [response] = await Promise.all([ + page.waitForNavigation(), + page.click('a'), + ]); + expect(response).toBe(null); + expect(page.url()).toBe(server.EMPTY_PAGE + '#foobar'); + }); + it('should work with clicking on links which do not commit navigation', async({page, server, httpsServer}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(`foobar`); + const [error] = await Promise.all([ + page.waitForNavigation().catch(e => e), + page.click('a'), + ]); + expectSSLError(error.message); + }); + it('should work with history.pushState()', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(` + SPA + + `); + const [response] = await Promise.all([ + page.waitForNavigation(), + page.click('a'), + ]); + expect(response).toBe(null); + expect(page.url()).toBe(server.PREFIX + '/wow.html'); + }); + it('should work with history.replaceState()', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(` + SPA + + `); + const [response] = await Promise.all([ + page.waitForNavigation(), + page.click('a'), + ]); + expect(response).toBe(null); + expect(page.url()).toBe(server.PREFIX + '/replaced.html'); + }); + it('should work with DOM history.back()/history.forward()', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(` + back + forward + + `); + expect(page.url()).toBe(server.PREFIX + '/second.html'); + const [backResponse] = await Promise.all([ + page.waitForNavigation(), + page.click('a#back'), + ]); + expect(backResponse).toBe(null); + expect(page.url()).toBe(server.PREFIX + '/first.html'); + const [forwardResponse] = await Promise.all([ + page.waitForNavigation(), + page.click('a#forward'), + ]); + expect(forwardResponse).toBe(null); + expect(page.url()).toBe(server.PREFIX + '/second.html'); }); - - describe('Frame.goto', function() { - it('should navigate subframes', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - expect(page.frames()[0].url()).toContain('/frames/one-frame.html'); - expect(page.frames()[1].url()).toContain('/frames/frame.html'); - - const response = await page.frames()[1].goto(server.EMPTY_PAGE); - expect(response.ok()).toBe(true); - expect(response.frame()).toBe(page.frames()[1]); - }); - it('should reject when frame detaches', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - - server.setRoute('/empty.html', () => {}); - const navigationPromise = page.frames()[1].goto(server.EMPTY_PAGE).catch(e => e); - await server.waitForRequest('/empty.html'); - - await page.$eval('iframe', frame => frame.remove()); - const error = await navigationPromise; - expect(error.message).toContain('frame was detached'); - }); - it('should return matching responses', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - // Attach three frames. - const frames = [ - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE), - await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE), - await utils.attachFrame(page, 'frame3', server.EMPTY_PAGE), - ]; - const serverResponses = []; - server.setRoute('/0.html', (req, res) => serverResponses.push(res)); - server.setRoute('/1.html', (req, res) => serverResponses.push(res)); - server.setRoute('/2.html', (req, res) => serverResponses.push(res)); - const navigations = []; - for (let i = 0; i < 3; ++i) { - navigations.push(frames[i].goto(server.PREFIX + '/' + i + '.html')); - await server.waitForRequest('/' + i + '.html'); - } - // Respond from server out-of-order. - const serverResponseTexts = ['AAA', 'BBB', 'CCC']; - for (const i of [1, 2, 0]) { - serverResponses[i].end(serverResponseTexts[i]); - const response = await navigations[i]; - expect(response.frame()).toBe(frames[i]); - expect(await response.text()).toBe(serverResponseTexts[i]); - } - }); + it('should work when subframe issues window.stop()', async({page, server}) => { + server.setRoute('/frames/style.css', (req, res) => {}); + const navigationPromise = page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = await new Promise(f => page.once('frameattached', f)); + await new Promise(fulfill => page.on('framenavigated', f => { + if (f === frame) + fulfill(); + })); + await Promise.all([ + frame.evaluate(() => window.stop()), + navigationPromise + ]); }); - - describe('Frame.waitForNavigation', function() { - it('should work', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = page.frames()[1]; - const [response] = await Promise.all([ - frame.waitForNavigation(), - frame.evaluate(url => window.location.href = url, server.PREFIX + '/grid.html') - ]); - expect(response.ok()).toBe(true); - expect(response.url()).toContain('grid.html'); - expect(response.frame()).toBe(frame); - expect(page.url()).toContain('/frames/one-frame.html'); - }); - it('should fail when frame detaches', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = page.frames()[1]; - server.setRoute('/empty.html', () => {}); - let error = null; - await Promise.all([ - frame.waitForNavigation().catch(e => error = e), - frame.evaluate('window.location = "/empty.html"'), - page.evaluate('setTimeout(() => document.querySelector("iframe").remove())'), - ]).catch(e => error = e); - expect(error.message).toContain('frame was detached'); - }); + it('should work with url match', async({page, server}) => { + let response1 = null; + const response1Promise = page.waitForNavigation({ url: /one-style\.html/ }).then(response => response1 = response); + let response2 = null; + const response2Promise = page.waitForNavigation({ url: /\/frame.html/ }).then(response => response2 = response); + let response3 = null; + const response3Promise = page.waitForNavigation({ url: url => url.searchParams.get('foo') === 'bar' }).then(response => response3 = response); + expect(response1).toBe(null); + expect(response2).toBe(null); + expect(response3).toBe(null); + await page.goto(server.EMPTY_PAGE); + expect(response1).toBe(null); + expect(response2).toBe(null); + expect(response3).toBe(null); + await page.goto(server.PREFIX + '/frame.html'); + expect(response1).toBe(null); + await response2Promise; + expect(response2).not.toBe(null); + expect(response3).toBe(null); + await page.goto(server.PREFIX + '/one-style.html'); + await response1Promise; + expect(response1).not.toBe(null); + expect(response2).not.toBe(null); + expect(response3).toBe(null); + await page.goto(server.PREFIX + '/frame.html?foo=bar'); + await response3Promise; + expect(response1).not.toBe(null); + expect(response2).not.toBe(null); + expect(response3).not.toBe(null); + await page.goto(server.PREFIX + '/empty.html'); + expect(response1.url()).toBe(server.PREFIX + '/one-style.html'); + expect(response2.url()).toBe(server.PREFIX + '/frame.html'); + expect(response3.url()).toBe(server.PREFIX + '/frame.html?foo=bar'); }); - - describe('Frame._waitForLodState', function() { - it('should work', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = page.frames()[1]; - - const requestPromise = new Promise(resolve => page.route(server.PREFIX + '/one-style.css',resolve)); - await frame.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); - const request = await requestPromise; - let resolved = false; - const loadPromise = frame.waitForLoadState().then(() => resolved = true); - // give the promise a chance to resolve, even though it shouldn't - await page.evaluate('1'); - expect(resolved).toBe(false); - request.continue(); - await loadPromise; + it('should work with url match for same document navigations', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + let resolved = false; + const waitPromise = page.waitForNavigation({ url: /third\.html/ }).then(() => resolved = true); + expect(resolved).toBe(false); + await page.evaluate(() => { + history.pushState({}, '', '/first.html'); }); - }); - - describe('Page.reload', function() { - it('should work', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => window._foo = 10); - await page.reload(); - expect(await page.evaluate(() => window._foo)).toBe(undefined); + expect(resolved).toBe(false); + await page.evaluate(() => { + history.pushState({}, '', '/second.html'); }); + expect(resolved).toBe(false); + await page.evaluate(() => { + history.pushState({}, '', '/third.html'); + }); + await waitPromise; + expect(resolved).toBe(true); }); + it('should work for cross-process navigations', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const waitPromise = page.waitForNavigation({waitUntil: 'domcontentloaded'}); + const url = server.CROSS_PROCESS_PREFIX + '/empty.html'; + const gotoPromise = page.goto(url); + const response = await waitPromise; + expect(response.url()).toBe(url); + expect(page.url()).toBe(url); + expect(await page.evaluate('document.location.href')).toBe(url); + await gotoPromise; + }); +}); - function expectSSLError(errorMessage) { - if (CHROMIUM) { - expect(errorMessage).toContain('net::ERR_CERT_AUTHORITY_INVALID'); - } else if (WEBKIT) { - if (MAC) - expect(errorMessage).toContain('The certificate for this server is invalid'); - else if (WIN) - expect(errorMessage).toContain('SSL peer certificate or SSH remote key was not OK'); - else - expect(errorMessage).toContain('Unacceptable TLS certificate'); - } else { - expect(errorMessage).toContain('SSL_ERROR_UNKNOWN'); +describe('Page.waitForLoadState', () => { + it('should pick up ongoing navigation', async({page, server}) => { + let response = null; + server.setRoute('/one-style.css', (req, res) => response = res); + await Promise.all([ + server.waitForRequest('/one-style.css'), + page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}), + ]); + const waitPromise = page.waitForLoadState(); + response.statusCode = 404; + response.end('Not found'); + await waitPromise; + }); + it('should respect timeout', async({page, server}) => { + server.setRoute('/one-style.css', (req, res) => response = res); + await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); + const error = await page.waitForLoadState('load', { timeout: 1 }).catch(e => e); + expect(error.message).toBe('Navigation timeout exceeded'); + }); + it('should resolve immediately if loaded', async({page, server}) => { + await page.goto(server.PREFIX + '/one-style.html'); + await page.waitForLoadState(); + }); + it('should resolve immediately if load state matches', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + server.setRoute('/one-style.css', (req, res) => response = res); + await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); + await page.waitForLoadState('domcontentloaded'); + }); + it('should work with pages that have loaded before being connected to', async({page, context, server}) => { + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window._popup = window.open(document.location.href)), + ]); + // The url is about:blank in FF. + // expect(popup.url()).toBe(server.EMPTY_PAGE); + await popup.waitForLoadState(); + expect(popup.url()).toBe(server.EMPTY_PAGE); + }); + it('should wait for load state of empty url popup', async({browser, page}) => { + const [popup, readyState] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => { + const popup = window.open(''); + return popup.document.readyState; + }), + ]); + await popup.waitForLoadState(); + expect(readyState).toBe(FFOX ? 'uninitialized' : 'complete'); + expect(await popup.evaluate(() => document.readyState)).toBe(FFOX ? 'uninitialized' : 'complete'); + }); + it('should wait for load state of about:blank popup ', async({browser, page}) => { + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('about:blank') && 1), + ]); + await popup.waitForLoadState(); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should wait for load state of about:blank popup with noopener ', async({browser, page}) => { + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('about:blank', null, 'noopener') && 1), + ]); + await popup.waitForLoadState(); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should wait for load state of popup with network url ', async({browser, page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window.open(url) && 1, server.EMPTY_PAGE), + ]); + await popup.waitForLoadState(); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should wait for load state of popup with network url and noopener ', async({browser, page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window.open(url, null, 'noopener') && 1, server.EMPTY_PAGE), + ]); + await popup.waitForLoadState(); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should work with clicking target=_blank', async({browser, page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.click('a'), + ]); + await popup.waitForLoadState(); + expect(await popup.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should wait for load state of newPage', async({browser, context, page, server}) => { + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + context.newPage(), + ]); + await newPage.waitForLoadState(); + expect(await newPage.evaluate(() => document.readyState)).toBe('complete'); + }); + it('should resolve after popup load', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + // Stall the 'load' by delaying css. + let cssResponse; + server.setRoute('/one-style.css', (req, res) => cssResponse = res); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + server.waitForRequest('/one-style.css'), + page.evaluate(url => window.popup = window.open(url), server.PREFIX + '/one-style.html'), + ]); + let resolved = false; + const loadSatePromise = popup.waitForLoadState().then(() => resolved = true); + // Round trips! + for (let i = 0; i < 5; i++) + await page.evaluate('window'); + expect(resolved).toBe(false); + cssResponse.end(''); + await loadSatePromise; + expect(resolved).toBe(true); + expect(popup.url()).toBe(server.PREFIX + '/one-style.html'); + await context.close(); + }); +}); + +describe('Page.goBack', function() { + it('should work', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.goto(server.PREFIX + '/grid.html'); + + let response = await page.goBack(); + expect(response.ok()).toBe(true); + expect(response.url()).toContain(server.EMPTY_PAGE); + + response = await page.goForward(); + expect(response.ok()).toBe(true); + expect(response.url()).toContain('/grid.html'); + + response = await page.goForward(); + expect(response).toBe(null); + }); + it('should work with HistoryAPI', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => { + history.pushState({}, '', '/first.html'); + history.pushState({}, '', '/second.html'); + }); + expect(page.url()).toBe(server.PREFIX + '/second.html'); + + await page.goBack(); + expect(page.url()).toBe(server.PREFIX + '/first.html'); + await page.goBack(); + expect(page.url()).toBe(server.EMPTY_PAGE); + await page.goForward(); + expect(page.url()).toBe(server.PREFIX + '/first.html'); + }); +}); + +describe('Frame.goto', function() { + it('should navigate subframes', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + expect(page.frames()[0].url()).toContain('/frames/one-frame.html'); + expect(page.frames()[1].url()).toContain('/frames/frame.html'); + + const response = await page.frames()[1].goto(server.EMPTY_PAGE); + expect(response.ok()).toBe(true); + expect(response.frame()).toBe(page.frames()[1]); + }); + it('should reject when frame detaches', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + + server.setRoute('/empty.html', () => {}); + const navigationPromise = page.frames()[1].goto(server.EMPTY_PAGE).catch(e => e); + await server.waitForRequest('/empty.html'); + + await page.$eval('iframe', frame => frame.remove()); + const error = await navigationPromise; + expect(error.message).toContain('frame was detached'); + }); + it('should return matching responses', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + // Attach three frames. + const frames = [ + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE), + await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE), + await utils.attachFrame(page, 'frame3', server.EMPTY_PAGE), + ]; + const serverResponses = []; + server.setRoute('/0.html', (req, res) => serverResponses.push(res)); + server.setRoute('/1.html', (req, res) => serverResponses.push(res)); + server.setRoute('/2.html', (req, res) => serverResponses.push(res)); + const navigations = []; + for (let i = 0; i < 3; ++i) { + navigations.push(frames[i].goto(server.PREFIX + '/' + i + '.html')); + await server.waitForRequest('/' + i + '.html'); } - } -}; + // Respond from server out-of-order. + const serverResponseTexts = ['AAA', 'BBB', 'CCC']; + for (const i of [1, 2, 0]) { + serverResponses[i].end(serverResponseTexts[i]); + const response = await navigations[i]; + expect(response.frame()).toBe(frames[i]); + expect(await response.text()).toBe(serverResponseTexts[i]); + } + }); +}); +describe('Frame.waitForNavigation', function() { + it('should work', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = page.frames()[1]; + const [response] = await Promise.all([ + frame.waitForNavigation(), + frame.evaluate(url => window.location.href = url, server.PREFIX + '/grid.html') + ]); + expect(response.ok()).toBe(true); + expect(response.url()).toContain('grid.html'); + expect(response.frame()).toBe(frame); + expect(page.url()).toContain('/frames/one-frame.html'); + }); + it('should fail when frame detaches', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = page.frames()[1]; + server.setRoute('/empty.html', () => {}); + let error = null; + await Promise.all([ + frame.waitForNavigation().catch(e => error = e), + frame.evaluate('window.location = "/empty.html"'), + page.evaluate('setTimeout(() => document.querySelector("iframe").remove())'), + ]).catch(e => error = e); + expect(error.message).toContain('frame was detached'); + }); +}); + +describe('Frame._waitForLodState', function() { + it('should work', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = page.frames()[1]; + + const requestPromise = new Promise(resolve => page.route(server.PREFIX + '/one-style.css',resolve)); + await frame.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); + const request = await requestPromise; + let resolved = false; + const loadPromise = frame.waitForLoadState().then(() => resolved = true); + // give the promise a chance to resolve, even though it shouldn't + await page.evaluate('1'); + expect(resolved).toBe(false); + request.continue(); + await loadPromise; + }); +}); + +describe('Page.reload', function() { + it('should work', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => window._foo = 10); + await page.reload(); + expect(await page.evaluate(() => window._foo)).toBe(undefined); + }); +}); + +function expectSSLError(errorMessage) { + if (CHROMIUM) { + expect(errorMessage).toContain('net::ERR_CERT_AUTHORITY_INVALID'); + } else if (WEBKIT) { + if (MAC) + expect(errorMessage).toContain('The certificate for this server is invalid'); + else if (WIN) + expect(errorMessage).toContain('SSL peer certificate or SSH remote key was not OK'); + else + expect(errorMessage).toContain('Unacceptable TLS certificate'); + } else { + expect(errorMessage).toContain('SSL_ERROR_UNKNOWN'); + } +} diff --git a/test/network.spec.js b/test/network.spec.js index 4187a144a6..b350764511 100644 --- a/test/network.spec.js +++ b/test/network.spec.js @@ -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.'); - }); - }); -}; +}); diff --git a/test/page.spec.js b/test/page.spec.js index c978685c63..9ddd203cc0 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -17,1114 +17,1109 @@ const path = require('path'); const vm = require('vm'); +const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType); -/** - * @type {PageTestSuite} - */ -module.exports.describe = function({playwright, FFOX, CHROMIUM, WEBKIT}) { +describe('Page.close', function() { + it('should reject all promises when page is closed', async({context}) => { + const newPage = await context.newPage(); + let error = null; + await Promise.all([ + newPage.evaluate(() => new Promise(r => {})).catch(e => error = e), + newPage.close(), + ]); + expect(error.message).toContain('Protocol error'); + }); + it('should not be visible in context.pages', async({context}) => { + const newPage = await context.newPage(); + expect(context.pages()).toContain(newPage); + await newPage.close(); + expect(context.pages()).not.toContain(newPage); + }); + it('should run beforeunload if asked for', async({context, server}) => { + const newPage = await context.newPage(); + await newPage.goto(server.PREFIX + '/beforeunload.html'); + // We have to interact with a page so that 'beforeunload' handlers + // fire. + await newPage.click('body'); + const pageClosingPromise = newPage.close({ runBeforeUnload: true }); + const dialog = await newPage.waitForEvent('dialog'); + expect(dialog.type()).toBe('beforeunload'); + expect(dialog.defaultValue()).toBe(''); + if (CHROMIUM) + expect(dialog.message()).toBe(''); + else if (WEBKIT) + expect(dialog.message()).toBe('Leave?'); + else + expect(dialog.message()).toBe('This page is asking you to confirm that you want to leave - data you have entered may not be saved.'); + await dialog.accept(); + await pageClosingPromise; + }); + it('should *not* run beforeunload by default', async({context, server}) => { + const newPage = await context.newPage(); + await newPage.goto(server.PREFIX + '/beforeunload.html'); + // We have to interact with a page so that 'beforeunload' handlers + // fire. + await newPage.click('body'); + await newPage.close(); + }); + it('should set the page close state', async({context}) => { + const newPage = await context.newPage(); + expect(newPage.isClosed()).toBe(false); + await newPage.close(); + expect(newPage.isClosed()).toBe(true); + }); + it('should terminate network waiters', async({context, server}) => { + const newPage = await context.newPage(); + const results = await Promise.all([ + newPage.waitForRequest(server.EMPTY_PAGE).catch(e => e), + newPage.waitForResponse(server.EMPTY_PAGE).catch(e => e), + newPage.close() + ]); + for (let i = 0; i < 2; i++) { + const message = results[i].message; + expect(message).toContain('Target closed'); + expect(message).not.toContain('Timeout'); + } + }); +}); - describe('Page.close', function() { - it('should reject all promises when page is closed', async({context}) => { - const newPage = await context.newPage(); - let error = null; - await Promise.all([ - newPage.evaluate(() => new Promise(r => {})).catch(e => error = e), - newPage.close(), - ]); - expect(error.message).toContain('Protocol error'); +describe('Page.Events.Load', function() { + it('should fire when expected', async({page, server}) => { + await Promise.all([ + page.goto('about:blank'), + page.waitForEvent('load'), + ]); + }); +}); + +describe('Async stacks', () => { + it('should work', async({page, server}) => { + server.setRoute('/empty.html', (req, res) => { + req.socket.end(); }); - it('should not be visible in context.pages', async({context}) => { - const newPage = await context.newPage(); - expect(context.pages()).toContain(newPage); - await newPage.close(); - expect(context.pages()).not.toContain(newPage); + let error = null; + await page.goto(server.EMPTY_PAGE).catch(e => error = e); + expect(error).not.toBe(null); + expect(error.stack).toContain(__filename); + }); +}); + +describe('Page.Events.error', function() { + it('should throw when page crashes', async({page}) => { + await page.setContent(`
This page should crash
`); + let error = null; + page.on('error', err => error = err); + if (CHROMIUM) + page.goto('chrome://crash').catch(e => {}); + else if (WEBKIT) + page._delegate._session.send('Page.crash', {}).catch(e => {}); + else if (FFOX) + page._delegate._session.send('Page.crash', {}).catch(e => {}); + await new Promise(f => page.on('error', f)); + expect(error.message).toBe('Page crashed!'); + }); +}); + +describe('Page.opener', function() { + it('should provide access to the opener page', async({page}) => { + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('about:blank')), + ]); + const opener = await popup.opener(); + expect(opener).toBe(page); + }); + it('should return null if parent page has been closed', async({page}) => { + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.open('about:blank')), + ]); + await page.close(); + const opener = await popup.opener(); + expect(opener).toBe(null); + }); +}); + +describe('Page.Events.Console', function() { + it('should work', async({page, server}) => { + let message = null; + page.once('console', m => message = m); + await Promise.all([ + page.evaluate(() => console.log('hello', 5, {foo: 'bar'})), + page.waitForEvent('console') + ]); + expect(message.text()).toEqual('hello 5 JSHandle@object'); + expect(message.type()).toEqual('log'); + expect(await message.args()[0].jsonValue()).toEqual('hello'); + expect(await message.args()[1].jsonValue()).toEqual(5); + expect(await message.args()[2].jsonValue()).toEqual({foo: 'bar'}); + }); + it('should work for different console API calls', async({page, server}) => { + const messages = []; + page.on('console', msg => messages.push(msg)); + // All console events will be reported before `page.evaluate` is finished. + await page.evaluate(() => { + // A pair of time/timeEnd generates only one Console API call. + console.time('calling console.time'); + console.timeEnd('calling console.time'); + console.trace('calling console.trace'); + console.dir('calling console.dir'); + console.warn('calling console.warn'); + console.error('calling console.error'); + console.log(Promise.resolve('should not wait until resolved!')); }); - it('should run beforeunload if asked for', async({context, server}) => { - const newPage = await context.newPage(); - await newPage.goto(server.PREFIX + '/beforeunload.html'); - // We have to interact with a page so that 'beforeunload' handlers - // fire. - await newPage.click('body'); - const pageClosingPromise = newPage.close({ runBeforeUnload: true }); - const dialog = await newPage.waitForEvent('dialog'); - expect(dialog.type()).toBe('beforeunload'); - expect(dialog.defaultValue()).toBe(''); - if (CHROMIUM) - expect(dialog.message()).toBe(''); - else if (WEBKIT) - expect(dialog.message()).toBe('Leave?'); - else - expect(dialog.message()).toBe('This page is asking you to confirm that you want to leave - data you have entered may not be saved.'); - await dialog.accept(); - await pageClosingPromise; - }); - it('should *not* run beforeunload by default', async({context, server}) => { - const newPage = await context.newPage(); - await newPage.goto(server.PREFIX + '/beforeunload.html'); - // We have to interact with a page so that 'beforeunload' handlers - // fire. - await newPage.click('body'); - await newPage.close(); - }); - it('should set the page close state', async({context}) => { - const newPage = await context.newPage(); - expect(newPage.isClosed()).toBe(false); - await newPage.close(); - expect(newPage.isClosed()).toBe(true); - }); - it('should terminate network waiters', async({context, server}) => { - const newPage = await context.newPage(); - const results = await Promise.all([ - newPage.waitForRequest(server.EMPTY_PAGE).catch(e => e), - newPage.waitForResponse(server.EMPTY_PAGE).catch(e => e), - newPage.close() - ]); - for (let i = 0; i < 2; i++) { - const message = results[i].message; - expect(message).toContain('Target closed'); - expect(message).not.toContain('Timeout'); - } + expect(messages.map(msg => msg.type())).toEqual([ + 'timeEnd', 'trace', 'dir', 'warning', 'error', 'log' + ]); + expect(messages[0].text()).toContain('calling console.time'); + expect(messages.slice(1).map(msg => msg.text())).toEqual([ + 'calling console.trace', + 'calling console.dir', + 'calling console.warn', + 'calling console.error', + 'JSHandle@promise', + ]); + }); + it('should not fail for window object', async({page, server}) => { + let message = null; + page.once('console', msg => message = msg); + await Promise.all([ + page.evaluate(() => console.error(window)), + page.waitForEvent('console') + ]); + expect(message.text()).toBe('JSHandle@object'); + }); + it('should trigger correct Log', async({page, server}) => { + await page.goto('about:blank'); + const [message] = await Promise.all([ + page.waitForEvent('console'), + page.evaluate(async url => fetch(url).catch(e => {}), server.EMPTY_PAGE) + ]); + expect(message.text()).toContain('Access-Control-Allow-Origin'); + expect(message.type()).toEqual('error'); + }); + it('should have location for console API calls', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [message] = await Promise.all([ + page.waitForEvent('console'), + page.goto(server.PREFIX + '/consolelog.html'), + ]); + expect(message.text()).toBe('yellow'); + expect(message.type()).toBe('log'); + const location = message.location(); + // Engines have different column notion. + delete location.columnNumber; + expect(location).toEqual({ + url: server.PREFIX + '/consolelog.html', + lineNumber: 7, }); }); - - describe('Page.Events.Load', function() { - it('should fire when expected', async({page, server}) => { - await Promise.all([ - page.goto('about:blank'), - page.waitForEvent('load'), - ]); - }); + // @see https://github.com/GoogleChrome/puppeteer/issues/3865 + it('should not throw when there are console messages in detached iframes', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(async() => { + // 1. Create a popup that Playwright is not connected to. + const win = window.open(''); + window._popup = win; + if (window.document.readyState !== 'complete') + await new Promise(f => window.addEventListener('load', f)); + // 2. In this popup, create an iframe that console.logs a message. + win.document.body.innerHTML = ``; + const frame = win.document.querySelector('iframe'); + if (!frame.contentDocument || frame.contentDocument.readyState !== 'complete') + await new Promise(f => frame.addEventListener('load', f)); + // 3. After that, remove the iframe. + frame.remove(); + }), + ]); + // 4. Connect to the popup and make sure it doesn't throw. + expect(await popup.evaluate('1 + 1')).toBe(2); }); +}); - describe('Async stacks', () => { - it('should work', async({page, server}) => { - server.setRoute('/empty.html', (req, res) => { - req.socket.end(); - }); - let error = null; - await page.goto(server.EMPTY_PAGE).catch(e => error = e); - expect(error).not.toBe(null); - expect(error.stack).toContain(__filename); - }); +describe('Page.Events.DOMContentLoaded', function() { + it('should fire when expected', async({page, server}) => { + const navigatedPromise = page.goto('about:blank'); + await page.waitForEvent('domcontentloaded'); + await navigatedPromise; }); +}); - describe('Page.Events.error', function() { - it('should throw when page crashes', async({page}) => { - await page.setContent(`
This page should crash
`); - let error = null; - page.on('error', err => error = err); - if (CHROMIUM) - page.goto('chrome://crash').catch(e => {}); - else if (WEBKIT) - page._delegate._session.send('Page.crash', {}).catch(e => {}); - else if (FFOX) - page._delegate._session.send('Page.crash', {}).catch(e => {}); - await new Promise(f => page.on('error', f)); - expect(error.message).toBe('Page crashed!'); - }); +describe('Page.waitForRequest', function() { + it('should work', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [request] = await Promise.all([ + page.waitForRequest(server.PREFIX + '/digits/2.png'), + page.evaluate(() => { + fetch('/digits/1.png'); + fetch('/digits/2.png'); + fetch('/digits/3.png'); + }) + ]); + expect(request.url()).toBe(server.PREFIX + '/digits/2.png'); }); - - describe('Page.opener', function() { - it('should provide access to the opener page', async({page}) => { - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.open('about:blank')), - ]); - const opener = await popup.opener(); - expect(opener).toBe(page); - }); - it('should return null if parent page has been closed', async({page}) => { - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.open('about:blank')), - ]); - await page.close(); - const opener = await popup.opener(); - expect(opener).toBe(null); - }); + it('should work with predicate', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [request] = await Promise.all([ + page.waitForEvent('request', request => request.url() === server.PREFIX + '/digits/2.png'), + page.evaluate(() => { + fetch('/digits/1.png'); + fetch('/digits/2.png'); + fetch('/digits/3.png'); + }) + ]); + expect(request.url()).toBe(server.PREFIX + '/digits/2.png'); }); - - describe('Page.Events.Console', function() { - it('should work', async({page, server}) => { - let message = null; - page.once('console', m => message = m); - await Promise.all([ - page.evaluate(() => console.log('hello', 5, {foo: 'bar'})), - page.waitForEvent('console') - ]); - expect(message.text()).toEqual('hello 5 JSHandle@object'); - expect(message.type()).toEqual('log'); - expect(await message.args()[0].jsonValue()).toEqual('hello'); - expect(await message.args()[1].jsonValue()).toEqual(5); - expect(await message.args()[2].jsonValue()).toEqual({foo: 'bar'}); - }); - it('should work for different console API calls', async({page, server}) => { - const messages = []; - page.on('console', msg => messages.push(msg)); - // All console events will be reported before `page.evaluate` is finished. - await page.evaluate(() => { - // A pair of time/timeEnd generates only one Console API call. - console.time('calling console.time'); - console.timeEnd('calling console.time'); - console.trace('calling console.trace'); - console.dir('calling console.dir'); - console.warn('calling console.warn'); - console.error('calling console.error'); - console.log(Promise.resolve('should not wait until resolved!')); - }); - expect(messages.map(msg => msg.type())).toEqual([ - 'timeEnd', 'trace', 'dir', 'warning', 'error', 'log' - ]); - expect(messages[0].text()).toContain('calling console.time'); - expect(messages.slice(1).map(msg => msg.text())).toEqual([ - 'calling console.trace', - 'calling console.dir', - 'calling console.warn', - 'calling console.error', - 'JSHandle@promise', - ]); - }); - it('should not fail for window object', async({page, server}) => { - let message = null; - page.once('console', msg => message = msg); - await Promise.all([ - page.evaluate(() => console.error(window)), - page.waitForEvent('console') - ]); - expect(message.text()).toBe('JSHandle@object'); - }); - it('should trigger correct Log', async({page, server}) => { - await page.goto('about:blank'); - const [message] = await Promise.all([ - page.waitForEvent('console'), - page.evaluate(async url => fetch(url).catch(e => {}), server.EMPTY_PAGE) - ]); - expect(message.text()).toContain('Access-Control-Allow-Origin'); - expect(message.type()).toEqual('error'); - }); - it('should have location for console API calls', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [message] = await Promise.all([ - page.waitForEvent('console'), - page.goto(server.PREFIX + '/consolelog.html'), - ]); - expect(message.text()).toBe('yellow'); - expect(message.type()).toBe('log'); - const location = message.location(); - // Engines have different column notion. - delete location.columnNumber; - expect(location).toEqual({ - url: server.PREFIX + '/consolelog.html', - lineNumber: 7, - }); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/3865 - it('should not throw when there are console messages in detached iframes', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(async() => { - // 1. Create a popup that Playwright is not connected to. - const win = window.open(''); - window._popup = win; - if (window.document.readyState !== 'complete') - await new Promise(f => window.addEventListener('load', f)); - // 2. In this popup, create an iframe that console.logs a message. - win.document.body.innerHTML = ``; - const frame = win.document.querySelector('iframe'); - if (!frame.contentDocument || frame.contentDocument.readyState !== 'complete') - await new Promise(f => frame.addEventListener('load', f)); - // 3. After that, remove the iframe. - frame.remove(); - }), - ]); - // 4. Connect to the popup and make sure it doesn't throw. - expect(await popup.evaluate('1 + 1')).toBe(2); - }); + it('should respect timeout', async({page, server}) => { + let error = null; + await page.waitForEvent('request', { predicate: () => false, timeout: 1 }).catch(e => error = e); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); }); - - describe('Page.Events.DOMContentLoaded', function() { - it('should fire when expected', async({page, server}) => { - const navigatedPromise = page.goto('about:blank'); - await page.waitForEvent('domcontentloaded'); - await navigatedPromise; - }); + it('should respect default timeout', async({page, server}) => { + let error = null; + page.setDefaultTimeout(1); + await page.waitForEvent('request', () => false).catch(e => error = e); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); }); - - describe('Page.waitForRequest', function() { - it('should work', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [request] = await Promise.all([ - page.waitForRequest(server.PREFIX + '/digits/2.png'), - page.evaluate(() => { - fetch('/digits/1.png'); - fetch('/digits/2.png'); - fetch('/digits/3.png'); - }) - ]); - expect(request.url()).toBe(server.PREFIX + '/digits/2.png'); - }); - it('should work with predicate', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [request] = await Promise.all([ - page.waitForEvent('request', request => request.url() === server.PREFIX + '/digits/2.png'), - page.evaluate(() => { - fetch('/digits/1.png'); - fetch('/digits/2.png'); - fetch('/digits/3.png'); - }) - ]); - expect(request.url()).toBe(server.PREFIX + '/digits/2.png'); - }); - it('should respect timeout', async({page, server}) => { - let error = null; - await page.waitForEvent('request', { predicate: () => false, timeout: 1 }).catch(e => error = e); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should respect default timeout', async({page, server}) => { - let error = null; - page.setDefaultTimeout(1); - await page.waitForEvent('request', () => false).catch(e => error = e); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should work with no timeout', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [request] = await Promise.all([ - page.waitForRequest(server.PREFIX + '/digits/2.png', {timeout: 0}), - page.evaluate(() => setTimeout(() => { - fetch('/digits/1.png'); - fetch('/digits/2.png'); - fetch('/digits/3.png'); - }, 50)) - ]); - expect(request.url()).toBe(server.PREFIX + '/digits/2.png'); - }); - it('should work with url match', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [request] = await Promise.all([ - page.waitForRequest(/digits\/\d\.png/), - page.evaluate(() => { - fetch('/digits/1.png'); - }) - ]); - expect(request.url()).toBe(server.PREFIX + '/digits/1.png'); - }); - it('should work with url match regular expression from a different context', async({page, server}) => { - const ctx = vm.createContext(); - const regexp = vm.runInContext('new RegExp(/digits\\/\\d\\.png/)', ctx); - - await page.goto(server.EMPTY_PAGE); - const [request] = await Promise.all([ - page.waitForRequest(regexp), - page.evaluate(() => { - fetch('/digits/1.png'); - }) - ]); - expect(request.url()).toBe(server.PREFIX + '/digits/1.png'); - }); + it('should work with no timeout', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [request] = await Promise.all([ + page.waitForRequest(server.PREFIX + '/digits/2.png', {timeout: 0}), + page.evaluate(() => setTimeout(() => { + fetch('/digits/1.png'); + fetch('/digits/2.png'); + fetch('/digits/3.png'); + }, 50)) + ]); + expect(request.url()).toBe(server.PREFIX + '/digits/2.png'); }); - - describe('Page.waitForResponse', function() { - it('should work', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [response] = await Promise.all([ - page.waitForResponse(server.PREFIX + '/digits/2.png'), - page.evaluate(() => { - fetch('/digits/1.png'); - fetch('/digits/2.png'); - fetch('/digits/3.png'); - }) - ]); - expect(response.url()).toBe(server.PREFIX + '/digits/2.png'); - }); - it('should respect timeout', async({page, server}) => { - let error = null; - await page.waitForEvent('response', { predicate: () => false, timeout: 1 }).catch(e => error = e); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should respect default timeout', async({page, server}) => { - let error = null; - page.setDefaultTimeout(1); - await page.waitForEvent('response', () => false).catch(e => error = e); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should work with predicate', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [response] = await Promise.all([ - page.waitForEvent('response', response => response.url() === server.PREFIX + '/digits/2.png'), - page.evaluate(() => { - fetch('/digits/1.png'); - fetch('/digits/2.png'); - fetch('/digits/3.png'); - }) - ]); - expect(response.url()).toBe(server.PREFIX + '/digits/2.png'); - }); - it('should work with no timeout', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const [response] = await Promise.all([ - page.waitForResponse(server.PREFIX + '/digits/2.png', { timeout: 0 }), - page.evaluate(() => setTimeout(() => { - fetch('/digits/1.png'); - fetch('/digits/2.png'); - fetch('/digits/3.png'); - }, 50)) - ]); - expect(response.url()).toBe(server.PREFIX + '/digits/2.png'); - }); + it('should work with url match', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [request] = await Promise.all([ + page.waitForRequest(/digits\/\d\.png/), + page.evaluate(() => { + fetch('/digits/1.png'); + }) + ]); + expect(request.url()).toBe(server.PREFIX + '/digits/1.png'); }); + it('should work with url match regular expression from a different context', async({page, server}) => { + const ctx = vm.createContext(); + const regexp = vm.runInContext('new RegExp(/digits\\/\\d\\.png/)', ctx); - describe('Page.exposeFunction', function() { - it('should work', async({page, server}) => { - await page.exposeFunction('compute', function(a, b) { - return a * b; - }); - const result = await page.evaluate(async function() { - return await compute(9, 4); - }); - expect(result).toBe(36); - }); - it('should throw exception in page context', async({page, server}) => { - await page.exposeFunction('woof', function() { - throw new Error('WOOF WOOF'); - }); - const {message, stack} = await page.evaluate(async() => { - try { - await woof(); - } catch (e) { - return {message: e.message, stack: e.stack}; - } - }); - expect(message).toBe('WOOF WOOF'); - expect(stack).toContain(__filename); - }); - it('should support throwing "null"', async({page, server}) => { - await page.exposeFunction('woof', function() { - throw null; - }); - const thrown = await page.evaluate(async() => { - try { - await woof(); - } catch (e) { - return e; - } - }); - expect(thrown).toBe(null); - }); - it('should be callable from-inside addInitScript', async({page, server}) => { - let called = false; - await page.exposeFunction('woof', function() { - called = true; - }); - await page.addInitScript(() => woof()); - await page.reload(); - expect(called).toBe(true); - }); - it('should survive navigation', async({page, server}) => { - await page.exposeFunction('compute', function(a, b) { - return a * b; - }); - - await page.goto(server.EMPTY_PAGE); - const result = await page.evaluate(async function() { - return await compute(9, 4); - }); - expect(result).toBe(36); - }); - it('should await returned promise', async({page, server}) => { - await page.exposeFunction('compute', function(a, b) { - return Promise.resolve(a * b); - }); - - const result = await page.evaluate(async function() { - return await compute(3, 5); - }); - expect(result).toBe(15); - }); - it('should work on frames', async({page, server}) => { - await page.exposeFunction('compute', function(a, b) { - return Promise.resolve(a * b); - }); - - await page.goto(server.PREFIX + '/frames/nested-frames.html'); - const frame = page.frames()[1]; - const result = await frame.evaluate(async function() { - return await compute(3, 5); - }); - expect(result).toBe(15); - }); - it('should work on frames before navigation', async({page, server}) => { - await page.goto(server.PREFIX + '/frames/nested-frames.html'); - await page.exposeFunction('compute', function(a, b) { - return Promise.resolve(a * b); - }); - - const frame = page.frames()[1]; - const result = await frame.evaluate(async function() { - return await compute(3, 5); - }); - expect(result).toBe(15); - }); - it('should work after cross origin navigation', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.exposeFunction('compute', function(a, b) { - return a * b; - }); - - await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); - const result = await page.evaluate(async function() { - return await compute(9, 4); - }); - expect(result).toBe(36); - }); - it('should work with complex objects', async({page, server}) => { - await page.exposeFunction('complexObject', function(a, b) { - return {x: a.x + b.x}; - }); - const result = await page.evaluate(async() => complexObject({x: 5}, {x: 2})); - expect(result.x).toBe(7); - }); + await page.goto(server.EMPTY_PAGE); + const [request] = await Promise.all([ + page.waitForRequest(regexp), + page.evaluate(() => { + fetch('/digits/1.png'); + }) + ]); + expect(request.url()).toBe(server.PREFIX + '/digits/1.png'); }); +}); - describe('Page.Events.PageError', function() { - it('should fire', async({page, server}) => { - let error = null; - page.once('pageerror', e => error = e); - await Promise.all([ - page.goto(server.PREFIX + '/error.html'), - new Promise(f => page.on('pageerror', f)) - ]); - expect(error.message).toContain('Fancy'); - }); +describe('Page.waitForResponse', function() { + it('should work', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [response] = await Promise.all([ + page.waitForResponse(server.PREFIX + '/digits/2.png'), + page.evaluate(() => { + fetch('/digits/1.png'); + fetch('/digits/2.png'); + fetch('/digits/3.png'); + }) + ]); + expect(response.url()).toBe(server.PREFIX + '/digits/2.png'); }); - - describe('Page.setContent', function() { - const expectedOutput = '
hello
'; - it('should work', async({page, server}) => { - await page.setContent('
hello
'); - const result = await page.content(); - expect(result).toBe(expectedOutput); - }); - it('should work with domcontentloaded', async({page, server}) => { - await page.setContent('
hello
', { waitUntil: 'domcontentloaded' }); - const result = await page.content(); - expect(result).toBe(expectedOutput); - }); - it('should work with doctype', async({page, server}) => { - const doctype = ''; - await page.setContent(`${doctype}
hello
`); - const result = await page.content(); - expect(result).toBe(`${doctype}${expectedOutput}`); - }); - it('should work with HTML 4 doctype', async({page, server}) => { - const doctype = ''; - await page.setContent(`${doctype}
hello
`); - const result = await page.content(); - expect(result).toBe(`${doctype}${expectedOutput}`); - }); - it('should respect timeout', async({page, server}) => { - const imgPath = '/img.png'; - // stall for image - server.setRoute(imgPath, (req, res) => {}); - let error = null; - await page.setContent(``, {timeout: 1}).catch(e => error = e); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should respect default navigation timeout', async({page, server}) => { - page.setDefaultNavigationTimeout(1); - const imgPath = '/img.png'; - // stall for image - server.setRoute(imgPath, (req, res) => {}); - let error = null; - await page.setContent(``).catch(e => error = e); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - }); - it('should await resources to load', async({page, server}) => { - const imgPath = '/img.png'; - let imgResponse = null; - server.setRoute(imgPath, (req, res) => imgResponse = res); - let loaded = false; - const contentPromise = page.setContent(``).then(() => loaded = true); - await server.waitForRequest(imgPath); - expect(loaded).toBe(false); - imgResponse.end(); - await contentPromise; - }); - it('should work fast enough', async({page, server}) => { - for (let i = 0; i < 20; ++i) - await page.setContent('
yo
'); - }); - it('should work with tricky content', async({page, server}) => { - await page.setContent('
hello world
' + '\x7F'); - expect(await page.$eval('div', div => div.textContent)).toBe('hello world'); - }); - it('should work with accents', async({page, server}) => { - await page.setContent('
aberración
'); - expect(await page.$eval('div', div => div.textContent)).toBe('aberración'); - }); - it('should work with emojis', async({page, server}) => { - await page.setContent('
🐥
'); - expect(await page.$eval('div', div => div.textContent)).toBe('🐥'); - }); - it('should work with newline', async({page, server}) => { - await page.setContent('
\n
'); - expect(await page.$eval('div', div => div.textContent)).toBe('\n'); - }); + it('should respect timeout', async({page, server}) => { + let error = null; + await page.waitForEvent('response', { predicate: () => false, timeout: 1 }).catch(e => error = e); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); }); + it('should respect default timeout', async({page, server}) => { + let error = null; + page.setDefaultTimeout(1); + await page.waitForEvent('response', () => false).catch(e => error = e); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); + }); + it('should work with predicate', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [response] = await Promise.all([ + page.waitForEvent('response', response => response.url() === server.PREFIX + '/digits/2.png'), + page.evaluate(() => { + fetch('/digits/1.png'); + fetch('/digits/2.png'); + fetch('/digits/3.png'); + }) + ]); + expect(response.url()).toBe(server.PREFIX + '/digits/2.png'); + }); + it('should work with no timeout', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const [response] = await Promise.all([ + page.waitForResponse(server.PREFIX + '/digits/2.png', { timeout: 0 }), + page.evaluate(() => setTimeout(() => { + fetch('/digits/1.png'); + fetch('/digits/2.png'); + fetch('/digits/3.png'); + }, 50)) + ]); + expect(response.url()).toBe(server.PREFIX + '/digits/2.png'); + }); +}); - - describe('Page.addScriptTag', function() { - it('should throw an error if no options are provided', async({page, server}) => { - let error = null; +describe('Page.exposeFunction', function() { + it('should work', async({page, server}) => { + await page.exposeFunction('compute', function(a, b) { + return a * b; + }); + const result = await page.evaluate(async function() { + return await compute(9, 4); + }); + expect(result).toBe(36); + }); + it('should throw exception in page context', async({page, server}) => { + await page.exposeFunction('woof', function() { + throw new Error('WOOF WOOF'); + }); + const {message, stack} = await page.evaluate(async() => { try { - await page.addScriptTag('/injectedfile.js'); + await woof(); } catch (e) { - error = e; + return {message: e.message, stack: e.stack}; } - expect(error.message).toBe('Provide an object with a `url`, `path` or `content` property'); }); - - it('should work with a url', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const scriptHandle = await page.addScriptTag({ url: '/injectedfile.js' }); - expect(scriptHandle.asElement()).not.toBeNull(); - expect(await page.evaluate(() => __injected)).toBe(42); + expect(message).toBe('WOOF WOOF'); + expect(stack).toContain(__filename); + }); + it('should support throwing "null"', async({page, server}) => { + await page.exposeFunction('woof', function() { + throw null; }); - - it('should work with a url and type=module', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({ url: '/es6/es6import.js', type: 'module' }); - expect(await page.evaluate(() => __es6injected)).toBe(42); - }); - - it('should work with a path and type=module', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({ path: path.join(__dirname, 'assets/es6/es6pathimport.js'), type: 'module' }); - await page.waitForFunction('window.__es6injected'); - expect(await page.evaluate(() => __es6injected)).toBe(42); - }); - - it('should work with a content and type=module', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({ content: `import num from '/es6/es6module.js';window.__es6injected = num;`, type: 'module' }); - await page.waitForFunction('window.__es6injected'); - expect(await page.evaluate(() => __es6injected)).toBe(42); - }); - - it('should throw an error if loading from url fail', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - let error = null; + const thrown = await page.evaluate(async() => { try { - await page.addScriptTag({ url: '/nonexistfile.js' }); + await woof(); } catch (e) { - error = e; + return e; } - expect(error).not.toBe(null); + }); + expect(thrown).toBe(null); + }); + it('should be callable from-inside addInitScript', async({page, server}) => { + let called = false; + await page.exposeFunction('woof', function() { + called = true; + }); + await page.addInitScript(() => woof()); + await page.reload(); + expect(called).toBe(true); + }); + it('should survive navigation', async({page, server}) => { + await page.exposeFunction('compute', function(a, b) { + return a * b; }); - it('should work with a path', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const scriptHandle = await page.addScriptTag({ path: path.join(__dirname, 'assets/injectedfile.js') }); - expect(scriptHandle.asElement()).not.toBeNull(); - expect(await page.evaluate(() => __injected)).toBe(42); + await page.goto(server.EMPTY_PAGE); + const result = await page.evaluate(async function() { + return await compute(9, 4); + }); + expect(result).toBe(36); + }); + it('should await returned promise', async({page, server}) => { + await page.exposeFunction('compute', function(a, b) { + return Promise.resolve(a * b); }); - it.skip(WEBKIT)('should include sourceURL when path is provided', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({ path: path.join(__dirname, 'assets/injectedfile.js') }); - const result = await page.evaluate(() => __injectedError.stack); - expect(result).toContain(path.join('assets', 'injectedfile.js')); + const result = await page.evaluate(async function() { + return await compute(3, 5); + }); + expect(result).toBe(15); + }); + it('should work on frames', async({page, server}) => { + await page.exposeFunction('compute', function(a, b) { + return Promise.resolve(a * b); }); - it('should work with content', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const scriptHandle = await page.addScriptTag({ content: 'window.__injected = 35;' }); - expect(scriptHandle.asElement()).not.toBeNull(); - expect(await page.evaluate(() => __injected)).toBe(35); + await page.goto(server.PREFIX + '/frames/nested-frames.html'); + const frame = page.frames()[1]; + const result = await frame.evaluate(async function() { + return await compute(3, 5); + }); + expect(result).toBe(15); + }); + it('should work on frames before navigation', async({page, server}) => { + await page.goto(server.PREFIX + '/frames/nested-frames.html'); + await page.exposeFunction('compute', function(a, b) { + return Promise.resolve(a * b); }); - // Firefox fires onload for blocked script before it issues the CSP console error. - it.fail(FFOX)('should throw when added with content to the CSP page', async({page, server}) => { - await page.goto(server.PREFIX + '/csp.html'); - let error = null; - await page.addScriptTag({ content: 'window.__injected = 35;' }).catch(e => error = e); - expect(error).toBeTruthy(); + const frame = page.frames()[1]; + const result = await frame.evaluate(async function() { + return await compute(3, 5); + }); + expect(result).toBe(15); + }); + it('should work after cross origin navigation', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.exposeFunction('compute', function(a, b) { + return a * b; }); - it('should throw when added with URL to the CSP page', async({page, server}) => { - await page.goto(server.PREFIX + '/csp.html'); - let error = null; - await page.addScriptTag({ url: server.CROSS_PROCESS_PREFIX + '/injectedfile.js' }).catch(e => error = e); - expect(error).toBeTruthy(); + await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + const result = await page.evaluate(async function() { + return await compute(9, 4); }); + expect(result).toBe(36); + }); + it('should work with complex objects', async({page, server}) => { + await page.exposeFunction('complexObject', function(a, b) { + return {x: a.x + b.x}; + }); + const result = await page.evaluate(async() => complexObject({x: 5}, {x: 2})); + expect(result.x).toBe(7); + }); +}); + +describe('Page.Events.PageError', function() { + it('should fire', async({page, server}) => { + let error = null; + page.once('pageerror', e => error = e); + await Promise.all([ + page.goto(server.PREFIX + '/error.html'), + new Promise(f => page.on('pageerror', f)) + ]); + expect(error.message).toContain('Fancy'); + }); +}); + +describe('Page.setContent', function() { + const expectedOutput = '
hello
'; + it('should work', async({page, server}) => { + await page.setContent('
hello
'); + const result = await page.content(); + expect(result).toBe(expectedOutput); + }); + it('should work with domcontentloaded', async({page, server}) => { + await page.setContent('
hello
', { waitUntil: 'domcontentloaded' }); + const result = await page.content(); + expect(result).toBe(expectedOutput); + }); + it('should work with doctype', async({page, server}) => { + const doctype = ''; + await page.setContent(`${doctype}
hello
`); + const result = await page.content(); + expect(result).toBe(`${doctype}${expectedOutput}`); + }); + it('should work with HTML 4 doctype', async({page, server}) => { + const doctype = ''; + await page.setContent(`${doctype}
hello
`); + const result = await page.content(); + expect(result).toBe(`${doctype}${expectedOutput}`); + }); + it('should respect timeout', async({page, server}) => { + const imgPath = '/img.png'; + // stall for image + server.setRoute(imgPath, (req, res) => {}); + let error = null; + await page.setContent(``, {timeout: 1}).catch(e => error = e); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); + }); + it('should respect default navigation timeout', async({page, server}) => { + page.setDefaultNavigationTimeout(1); + const imgPath = '/img.png'; + // stall for image + server.setRoute(imgPath, (req, res) => {}); + let error = null; + await page.setContent(``).catch(e => error = e); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); + }); + it('should await resources to load', async({page, server}) => { + const imgPath = '/img.png'; + let imgResponse = null; + server.setRoute(imgPath, (req, res) => imgResponse = res); + let loaded = false; + const contentPromise = page.setContent(``).then(() => loaded = true); + await server.waitForRequest(imgPath); + expect(loaded).toBe(false); + imgResponse.end(); + await contentPromise; + }); + it('should work fast enough', async({page, server}) => { + for (let i = 0; i < 20; ++i) + await page.setContent('
yo
'); + }); + it('should work with tricky content', async({page, server}) => { + await page.setContent('
hello world
' + '\x7F'); + expect(await page.$eval('div', div => div.textContent)).toBe('hello world'); + }); + it('should work with accents', async({page, server}) => { + await page.setContent('
aberración
'); + expect(await page.$eval('div', div => div.textContent)).toBe('aberración'); + }); + it('should work with emojis', async({page, server}) => { + await page.setContent('
🐥
'); + expect(await page.$eval('div', div => div.textContent)).toBe('🐥'); + }); + it('should work with newline', async({page, server}) => { + await page.setContent('
\n
'); + expect(await page.$eval('div', div => div.textContent)).toBe('\n'); + }); +}); + + +describe('Page.addScriptTag', function() { + it('should throw an error if no options are provided', async({page, server}) => { + let error = null; + try { + await page.addScriptTag('/injectedfile.js'); + } catch (e) { + error = e; + } + expect(error.message).toBe('Provide an object with a `url`, `path` or `content` property'); }); - describe('Page.addStyleTag', function() { - it('should throw an error if no options are provided', async({page, server}) => { - let error = null; - try { - await page.addStyleTag('/injectedstyle.css'); - } catch (e) { - error = e; - } - expect(error.message).toBe('Provide an object with a `url`, `path` or `content` property'); - }); - - it('should work with a url', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const styleHandle = await page.addStyleTag({ url: '/injectedstyle.css' }); - expect(styleHandle.asElement()).not.toBeNull(); - expect(await page.evaluate(`window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')`)).toBe('rgb(255, 0, 0)'); - }); - - it('should throw an error if loading from url fail', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - let error = null; - try { - await page.addStyleTag({ url: '/nonexistfile.js' }); - } catch (e) { - error = e; - } - expect(error).not.toBe(null); - }); - - it('should work with a path', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const styleHandle = await page.addStyleTag({ path: path.join(__dirname, 'assets/injectedstyle.css') }); - expect(styleHandle.asElement()).not.toBeNull(); - expect(await page.evaluate(`window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')`)).toBe('rgb(255, 0, 0)'); - }); - - it('should include sourceURL when path is provided', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.addStyleTag({ path: path.join(__dirname, 'assets/injectedstyle.css') }); - const styleHandle = await page.$('style'); - const styleContent = await page.evaluate(style => style.innerHTML, styleHandle); - expect(styleContent).toContain(path.join('assets', 'injectedstyle.css')); - }); - - it('should work with content', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const styleHandle = await page.addStyleTag({ content: 'body { background-color: green; }' }); - expect(styleHandle.asElement()).not.toBeNull(); - expect(await page.evaluate(`window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')`)).toBe('rgb(0, 128, 0)'); - }); - - it('should throw when added with content to the CSP page', async({page, server}) => { - await page.goto(server.PREFIX + '/csp.html'); - let error = null; - await page.addStyleTag({ content: 'body { background-color: green; }' }).catch(e => error = e); - expect(error).toBeTruthy(); - }); - - it('should throw when added with URL to the CSP page', async({page, server}) => { - await page.goto(server.PREFIX + '/csp.html'); - let error = null; - await page.addStyleTag({ url: server.CROSS_PROCESS_PREFIX + '/injectedstyle.css' }).catch(e => error = e); - expect(error).toBeTruthy(); - }); + it('should work with a url', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const scriptHandle = await page.addScriptTag({ url: '/injectedfile.js' }); + expect(scriptHandle.asElement()).not.toBeNull(); + expect(await page.evaluate(() => __injected)).toBe(42); }); - describe('Page.url', function() { - it('should work', async({page, server}) => { - expect(page.url()).toBe('about:blank'); - await page.goto(server.EMPTY_PAGE); - expect(page.url()).toBe(server.EMPTY_PAGE); - }); + it('should work with a url and type=module', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.addScriptTag({ url: '/es6/es6import.js', type: 'module' }); + expect(await page.evaluate(() => __es6injected)).toBe(42); }); - describe('Page.title', function() { - it('should return the page title', async({page, server}) => { - await page.goto(server.PREFIX + '/title.html'); - expect(await page.title()).toBe('Woof-Woof'); - }); + it('should work with a path and type=module', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.addScriptTag({ path: path.join(__dirname, 'assets/es6/es6pathimport.js'), type: 'module' }); + await page.waitForFunction('window.__es6injected'); + expect(await page.evaluate(() => __es6injected)).toBe(42); }); - describe('Page.select', function() { - it('should select single option', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.selectOption('select', 'blue'); - expect(await page.evaluate(() => result.onInput)).toEqual(['blue']); - expect(await page.evaluate(() => result.onChange)).toEqual(['blue']); - }); - it('should select single option by value', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.selectOption('select', { value: 'blue' }); - expect(await page.evaluate(() => result.onInput)).toEqual(['blue']); - expect(await page.evaluate(() => result.onChange)).toEqual(['blue']); - }); - it('should select single option by label', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.selectOption('select', { label: 'Indigo' }); - expect(await page.evaluate(() => result.onInput)).toEqual(['indigo']); - expect(await page.evaluate(() => result.onChange)).toEqual(['indigo']); - }); - it('should select single option by handle', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.selectOption('select', await page.$('[id=whiteOption]')); - expect(await page.evaluate(() => result.onInput)).toEqual(['white']); - expect(await page.evaluate(() => result.onChange)).toEqual(['white']); - }); - it('should select single option by index', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.selectOption('select', { index: 2 }); - expect(await page.evaluate(() => result.onInput)).toEqual(['brown']); - expect(await page.evaluate(() => result.onChange)).toEqual(['brown']); - }); - it('should select single option by multiple attributes', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.selectOption('select', { value: 'green', label: 'Green' }); - expect(await page.evaluate(() => result.onInput)).toEqual(['green']); - expect(await page.evaluate(() => result.onChange)).toEqual(['green']); - }); - it('should not select single option when some attributes do not match', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.selectOption('select', { value: 'green', label: 'Brown' }); - expect(await page.evaluate(() => document.querySelector('select').value)).toEqual(''); - }); - it('should select only first option', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.selectOption('select', 'blue', 'green', 'red'); - expect(await page.evaluate(() => result.onInput)).toEqual(['blue']); - expect(await page.evaluate(() => result.onChange)).toEqual(['blue']); - }); - it('should not throw when select causes navigation', async({page, server}) => { await page.goto(server.PREFIX + '/input/select.html'); - await page.$eval('select', select => select.addEventListener('input', () => window.location = '/empty.html')); - await Promise.all([ - page.selectOption('select', 'blue'), - page.waitForNavigation(), - ]); - expect(page.url()).toContain('empty.html'); - }); - it('should select multiple options', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.evaluate(() => makeMultiple()); - await page.selectOption('select', ['blue', 'green', 'red']); - expect(await page.evaluate(() => result.onInput)).toEqual(['blue', 'green', 'red']); - expect(await page.evaluate(() => result.onChange)).toEqual(['blue', 'green', 'red']); - }); - it('should select multiple options with attributes', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.evaluate(() => makeMultiple()); - await page.selectOption('select', ['blue', { label: 'Green' }, { index: 4 }]); - expect(await page.evaluate(() => result.onInput)).toEqual(['blue', 'gray', 'green']); - expect(await page.evaluate(() => result.onChange)).toEqual(['blue', 'gray', 'green']); - }); - it('should respect event bubbling', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.selectOption('select', 'blue'); - expect(await page.evaluate(() => result.onBubblingInput)).toEqual(['blue']); - expect(await page.evaluate(() => result.onBubblingChange)).toEqual(['blue']); - }); - it('should throw when element is not a element.'); - }); - it('should return [] on no matched values', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - const result = await page.selectOption('select','42','abc'); - expect(result).toEqual([]); - }); - it('should return an array of matched values', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.evaluate(() => makeMultiple()); - const result = await page.selectOption('select','blue','black','magenta'); - expect(result.reduce((accumulator,current) => ['blue', 'black', 'magenta'].includes(current) && accumulator, true)).toEqual(true); - }); - it('should return an array of one element when multiple is not set', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - const result = await page.selectOption('select',['42','blue','black','magenta']); - expect(result.length).toEqual(1); - }); - it('should return [] on no values',async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - const result = await page.selectOption('select', []); - expect(result).toEqual([]); - }); - it('should deselect all options when passed no values for a multiple select',async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.evaluate(() => makeMultiple()); - await page.selectOption('select', ['blue','black','magenta']); - await page.selectOption('select', []); - expect(await page.$eval('select', select => Array.from(select.options).every(option => !option.selected))).toEqual(true); - }); - it('should deselect all options when passed no values for a select without multiple',async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.selectOption('select', ['blue','black','magenta']); - await page.selectOption('select', []); - expect(await page.$eval('select', select => Array.from(select.options).every(option => !option.selected))).toEqual(true); - }); - it('should throw if passed wrong types', async({page, server}) => { - let error; - await page.setContent(''); - - error = null; - try { - await page.selectOption('select', 12); - } catch (e) { - error = e; - } - expect(error.message).toContain('Values must be strings'); - - error = null; - try { - await page.selectOption('select', { value: 12 }); - } catch (e) { - error = e; - } - expect(error.message).toContain('Values must be strings'); - - error = null; - try { - await page.selectOption('select', { label: 12 }); - } catch (e) { - error = e; - } - expect(error.message).toContain('Labels must be strings'); - - error = null; - try { - await page.selectOption('select', { index: '12' }); - } catch (e) { - error = e; - } - expect(error.message).toContain('Indices must be numbers'); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/3327 - it('should work when re-defining top-level Event class', async({page, server}) => { - await page.goto(server.PREFIX + '/input/select.html'); - await page.evaluate(() => window.Event = null); - await page.selectOption('select', 'blue'); - expect(await page.evaluate(() => result.onInput)).toEqual(['blue']); - expect(await page.evaluate(() => result.onChange)).toEqual(['blue']); - }); + it('should work with a content and type=module', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.addScriptTag({ content: `import num from '/es6/es6module.js';window.__es6injected = num;`, type: 'module' }); + await page.waitForFunction('window.__es6injected'); + expect(await page.evaluate(() => __es6injected)).toBe(42); }); - describe('Page.fill', function() { - it('should fill textarea', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.fill('textarea', 'some value'); - expect(await page.evaluate(() => result)).toBe('some value'); - }); - it('should fill input', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.fill('input', 'some value'); - expect(await page.evaluate(() => result)).toBe('some value'); - }); - it('should throw on unsupported inputs', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - for (const type of ['color', 'file']) { - await page.$eval('input', (input, type) => input.setAttribute('type', type), type); - let error = null; - await page.fill('input', '').catch(e => error = e); - expect(error.message).toContain('Cannot fill input of type'); - } - }); - it('should fill different input types', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - for (const type of ['password', 'search', 'tel', 'text', 'url']) { - await page.$eval('input', (input, type) => input.setAttribute('type', type), type); - await page.fill('input', 'text ' + type); - expect(await page.evaluate(() => result)).toBe('text ' + type); - } - }); - it('should fill date input after clicking', async({page, server}) => { - await page.setContent(''); - await page.click('input'); - await page.fill('input', '2020-03-02'); - expect(await page.$eval('input', input => input.value)).toBe('2020-03-02'); - }); - it.skip(WEBKIT)('should throw on incorrect date', async({page, server}) => { - await page.setContent(''); - const error = await page.fill('input', '2020-13-05').catch(e => e); - expect(error.message).toBe('Malformed date "2020-13-05"'); - }); - it('should fill time input', async({page, server}) => { - await page.setContent(''); - await page.fill('input', '13:15'); - expect(await page.$eval('input', input => input.value)).toBe('13:15'); - }); - it.skip(WEBKIT)('should throw on incorrect time', async({page, server}) => { - await page.setContent(''); - const error = await page.fill('input', '25:05').catch(e => e); - expect(error.message).toBe('Malformed time "25:05"'); - }); - it('should fill datetime-local input', async({page, server}) => { - await page.setContent(''); - await page.fill('input', '2020-03-02T05:15'); - expect(await page.$eval('input', input => input.value)).toBe('2020-03-02T05:15'); - }); - it.skip(WEBKIT || FFOX)('should throw on incorrect datetime-local', async({page, server}) => { - await page.setContent(''); - const error = await page.fill('input', 'abc').catch(e => e); - expect(error.message).toBe('Malformed datetime-local "abc"'); - }); - it('should fill contenteditable', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.fill('div[contenteditable]', 'some value'); - expect(await page.$eval('div[contenteditable]', div => div.textContent)).toBe('some value'); - }); - it('should fill elements with existing value and selection', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); + it('should throw an error if loading from url fail', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + let error = null; + try { + await page.addScriptTag({ url: '/nonexistfile.js' }); + } catch (e) { + error = e; + } + expect(error).not.toBe(null); + }); - await page.$eval('input', input => input.value = 'value one'); - await page.fill('input', 'another value'); - expect(await page.evaluate(() => result)).toBe('another value'); + it('should work with a path', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const scriptHandle = await page.addScriptTag({ path: path.join(__dirname, 'assets/injectedfile.js') }); + expect(scriptHandle.asElement()).not.toBeNull(); + expect(await page.evaluate(() => __injected)).toBe(42); + }); - await page.$eval('input', input => { - input.selectionStart = 1; - input.selectionEnd = 2; - }); - await page.fill('input', 'maybe this one'); - expect(await page.evaluate(() => result)).toBe('maybe this one'); + it.skip(WEBKIT)('should include sourceURL when path is provided', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.addScriptTag({ path: path.join(__dirname, 'assets/injectedfile.js') }); + const result = await page.evaluate(() => __injectedError.stack); + expect(result).toContain(path.join('assets', 'injectedfile.js')); + }); - await page.$eval('div[contenteditable]', div => { - div.innerHTML = 'some text some more text and even more text'; - const range = document.createRange(); - range.selectNodeContents(div.querySelector('span')); - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); - }); - await page.fill('div[contenteditable]', 'replace with this'); - expect(await page.$eval('div[contenteditable]', div => div.textContent)).toBe('replace with this'); - }); - it('should throw when element is not an ,