diff --git a/.eslintrc.js b/.eslintrc.js index 2cf2cc9e02bfe..6f6672c6089cf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -13,33 +13,29 @@ module.exports = { "unicorn" ], + "extends": [ + "plugin:prettier/recommended" + ], + "rules": { + // Error if files are not formatted with Prettier correctly. + "prettier/prettier": 2, // syntax preferences "quotes": [2, "single", { "avoidEscape": true, "allowTemplateLiterals": true }], - "semi": 2, - "no-extra-semi": 2, - "comma-style": [2, "last"], - "wrap-iife": [2, "inside"], "spaced-comment": [2, "always", { "markers": ["*"] }], "eqeqeq": [2], - "arrow-body-style": [2, "as-needed"], "accessor-pairs": [2, { "getWithoutSet": false, "setWithoutGet": false }], - "brace-style": [2, "1tbs", {"allowSingleLine": true}], - "curly": [2, "multi-or-nest", "consistent"], - "object-curly-spacing": [2, "never"], "new-parens": 2, "func-call-spacing": 2, - "arrow-parens": [2, "as-needed"], "prefer-const": 2, - "quote-props": [2, "consistent"], // anti-patterns "no-var": 2, @@ -68,37 +64,6 @@ module.exports = { "require-yield": 2, "template-curly-spacing": [2, "never"], - // spacing details - "space-infix-ops": 2, - "space-in-parens": [2, "never"], - "space-before-function-paren": [2, "never"], - "no-whitespace-before-property": 2, - "keyword-spacing": [2, { - "overrides": { - "if": {"after": true}, - "else": {"after": true}, - "for": {"after": true}, - "while": {"after": true}, - "do": {"after": true}, - "switch": {"after": true}, - "return": {"after": true} - } - }], - "arrow-spacing": [2, { - "after": true, - "before": true - }], - - // file whitespace - "no-multiple-empty-lines": [2, {"max": 2}], - "no-mixed-spaces-and-tabs": 2, - "no-trailing-spaces": 2, - "linebreak-style": [ process.platform === "win32" ? 0 : 2, "unix" ], - "indent": [2, 2, { "SwitchCase": 1, "CallExpression": {"arguments": 2}, "MemberExpression": 2 }], - "key-spacing": [2, { - "beforeColon": false - }], - // ensure we don't have any it.only or describe.only in prod "mocha/no-exclusive-tests": "error", diff --git a/examples/block-images.js b/examples/block-images.js index cbad0abc84eca..298e473e82b72 100644 --- a/examples/block-images.js +++ b/examples/block-images.js @@ -18,19 +18,16 @@ const puppeteer = require('puppeteer'); -(async() => { +(async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.setRequestInterception(true); - page.on('request', request => { - if (request.resourceType() === 'image') - request.abort(); - else - request.continue(); + page.on('request', (request) => { + if (request.resourceType() === 'image') request.abort(); + else request.continue(); }); await page.goto('https://news.google.com/news/'); - await page.screenshot({path: 'news.png', fullPage: true}); + await page.screenshot({ path: 'news.png', fullPage: true }); await browser.close(); })(); - diff --git a/examples/cross-browser.js b/examples/cross-browser.js index 1206d90c4bede..f709abf5bbec9 100644 --- a/examples/cross-browser.js +++ b/examples/cross-browser.js @@ -25,7 +25,7 @@ const firefoxOptions = { dumpio: true, }; -(async() => { +(async () => { const browser = await puppeteer.launch(firefoxOptions); const page = await browser.newPage(); @@ -35,9 +35,9 @@ const firefoxOptions = { // Extract articles from the page. const resultsSelector = '.storylink'; - const links = await page.evaluate(resultsSelector => { + const links = await page.evaluate((resultsSelector) => { const anchors = Array.from(document.querySelectorAll(resultsSelector)); - return anchors.map(anchor => { + return anchors.map((anchor) => { const title = anchor.textContent.trim(); return `${title} - ${anchor.href}`; }); diff --git a/examples/custom-event.js b/examples/custom-event.js index 351b21ff10c0a..44f3f9693637e 100644 --- a/examples/custom-event.js +++ b/examples/custom-event.js @@ -18,12 +18,12 @@ const puppeteer = require('puppeteer'); -(async() => { +(async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); // Define a window.onCustomEvent function on the page. - await page.exposeFunction('onCustomEvent', e => { + await page.exposeFunction('onCustomEvent', (e) => { console.log(`${e.type} fired`, e.detail || ''); }); @@ -33,16 +33,18 @@ const puppeteer = require('puppeteer'); * @return {!Promise} */ function listenFor(type) { - return page.evaluateOnNewDocument(type => { - document.addEventListener(type, e => { - window.onCustomEvent({type, detail: e.detail}); + return page.evaluateOnNewDocument((type) => { + document.addEventListener(type, (e) => { + window.onCustomEvent({ type, detail: e.detail }); }); }, type); } await listenFor('app-ready'); // Listen for "app-ready" custom event on page load. - await page.goto('https://www.chromestatus.com/features', {waitUntil: 'networkidle0'}); + await page.goto('https://www.chromestatus.com/features', { + waitUntil: 'networkidle0', + }); await browser.close(); })(); diff --git a/examples/detect-sniff.js b/examples/detect-sniff.js index 7a35dd0bba902..76bb75b386d79 100644 --- a/examples/detect-sniff.js +++ b/examples/detect-sniff.js @@ -22,22 +22,22 @@ function sniffDetector() { const userAgent = window.navigator.userAgent; const platform = window.navigator.platform; - window.navigator.__defineGetter__('userAgent', function() { + window.navigator.__defineGetter__('userAgent', function () { window.navigator.sniffed = true; return userAgent; }); - window.navigator.__defineGetter__('platform', function() { + window.navigator.__defineGetter__('platform', function () { window.navigator.sniffed = true; return platform; }); } -(async() => { +(async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.evaluateOnNewDocument(sniffDetector); - await page.goto('https://www.google.com', {waitUntil: 'networkidle2'}); + await page.goto('https://www.google.com', { waitUntil: 'networkidle2' }); console.log('Sniffed: ' + (await page.evaluate(() => !!navigator.sniffed))); await browser.close(); diff --git a/examples/pdf.js b/examples/pdf.js index c2c9f5e774907..9f7ac92e0a1f5 100644 --- a/examples/pdf.js +++ b/examples/pdf.js @@ -18,15 +18,17 @@ const puppeteer = require('puppeteer'); -(async() => { +(async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); - await page.goto('https://news.ycombinator.com', {waitUntil: 'networkidle2'}); + await page.goto('https://news.ycombinator.com', { + waitUntil: 'networkidle2', + }); // page.pdf() is currently supported only in headless mode. // @see https://bugs.chromium.org/p/chromium/issues/detail?id=753118 await page.pdf({ path: 'hn.pdf', - format: 'letter' + format: 'letter', }); await browser.close(); diff --git a/examples/proxy.js b/examples/proxy.js index 210b725323faa..5490822b8b27b 100644 --- a/examples/proxy.js +++ b/examples/proxy.js @@ -18,7 +18,7 @@ const puppeteer = require('puppeteer'); -(async() => { +(async () => { const browser = await puppeteer.launch({ // Launch chromium using a proxy server on port 9876. // More on proxying: @@ -27,7 +27,7 @@ const puppeteer = require('puppeteer'); '--proxy-server=127.0.0.1:9876', // Use proxy for localhost URLs '--proxy-bypass-list=<-loopback>', - ] + ], }); const page = await browser.newPage(); await page.goto('https://google.com'); diff --git a/examples/screenshot-fullpage.js b/examples/screenshot-fullpage.js index fcb24ab097eba..e6ca765760073 100644 --- a/examples/screenshot-fullpage.js +++ b/examples/screenshot-fullpage.js @@ -19,11 +19,11 @@ const puppeteer = require('puppeteer'); const devices = require('puppeteer/DeviceDescriptors'); -(async() => { +(async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.emulate(devices['iPhone 6']); await page.goto('https://www.nytimes.com/'); - await page.screenshot({path: 'full.png', fullPage: true}); + await page.screenshot({ path: 'full.png', fullPage: true }); await browser.close(); })(); diff --git a/examples/screenshot.js b/examples/screenshot.js index 25fe931b3e52c..28b4dbb274ac2 100644 --- a/examples/screenshot.js +++ b/examples/screenshot.js @@ -18,10 +18,10 @@ const puppeteer = require('puppeteer'); -(async() => { +(async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://example.com'); - await page.screenshot({path: 'example.png'}); + await page.screenshot({ path: 'example.png' }); await browser.close(); })(); diff --git a/examples/search.js b/examples/search.js index 4845ab5d70eed..abef3889fdae0 100644 --- a/examples/search.js +++ b/examples/search.js @@ -23,7 +23,7 @@ const puppeteer = require('puppeteer'); -(async() => { +(async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); @@ -42,9 +42,9 @@ const puppeteer = require('puppeteer'); await page.waitForSelector(resultsSelector); // Extract the results from the page. - const links = await page.evaluate(resultsSelector => { + const links = await page.evaluate((resultsSelector) => { const anchors = Array.from(document.querySelectorAll(resultsSelector)); - return anchors.map(anchor => { + return anchors.map((anchor) => { const title = anchor.textContent.split('|')[0].trim(); return `${title} - ${anchor.href}`; }); diff --git a/index.js b/index.js index fe2c26af7e418..efa85f945a242 100644 --- a/index.js +++ b/index.js @@ -14,9 +14,9 @@ * limitations under the License. */ -const {helper} = require('./lib/helper'); +const { helper } = require('./lib/helper'); const api = require('./lib/api'); -const {Page} = require('./lib/Page'); +const { Page } = require('./lib/Page'); for (const className in api) { if (typeof api[className] === 'function') helper.installAsyncStackHooks(api[className]); @@ -25,16 +25,25 @@ for (const className in api) { // Expose alias for deprecated method. Page.prototype.emulateMedia = Page.prototype.emulateMediaType; -const {Puppeteer} = require('./lib/Puppeteer'); +const { Puppeteer } = require('./lib/Puppeteer'); const packageJson = require('./package.json'); let preferredRevision = packageJson.puppeteer.chromium_revision; const isPuppeteerCore = packageJson.name === 'puppeteer-core'; // puppeteer-core ignores environment variables -const product = isPuppeteerCore ? undefined : process.env.PUPPETEER_PRODUCT || process.env.npm_config_puppeteer_product || process.env.npm_package_config_puppeteer_product; +const product = isPuppeteerCore + ? undefined + : process.env.PUPPETEER_PRODUCT || + process.env.npm_config_puppeteer_product || + process.env.npm_package_config_puppeteer_product; if (!isPuppeteerCore && product === 'firefox') preferredRevision = packageJson.puppeteer.firefox_revision; -const puppeteer = new Puppeteer(__dirname, preferredRevision, isPuppeteerCore, product); +const puppeteer = new Puppeteer( + __dirname, + preferredRevision, + isPuppeteerCore, + product +); // The introspection in `Helper.installAsyncStackHooks` references `Puppeteer._launcher` // before the Puppeteer ctor is called, such that an invalid Launcher is selected at import, diff --git a/install.js b/install.js index 5bc0a7b98568c..61d0025a1969e 100644 --- a/install.js +++ b/install.js @@ -27,27 +27,44 @@ const compileTypeScriptIfRequired = require('./typescript-if-required'); const supportedProducts = { - 'chrome': 'Chromium', - 'firefox': 'Firefox Nightly' + chrome: 'Chromium', + firefox: 'Firefox Nightly', }; async function download() { await compileTypeScriptIfRequired(); - const downloadHost = process.env.PUPPETEER_DOWNLOAD_HOST || process.env.npm_config_puppeteer_download_host || process.env.npm_package_config_puppeteer_download_host; + const downloadHost = + process.env.PUPPETEER_DOWNLOAD_HOST || + process.env.npm_config_puppeteer_download_host || + process.env.npm_package_config_puppeteer_download_host; const puppeteer = require('./index'); - const product = process.env.PUPPETEER_PRODUCT || process.env.npm_config_puppeteer_product || process.env.npm_package_config_puppeteer_product || 'chrome'; - const browserFetcher = puppeteer.createBrowserFetcher({product, host: downloadHost}); + const product = + process.env.PUPPETEER_PRODUCT || + process.env.npm_config_puppeteer_product || + process.env.npm_package_config_puppeteer_product || + 'chrome'; + const browserFetcher = puppeteer.createBrowserFetcher({ + product, + host: downloadHost, + }); const revision = await getRevision(); await fetchBinary(revision); function getRevision() { if (product === 'chrome') { - return process.env.PUPPETEER_CHROMIUM_REVISION || process.env.npm_config_puppeteer_chromium_revision || process.env.npm_package_config_puppeteer_chromium_revision - || require('./package.json').puppeteer.chromium_revision; + return ( + process.env.PUPPETEER_CHROMIUM_REVISION || + process.env.npm_config_puppeteer_chromium_revision || + process.env.npm_package_config_puppeteer_chromium_revision || + require('./package.json').puppeteer.chromium_revision + ); } else if (product === 'firefox') { puppeteer._preferredRevision = require('./package.json').puppeteer.firefox_revision; - return getFirefoxNightlyVersion(browserFetcher.host()).catch(error => { console.error(error); process.exit(1); }); + return getFirefoxNightlyVersion(browserFetcher.host()).catch((error) => { + console.error(error); + process.exit(1); + }); } else { throw new Error(`Unsupported product ${product}`); } @@ -58,30 +75,37 @@ async function download() { // Do nothing if the revision is already downloaded. if (revisionInfo.local) { - logPolitely(`${supportedProducts[product]} is already in ${revisionInfo.folderPath}; skipping download.`); + logPolitely( + `${supportedProducts[product]} is already in ${revisionInfo.folderPath}; skipping download.` + ); return; } // Override current environment proxy settings with npm configuration, if any. - const NPM_HTTPS_PROXY = process.env.npm_config_https_proxy || process.env.npm_config_proxy; - const NPM_HTTP_PROXY = process.env.npm_config_http_proxy || process.env.npm_config_proxy; + const NPM_HTTPS_PROXY = + process.env.npm_config_https_proxy || process.env.npm_config_proxy; + const NPM_HTTP_PROXY = + process.env.npm_config_http_proxy || process.env.npm_config_proxy; const NPM_NO_PROXY = process.env.npm_config_no_proxy; - if (NPM_HTTPS_PROXY) - process.env.HTTPS_PROXY = NPM_HTTPS_PROXY; - if (NPM_HTTP_PROXY) - process.env.HTTP_PROXY = NPM_HTTP_PROXY; - if (NPM_NO_PROXY) - process.env.NO_PROXY = NPM_NO_PROXY; + if (NPM_HTTPS_PROXY) process.env.HTTPS_PROXY = NPM_HTTPS_PROXY; + if (NPM_HTTP_PROXY) process.env.HTTP_PROXY = NPM_HTTP_PROXY; + if (NPM_NO_PROXY) process.env.NO_PROXY = NPM_NO_PROXY; /** * @param {!Array} * @return {!Promise} */ function onSuccess(localRevisions) { - logPolitely(`${supportedProducts[product]} (${revisionInfo.revision}) downloaded to ${revisionInfo.folderPath}`); - localRevisions = localRevisions.filter(revision => revision !== revisionInfo.revision); - const cleanupOldVersions = localRevisions.map(revision => browserFetcher.remove(revision)); + logPolitely( + `${supportedProducts[product]} (${revisionInfo.revision}) downloaded to ${revisionInfo.folderPath}` + ); + localRevisions = localRevisions.filter( + (revision) => revision !== revisionInfo.revision + ); + const cleanupOldVersions = localRevisions.map((revision) => + browserFetcher.remove(revision) + ); Promise.all([...cleanupOldVersions]); } @@ -89,7 +113,9 @@ async function download() { * @param {!Error} error */ function onError(error) { - console.error(`ERROR: Failed to set up ${supportedProducts[product]} r${revision}! Set "PUPPETEER_SKIP_DOWNLOAD" env variable to skip download.`); + console.error( + `ERROR: Failed to set up ${supportedProducts[product]} r${revision}! Set "PUPPETEER_SKIP_DOWNLOAD" env variable to skip download.` + ); console.error(error); process.exit(1); } @@ -99,22 +125,28 @@ async function download() { function onProgress(downloadedBytes, totalBytes) { if (!progressBar) { const ProgressBar = require('progress'); - progressBar = new ProgressBar(`Downloading ${supportedProducts[product]} r${revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, { - complete: '=', - incomplete: ' ', - width: 20, - total: totalBytes, - }); + progressBar = new ProgressBar( + `Downloading ${ + supportedProducts[product] + } r${revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, + { + complete: '=', + incomplete: ' ', + width: 20, + total: totalBytes, + } + ); } const delta = downloadedBytes - lastDownloadedBytes; lastDownloadedBytes = downloadedBytes; progressBar.tick(delta); } - return browserFetcher.download(revisionInfo.revision, onProgress) - .then(() => browserFetcher.localRevisions()) - .then(onSuccess) - .catch(onError); + return browserFetcher + .download(revisionInfo.revision, onProgress) + .then(() => browserFetcher.localRevisions()) + .then(onSuccess) + .catch(onError); } function toMegabytes(bytes) { @@ -127,14 +159,16 @@ async function download() { const promise = new Promise((resolve, reject) => { let data = ''; logPolitely(`Requesting latest Firefox Nightly version from ${host}`); - https.get(host + '/', r => { - if (r.statusCode >= 400) - return reject(new Error(`Got status code ${r.statusCode}`)); - r.on('data', chunk => { - data += chunk; - }); - r.on('end', parseVersion); - }).on('error', reject); + https + .get(host + '/', (r) => { + if (r.statusCode >= 400) + return reject(new Error(`Got status code ${r.statusCode}`)); + r.on('data', (chunk) => { + data += chunk; + }); + r.on('end', parseVersion); + }) + .on('error', reject); function parseVersion() { const regex = /firefox\-(?\d\d)\..*/gm; @@ -142,11 +176,9 @@ async function download() { let match; while ((match = regex.exec(data)) !== null) { const version = parseInt(match.groups.version, 10); - if (version > result) - result = version; + if (version > result) result = version; } - if (result) - resolve(result.toString()); + if (result) resolve(result.toString()); else reject(new Error('Firefox version not found')); } }); @@ -158,34 +190,56 @@ function logPolitely(toBeLogged) { const logLevel = process.env.npm_config_loglevel; const logLevelDisplay = ['silent', 'error', 'warn'].indexOf(logLevel) > -1; - if (!logLevelDisplay) - console.log(toBeLogged); + if (!logLevelDisplay) console.log(toBeLogged); } if (process.env.PUPPETEER_SKIP_DOWNLOAD) { - logPolitely('**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" environment variable was found.'); + logPolitely( + '**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" environment variable was found.' + ); return; } -if (process.env.NPM_CONFIG_PUPPETEER_SKIP_DOWNLOAD || process.env.npm_config_puppeteer_skip_download) { - logPolitely('**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" was set in npm config.'); +if ( + process.env.NPM_CONFIG_PUPPETEER_SKIP_DOWNLOAD || + process.env.npm_config_puppeteer_skip_download +) { + logPolitely( + '**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" was set in npm config.' + ); return; } -if (process.env.NPM_PACKAGE_CONFIG_PUPPETEER_SKIP_DOWNLOAD || process.env.npm_package_config_puppeteer_skip_download) { - logPolitely('**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" was set in project config.'); +if ( + process.env.NPM_PACKAGE_CONFIG_PUPPETEER_SKIP_DOWNLOAD || + process.env.npm_package_config_puppeteer_skip_download +) { + logPolitely( + '**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" was set in project config.' + ); return; } if (process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD) { - logPolitely('**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" environment variable was found.'); + logPolitely( + '**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" environment variable was found.' + ); return; } -if (process.env.NPM_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD || process.env.npm_config_puppeteer_skip_chromium_download) { - logPolitely('**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in npm config.'); +if ( + process.env.NPM_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD || + process.env.npm_config_puppeteer_skip_chromium_download +) { + logPolitely( + '**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in npm config.' + ); return; } -if (process.env.NPM_PACKAGE_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD || process.env.npm_package_config_puppeteer_skip_chromium_download) { - logPolitely('**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in project config.'); +if ( + process.env.NPM_PACKAGE_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD || + process.env.npm_package_config_puppeteer_skip_chromium_download +) { + logPolitely( + '**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in project config.' + ); return; } download(); - diff --git a/package.json b/package.json index 57a9744fe00f9..c7f38b058edf2 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,9 @@ "commonmark": "^0.28.1", "cross-env": "^5.0.5", "eslint": "^6.8.0", + "eslint-config-prettier": "^6.11.0", "eslint-plugin-mocha": "^6.3.0", + "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-unicorn": "^19.0.1", "esprima": "^4.0.0", "expect": "^25.2.7", @@ -77,6 +79,7 @@ "ncp": "^2.0.0", "pixelmatch": "^4.0.2", "pngjs": "^5.0.0", + "prettier": "^2.0.5", "text-diff": "^1.0.1", "typescript": "3.8.3" }, diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000000000..4c5c41c6877d5 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,5 @@ +module.exports = { + semi: true, + trailingComma: 'es5', + singleQuote: true, +}; diff --git a/src/Accessibility.ts b/src/Accessibility.ts index e609c056cefaf..4c4a7658a0b6f 100644 --- a/src/Accessibility.ts +++ b/src/Accessibility.ts @@ -16,16 +16,15 @@ // Used as a TypeDef // eslint-disable-next-line no-unused-vars -import {CDPSession} from './Connection'; +import { CDPSession } from './Connection'; // Used as a TypeDef // eslint-disable-next-line no-unused-vars -import {ElementHandle} from './JSHandle'; - +import { ElementHandle } from './JSHandle'; interface SerializedAXNode { role: string; name?: string; - value?: string|number; + value?: string | number; description?: string; keyshortcuts?: string; roledescription?: string; @@ -39,8 +38,8 @@ interface SerializedAXNode { readonly?: boolean; required?: boolean; selected?: boolean; - checked?: boolean|'mixed'; - pressed?: boolean|'mixed'; + checked?: boolean | 'mixed'; + pressed?: boolean | 'mixed'; level?: number; valuemin?: number; valuemax?: number; @@ -58,31 +57,31 @@ export class Accessibility { this._client = client; } - async snapshot(options: {interestingOnly?: boolean; root?: ElementHandle} = {}): Promise { - const { - interestingOnly = true, - root = null, - } = options; - const {nodes} = await this._client.send('Accessibility.getFullAXTree'); + async snapshot( + options: { interestingOnly?: boolean; root?: ElementHandle } = {} + ): Promise { + const { interestingOnly = true, root = null } = options; + const { nodes } = await this._client.send('Accessibility.getFullAXTree'); let backendNodeId = null; if (root) { - const {node} = await this._client.send('DOM.describeNode', {objectId: root._remoteObject.objectId}); + const { node } = await this._client.send('DOM.describeNode', { + objectId: root._remoteObject.objectId, + }); backendNodeId = node.backendNodeId; } const defaultRoot = AXNode.createTree(nodes); let needle = defaultRoot; if (backendNodeId) { - needle = defaultRoot.find(node => node._payload.backendDOMNodeId === backendNodeId); - if (!needle) - return null; + needle = defaultRoot.find( + (node) => node._payload.backendDOMNodeId === backendNodeId + ); + if (!needle) return null; } - if (!interestingOnly) - return serializeTree(needle)[0]; + if (!interestingOnly) return serializeTree(needle)[0]; const interestingNodes = new Set(); collectInterestingNodes(interestingNodes, defaultRoot, false); - if (!interestingNodes.has(needle)) - return null; + if (!interestingNodes.has(needle)) return null; return serializeTree(needle, interestingNodes)[0]; } } @@ -92,31 +91,33 @@ export class Accessibility { * @param {!AXNode} node * @param {boolean} insideControl */ -function collectInterestingNodes(collection: Set, node: AXNode, insideControl: boolean): void { - if (node.isInteresting(insideControl)) - collection.add(node); - if (node.isLeafNode()) - return; +function collectInterestingNodes( + collection: Set, + node: AXNode, + insideControl: boolean +): void { + if (node.isInteresting(insideControl)) collection.add(node); + if (node.isLeafNode()) return; insideControl = insideControl || node.isControl(); for (const child of node._children) collectInterestingNodes(collection, child, insideControl); } -function serializeTree(node: AXNode, whitelistedNodes?: Set): SerializedAXNode[] { +function serializeTree( + node: AXNode, + whitelistedNodes?: Set +): SerializedAXNode[] { const children: SerializedAXNode[] = []; for (const child of node._children) children.push(...serializeTree(child, whitelistedNodes)); - if (whitelistedNodes && !whitelistedNodes.has(node)) - return children; + if (whitelistedNodes && !whitelistedNodes.has(node)) return children; const serializedNode = node.serialize(); - if (children.length) - serializedNode.children = children; + if (children.length) serializedNode.children = children; return [serializedNode]; } - class AXNode { _payload: Protocol.Accessibility.AXNode; _children: AXNode[] = []; @@ -139,27 +140,25 @@ class AXNode { this._richlyEditable = property.value.value === 'richtext'; this._editable = true; } - if (property.name === 'focusable') - this._focusable = property.value.value; - if (property.name === 'expanded') - this._expanded = property.value.value; - if (property.name === 'hidden') - this._hidden = property.value.value; + if (property.name === 'focusable') this._focusable = property.value.value; + if (property.name === 'expanded') this._expanded = property.value.value; + if (property.name === 'hidden') this._hidden = property.value.value; } } _isPlainTextField(): boolean { - if (this._richlyEditable) - return false; - if (this._editable) - return true; - return this._role === 'textbox' || this._role === 'ComboBox' || this._role === 'searchbox'; + if (this._richlyEditable) return false; + if (this._editable) return true; + return ( + this._role === 'textbox' || + this._role === 'ComboBox' || + this._role === 'searchbox' + ); } _isTextOnlyObject(): boolean { const role = this._role; - return (role === 'LineBreak' || role === 'text' || - role === 'InlineTextBox'); + return role === 'LineBreak' || role === 'text' || role === 'InlineTextBox'; } _hasFocusableChild(): boolean { @@ -176,26 +175,22 @@ class AXNode { } find(predicate: (x: AXNode) => boolean): AXNode | null { - if (predicate(this)) - return this; + if (predicate(this)) return this; for (const child of this._children) { const result = child.find(predicate); - if (result) - return result; + if (result) return result; } return null; } isLeafNode(): boolean { - if (!this._children.length) - return true; + if (!this._children.length) return true; // These types of objects may have children that we use as internal // implementation details, but we want to expose them as leaves to platform // accessibility APIs because screen readers might be confused if they find // any children. - if (this._isPlainTextField() || this._isTextOnlyObject()) - return true; + if (this._isPlainTextField() || this._isTextOnlyObject()) return true; // Roles whose children are only presentational according to the ARIA and // HTML5 Specs should be hidden from screen readers. @@ -216,12 +211,9 @@ class AXNode { } // Here and below: Android heuristics - if (this._hasFocusableChild()) - return false; - if (this._focusable && this._name) - return true; - if (this._role === 'heading' && this._name) - return true; + if (this._hasFocusableChild()) return false; + if (this._focusable && this._name) return true; + if (this._role === 'heading' && this._name) return true; return false; } @@ -259,39 +251,39 @@ class AXNode { */ isInteresting(insideControl: boolean): boolean { const role = this._role; - if (role === 'Ignored' || this._hidden) - return false; + if (role === 'Ignored' || this._hidden) return false; - if (this._focusable || this._richlyEditable) - return true; + if (this._focusable || this._richlyEditable) return true; // If it's not focusable but has a control role, then it's interesting. - if (this.isControl()) - return true; + if (this.isControl()) return true; // A non focusable child of a control is not interesting - if (insideControl) - return false; + if (insideControl) return false; return this.isLeafNode() && !!this._name; } serialize(): SerializedAXNode { - const properties = new Map(); + const properties = new Map(); for (const property of this._payload.properties || []) properties.set(property.name.toLowerCase(), property.value.value); - if (this._payload.name) - properties.set('name', this._payload.name.value); - if (this._payload.value) - properties.set('value', this._payload.value.value); + if (this._payload.name) properties.set('name', this._payload.name.value); + if (this._payload.value) properties.set('value', this._payload.value.value); if (this._payload.description) properties.set('description', this._payload.description.value); const node: SerializedAXNode = { - role: this._role + role: this._role, }; - type UserStringProperty = 'name'|'value'|'description'|'keyshortcuts'|'roledescription'|'valuetext'; + type UserStringProperty = + | 'name' + | 'value' + | 'description' + | 'keyshortcuts' + | 'roledescription' + | 'valuetext'; const userStringProperties: UserStringProperty[] = [ 'name', @@ -301,16 +293,25 @@ class AXNode { 'roledescription', 'valuetext', ]; - const getUserStringPropertyValue = (key: UserStringProperty): string => properties.get(key) as string; + const getUserStringPropertyValue = (key: UserStringProperty): string => + properties.get(key) as string; for (const userStringProperty of userStringProperties) { - if (!properties.has(userStringProperty)) - continue; + if (!properties.has(userStringProperty)) continue; node[userStringProperty] = getUserStringPropertyValue(userStringProperty); } - type BooleanProperty = 'disabled'|'expanded'|'focused'|'modal'|'multiline'|'multiselectable'|'readonly'|'required'|'selected'; + type BooleanProperty = + | 'disabled' + | 'expanded' + | 'focused' + | 'modal' + | 'multiline' + | 'multiselectable' + | 'readonly' + | 'required' + | 'selected'; const booleanProperties: BooleanProperty[] = [ 'disabled', 'expanded', @@ -322,58 +323,56 @@ class AXNode { 'required', 'selected', ]; - const getBooleanPropertyValue = (key: BooleanProperty): boolean => properties.get(key) as boolean; + const getBooleanPropertyValue = (key: BooleanProperty): boolean => + properties.get(key) as boolean; for (const booleanProperty of booleanProperties) { // WebArea's treat focus differently than other nodes. They report whether their frame has focus, // not whether focus is specifically on the root node. - if (booleanProperty === 'focused' && this._role === 'WebArea') - continue; + if (booleanProperty === 'focused' && this._role === 'WebArea') continue; const value = getBooleanPropertyValue(booleanProperty); - if (!value) - continue; + if (!value) continue; node[booleanProperty] = getBooleanPropertyValue(booleanProperty); } - type TristateProperty = 'checked'|'pressed'; - const tristateProperties: TristateProperty[] = [ - 'checked', - 'pressed', - ]; + type TristateProperty = 'checked' | 'pressed'; + const tristateProperties: TristateProperty[] = ['checked', 'pressed']; for (const tristateProperty of tristateProperties) { - if (!properties.has(tristateProperty)) - continue; + if (!properties.has(tristateProperty)) continue; const value = properties.get(tristateProperty); - node[tristateProperty] = value === 'mixed' ? 'mixed' : value === 'true' ? true : false; + node[tristateProperty] = + value === 'mixed' ? 'mixed' : value === 'true' ? true : false; } - - type NumbericalProperty = 'level'|'valuemax'|'valuemin'; + type NumbericalProperty = 'level' | 'valuemax' | 'valuemin'; const numericalProperties: NumbericalProperty[] = [ 'level', 'valuemax', 'valuemin', ]; - const getNumericalPropertyValue = (key: NumbericalProperty): number => properties.get(key) as number; + const getNumericalPropertyValue = (key: NumbericalProperty): number => + properties.get(key) as number; for (const numericalProperty of numericalProperties) { - if (!properties.has(numericalProperty)) - continue; + if (!properties.has(numericalProperty)) continue; node[numericalProperty] = getNumericalPropertyValue(numericalProperty); } - - type TokenProperty = 'autocomplete'|'haspopup'|'invalid'|'orientation'; + type TokenProperty = + | 'autocomplete' + | 'haspopup' + | 'invalid' + | 'orientation'; const tokenProperties: TokenProperty[] = [ 'autocomplete', 'haspopup', 'invalid', 'orientation', ]; - const getTokenPropertyValue = (key: TokenProperty): string => properties.get(key) as string; + const getTokenPropertyValue = (key: TokenProperty): string => + properties.get(key) as string; for (const tokenProperty of tokenProperties) { const value = getTokenPropertyValue(tokenProperty); - if (!value || value === 'false') - continue; + if (!value || value === 'false') continue; node[tokenProperty] = getTokenPropertyValue(tokenProperty); } return node; diff --git a/src/Browser.ts b/src/Browser.ts index 98afacf699536..2dcd11651eb67 100644 --- a/src/Browser.ts +++ b/src/Browser.ts @@ -14,214 +14,283 @@ * limitations under the License. */ -import {helper, assert} from './helper'; -import {Target} from './Target'; +import { helper, assert } from './helper'; +import { Target } from './Target'; import * as EventEmitter from 'events'; -import {TaskQueue} from './TaskQueue'; -import {Events} from './Events'; -import {Connection} from './Connection'; -import {Page} from './Page'; -import {ChildProcess} from 'child_process'; -import type {Viewport} from './PuppeteerViewport'; +import { TaskQueue } from './TaskQueue'; +import { Events } from './Events'; +import { Connection } from './Connection'; +import { Page } from './Page'; +import { ChildProcess } from 'child_process'; +import type { Viewport } from './PuppeteerViewport'; type BrowserCloseCallback = () => Promise | void; export class Browser extends EventEmitter { - static async create(connection: Connection, contextIds: string[], ignoreHTTPSErrors: boolean, defaultViewport?: Viewport, process?: ChildProcess, closeCallback?: BrowserCloseCallback): Promise { - const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback); - await connection.send('Target.setDiscoverTargets', {discover: true}); + static async create( + connection: Connection, + contextIds: string[], + ignoreHTTPSErrors: boolean, + defaultViewport?: Viewport, + process?: ChildProcess, + closeCallback?: BrowserCloseCallback + ): Promise { + const browser = new Browser( + connection, + contextIds, + ignoreHTTPSErrors, + defaultViewport, + process, + closeCallback + ); + await connection.send('Target.setDiscoverTargets', { discover: true }); return browser; } - _ignoreHTTPSErrors: boolean; - _defaultViewport?: Viewport; - _process?: ChildProcess; - _screenshotTaskQueue = new TaskQueue(); - _connection: Connection; - _closeCallback: BrowserCloseCallback; - _defaultContext: BrowserContext; - _contexts: Map; - // TODO: once Target is in TypeScript we can type this properly. - _targets: Map; - - constructor(connection: Connection, contextIds: string[], ignoreHTTPSErrors: boolean, defaultViewport?: Viewport, process?: ChildProcess, closeCallback?: BrowserCloseCallback) { - super(); - this._ignoreHTTPSErrors = ignoreHTTPSErrors; - this._defaultViewport = defaultViewport; - this._process = process; - this._screenshotTaskQueue = new TaskQueue(); - this._connection = connection; - this._closeCallback = closeCallback || function(): void {}; - - this._defaultContext = new BrowserContext(this._connection, this, null); - this._contexts = new Map(); - for (const contextId of contextIds) - this._contexts.set(contextId, new BrowserContext(this._connection, this, contextId)); - - this._targets = new Map(); - this._connection.on(Events.Connection.Disconnected, () => this.emit(Events.Browser.Disconnected)); - this._connection.on('Target.targetCreated', this._targetCreated.bind(this)); - this._connection.on('Target.targetDestroyed', this._targetDestroyed.bind(this)); - this._connection.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this)); - } - - process(): ChildProcess | null { - return this._process; - } - - async createIncognitoBrowserContext(): Promise { - const {browserContextId} = await this._connection.send('Target.createBrowserContext'); - const context = new BrowserContext(this._connection, this, browserContextId); - this._contexts.set(browserContextId, context); - return context; - } - - browserContexts(): BrowserContext[] { - return [this._defaultContext, ...Array.from(this._contexts.values())]; - } - - defaultBrowserContext(): BrowserContext { - return this._defaultContext; - } - - /** + _ignoreHTTPSErrors: boolean; + _defaultViewport?: Viewport; + _process?: ChildProcess; + _screenshotTaskQueue = new TaskQueue(); + _connection: Connection; + _closeCallback: BrowserCloseCallback; + _defaultContext: BrowserContext; + _contexts: Map; + // TODO: once Target is in TypeScript we can type this properly. + _targets: Map; + + constructor( + connection: Connection, + contextIds: string[], + ignoreHTTPSErrors: boolean, + defaultViewport?: Viewport, + process?: ChildProcess, + closeCallback?: BrowserCloseCallback + ) { + super(); + this._ignoreHTTPSErrors = ignoreHTTPSErrors; + this._defaultViewport = defaultViewport; + this._process = process; + this._screenshotTaskQueue = new TaskQueue(); + this._connection = connection; + this._closeCallback = closeCallback || function (): void {}; + + this._defaultContext = new BrowserContext(this._connection, this, null); + this._contexts = new Map(); + for (const contextId of contextIds) + this._contexts.set( + contextId, + new BrowserContext(this._connection, this, contextId) + ); + + this._targets = new Map(); + this._connection.on(Events.Connection.Disconnected, () => + this.emit(Events.Browser.Disconnected) + ); + this._connection.on('Target.targetCreated', this._targetCreated.bind(this)); + this._connection.on( + 'Target.targetDestroyed', + this._targetDestroyed.bind(this) + ); + this._connection.on( + 'Target.targetInfoChanged', + this._targetInfoChanged.bind(this) + ); + } + + process(): ChildProcess | null { + return this._process; + } + + async createIncognitoBrowserContext(): Promise { + const { browserContextId } = await this._connection.send( + 'Target.createBrowserContext' + ); + const context = new BrowserContext( + this._connection, + this, + browserContextId + ); + this._contexts.set(browserContextId, context); + return context; + } + + browserContexts(): BrowserContext[] { + return [this._defaultContext, ...Array.from(this._contexts.values())]; + } + + defaultBrowserContext(): BrowserContext { + return this._defaultContext; + } + + /** * @param {?string} contextId */ - async _disposeContext(contextId?: string): Promise { - await this._connection.send('Target.disposeBrowserContext', {browserContextId: contextId || undefined}); - this._contexts.delete(contextId); - } - - async _targetCreated(event: Protocol.Target.targetCreatedPayload): Promise { - const targetInfo = event.targetInfo; - const {browserContextId} = targetInfo; - const context = (browserContextId && this._contexts.has(browserContextId)) ? this._contexts.get(browserContextId) : this._defaultContext; - - const target = new Target(targetInfo, context, () => this._connection.createSession(targetInfo), this._ignoreHTTPSErrors, this._defaultViewport, this._screenshotTaskQueue); - assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated'); - this._targets.set(event.targetInfo.targetId, target); - - if (await target._initializedPromise) { - this.emit(Events.Browser.TargetCreated, target); - context.emit(Events.BrowserContext.TargetCreated, target); - } - } - - /** + async _disposeContext(contextId?: string): Promise { + await this._connection.send('Target.disposeBrowserContext', { + browserContextId: contextId || undefined, + }); + this._contexts.delete(contextId); + } + + async _targetCreated( + event: Protocol.Target.targetCreatedPayload + ): Promise { + const targetInfo = event.targetInfo; + const { browserContextId } = targetInfo; + const context = + browserContextId && this._contexts.has(browserContextId) + ? this._contexts.get(browserContextId) + : this._defaultContext; + + const target = new Target( + targetInfo, + context, + () => this._connection.createSession(targetInfo), + this._ignoreHTTPSErrors, + this._defaultViewport, + this._screenshotTaskQueue + ); + assert( + !this._targets.has(event.targetInfo.targetId), + 'Target should not exist before targetCreated' + ); + this._targets.set(event.targetInfo.targetId, target); + + if (await target._initializedPromise) { + this.emit(Events.Browser.TargetCreated, target); + context.emit(Events.BrowserContext.TargetCreated, target); + } + } + + /** * @param {{targetId: string}} event */ - async _targetDestroyed(event: { targetId: string}): Promise { - const target = this._targets.get(event.targetId); - target._initializedCallback(false); - this._targets.delete(event.targetId); - target._closedCallback(); - if (await target._initializedPromise) { - this.emit(Events.Browser.TargetDestroyed, target); - target.browserContext().emit(Events.BrowserContext.TargetDestroyed, target); - } - } - - /** + async _targetDestroyed(event: { targetId: string }): Promise { + const target = this._targets.get(event.targetId); + target._initializedCallback(false); + this._targets.delete(event.targetId); + target._closedCallback(); + if (await target._initializedPromise) { + this.emit(Events.Browser.TargetDestroyed, target); + target + .browserContext() + .emit(Events.BrowserContext.TargetDestroyed, target); + } + } + + /** * @param {!Protocol.Target.targetInfoChangedPayload} event */ - _targetInfoChanged(event: Protocol.Target.targetInfoChangedPayload): void { - const target = this._targets.get(event.targetInfo.targetId); - assert(target, 'target should exist before targetInfoChanged'); - const previousURL = target.url(); - const wasInitialized = target._isInitialized; - target._targetInfoChanged(event.targetInfo); - if (wasInitialized && previousURL !== target.url()) { - this.emit(Events.Browser.TargetChanged, target); - target.browserContext().emit(Events.BrowserContext.TargetChanged, target); - } - } - - wsEndpoint(): string { - return this._connection.url(); - } - - async newPage(): Promise { - return this._defaultContext.newPage(); - } - - async _createPageInContext(contextId?: string): Promise { - const {targetId} = await this._connection.send('Target.createTarget', {url: 'about:blank', browserContextId: contextId || undefined}); - const target = await this._targets.get(targetId); - assert(await target._initializedPromise, 'Failed to create target for page'); - const page = await target.page(); - return page; - } - - targets(): Target[] { - return Array.from(this._targets.values()).filter(target => target._isInitialized); - } - - target(): Target { - return this.targets().find(target => target.type() === 'browser'); - } - - /** + _targetInfoChanged(event: Protocol.Target.targetInfoChangedPayload): void { + const target = this._targets.get(event.targetInfo.targetId); + assert(target, 'target should exist before targetInfoChanged'); + const previousURL = target.url(); + const wasInitialized = target._isInitialized; + target._targetInfoChanged(event.targetInfo); + if (wasInitialized && previousURL !== target.url()) { + this.emit(Events.Browser.TargetChanged, target); + target.browserContext().emit(Events.BrowserContext.TargetChanged, target); + } + } + + wsEndpoint(): string { + return this._connection.url(); + } + + async newPage(): Promise { + return this._defaultContext.newPage(); + } + + async _createPageInContext(contextId?: string): Promise { + const { targetId } = await this._connection.send('Target.createTarget', { + url: 'about:blank', + browserContextId: contextId || undefined, + }); + const target = await this._targets.get(targetId); + assert( + await target._initializedPromise, + 'Failed to create target for page' + ); + const page = await target.page(); + return page; + } + + targets(): Target[] { + return Array.from(this._targets.values()).filter( + (target) => target._isInitialized + ); + } + + target(): Target { + return this.targets().find((target) => target.type() === 'browser'); + } + + /** * @param {function(!Target):boolean} predicate * @param {{timeout?: number}=} options * @return {!Promise} */ - async waitForTarget(predicate: (x: Target) => boolean, options: { timeout?: number} = {}): Promise { - const { - timeout = 30000 - } = options; - const existingTarget = this.targets().find(predicate); - if (existingTarget) - return existingTarget; - let resolve; - const targetPromise = new Promise(x => resolve = x); - this.on(Events.Browser.TargetCreated, check); - this.on(Events.Browser.TargetChanged, check); - try { - if (!timeout) - return await targetPromise; - return await helper.waitWithTimeout(targetPromise, 'target', timeout); - } finally { - this.removeListener(Events.Browser.TargetCreated, check); - this.removeListener(Events.Browser.TargetChanged, check); - } - - function check(target: Target): void { - if (predicate(target)) - resolve(target); - } - } - - async pages(): Promise { - const contextPages = await Promise.all(this.browserContexts().map(context => context.pages())); - // Flatten array. - return contextPages.reduce((acc, x) => acc.concat(x), []); - } - - async version(): Promise { - const version = await this._getVersion(); - return version.product; - } - - async userAgent(): Promise { - const version = await this._getVersion(); - return version.userAgent; - } - - async close(): Promise { - await this._closeCallback.call(null); - this.disconnect(); - } - - disconnect(): void { - this._connection.dispose(); - } - - isConnected(): boolean { - return !this._connection._closed; - } - - _getVersion(): Promise { - return this._connection.send('Browser.getVersion'); - } + async waitForTarget( + predicate: (x: Target) => boolean, + options: { timeout?: number } = {} + ): Promise { + const { timeout = 30000 } = options; + const existingTarget = this.targets().find(predicate); + if (existingTarget) return existingTarget; + let resolve; + const targetPromise = new Promise((x) => (resolve = x)); + this.on(Events.Browser.TargetCreated, check); + this.on(Events.Browser.TargetChanged, check); + try { + if (!timeout) return await targetPromise; + return await helper.waitWithTimeout( + targetPromise, + 'target', + timeout + ); + } finally { + this.removeListener(Events.Browser.TargetCreated, check); + this.removeListener(Events.Browser.TargetChanged, check); + } + + function check(target: Target): void { + if (predicate(target)) resolve(target); + } + } + + async pages(): Promise { + const contextPages = await Promise.all( + this.browserContexts().map((context) => context.pages()) + ); + // Flatten array. + return contextPages.reduce((acc, x) => acc.concat(x), []); + } + + async version(): Promise { + const version = await this._getVersion(); + return version.product; + } + + async userAgent(): Promise { + const version = await this._getVersion(); + return version.userAgent; + } + + async close(): Promise { + await this._closeCallback.call(null); + this.disconnect(); + } + + disconnect(): void { + this._connection.dispose(); + } + + isConnected(): boolean { + return !this._connection._closed; + } + + _getVersion(): Promise { + return this._connection.send('Browser.getVersion'); + } } export class BrowserContext extends EventEmitter { @@ -237,28 +306,42 @@ export class BrowserContext extends EventEmitter { } targets(): Target[] { - return this._browser.targets().filter(target => target.browserContext() === this); + return this._browser + .targets() + .filter((target) => target.browserContext() === this); } - waitForTarget(predicate: (x: Target) => boolean, options: { timeout?: number}): Promise { - return this._browser.waitForTarget(target => target.browserContext() === this && predicate(target), options); + waitForTarget( + predicate: (x: Target) => boolean, + options: { timeout?: number } + ): Promise { + return this._browser.waitForTarget( + (target) => target.browserContext() === this && predicate(target), + options + ); } async pages(): Promise { const pages = await Promise.all( - this.targets() - .filter(target => target.type() === 'page') - .map(target => target.page()) + this.targets() + .filter((target) => target.type() === 'page') + .map((target) => target.page()) ); - return pages.filter(page => !!page); + return pages.filter((page) => !!page); } isIncognito(): boolean { return !!this._id; } - async overridePermissions(origin: string, permissions: Protocol.Browser.PermissionType[]): Promise { - const webPermissionToProtocol = new Map([ + async overridePermissions( + origin: string, + permissions: Protocol.Browser.PermissionType[] + ): Promise { + const webPermissionToProtocol = new Map< + string, + Protocol.Browser.PermissionType + >([ ['geolocation', 'geolocation'], ['midi', 'midi'], ['notifications', 'notifications'], @@ -278,17 +361,23 @@ export class BrowserContext extends EventEmitter { // chrome-specific permissions we have. ['midi-sysex', 'midiSysex'], ]); - permissions = permissions.map(permission => { + permissions = permissions.map((permission) => { const protocolPermission = webPermissionToProtocol.get(permission); if (!protocolPermission) throw new Error('Unknown permission: ' + permission); return protocolPermission; }); - await this._connection.send('Browser.grantPermissions', {origin, browserContextId: this._id || undefined, permissions}); + await this._connection.send('Browser.grantPermissions', { + origin, + browserContextId: this._id || undefined, + permissions, + }); } async clearPermissionOverrides(): Promise { - await this._connection.send('Browser.resetPermissions', {browserContextId: this._id || undefined}); + await this._connection.send('Browser.resetPermissions', { + browserContextId: this._id || undefined, + }); } newPage(): Promise { diff --git a/src/BrowserFetcher.ts b/src/BrowserFetcher.ts index 2ab567c74ad00..be3363ee2136b 100644 --- a/src/BrowserFetcher.ts +++ b/src/BrowserFetcher.ts @@ -27,9 +27,9 @@ import * as debug from 'debug'; import * as removeRecursive from 'rimraf'; import * as URL from 'url'; import * as ProxyAgent from 'https-proxy-agent'; -import {getProxyForUrl} from 'proxy-from-env'; +import { getProxyForUrl } from 'proxy-from-env'; -import {helper, assert} from './helper'; +import { helper, assert } from './helper'; const debugFetcher = debug(`puppeteer:fetcher`); const downloadURLs = { @@ -53,20 +53,23 @@ const browserConfig = { destination: '.local-chromium', }, firefox: { - host: 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central', + host: + 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central', destination: '.local-firefox', - } + }, } as const; type Platform = 'linux' | 'mac' | 'win32' | 'win64'; type Product = 'chrome' | 'firefox'; -function archiveName(product: Product, platform: Platform, revision: string): string { +function archiveName( + product: Product, + platform: Platform, + revision: string +): string { if (product === 'chrome') { - if (platform === 'linux') - return 'chrome-linux'; - if (platform === 'mac') - return 'chrome-mac'; + if (platform === 'linux') return 'chrome-linux'; + if (platform === 'mac') return 'chrome-mac'; if (platform === 'win32' || platform === 'win64') { // Windows archive name changed at r591479. return parseInt(revision, 10) > 591479 ? 'chrome-win' : 'chrome-win32'; @@ -83,8 +86,18 @@ function archiveName(product: Product, platform: Platform, revision: string): st * @param {string} revision * @return {string} */ -function downloadURL(product: Product, platform: Platform, host: string, revision: string): string { - const url = util.format(downloadURLs[product][platform], host, revision, archiveName(product, platform, revision)); +function downloadURL( + product: Product, + platform: Platform, + host: string, + revision: string +): string { + const url = util.format( + downloadURLs[product][platform], + host, + revision, + archiveName(product, platform, revision) + ); return url; } @@ -94,8 +107,8 @@ const unlinkAsync = helper.promisify(fs.unlink.bind(fs)); const chmodAsync = helper.promisify(fs.chmod.bind(fs)); function existsAsync(filePath: string): Promise { - return new Promise(resolve => { - fs.access(filePath, err => resolve(!err)); + return new Promise((resolve) => { + fs.access(filePath, (err) => resolve(!err)); }); } @@ -125,12 +138,20 @@ export class BrowserFetcher { constructor(projectRoot: string, options: BrowserFetcherOptions = {}) { this._product = (options.product || 'chrome').toLowerCase() as Product; - assert(this._product === 'chrome' || this._product === 'firefox', `Unknown product: "${options.product}"`); - - this._downloadsFolder = options.path || path.join(projectRoot, browserConfig[this._product].destination); + assert( + this._product === 'chrome' || this._product === 'firefox', + `Unknown product: "${options.product}"` + ); + + this._downloadsFolder = + options.path || + path.join(projectRoot, browserConfig[this._product].destination); this._downloadHost = options.host || browserConfig[this._product].host; this.setPlatform(options.platform); - assert(downloadURLs[this._product][this._platform], 'Unsupported platform: ' + this._platform); + assert( + downloadURLs[this._product][this._platform], + 'Unsupported platform: ' + this._platform + ); } private setPlatform(platformFromOptions?: Platform): void { @@ -140,14 +161,11 @@ export class BrowserFetcher { } const platform = os.platform(); - if (platform === 'darwin') - this._platform = 'mac'; - else if (platform === 'linux') - this._platform = 'linux'; + if (platform === 'darwin') this._platform = 'mac'; + else if (platform === 'linux') this._platform = 'linux'; else if (platform === 'win32') this._platform = os.arch() === 'x64' ? 'win64' : 'win32'; - else - assert(this._platform, 'Unsupported platform: ' + os.platform()); + else assert(this._platform, 'Unsupported platform: ' + os.platform()); } platform(): string { @@ -163,12 +181,17 @@ export class BrowserFetcher { } canDownload(revision: string): Promise { - const url = downloadURL(this._product, this._platform, this._downloadHost, revision); - return new Promise(resolve => { - const request = httpRequest(url, 'HEAD', response => { + const url = downloadURL( + this._product, + this._platform, + this._downloadHost, + revision + ); + return new Promise((resolve) => { + const request = httpRequest(url, 'HEAD', (response) => { resolve(response.statusCode === 200); }); - request.on('error', error => { + request.on('error', (error) => { console.error(error); resolve(false); }); @@ -180,39 +203,49 @@ export class BrowserFetcher { * @param {?function(number, number):void} progressCallback * @return {!Promise} */ - async download(revision: string, progressCallback: (x: number, y: number) => void): Promise { - const url = downloadURL(this._product, this._platform, this._downloadHost, revision); + async download( + revision: string, + progressCallback: (x: number, y: number) => void + ): Promise { + const url = downloadURL( + this._product, + this._platform, + this._downloadHost, + revision + ); const fileName = url.split('/').pop(); const archivePath = path.join(this._downloadsFolder, fileName); const outputPath = this._getFolderPath(revision); - if (await existsAsync(outputPath)) - return this.revisionInfo(revision); + if (await existsAsync(outputPath)) return this.revisionInfo(revision); if (!(await existsAsync(this._downloadsFolder))) await mkdirAsync(this._downloadsFolder); try { await downloadFile(url, archivePath, progressCallback); await install(archivePath, outputPath); } finally { - if (await existsAsync(archivePath)) - await unlinkAsync(archivePath); + if (await existsAsync(archivePath)) await unlinkAsync(archivePath); } const revisionInfo = this.revisionInfo(revision); - if (revisionInfo) - await chmodAsync(revisionInfo.executablePath, 0o755); + if (revisionInfo) await chmodAsync(revisionInfo.executablePath, 0o755); return revisionInfo; } async localRevisions(): Promise { - if (!await existsAsync(this._downloadsFolder)) - return []; + if (!(await existsAsync(this._downloadsFolder))) return []; const fileNames = await readdirAsync(this._downloadsFolder); - return fileNames.map(fileName => parseFolderPath(this._product, fileName)).filter(entry => entry && entry.platform === this._platform).map(entry => entry.revision); + return fileNames + .map((fileName) => parseFolderPath(this._product, fileName)) + .filter((entry) => entry && entry.platform === this._platform) + .map((entry) => entry.revision); } async remove(revision: string): Promise { const folderPath = this._getFolderPath(revision); - assert(await existsAsync(folderPath), `Failed to remove: revision ${revision} is not downloaded`); - await new Promise(fulfill => removeRecursive(folderPath, fulfill)); + assert( + await existsAsync(folderPath), + `Failed to remove: revision ${revision} is not downloaded` + ); + await new Promise((fulfill) => removeRecursive(folderPath, fulfill)); } revisionInfo(revision: string): BrowserFetcherRevisionInfo { @@ -220,29 +253,67 @@ export class BrowserFetcher { let executablePath = ''; if (this._product === 'chrome') { if (this._platform === 'mac') - executablePath = path.join(folderPath, archiveName(this._product, this._platform, revision), 'Chromium.app', 'Contents', 'MacOS', 'Chromium'); + executablePath = path.join( + folderPath, + archiveName(this._product, this._platform, revision), + 'Chromium.app', + 'Contents', + 'MacOS', + 'Chromium' + ); else if (this._platform === 'linux') - executablePath = path.join(folderPath, archiveName(this._product, this._platform, revision), 'chrome'); + executablePath = path.join( + folderPath, + archiveName(this._product, this._platform, revision), + 'chrome' + ); else if (this._platform === 'win32' || this._platform === 'win64') - executablePath = path.join(folderPath, archiveName(this._product, this._platform, revision), 'chrome.exe'); - else - throw new Error('Unsupported platform: ' + this._platform); + executablePath = path.join( + folderPath, + archiveName(this._product, this._platform, revision), + 'chrome.exe' + ); + else throw new Error('Unsupported platform: ' + this._platform); } else if (this._product === 'firefox') { if (this._platform === 'mac') - executablePath = path.join(folderPath, 'Firefox Nightly.app', 'Contents', 'MacOS', 'firefox'); + executablePath = path.join( + folderPath, + 'Firefox Nightly.app', + 'Contents', + 'MacOS', + 'firefox' + ); else if (this._platform === 'linux') executablePath = path.join(folderPath, 'firefox', 'firefox'); else if (this._platform === 'win32' || this._platform === 'win64') executablePath = path.join(folderPath, 'firefox', 'firefox.exe'); - else - throw new Error('Unsupported platform: ' + this._platform); + else throw new Error('Unsupported platform: ' + this._platform); } else { throw new Error('Unsupported product: ' + this._product); } - const url = downloadURL(this._product, this._platform, this._downloadHost, revision); + const url = downloadURL( + this._product, + this._platform, + this._downloadHost, + revision + ); const local = fs.existsSync(folderPath); - debugFetcher({revision, executablePath, folderPath, local, url, product: this._product}); - return {revision, executablePath, folderPath, local, url, product: this._product}; + debugFetcher({ + revision, + executablePath, + folderPath, + local, + url, + product: this._product, + }); + return { + revision, + executablePath, + folderPath, + local, + url, + product: this._product, + }; } /** @@ -254,15 +325,16 @@ export class BrowserFetcher { } } -function parseFolderPath(product: Product, folderPath: string): {product: string; platform: string; revision: string} | null { +function parseFolderPath( + product: Product, + folderPath: string +): { product: string; platform: string; revision: string } | null { const name = path.basename(folderPath); const splits = name.split('-'); - if (splits.length !== 2) - return null; + if (splits.length !== 2) return null; const [platform, revision] = splits; - if (!downloadURLs[product][platform]) - return null; - return {product, platform, revision}; + if (!downloadURLs[product][platform]) return null; + return { product, platform, revision }; } /** @@ -271,17 +343,26 @@ function parseFolderPath(product: Product, folderPath: string): {product: string * @param {?function(number, number):void} progressCallback * @return {!Promise} */ -function downloadFile(url: string, destinationPath: string, progressCallback: (x: number, y: number) => void): Promise { +function downloadFile( + url: string, + destinationPath: string, + progressCallback: (x: number, y: number) => void +): Promise { debugFetcher(`Downloading binary from ${url}`); let fulfill, reject; let downloadedBytes = 0; let totalBytes = 0; - const promise = new Promise((x, y) => { fulfill = x; reject = y; }); + const promise = new Promise((x, y) => { + fulfill = x; + reject = y; + }); - const request = httpRequest(url, 'GET', response => { + const request = httpRequest(url, 'GET', (response) => { if (response.statusCode !== 200) { - const error = new Error(`Download failed: server returned code ${response.statusCode}. URL: ${url}`); + const error = new Error( + `Download failed: server returned code ${response.statusCode}. URL: ${url}` + ); // consume response data to free up memory response.resume(); reject(error); @@ -289,13 +370,15 @@ function downloadFile(url: string, destinationPath: string, progressCallback: (x } const file = fs.createWriteStream(destinationPath); file.on('finish', () => fulfill()); - file.on('error', error => reject(error)); + file.on('error', (error) => reject(error)); response.pipe(file); - totalBytes = parseInt(/** @type {string} */ (response.headers['content-length']), 10); - if (progressCallback) - response.on('data', onData); + totalBytes = parseInt( + /** @type {string} */ response.headers['content-length'], + 10 + ); + if (progressCallback) response.on('data', onData); }); - request.on('error', error => reject(error)); + request.on('error', (error) => reject(error)); return promise; function onData(chunk: string): void { @@ -307,13 +390,14 @@ function downloadFile(url: string, destinationPath: string, progressCallback: (x function install(archivePath: string, folderPath: string): Promise { debugFetcher(`Installing ${archivePath} to ${folderPath}`); if (archivePath.endsWith('.zip')) - return extractZip(archivePath, {dir: folderPath}); + return extractZip(archivePath, { dir: folderPath }); else if (archivePath.endsWith('.tar.bz2')) return extractTar(archivePath, folderPath); else if (archivePath.endsWith('.dmg')) - return mkdirAsync(folderPath).then(() => installDMG(archivePath, folderPath)); - else - throw new Error(`Unsupported archive format: ${archivePath}`); + return mkdirAsync(folderPath).then(() => + installDMG(archivePath, folderPath) + ); + else throw new Error(`Unsupported archive format: ${archivePath}`); } /** @@ -331,7 +415,9 @@ function extractTar(tarPath: string, folderPath: string): Promise { tarStream.on('error', reject); tarStream.on('finish', fulfill); const readStream = fs.createReadStream(tarPath); - readStream.on('data', () => { process.stdout.write('\rExtracting...'); }); + readStream.on('data', () => { + process.stdout.write('\rExtracting...'); + }); readStream.pipe(bzip()).pipe(tarStream); }); } @@ -349,88 +435,94 @@ function installDMG(dmgPath: string, folderPath: string): Promise { function mountAndCopy(fulfill: () => void, reject: (Error) => void): void { const mountCommand = `hdiutil attach -nobrowse -noautoopen "${dmgPath}"`; childProcess.exec(mountCommand, (err, stdout) => { - if (err) - return reject(err); + if (err) return reject(err); const volumes = stdout.match(/\/Volumes\/(.*)/m); if (!volumes) return reject(new Error(`Could not find volume path in ${stdout}`)); mountPath = volumes[0]; - readdirAsync(mountPath).then(fileNames => { - const appName = fileNames.filter(item => typeof item === 'string' && item.endsWith('.app'))[0]; - if (!appName) - return reject(new Error(`Cannot find app in ${mountPath}`)); - const copyPath = path.join(mountPath, appName); - debugFetcher(`Copying ${copyPath} to ${folderPath}`); - childProcess.exec(`cp -R "${copyPath}" "${folderPath}"`, err => { - if (err) - reject(err); - else - fulfill(); - }); - }).catch(reject); + readdirAsync(mountPath) + .then((fileNames) => { + const appName = fileNames.filter( + (item) => typeof item === 'string' && item.endsWith('.app') + )[0]; + if (!appName) + return reject(new Error(`Cannot find app in ${mountPath}`)); + const copyPath = path.join(mountPath, appName); + debugFetcher(`Copying ${copyPath} to ${folderPath}`); + childProcess.exec(`cp -R "${copyPath}" "${folderPath}"`, (err) => { + if (err) reject(err); + else fulfill(); + }); + }) + .catch(reject); }); } function unmount(): void { - if (!mountPath) - return; + if (!mountPath) return; const unmountCommand = `hdiutil detach "${mountPath}" -quiet`; debugFetcher(`Unmounting ${mountPath}`); - childProcess.exec(unmountCommand, err => { - if (err) - console.error(`Error unmounting dmg: ${err}`); + childProcess.exec(unmountCommand, (err) => { + if (err) console.error(`Error unmounting dmg: ${err}`); }); } - return new Promise(mountAndCopy).catch(error => { console.error(error); }).finally(unmount); + return new Promise(mountAndCopy) + .catch((error) => { + console.error(error); + }) + .finally(unmount); } -function httpRequest(url: string, method: string, response: (x: http.IncomingMessage) => void): http.ClientRequest { +function httpRequest( + url: string, + method: string, + response: (x: http.IncomingMessage) => void +): http.ClientRequest { const urlParsed = URL.parse(url); - type Options = Partial & { - method?: string; - agent?: ProxyAgent; - rejectUnauthorized?: boolean; - }; - - let options: Options = { - ...urlParsed, - method, - }; - - const proxyURL = getProxyForUrl(url); - if (proxyURL) { - if (url.startsWith('http:')) { - const proxy = URL.parse(proxyURL); - options = { - path: options.href, - host: proxy.hostname, - port: proxy.port, - }; - } else { - const parsedProxyURL = URL.parse(proxyURL); - - const proxyOptions = { - ...parsedProxyURL, - secureProxy: parsedProxyURL.protocol === 'https:', - } as ProxyAgent.HttpsProxyAgentOptions; - - options.agent = new ProxyAgent(proxyOptions); - options.rejectUnauthorized = false; - } - } - - const requestCallback = (res: http.IncomingMessage): void => { - if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) - httpRequest(res.headers.location, method, response); - else - response(res); - }; - const request = options.protocol === 'https:' ? - https.request(options, requestCallback) : - http.request(options, requestCallback); - request.end(); - return request; -} + type Options = Partial & { + method?: string; + agent?: ProxyAgent; + rejectUnauthorized?: boolean; + }; + + let options: Options = { + ...urlParsed, + method, + }; + + const proxyURL = getProxyForUrl(url); + if (proxyURL) { + if (url.startsWith('http:')) { + const proxy = URL.parse(proxyURL); + options = { + path: options.href, + host: proxy.hostname, + port: proxy.port, + }; + } else { + const parsedProxyURL = URL.parse(proxyURL); + const proxyOptions = { + ...parsedProxyURL, + secureProxy: parsedProxyURL.protocol === 'https:', + } as ProxyAgent.HttpsProxyAgentOptions; + + options.agent = new ProxyAgent(proxyOptions); + options.rejectUnauthorized = false; + } + } + + const requestCallback = (res: http.IncomingMessage): void => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) + httpRequest(res.headers.location, method, response); + else response(res); + }; + const request = + options.protocol === 'https:' + ? https.request(options, requestCallback) + : http.request(options, requestCallback); + request.end(); + return request; +} diff --git a/src/Connection.ts b/src/Connection.ts index 57e68684e3fac..a79f0decb3840 100644 --- a/src/Connection.ts +++ b/src/Connection.ts @@ -13,20 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {assert} from './helper'; -import {Events} from './Events'; +import { assert } from './helper'; +import { Events } from './Events'; import * as debug from 'debug'; const debugProtocol = debug('puppeteer:protocol'); -import type {ConnectionTransport} from './ConnectionTransport'; +import type { ConnectionTransport } from './ConnectionTransport'; import * as EventEmitter from 'events'; interface ConnectionCallback { - resolve: Function; - reject: Function; - error: Error; - method: string; - + resolve: Function; + reject: Function; + error: Error; + method: string; } export class Connection extends EventEmitter { @@ -65,29 +64,35 @@ export class Connection extends EventEmitter { return this._url; } - send(method: T, params?: Protocol.CommandParameters[T]): Promise { - const id = this._rawSend({method, params}); + send( + method: T, + params?: Protocol.CommandParameters[T] + ): Promise { + const id = this._rawSend({ method, params }); return new Promise((resolve, reject) => { - this._callbacks.set(id, {resolve, reject, error: new Error(), method}); + this._callbacks.set(id, { resolve, reject, error: new Error(), method }); }); } _rawSend(message: {}): number { const id = ++this._lastId; - message = JSON.stringify(Object.assign({}, message, {id})); + message = JSON.stringify(Object.assign({}, message, { id })); debugProtocol('SEND ► ' + message); this._transport.send(message); return id; } async _onMessage(message: string): Promise { - if (this._delay) - await new Promise(f => setTimeout(f, this._delay)); + if (this._delay) await new Promise((f) => setTimeout(f, this._delay)); debugProtocol('◀ RECV ' + message); const object = JSON.parse(message); if (object.method === 'Target.attachedToTarget') { const sessionId = object.params.sessionId; - const session = new CDPSession(this, object.params.targetInfo.type, sessionId); + const session = new CDPSession( + this, + object.params.targetInfo.type, + sessionId + ); this._sessions.set(sessionId, session); } else if (object.method === 'Target.detachedFromTarget') { const session = this._sessions.get(object.params.sessionId); @@ -98,17 +103,17 @@ export class Connection extends EventEmitter { } if (object.sessionId) { const session = this._sessions.get(object.sessionId); - if (session) - session._onMessage(object); + if (session) session._onMessage(object); } else if (object.id) { const callback = this._callbacks.get(object.id); // Callbacks could be all rejected if someone has called `.dispose()`. if (callback) { this._callbacks.delete(object.id); if (object.error) - callback.reject(createProtocolError(callback.error, callback.method, object)); - else - callback.resolve(object.result); + callback.reject( + createProtocolError(callback.error, callback.method, object) + ); + else callback.resolve(object.result); } } else { this.emit(object.method, object.params); @@ -116,16 +121,19 @@ export class Connection extends EventEmitter { } _onClose(): void { - if (this._closed) - return; + if (this._closed) return; this._closed = true; this._transport.onmessage = null; this._transport.onclose = null; for (const callback of this._callbacks.values()) - callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`)); + callback.reject( + rewriteError( + callback.error, + `Protocol error (${callback.method}): Target closed.` + ) + ); this._callbacks.clear(); - for (const session of this._sessions.values()) - session._onClosed(); + for (const session of this._sessions.values()) session._onClosed(); this._sessions.clear(); this.emit(Events.Connection.Disconnected); } @@ -139,19 +147,23 @@ export class Connection extends EventEmitter { * @param {Protocol.Target.TargetInfo} targetInfo * @return {!Promise} */ - async createSession(targetInfo: Protocol.Target.TargetInfo): Promise { - const {sessionId} = await this.send('Target.attachToTarget', {targetId: targetInfo.targetId, flatten: true}); + async createSession( + targetInfo: Protocol.Target.TargetInfo + ): Promise { + const { sessionId } = await this.send('Target.attachToTarget', { + targetId: targetInfo.targetId, + flatten: true, + }); return this._sessions.get(sessionId); } } interface CDPSessionOnMessageObject { - id?: number; - method: string; - params: {}; - error: {message: string; data: any}; - result?: any; - + id?: number; + method: string; + params: {}; + error: { message: string; data: any }; + result?: any; } export class CDPSession extends EventEmitter { _connection: Connection; @@ -166,9 +178,16 @@ export class CDPSession extends EventEmitter { this._sessionId = sessionId; } - send(method: T, params?: Protocol.CommandParameters[T]): Promise { + send( + method: T, + params?: Protocol.CommandParameters[T] + ): Promise { if (!this._connection) - return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`)); + return Promise.reject( + new Error( + `Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.` + ) + ); const id = this._connection._rawSend({ sessionId: this._sessionId, @@ -177,11 +196,11 @@ export class CDPSession extends EventEmitter { * we no longer need the `|| {}` check * https://bugzilla.mozilla.org/show_bug.cgi?id=1631570 */ - params: params || {} + params: params || {}, }); return new Promise((resolve, reject) => { - this._callbacks.set(id, {resolve, reject, error: new Error(), method}); + this._callbacks.set(id, { resolve, reject, error: new Error(), method }); }); } @@ -190,9 +209,10 @@ export class CDPSession extends EventEmitter { const callback = this._callbacks.get(object.id); this._callbacks.delete(object.id); if (object.error) - callback.reject(createProtocolError(callback.error, callback.method, object)); - else - callback.resolve(object.result); + callback.reject( + createProtocolError(callback.error, callback.method, object) + ); + else callback.resolve(object.result); } else { assert(!object.id); this.emit(object.method, object.params); @@ -201,13 +221,22 @@ export class CDPSession extends EventEmitter { async detach(): Promise { if (!this._connection) - throw new Error(`Session already detached. Most likely the ${this._targetType} has been closed.`); - await this._connection.send('Target.detachFromTarget', {sessionId: this._sessionId}); + throw new Error( + `Session already detached. Most likely the ${this._targetType} has been closed.` + ); + await this._connection.send('Target.detachFromTarget', { + sessionId: this._sessionId, + }); } _onClosed(): void { for (const callback of this._callbacks.values()) - callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`)); + callback.reject( + rewriteError( + callback.error, + `Protocol error (${callback.method}): Target closed.` + ) + ); this._callbacks.clear(); this._connection = null; this.emit(Events.CDPSession.Disconnected); @@ -220,10 +249,13 @@ export class CDPSession extends EventEmitter { * @param {{error: {message: string, data: any}}} object * @return {!Error} */ -function createProtocolError(error: Error, method: string, object: { error: { message: string; data: any}}): Error { +function createProtocolError( + error: Error, + method: string, + object: { error: { message: string; data: any } } +): Error { let message = `Protocol error (${method}): ${object.error.message}`; - if ('data' in object.error) - message += ` ${object.error.data}`; + if ('data' in object.error) message += ` ${object.error.data}`; return rewriteError(error, message); } diff --git a/src/Coverage.ts b/src/Coverage.ts index cee70a8addef1..92d97d5aa8c4b 100644 --- a/src/Coverage.ts +++ b/src/Coverage.ts @@ -14,15 +14,15 @@ * limitations under the License. */ -import {helper, debugError, assert, PuppeteerEventListener} from './helper'; -import {CDPSession} from './Connection'; +import { helper, debugError, assert, PuppeteerEventListener } from './helper'; +import { CDPSession } from './Connection'; -import {EVALUATION_SCRIPT_URL} from './ExecutionContext'; +import { EVALUATION_SCRIPT_URL } from './ExecutionContext'; interface CoverageEntry { url: string; text: string; - ranges: Array<{start: number; end: number}>; + ranges: Array<{ start: number; end: number }>; } export class Coverage { @@ -34,7 +34,10 @@ export class Coverage { this._cssCoverage = new CSSCoverage(client); } - async startJSCoverage(options: {resetOnNavigation?: boolean; reportAnonymousScripts?: boolean}): Promise { + async startJSCoverage(options: { + resetOnNavigation?: boolean; + reportAnonymousScripts?: boolean; + }): Promise { return await this._jsCoverage.start(options); } @@ -42,7 +45,9 @@ export class Coverage { return await this._jsCoverage.stop(); } - async startCSSCoverage(options: {resetOnNavigation?: boolean}): Promise { + async startCSSCoverage(options: { + resetOnNavigation?: boolean; + }): Promise { return await this._cssCoverage.start(options); } @@ -64,11 +69,16 @@ class JSCoverage { this._client = client; } - async start(options: {resetOnNavigation?: boolean; reportAnonymousScripts?: boolean} = {}): Promise { + async start( + options: { + resetOnNavigation?: boolean; + reportAnonymousScripts?: boolean; + } = {} + ): Promise { assert(!this._enabled, 'JSCoverage is already enabled'); const { resetOnNavigation = true, - reportAnonymousScripts = false + reportAnonymousScripts = false, } = options; this._resetOnNavigation = resetOnNavigation; this._reportAnonymousScripts = reportAnonymousScripts; @@ -76,33 +86,45 @@ class JSCoverage { this._scriptURLs.clear(); this._scriptSources.clear(); this._eventListeners = [ - helper.addEventListener(this._client, 'Debugger.scriptParsed', this._onScriptParsed.bind(this)), - helper.addEventListener(this._client, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)), + helper.addEventListener( + this._client, + 'Debugger.scriptParsed', + this._onScriptParsed.bind(this) + ), + helper.addEventListener( + this._client, + 'Runtime.executionContextsCleared', + this._onExecutionContextsCleared.bind(this) + ), ]; await Promise.all([ this._client.send('Profiler.enable'), - this._client.send('Profiler.startPreciseCoverage', {callCount: false, detailed: true}), + this._client.send('Profiler.startPreciseCoverage', { + callCount: false, + detailed: true, + }), this._client.send('Debugger.enable'), - this._client.send('Debugger.setSkipAllPauses', {skip: true}) + this._client.send('Debugger.setSkipAllPauses', { skip: true }), ]); } _onExecutionContextsCleared(): void { - if (!this._resetOnNavigation) - return; + if (!this._resetOnNavigation) return; this._scriptURLs.clear(); this._scriptSources.clear(); } - async _onScriptParsed(event: Protocol.Debugger.scriptParsedPayload): Promise { + async _onScriptParsed( + event: Protocol.Debugger.scriptParsedPayload + ): Promise { // Ignore puppeteer-injected scripts - if (event.url === EVALUATION_SCRIPT_URL) - return; + if (event.url === EVALUATION_SCRIPT_URL) return; // Ignore other anonymous scripts unless the reportAnonymousScripts option is true. - if (!event.url && !this._reportAnonymousScripts) - return; + if (!event.url && !this._reportAnonymousScripts) return; try { - const response = await this._client.send('Debugger.getScriptSource', {scriptId: event.scriptId}); + const response = await this._client.send('Debugger.getScriptSource', { + scriptId: event.scriptId, + }); this._scriptURLs.set(event.scriptId, event.url); this._scriptSources.set(event.scriptId, response.scriptSource); } catch (error) { @@ -137,13 +159,11 @@ class JSCoverage { if (!url && this._reportAnonymousScripts) url = 'debugger://VM' + entry.scriptId; const text = this._scriptSources.get(entry.scriptId); - if (text === undefined || url === undefined) - continue; + if (text === undefined || url === undefined) continue; const flattenRanges = []; - for (const func of entry.functions) - flattenRanges.push(...func.ranges); + for (const func of entry.functions) flattenRanges.push(...func.ranges); const ranges = convertToDisjointRanges(flattenRanges); - coverage.push({url, ranges, text}); + coverage.push({ url, ranges, text }); } return coverage; } @@ -162,16 +182,24 @@ class CSSCoverage { this._client = client; } - async start(options: {resetOnNavigation?: boolean} = {}): Promise { + async start(options: { resetOnNavigation?: boolean } = {}): Promise { assert(!this._enabled, 'CSSCoverage is already enabled'); - const {resetOnNavigation = true} = options; + const { resetOnNavigation = true } = options; this._resetOnNavigation = resetOnNavigation; this._enabled = true; this._stylesheetURLs.clear(); this._stylesheetSources.clear(); this._eventListeners = [ - helper.addEventListener(this._client, 'CSS.styleSheetAdded', this._onStyleSheet.bind(this)), - helper.addEventListener(this._client, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)), + helper.addEventListener( + this._client, + 'CSS.styleSheetAdded', + this._onStyleSheet.bind(this) + ), + helper.addEventListener( + this._client, + 'Runtime.executionContextsCleared', + this._onExecutionContextsCleared.bind(this) + ), ]; await Promise.all([ this._client.send('DOM.enable'), @@ -181,19 +209,21 @@ class CSSCoverage { } _onExecutionContextsCleared(): void { - if (!this._resetOnNavigation) - return; + if (!this._resetOnNavigation) return; this._stylesheetURLs.clear(); this._stylesheetSources.clear(); } - async _onStyleSheet(event: Protocol.CSS.styleSheetAddedPayload): Promise { + async _onStyleSheet( + event: Protocol.CSS.styleSheetAddedPayload + ): Promise { const header = event.header; // Ignore anonymous scripts - if (!header.sourceURL) - return; + if (!header.sourceURL) return; try { - const response = await this._client.send('CSS.getStyleSheetText', {styleSheetId: header.styleSheetId}); + const response = await this._client.send('CSS.getStyleSheetText', { + styleSheetId: header.styleSheetId, + }); this._stylesheetURLs.set(header.styleSheetId, header.sourceURL); this._stylesheetSources.set(header.styleSheetId, response.text); } catch (error) { @@ -205,7 +235,9 @@ class CSSCoverage { async stop(): Promise { assert(this._enabled, 'CSSCoverage is not enabled'); this._enabled = false; - const ruleTrackingResponse = await this._client.send('CSS.stopRuleUsageTracking'); + const ruleTrackingResponse = await this._client.send( + 'CSS.stopRuleUsageTracking' + ); await Promise.all([ this._client.send('CSS.disable'), this._client.send('DOM.disable'), @@ -231,33 +263,34 @@ class CSSCoverage { for (const styleSheetId of this._stylesheetURLs.keys()) { const url = this._stylesheetURLs.get(styleSheetId); const text = this._stylesheetSources.get(styleSheetId); - const ranges = convertToDisjointRanges(styleSheetIdToCoverage.get(styleSheetId) || []); - coverage.push({url, ranges, text}); + const ranges = convertToDisjointRanges( + styleSheetIdToCoverage.get(styleSheetId) || [] + ); + coverage.push({ url, ranges, text }); } return coverage; } } -function convertToDisjointRanges(nestedRanges: Array<{startOffset: number; endOffset: number; count: number}>): Array<{start: number; end: number}> { +function convertToDisjointRanges( + nestedRanges: Array<{ startOffset: number; endOffset: number; count: number }> +): Array<{ start: number; end: number }> { const points = []; for (const range of nestedRanges) { - points.push({offset: range.startOffset, type: 0, range}); - points.push({offset: range.endOffset, type: 1, range}); + points.push({ offset: range.startOffset, type: 0, range }); + points.push({ offset: range.endOffset, type: 1, range }); } // Sort points to form a valid parenthesis sequence. points.sort((a, b) => { // Sort with increasing offsets. - if (a.offset !== b.offset) - return a.offset - b.offset; + if (a.offset !== b.offset) return a.offset - b.offset; // All "end" points should go before "start" points. - if (a.type !== b.type) - return b.type - a.type; + if (a.type !== b.type) return b.type - a.type; const aLength = a.range.endOffset - a.range.startOffset; const bLength = b.range.endOffset - b.range.startOffset; // For two "start" points, the one with longer range goes first. - if (a.type === 0) - return bLength - aLength; + if (a.type === 0) return bLength - aLength; // For two "end" points, the one with shorter range goes first. return aLength - bLength; }); @@ -267,20 +300,20 @@ function convertToDisjointRanges(nestedRanges: Array<{startOffset: number; endOf let lastOffset = 0; // Run scanning line to intersect all ranges. for (const point of points) { - if (hitCountStack.length && lastOffset < point.offset && hitCountStack[hitCountStack.length - 1] > 0) { + if ( + hitCountStack.length && + lastOffset < point.offset && + hitCountStack[hitCountStack.length - 1] > 0 + ) { const lastResult = results.length ? results[results.length - 1] : null; if (lastResult && lastResult.end === lastOffset) lastResult.end = point.offset; - else - results.push({start: lastOffset, end: point.offset}); + else results.push({ start: lastOffset, end: point.offset }); } lastOffset = point.offset; - if (point.type === 0) - hitCountStack.push(point.range.count); - else - hitCountStack.pop(); + if (point.type === 0) hitCountStack.push(point.range.count); + else hitCountStack.pop(); } // Filter out empty ranges. - return results.filter(range => range.end - range.start > 1); + return results.filter((range) => range.end - range.start > 1); } - diff --git a/src/DOMWorld.ts b/src/DOMWorld.ts index bc184610c7053..43ad349d2c6ab 100644 --- a/src/DOMWorld.ts +++ b/src/DOMWorld.ts @@ -15,15 +15,15 @@ */ import * as fs from 'fs'; -import {helper, assert} from './helper'; -import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher'; -import {TimeoutError} from './Errors'; -import {JSHandle, ElementHandle} from './JSHandle'; -import {ExecutionContext} from './ExecutionContext'; -import {TimeoutSettings} from './TimeoutSettings'; -import {MouseButtonInput} from './Input'; -import {FrameManager, Frame} from './FrameManager'; -import {getQueryHandlerAndSelector, QueryHandler} from './QueryHandler'; +import { helper, assert } from './helper'; +import { LifecycleWatcher, PuppeteerLifeCycleEvent } from './LifecycleWatcher'; +import { TimeoutError } from './Errors'; +import { JSHandle, ElementHandle } from './JSHandle'; +import { ExecutionContext } from './ExecutionContext'; +import { TimeoutSettings } from './TimeoutSettings'; +import { MouseButtonInput } from './Input'; +import { FrameManager, Frame } from './FrameManager'; +import { getQueryHandlerAndSelector, QueryHandler } from './QueryHandler'; // This predicateQueryHandler is declared here so that TypeScript knows about it // when it is used in the predicate function below. @@ -49,7 +49,11 @@ export class DOMWorld { _detached = false; _waitTasks = new Set(); - constructor(frameManager: FrameManager, frame: Frame, timeoutSettings: TimeoutSettings) { + constructor( + frameManager: FrameManager, + frame: Frame, + timeoutSettings: TimeoutSettings + ) { this._frameManager = frameManager; this._frame = frame; this._timeoutSettings = timeoutSettings; @@ -67,11 +71,10 @@ export class DOMWorld { if (context) { this._contextResolveCallback.call(null, context); this._contextResolveCallback = null; - for (const waitTask of this._waitTasks) - waitTask.rerun(); + for (const waitTask of this._waitTasks) waitTask.rerun(); } else { this._documentPromise = null; - this._contextPromise = new Promise(fulfill => { + this._contextPromise = new Promise((fulfill) => { this._contextResolveCallback = fulfill; }); } @@ -84,7 +87,9 @@ export class DOMWorld { _detach(): void { this._detached = true; for (const waitTask of this._waitTasks) - waitTask.terminate(new Error('waitForFunction failed: frame got detached.')); + waitTask.terminate( + new Error('waitForFunction failed: frame got detached.') + ); } /** @@ -92,7 +97,9 @@ export class DOMWorld { */ executionContext(): Promise { if (this._detached) - throw new Error(`Execution Context is not available in detached frame "${this._frame.url()}" (are you trying to evaluate?)`); + throw new Error( + `Execution Context is not available in detached frame "${this._frame.url()}" (are you trying to evaluate?)` + ); return this._contextPromise; } @@ -101,7 +108,10 @@ export class DOMWorld { * @param {!Array<*>} args * @return {!Promise} */ - async evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise { + async evaluateHandle( + pageFunction: Function | string, + ...args: unknown[] + ): Promise { const context = await this.executionContext(); return context.evaluateHandle(pageFunction, ...args); } @@ -111,7 +121,10 @@ export class DOMWorld { * @param {!Array<*>} args * @return {!Promise<*>} */ - async evaluate(pageFunction: Function | string, ...args: unknown[]): Promise { + async evaluate( + pageFunction: Function | string, + ...args: unknown[] + ): Promise { const context = await this.executionContext(); return context.evaluate(pageFunction, ...args); } @@ -127,9 +140,8 @@ export class DOMWorld { } async _document(): Promise { - if (this._documentPromise) - return this._documentPromise; - this._documentPromise = this.executionContext().then(async context => { + if (this._documentPromise) return this._documentPromise; + this._documentPromise = this.executionContext().then(async (context) => { const document = await context.evaluateHandle('document'); return document.asElement(); }); @@ -142,14 +154,26 @@ export class DOMWorld { return value; } - async $eval(selector: string, pageFunction: Function | string, ...args: unknown[]): Promise { + async $eval( + selector: string, + pageFunction: Function | string, + ...args: unknown[] + ): Promise { const document = await this._document(); return document.$eval(selector, pageFunction, ...args); } - async $$eval(selector: string, pageFunction: Function | string, ...args: unknown[]): Promise { + async $$eval( + selector: string, + pageFunction: Function | string, + ...args: unknown[] + ): Promise { const document = await this._document(); - const value = await document.$$eval(selector, pageFunction, ...args); + const value = await document.$$eval( + selector, + pageFunction, + ...args + ); return value; } @@ -174,43 +198,55 @@ export class DOMWorld { }); } - async setContent(html: string, options: {timeout?: number; waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]} = {}): Promise { + async setContent( + html: string, + options: { + timeout?: number; + waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; + } = {} + ): Promise { const { waitUntil = ['load'], timeout = this._timeoutSettings.navigationTimeout(), } = options; // We rely upon the fact that document.open() will reset frame lifecycle with "init" // lifecycle event. @see https://crrev.com/608658 - await this.evaluate(html => { + await this.evaluate((html) => { document.open(); document.write(html); document.close(); }, html); - const watcher = new LifecycleWatcher(this._frameManager, this._frame, waitUntil, timeout); + const watcher = new LifecycleWatcher( + this._frameManager, + this._frame, + waitUntil, + timeout + ); const error = await Promise.race([ watcher.timeoutOrTerminationPromise(), watcher.lifecyclePromise(), ]); watcher.dispose(); - if (error) - throw error; + if (error) throw error; } /** * @param {!{url?: string, path?: string, content?: string, type?: string}} options * @return {!Promise} */ - async addScriptTag(options: {url?: string; path?: string; content?: string; type?: string}): Promise { - const { - url = null, - path = null, - content = null, - type = '' - } = options; + async addScriptTag(options: { + url?: string; + path?: string; + content?: string; + type?: string; + }): Promise { + const { url = null, path = null, content = null, type = '' } = options; if (url !== null) { try { const context = await this.executionContext(); - return (await context.evaluateHandle(addScriptUrl, url, type)).asElement(); + return ( + await context.evaluateHandle(addScriptUrl, url, type) + ).asElement(); } catch (error) { throw new Error(`Loading script from ${url} failed`); } @@ -220,21 +256,29 @@ export class DOMWorld { let contents = await readFileAsync(path, 'utf8'); contents += '//# sourceURL=' + path.replace(/\n/g, ''); const context = await this.executionContext(); - return (await context.evaluateHandle(addScriptContent, contents, type)).asElement(); + return ( + await context.evaluateHandle(addScriptContent, contents, type) + ).asElement(); } if (content !== null) { const context = await this.executionContext(); - return (await context.evaluateHandle(addScriptContent, content, type)).asElement(); + return ( + await context.evaluateHandle(addScriptContent, content, type) + ).asElement(); } - throw new Error('Provide an object with a `url`, `path` or `content` property'); + throw new Error( + 'Provide an object with a `url`, `path` or `content` property' + ); - async function addScriptUrl(url: string, type: string): Promise { + async function addScriptUrl( + url: string, + type: string + ): Promise { const script = document.createElement('script'); script.src = url; - if (type) - script.type = type; + if (type) script.type = type; const promise = new Promise((res, rej) => { script.onload = res; script.onerror = rej; @@ -244,25 +288,27 @@ export class DOMWorld { return script; } - function addScriptContent(content: string, type = 'text/javascript'): HTMLElement { + function addScriptContent( + content: string, + type = 'text/javascript' + ): HTMLElement { const script = document.createElement('script'); script.type = type; script.text = content; let error = null; - script.onerror = e => error = e; + script.onerror = (e) => (error = e); document.head.appendChild(script); - if (error) - throw error; + if (error) throw error; return script; } } - async addStyleTag(options: {url?: string; path?: string; content?: string}): Promise { - const { - url = null, - path = null, - content = null - } = options; + async addStyleTag(options: { + url?: string; + path?: string; + content?: string; + }): Promise { + const { url = null, path = null, content = null } = options; if (url !== null) { try { const context = await this.executionContext(); @@ -276,15 +322,21 @@ export class DOMWorld { let contents = await readFileAsync(path, 'utf8'); contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/'; const context = await this.executionContext(); - return (await context.evaluateHandle(addStyleContent, contents)).asElement(); + return ( + await context.evaluateHandle(addStyleContent, contents) + ).asElement(); } if (content !== null) { const context = await this.executionContext(); - return (await context.evaluateHandle(addStyleContent, content)).asElement(); + return ( + await context.evaluateHandle(addStyleContent, content) + ).asElement(); } - throw new Error('Provide an object with a `url`, `path` or `content` property'); + throw new Error( + 'Provide an object with a `url`, `path` or `content` property' + ); async function addStyleUrl(url: string): Promise { const link = document.createElement('link'); @@ -313,7 +365,10 @@ export class DOMWorld { } } - async click(selector: string, options: {delay?: number; button?: MouseButtonInput; clickCount?: number}): Promise { + async click( + selector: string, + options: { delay?: number; button?: MouseButtonInput; clickCount?: number } + ): Promise { const handle = await this.$(selector); assert(handle, 'No node found for selector: ' + selector); await handle.click(options); @@ -349,43 +404,87 @@ export class DOMWorld { await handle.dispose(); } - async type(selector: string, text: string, options?: {delay: number}): Promise { + async type( + selector: string, + text: string, + options?: { delay: number } + ): Promise { const handle = await this.$(selector); assert(handle, 'No node found for selector: ' + selector); await handle.type(text, options); await handle.dispose(); } - waitForSelector(selector: string, options: WaitForSelectorOptions): Promise { + waitForSelector( + selector: string, + options: WaitForSelectorOptions + ): Promise { return this._waitForSelectorOrXPath(selector, false, options); } - waitForXPath(xpath: string, options: WaitForSelectorOptions): Promise { + waitForXPath( + xpath: string, + options: WaitForSelectorOptions + ): Promise { return this._waitForSelectorOrXPath(xpath, true, options); } - waitForFunction(pageFunction: Function | string, options: {polling?: string | number; timeout?: number} = {}, ...args: unknown[]): Promise { + waitForFunction( + pageFunction: Function | string, + options: { polling?: string | number; timeout?: number } = {}, + ...args: unknown[] + ): Promise { const { polling = 'raf', timeout = this._timeoutSettings.timeout(), } = options; - return new WaitTask(this, pageFunction, undefined, 'function', polling, timeout, ...args).promise; + return new WaitTask( + this, + pageFunction, + undefined, + 'function', + polling, + timeout, + ...args + ).promise; } async title(): Promise { return this.evaluate(() => document.title); } - private async _waitForSelectorOrXPath(selectorOrXPath: string, isXPath: boolean, options: WaitForSelectorOptions = {}): Promise { + private async _waitForSelectorOrXPath( + selectorOrXPath: string, + isXPath: boolean, + options: WaitForSelectorOptions = {} + ): Promise { const { visible: waitForVisible = false, hidden: waitForHidden = false, timeout = this._timeoutSettings.timeout(), } = options; const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation'; - const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`; - const {updatedSelector, queryHandler} = getQueryHandlerAndSelector(selectorOrXPath, (element, selector) => document.querySelector(selector)); - const waitTask = new WaitTask(this, predicate, queryHandler, title, polling, timeout, updatedSelector, isXPath, waitForVisible, waitForHidden); + const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${ + waitForHidden ? ' to be hidden' : '' + }`; + const { + updatedSelector, + queryHandler, + } = getQueryHandlerAndSelector(selectorOrXPath, (element, selector) => + document.querySelector(selector) + ); + const waitTask = new WaitTask( + this, + predicate, + queryHandler, + title, + polling, + timeout, + updatedSelector, + isXPath, + waitForVisible, + waitForHidden + ); const handle = await waitTask.promise; if (!handle.asElement()) { await handle.dispose(); @@ -400,19 +499,35 @@ export class DOMWorld { * @param {boolean} waitForHidden * @return {?Node|boolean} */ - function predicate(selectorOrXPath: string, isXPath: boolean, waitForVisible: boolean, waitForHidden: boolean): Node | null | boolean { + function predicate( + selectorOrXPath: string, + isXPath: boolean, + waitForVisible: boolean, + waitForHidden: boolean + ): Node | null | boolean { const node = isXPath - ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue - : predicateQueryHandler ? predicateQueryHandler(document, selectorOrXPath) as Element : document.querySelector(selectorOrXPath); - if (!node) - return waitForHidden; - if (!waitForVisible && !waitForHidden) - return node; - const element = (node.nodeType === Node.TEXT_NODE ? node.parentElement : node as Element); + ? document.evaluate( + selectorOrXPath, + document, + null, + XPathResult.FIRST_ORDERED_NODE_TYPE, + null + ).singleNodeValue + : predicateQueryHandler + ? (predicateQueryHandler(document, selectorOrXPath) as Element) + : document.querySelector(selectorOrXPath); + if (!node) return waitForHidden; + if (!waitForVisible && !waitForHidden) return node; + const element = + node.nodeType === Node.TEXT_NODE + ? node.parentElement + : (node as Element); const style = window.getComputedStyle(element); - const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox(); - const success = (waitForVisible === isVisible || waitForHidden === !isVisible); + const isVisible = + style && style.visibility !== 'hidden' && hasVisibleBoundingBox(); + const success = + waitForVisible === isVisible || waitForHidden === !isVisible; return success ? node : null; function hasVisibleBoundingBox(): boolean { @@ -436,17 +551,29 @@ class WaitTask { _timeoutTimer?: NodeJS.Timeout; _terminated = false; - constructor(domWorld: DOMWorld, predicateBody: Function | string, predicateQueryHandlerBody: Function | string | undefined, title: string, polling: string | number, timeout: number, ...args: unknown[]) { + constructor( + domWorld: DOMWorld, + predicateBody: Function | string, + predicateQueryHandlerBody: Function | string | undefined, + title: string, + polling: string | number, + timeout: number, + ...args: unknown[] + ) { if (helper.isString(polling)) - assert(polling === 'raf' || polling === 'mutation', 'Unknown polling option: ' + polling); + assert( + polling === 'raf' || polling === 'mutation', + 'Unknown polling option: ' + polling + ); else if (helper.isNumber(polling)) assert(polling > 0, 'Cannot poll with non-positive interval: ' + polling); - else - throw new Error('Unknown polling options: ' + polling); + else throw new Error('Unknown polling options: ' + polling); - function getPredicateBody(predicateBody: Function | string, predicateQueryHandlerBody: Function | string) { - if (helper.isString(predicateBody)) - return `return (${predicateBody});`; + function getPredicateBody( + predicateBody: Function | string, + predicateQueryHandlerBody: Function | string + ) { + if (helper.isString(predicateBody)) return `return (${predicateBody});`; if (predicateQueryHandlerBody) { return ` return (function wrapper(args) { @@ -460,7 +587,10 @@ class WaitTask { this._domWorld = domWorld; this._polling = polling; this._timeout = timeout; - this._predicateBody = getPredicateBody(predicateBody, predicateQueryHandlerBody); + this._predicateBody = getPredicateBody( + predicateBody, + predicateQueryHandlerBody + ); this._args = args; this._runCount = 0; domWorld._waitTasks.add(this); @@ -471,8 +601,13 @@ class WaitTask { // Since page navigation requires us to re-install the pageScript, we should track // timeout on our end. if (timeout) { - const timeoutError = new TimeoutError(`waiting for ${title} failed: timeout ${timeout}ms exceeded`); - this._timeoutTimer = setTimeout(() => this.terminate(timeoutError), timeout); + const timeoutError = new TimeoutError( + `waiting for ${title} failed: timeout ${timeout}ms exceeded` + ); + this._timeoutTimer = setTimeout( + () => this.terminate(timeoutError), + timeout + ); } this.rerun(); } @@ -489,21 +624,29 @@ class WaitTask { let success = null; let error = null; try { - success = await (await this._domWorld.executionContext()).evaluateHandle(waitForPredicatePageFunction, this._predicateBody, this._polling, this._timeout, ...this._args); + success = await (await this._domWorld.executionContext()).evaluateHandle( + waitForPredicatePageFunction, + this._predicateBody, + this._polling, + this._timeout, + ...this._args + ); } catch (error_) { error = error_; } if (this._terminated || runCount !== this._runCount) { - if (success) - await success.dispose(); + if (success) await success.dispose(); return; } // Ignore timeouts in pageScript - we track timeouts ourselves. // If the frame's execution context has already changed, `frame.evaluate` will // throw an error - ignore this predicate run altogether. - if (!error && await this._domWorld.evaluate(s => !s, success).catch(() => true)) { + if ( + !error && + (await this._domWorld.evaluate((s) => !s, success).catch(() => true)) + ) { await success.dispose(); return; } @@ -515,13 +658,14 @@ class WaitTask { // We could have tried to evaluate in a context which was already // destroyed. - if (error && error.message.includes('Cannot find context with specified id')) + if ( + error && + error.message.includes('Cannot find context with specified id') + ) return; - if (error) - this._reject(error); - else - this._resolve(success); + if (error) this._reject(error); + else this._resolve(success); this._cleanup(); } @@ -532,28 +676,28 @@ class WaitTask { } } -async function waitForPredicatePageFunction(predicateBody: string, polling: string, timeout: number, ...args: unknown[]): Promise { +async function waitForPredicatePageFunction( + predicateBody: string, + polling: string, + timeout: number, + ...args: unknown[] +): Promise { const predicate = new Function('...args', predicateBody); let timedOut = false; - if (timeout) - setTimeout(() => timedOut = true, timeout); - if (polling === 'raf') - return await pollRaf(); - if (polling === 'mutation') - return await pollMutation(); - if (typeof polling === 'number') - return await pollInterval(polling); + if (timeout) setTimeout(() => (timedOut = true), timeout); + if (polling === 'raf') return await pollRaf(); + if (polling === 'mutation') return await pollMutation(); + if (typeof polling === 'number') return await pollInterval(polling); /** * @return {!Promise<*>} */ function pollMutation(): Promise { const success = predicate(...args); - if (success) - return Promise.resolve(success); + if (success) return Promise.resolve(success); let fulfill; - const result = new Promise(x => fulfill = x); + const result = new Promise((x) => (fulfill = x)); const observer = new MutationObserver(() => { if (timedOut) { observer.disconnect(); @@ -568,14 +712,14 @@ async function waitForPredicatePageFunction(predicateBody: string, polling: stri observer.observe(document, { childList: true, subtree: true, - attributes: true + attributes: true, }); return result; } function pollRaf(): Promise { let fulfill; - const result = new Promise(x => fulfill = x); + const result = new Promise((x) => (fulfill = x)); onRaf(); return result; @@ -585,16 +729,14 @@ async function waitForPredicatePageFunction(predicateBody: string, polling: stri return; } const success = predicate(...args); - if (success) - fulfill(success); - else - requestAnimationFrame(onRaf); + if (success) fulfill(success); + else requestAnimationFrame(onRaf); } } function pollInterval(pollInterval: number): Promise { let fulfill; - const result = new Promise(x => fulfill = x); + const result = new Promise((x) => (fulfill = x)); onTimeout(); return result; @@ -604,10 +746,8 @@ async function waitForPredicatePageFunction(predicateBody: string, polling: stri return; } const success = predicate(...args); - if (success) - fulfill(success); - else - setTimeout(onTimeout, pollInterval); + if (success) fulfill(success); + else setTimeout(onTimeout, pollInterval); } } } diff --git a/src/DeviceDescriptors.ts b/src/DeviceDescriptors.ts index c37d8171c24c6..a4a4960b9461c 100644 --- a/src/DeviceDescriptors.ts +++ b/src/DeviceDescriptors.ts @@ -29,857 +29,928 @@ interface Device { const devices: Device[] = [ { - 'name': 'Blackberry PlayBook', - 'userAgent': 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+', - 'viewport': { - 'width': 600, - 'height': 1024, - 'deviceScaleFactor': 1, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Blackberry PlayBook landscape', - 'userAgent': 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+', - 'viewport': { - 'width': 1024, - 'height': 600, - 'deviceScaleFactor': 1, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'BlackBerry Z30', - 'userAgent': 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+', - 'viewport': { - 'width': 360, - 'height': 640, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'BlackBerry Z30 landscape', - 'userAgent': 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+', - 'viewport': { - 'width': 640, - 'height': 360, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Galaxy Note 3', - 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', - 'viewport': { - 'width': 360, - 'height': 640, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Galaxy Note 3 landscape', - 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', - 'viewport': { - 'width': 640, - 'height': 360, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Galaxy Note II', - 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', - 'viewport': { - 'width': 360, - 'height': 640, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Galaxy Note II landscape', - 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', - 'viewport': { - 'width': 640, - 'height': 360, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Galaxy S III', - 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', - 'viewport': { - 'width': 360, - 'height': 640, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Galaxy S III landscape', - 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', - 'viewport': { - 'width': 640, - 'height': 360, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Galaxy S5', - 'userAgent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 360, - 'height': 640, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Galaxy S5 landscape', - 'userAgent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 640, - 'height': 360, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPad', - 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', - 'viewport': { - 'width': 768, - 'height': 1024, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPad landscape', - 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', - 'viewport': { - 'width': 1024, - 'height': 768, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPad Mini', - 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', - 'viewport': { - 'width': 768, - 'height': 1024, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPad Mini landscape', - 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', - 'viewport': { - 'width': 1024, - 'height': 768, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPad Pro', - 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', - 'viewport': { - 'width': 1024, - 'height': 1366, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPad Pro landscape', - 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', - 'viewport': { - 'width': 1366, - 'height': 1024, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPhone 4', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53', - 'viewport': { - 'width': 320, - 'height': 480, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPhone 4 landscape', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53', - 'viewport': { - 'width': 480, - 'height': 320, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPhone 5', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', - 'viewport': { - 'width': 320, - 'height': 568, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPhone 5 landscape', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', - 'viewport': { - 'width': 568, - 'height': 320, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPhone 6', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 375, - 'height': 667, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPhone 6 landscape', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 667, - 'height': 375, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPhone 6 Plus', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 414, - 'height': 736, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPhone 6 Plus landscape', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 736, - 'height': 414, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPhone 7', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 375, - 'height': 667, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPhone 7 landscape', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 667, - 'height': 375, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPhone 7 Plus', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 414, - 'height': 736, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPhone 7 Plus landscape', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 736, - 'height': 414, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPhone 8', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 375, - 'height': 667, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPhone 8 landscape', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 667, - 'height': 375, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPhone 8 Plus', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 414, - 'height': 736, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPhone 8 Plus landscape', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 736, - 'height': 414, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPhone SE', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', - 'viewport': { - 'width': 320, - 'height': 568, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPhone SE landscape', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', - 'viewport': { - 'width': 568, - 'height': 320, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPhone X', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 375, - 'height': 812, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPhone X landscape', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', - 'viewport': { - 'width': 812, - 'height': 375, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'iPhone XR', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', - 'viewport': { - 'width': 414, - 'height': 896, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'iPhone XR landscape', - 'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', - 'viewport': { - 'width': 896, - 'height': 414, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'JioPhone 2', - 'userAgent': 'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5', - 'viewport': { - 'width': 240, - 'height': 320, - 'deviceScaleFactor': 1, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'JioPhone 2 landscape', - 'userAgent': 'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5', - 'viewport': { - 'width': 320, - 'height': 240, - 'deviceScaleFactor': 1, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Kindle Fire HDX', - 'userAgent': 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true', - 'viewport': { - 'width': 800, - 'height': 1280, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Kindle Fire HDX landscape', - 'userAgent': 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true', - 'viewport': { - 'width': 1280, - 'height': 800, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'LG Optimus L70', - 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 384, - 'height': 640, - 'deviceScaleFactor': 1.25, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'LG Optimus L70 landscape', - 'userAgent': 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 640, - 'height': 384, - 'deviceScaleFactor': 1.25, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Microsoft Lumia 550', - 'userAgent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263', - 'viewport': { - 'width': 640, - 'height': 360, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Microsoft Lumia 950', - 'userAgent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263', - 'viewport': { - 'width': 360, - 'height': 640, - 'deviceScaleFactor': 4, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Microsoft Lumia 950 landscape', - 'userAgent': 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263', - 'viewport': { - 'width': 640, - 'height': 360, - 'deviceScaleFactor': 4, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Nexus 10', - 'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36', - 'viewport': { - 'width': 800, - 'height': 1280, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Nexus 10 landscape', - 'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36', - 'viewport': { - 'width': 1280, - 'height': 800, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Nexus 4', - 'userAgent': 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 384, - 'height': 640, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Nexus 4 landscape', - 'userAgent': 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 640, - 'height': 384, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Nexus 5', - 'userAgent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 360, - 'height': 640, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Nexus 5 landscape', - 'userAgent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 640, - 'height': 360, - 'deviceScaleFactor': 3, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Nexus 5X', - 'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 412, - 'height': 732, - 'deviceScaleFactor': 2.625, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Nexus 5X landscape', - 'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 732, - 'height': 412, - 'deviceScaleFactor': 2.625, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Nexus 6', - 'userAgent': 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 412, - 'height': 732, - 'deviceScaleFactor': 3.5, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Nexus 6 landscape', - 'userAgent': 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 732, - 'height': 412, - 'deviceScaleFactor': 3.5, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Nexus 6P', - 'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 412, - 'height': 732, - 'deviceScaleFactor': 3.5, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Nexus 6P landscape', - 'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 732, - 'height': 412, - 'deviceScaleFactor': 3.5, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Nexus 7', - 'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36', - 'viewport': { - 'width': 600, - 'height': 960, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Nexus 7 landscape', - 'userAgent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36', - 'viewport': { - 'width': 960, - 'height': 600, - 'deviceScaleFactor': 2, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Nokia Lumia 520', - 'userAgent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)', - 'viewport': { - 'width': 320, - 'height': 533, - 'deviceScaleFactor': 1.5, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Nokia Lumia 520 landscape', - 'userAgent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)', - 'viewport': { - 'width': 533, - 'height': 320, - 'deviceScaleFactor': 1.5, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Nokia N9', - 'userAgent': 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13', - 'viewport': { - 'width': 480, - 'height': 854, - 'deviceScaleFactor': 1, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Nokia N9 landscape', - 'userAgent': 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13', - 'viewport': { - 'width': 854, - 'height': 480, - 'deviceScaleFactor': 1, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Pixel 2', - 'userAgent': 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 411, - 'height': 731, - 'deviceScaleFactor': 2.625, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Pixel 2 landscape', - 'userAgent': 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 731, - 'height': 411, - 'deviceScaleFactor': 2.625, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - }, - { - 'name': 'Pixel 2 XL', - 'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 411, - 'height': 823, - 'deviceScaleFactor': 3.5, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': false - } - }, - { - 'name': 'Pixel 2 XL landscape', - 'userAgent': 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', - 'viewport': { - 'width': 823, - 'height': 411, - 'deviceScaleFactor': 3.5, - 'isMobile': true, - 'hasTouch': true, - 'isLandscape': true - } - } + name: 'Blackberry PlayBook', + userAgent: + 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+', + viewport: { + width: 600, + height: 1024, + deviceScaleFactor: 1, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Blackberry PlayBook landscape', + userAgent: + 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+', + viewport: { + width: 1024, + height: 600, + deviceScaleFactor: 1, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'BlackBerry Z30', + userAgent: + 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+', + viewport: { + width: 360, + height: 640, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'BlackBerry Z30 landscape', + userAgent: + 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+', + viewport: { + width: 640, + height: 360, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Galaxy Note 3', + userAgent: + 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + viewport: { + width: 360, + height: 640, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Galaxy Note 3 landscape', + userAgent: + 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + viewport: { + width: 640, + height: 360, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Galaxy Note II', + userAgent: + 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + viewport: { + width: 360, + height: 640, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Galaxy Note II landscape', + userAgent: + 'Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + viewport: { + width: 640, + height: 360, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Galaxy S III', + userAgent: + 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + viewport: { + width: 360, + height: 640, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Galaxy S III landscape', + userAgent: + 'Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + viewport: { + width: 640, + height: 360, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Galaxy S5', + userAgent: + 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 360, + height: 640, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Galaxy S5 landscape', + userAgent: + 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 640, + height: 360, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPad', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', + viewport: { + width: 768, + height: 1024, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPad landscape', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', + viewport: { + width: 1024, + height: 768, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPad Mini', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', + viewport: { + width: 768, + height: 1024, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPad Mini landscape', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', + viewport: { + width: 1024, + height: 768, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPad Pro', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', + viewport: { + width: 1024, + height: 1366, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPad Pro landscape', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', + viewport: { + width: 1366, + height: 1024, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPhone 4', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53', + viewport: { + width: 320, + height: 480, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPhone 4 landscape', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53', + viewport: { + width: 480, + height: 320, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPhone 5', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', + viewport: { + width: 320, + height: 568, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPhone 5 landscape', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', + viewport: { + width: 568, + height: 320, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPhone 6', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 375, + height: 667, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPhone 6 landscape', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 667, + height: 375, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPhone 6 Plus', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 414, + height: 736, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPhone 6 Plus landscape', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 736, + height: 414, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPhone 7', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 375, + height: 667, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPhone 7 landscape', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 667, + height: 375, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPhone 7 Plus', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 414, + height: 736, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPhone 7 Plus landscape', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 736, + height: 414, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPhone 8', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 375, + height: 667, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPhone 8 landscape', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 667, + height: 375, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPhone 8 Plus', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 414, + height: 736, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPhone 8 Plus landscape', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 736, + height: 414, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPhone SE', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', + viewport: { + width: 320, + height: 568, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPhone SE landscape', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', + viewport: { + width: 568, + height: 320, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPhone X', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 375, + height: 812, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPhone X landscape', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + viewport: { + width: 812, + height: 375, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'iPhone XR', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', + viewport: { + width: 414, + height: 896, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'iPhone XR landscape', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', + viewport: { + width: 896, + height: 414, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'JioPhone 2', + userAgent: + 'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5', + viewport: { + width: 240, + height: 320, + deviceScaleFactor: 1, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'JioPhone 2 landscape', + userAgent: + 'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5', + viewport: { + width: 320, + height: 240, + deviceScaleFactor: 1, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Kindle Fire HDX', + userAgent: + 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true', + viewport: { + width: 800, + height: 1280, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Kindle Fire HDX landscape', + userAgent: + 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true', + viewport: { + width: 1280, + height: 800, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'LG Optimus L70', + userAgent: + 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 384, + height: 640, + deviceScaleFactor: 1.25, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'LG Optimus L70 landscape', + userAgent: + 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 640, + height: 384, + deviceScaleFactor: 1.25, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Microsoft Lumia 550', + userAgent: + 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263', + viewport: { + width: 640, + height: 360, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Microsoft Lumia 950', + userAgent: + 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263', + viewport: { + width: 360, + height: 640, + deviceScaleFactor: 4, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Microsoft Lumia 950 landscape', + userAgent: + 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263', + viewport: { + width: 640, + height: 360, + deviceScaleFactor: 4, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Nexus 10', + userAgent: + 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36', + viewport: { + width: 800, + height: 1280, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Nexus 10 landscape', + userAgent: + 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36', + viewport: { + width: 1280, + height: 800, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Nexus 4', + userAgent: + 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 384, + height: 640, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Nexus 4 landscape', + userAgent: + 'Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 640, + height: 384, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Nexus 5', + userAgent: + 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 360, + height: 640, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Nexus 5 landscape', + userAgent: + 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 640, + height: 360, + deviceScaleFactor: 3, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Nexus 5X', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 412, + height: 732, + deviceScaleFactor: 2.625, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Nexus 5X landscape', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 732, + height: 412, + deviceScaleFactor: 2.625, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Nexus 6', + userAgent: + 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 412, + height: 732, + deviceScaleFactor: 3.5, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Nexus 6 landscape', + userAgent: + 'Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 732, + height: 412, + deviceScaleFactor: 3.5, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Nexus 6P', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 412, + height: 732, + deviceScaleFactor: 3.5, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Nexus 6P landscape', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 732, + height: 412, + deviceScaleFactor: 3.5, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Nexus 7', + userAgent: + 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36', + viewport: { + width: 600, + height: 960, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Nexus 7 landscape', + userAgent: + 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Safari/537.36', + viewport: { + width: 960, + height: 600, + deviceScaleFactor: 2, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Nokia Lumia 520', + userAgent: + 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)', + viewport: { + width: 320, + height: 533, + deviceScaleFactor: 1.5, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Nokia Lumia 520 landscape', + userAgent: + 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)', + viewport: { + width: 533, + height: 320, + deviceScaleFactor: 1.5, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Nokia N9', + userAgent: + 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13', + viewport: { + width: 480, + height: 854, + deviceScaleFactor: 1, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Nokia N9 landscape', + userAgent: + 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13', + viewport: { + width: 854, + height: 480, + deviceScaleFactor: 1, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Pixel 2', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 411, + height: 731, + deviceScaleFactor: 2.625, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Pixel 2 landscape', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 731, + height: 411, + deviceScaleFactor: 2.625, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, + { + name: 'Pixel 2 XL', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 411, + height: 823, + deviceScaleFactor: 3.5, + isMobile: true, + hasTouch: true, + isLandscape: false, + }, + }, + { + name: 'Pixel 2 XL landscape', + userAgent: + 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36', + viewport: { + width: 823, + height: 411, + deviceScaleFactor: 3.5, + isMobile: true, + hasTouch: true, + isLandscape: true, + }, + }, ]; export type DevicesMap = { @@ -888,7 +959,6 @@ export type DevicesMap = { const devicesMap: DevicesMap = {}; -for (const device of devices) - devicesMap[device.name] = device; +for (const device of devices) devicesMap[device.name] = device; -export {devicesMap}; +export { devicesMap }; diff --git a/src/Dialog.ts b/src/Dialog.ts index 214c7711379e4..b9813a388cf4f 100644 --- a/src/Dialog.ts +++ b/src/Dialog.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import {assert} from './helper'; -import {CDPSession} from './Connection'; +import { assert } from './helper'; +import { CDPSession } from './Connection'; /* TODO(jacktfranklin): protocol.d.ts defines this * so let's ditch this and avoid the duplication @@ -24,7 +24,7 @@ export enum DialogType { Alert = 'alert', BeforeUnload = 'beforeunload', Confirm = 'confirm', - Prompt = 'prompt' + Prompt = 'prompt', } export class Dialog { @@ -36,7 +36,12 @@ export class Dialog { private _defaultValue: string; private _handled = false; - constructor(client: CDPSession, type: DialogType, message: string, defaultValue = '') { + constructor( + client: CDPSession, + type: DialogType, + message: string, + defaultValue = '' + ) { this._client = client; this._type = type; this._message = message; @@ -60,7 +65,7 @@ export class Dialog { this._handled = true; await this._client.send('Page.handleJavaScriptDialog', { accept: true, - promptText: promptText + promptText: promptText, }); } @@ -68,7 +73,7 @@ export class Dialog { assert(!this._handled, 'Cannot dismiss dialog which is already handled!'); this._handled = true; await this._client.send('Page.handleJavaScriptDialog', { - accept: false + accept: false, }); } } diff --git a/src/EmulationManager.ts b/src/EmulationManager.ts index 9bc173e8402b3..dbb40dc7b33a1 100644 --- a/src/EmulationManager.ts +++ b/src/EmulationManager.ts @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {CDPSession} from './Connection'; -import type {Viewport} from './PuppeteerViewport'; +import { CDPSession } from './Connection'; +import type { Viewport } from './PuppeteerViewport'; export class EmulationManager { _client: CDPSession; @@ -30,17 +30,26 @@ export class EmulationManager { const width = viewport.width; const height = viewport.height; const deviceScaleFactor = viewport.deviceScaleFactor || 1; - const screenOrientation: Protocol.Emulation.ScreenOrientation = viewport.isLandscape ? {angle: 90, type: 'landscapePrimary'} : {angle: 0, type: 'portraitPrimary'}; + const screenOrientation: Protocol.Emulation.ScreenOrientation = viewport.isLandscape + ? { angle: 90, type: 'landscapePrimary' } + : { angle: 0, type: 'portraitPrimary' }; const hasTouch = viewport.hasTouch || false; await Promise.all([ - this._client.send('Emulation.setDeviceMetricsOverride', {mobile, width, height, deviceScaleFactor, screenOrientation}), + this._client.send('Emulation.setDeviceMetricsOverride', { + mobile, + width, + height, + deviceScaleFactor, + screenOrientation, + }), this._client.send('Emulation.setTouchEmulationEnabled', { - enabled: hasTouch - }) + enabled: hasTouch, + }), ]); - const reloadNeeded = this._emulatingMobile !== mobile || this._hasTouch !== hasTouch; + const reloadNeeded = + this._emulatingMobile !== mobile || this._hasTouch !== hasTouch; this._emulatingMobile = mobile; this._hasTouch = hasTouch; return reloadNeeded; diff --git a/src/Events.ts b/src/Events.ts index 826f962746073..318c4353072c1 100644 --- a/src/Events.ts +++ b/src/Events.ts @@ -42,7 +42,7 @@ export const Events = { TargetCreated: 'targetcreated', TargetDestroyed: 'targetdestroyed', TargetChanged: 'targetchanged', - Disconnected: 'disconnected' + Disconnected: 'disconnected', }, BrowserContext: { @@ -63,9 +63,15 @@ export const Events = { FrameNavigated: Symbol('Events.FrameManager.FrameNavigated'), FrameDetached: Symbol('Events.FrameManager.FrameDetached'), LifecycleEvent: Symbol('Events.FrameManager.LifecycleEvent'), - FrameNavigatedWithinDocument: Symbol('Events.FrameManager.FrameNavigatedWithinDocument'), - ExecutionContextCreated: Symbol('Events.FrameManager.ExecutionContextCreated'), - ExecutionContextDestroyed: Symbol('Events.FrameManager.ExecutionContextDestroyed'), + FrameNavigatedWithinDocument: Symbol( + 'Events.FrameManager.FrameNavigatedWithinDocument' + ), + ExecutionContextCreated: Symbol( + 'Events.FrameManager.ExecutionContextCreated' + ), + ExecutionContextDestroyed: Symbol( + 'Events.FrameManager.ExecutionContextDestroyed' + ), }, Connection: { diff --git a/src/ExecutionContext.ts b/src/ExecutionContext.ts index 8201cb2d7ae08..de108cbff0efa 100644 --- a/src/ExecutionContext.ts +++ b/src/ExecutionContext.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import {helper, assert} from './helper'; -import {createJSHandle, JSHandle, ElementHandle} from './JSHandle'; -import {CDPSession} from './Connection'; -import {DOMWorld} from './DOMWorld'; -import {Frame} from './FrameManager'; +import { helper, assert } from './helper'; +import { createJSHandle, JSHandle, ElementHandle } from './JSHandle'; +import { CDPSession } from './Connection'; +import { DOMWorld } from './DOMWorld'; +import { Frame } from './FrameManager'; export const EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__'; const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m; @@ -28,7 +28,11 @@ export class ExecutionContext { _world: DOMWorld; _contextId: number; - constructor(client: CDPSession, contextPayload: Protocol.Runtime.ExecutionContextDescription, world: DOMWorld) { + constructor( + client: CDPSession, + contextPayload: Protocol.Runtime.ExecutionContextDescription, + world: DOMWorld + ) { this._client = client; this._world = world; this._contextId = contextPayload.id; @@ -38,38 +42,62 @@ export class ExecutionContext { return this._world ? this._world.frame() : null; } - async evaluate(pageFunction: Function | string, ...args: unknown[]): Promise { - return await this._evaluateInternal(true, pageFunction, ...args); + async evaluate( + pageFunction: Function | string, + ...args: unknown[] + ): Promise { + return await this._evaluateInternal( + true, + pageFunction, + ...args + ); } - async evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise { + async evaluateHandle( + pageFunction: Function | string, + ...args: unknown[] + ): Promise { return this._evaluateInternal(false, pageFunction, ...args); } - private async _evaluateInternal(returnByValue, pageFunction: Function | string, ...args: unknown[]): Promise { + private async _evaluateInternal( + returnByValue, + pageFunction: Function | string, + ...args: unknown[] + ): Promise { const suffix = `//# sourceURL=${EVALUATION_SCRIPT_URL}`; if (helper.isString(pageFunction)) { const contextId = this._contextId; const expression = pageFunction; - const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression) ? expression : expression + '\n' + suffix; - - const {exceptionDetails, result: remoteObject} = await this._client.send('Runtime.evaluate', { - expression: expressionWithSourceUrl, - contextId, - returnByValue, - awaitPromise: true, - userGesture: true - }).catch(rewriteError); + const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression) + ? expression + : expression + '\n' + suffix; + + const { exceptionDetails, result: remoteObject } = await this._client + .send('Runtime.evaluate', { + expression: expressionWithSourceUrl, + contextId, + returnByValue, + awaitPromise: true, + userGesture: true, + }) + .catch(rewriteError); if (exceptionDetails) - throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails)); + throw new Error( + 'Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails) + ); - return returnByValue ? helper.valueFromRemoteObject(remoteObject) : createJSHandle(this, remoteObject); + return returnByValue + ? helper.valueFromRemoteObject(remoteObject) + : createJSHandle(this, remoteObject); } if (typeof pageFunction !== 'function') - throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`); + throw new Error( + `Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.` + ); let functionText = pageFunction.toString(); try { @@ -78,11 +106,11 @@ export class ExecutionContext { // This means we might have a function shorthand. Try another // time prefixing 'function '. if (functionText.startsWith('async ')) - functionText = 'async function ' + functionText.substring('async '.length); - else - functionText = 'function ' + functionText; + functionText = + 'async function ' + functionText.substring('async '.length); + else functionText = 'function ' + functionText; try { - new Function('(' + functionText + ')'); + new Function('(' + functionText + ')'); } catch (error) { // We tried hard to serialize, but there's a weird beast here. throw new Error('Passed function is not well-serializable!'); @@ -96,17 +124,27 @@ export class ExecutionContext { arguments: args.map(convertArgument.bind(this)), returnByValue, awaitPromise: true, - userGesture: true + userGesture: true, }); } catch (error) { - if (error instanceof TypeError && error.message.startsWith('Converting circular structure to JSON')) + if ( + error instanceof TypeError && + error.message.startsWith('Converting circular structure to JSON') + ) error.message += ' Are you passing a nested JSHandle?'; throw error; } - const {exceptionDetails, result: remoteObject} = await callFunctionOnPromise.catch(rewriteError); + const { + exceptionDetails, + result: remoteObject, + } = await callFunctionOnPromise.catch(rewriteError); if (exceptionDetails) - throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails)); - return returnByValue ? helper.valueFromRemoteObject(remoteObject) : createJSHandle(this, remoteObject); + throw new Error( + 'Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails) + ); + return returnByValue + ? helper.valueFromRemoteObject(remoteObject) + : createJSHandle(this, remoteObject); /** * @param {*} arg @@ -114,62 +152,78 @@ export class ExecutionContext { * @this {ExecutionContext} */ function convertArgument(this: ExecutionContext, arg: unknown): unknown { - if (typeof arg === 'bigint') // eslint-disable-line valid-typeof - return {unserializableValue: `${arg.toString()}n`}; - if (Object.is(arg, -0)) - return {unserializableValue: '-0'}; - if (Object.is(arg, Infinity)) - return {unserializableValue: 'Infinity'}; + if (typeof arg === 'bigint') + // eslint-disable-line valid-typeof + return { unserializableValue: `${arg.toString()}n` }; + if (Object.is(arg, -0)) return { unserializableValue: '-0' }; + if (Object.is(arg, Infinity)) return { unserializableValue: 'Infinity' }; if (Object.is(arg, -Infinity)) - return {unserializableValue: '-Infinity'}; - if (Object.is(arg, NaN)) - return {unserializableValue: 'NaN'}; - const objectHandle = arg && (arg instanceof JSHandle) ? arg : null; + return { unserializableValue: '-Infinity' }; + if (Object.is(arg, NaN)) return { unserializableValue: 'NaN' }; + const objectHandle = arg && arg instanceof JSHandle ? arg : null; if (objectHandle) { if (objectHandle._context !== this) - throw new Error('JSHandles can be evaluated only in the context they were created!'); - if (objectHandle._disposed) - throw new Error('JSHandle is disposed!'); + throw new Error( + 'JSHandles can be evaluated only in the context they were created!' + ); + if (objectHandle._disposed) throw new Error('JSHandle is disposed!'); if (objectHandle._remoteObject.unserializableValue) - return {unserializableValue: objectHandle._remoteObject.unserializableValue}; + return { + unserializableValue: objectHandle._remoteObject.unserializableValue, + }; if (!objectHandle._remoteObject.objectId) - return {value: objectHandle._remoteObject.value}; - return {objectId: objectHandle._remoteObject.objectId}; + return { value: objectHandle._remoteObject.value }; + return { objectId: objectHandle._remoteObject.objectId }; } - return {value: arg}; + return { value: arg }; } function rewriteError(error: Error): Protocol.Runtime.evaluateReturnValue { if (error.message.includes('Object reference chain is too long')) - return {result: {type: 'undefined'}}; - if (error.message.includes('Object couldn\'t be returned by value')) - return {result: {type: 'undefined'}}; - - if (error.message.endsWith('Cannot find context with specified id') || error.message.endsWith('Inspected target navigated or closed')) - throw new Error('Execution context was destroyed, most likely because of a navigation.'); + return { result: { type: 'undefined' } }; + if (error.message.includes("Object couldn't be returned by value")) + return { result: { type: 'undefined' } }; + + if ( + error.message.endsWith('Cannot find context with specified id') || + error.message.endsWith('Inspected target navigated or closed') + ) + throw new Error( + 'Execution context was destroyed, most likely because of a navigation.' + ); throw error; } } async queryObjects(prototypeHandle: JSHandle): Promise { assert(!prototypeHandle._disposed, 'Prototype JSHandle is disposed!'); - assert(prototypeHandle._remoteObject.objectId, 'Prototype JSHandle must not be referencing primitive value'); + assert( + prototypeHandle._remoteObject.objectId, + 'Prototype JSHandle must not be referencing primitive value' + ); const response = await this._client.send('Runtime.queryObjects', { - prototypeObjectId: prototypeHandle._remoteObject.objectId + prototypeObjectId: prototypeHandle._remoteObject.objectId, }); return createJSHandle(this, response.objects); } - async _adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId): Promise { - const {object} = await this._client.send('DOM.resolveNode', { + async _adoptBackendNodeId( + backendNodeId: Protocol.DOM.BackendNodeId + ): Promise { + const { object } = await this._client.send('DOM.resolveNode', { backendNodeId: backendNodeId, executionContextId: this._contextId, }); return createJSHandle(this, object) as ElementHandle; } - async _adoptElementHandle(elementHandle: ElementHandle): Promise { - assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context'); + async _adoptElementHandle( + elementHandle: ElementHandle + ): Promise { + assert( + elementHandle.executionContext() !== this, + 'Cannot adopt handle that already belongs to this execution context' + ); assert(this._world, 'Cannot adopt handle without DOMWorld'); const nodeInfo = await this._client.send('DOM.describeNode', { objectId: elementHandle._remoteObject.objectId, diff --git a/src/FrameManager.ts b/src/FrameManager.ts index f7b4712a878ea..5bdca2257acc0 100644 --- a/src/FrameManager.ts +++ b/src/FrameManager.ts @@ -15,17 +15,17 @@ */ import * as EventEmitter from 'events'; -import {helper, assert, debugError} from './helper'; -import {Events} from './Events'; -import {ExecutionContext, EVALUATION_SCRIPT_URL} from './ExecutionContext'; -import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher'; -import {DOMWorld, WaitForSelectorOptions} from './DOMWorld'; -import {NetworkManager, Response} from './NetworkManager'; -import {TimeoutSettings} from './TimeoutSettings'; -import {CDPSession} from './Connection'; -import {JSHandle, ElementHandle} from './JSHandle'; -import {MouseButtonInput} from './Input'; -import {Page} from './Page'; +import { helper, assert, debugError } from './helper'; +import { Events } from './Events'; +import { ExecutionContext, EVALUATION_SCRIPT_URL } from './ExecutionContext'; +import { LifecycleWatcher, PuppeteerLifeCycleEvent } from './LifecycleWatcher'; +import { DOMWorld, WaitForSelectorOptions } from './DOMWorld'; +import { NetworkManager, Response } from './NetworkManager'; +import { TimeoutSettings } from './TimeoutSettings'; +import { CDPSession } from './Connection'; +import { JSHandle, ElementHandle } from './JSHandle'; +import { MouseButtonInput } from './Input'; +import { Page } from './Page'; const UTILITY_WORLD_NAME = '__puppeteer_utility_world__'; @@ -39,34 +39,62 @@ export class FrameManager extends EventEmitter { _isolatedWorlds = new Set(); _mainFrame: Frame; - constructor(client: CDPSession, page: Page, ignoreHTTPSErrors: boolean, timeoutSettings: TimeoutSettings) { + constructor( + client: CDPSession, + page: Page, + ignoreHTTPSErrors: boolean, + timeoutSettings: TimeoutSettings + ) { super(); this._client = client; this._page = page; this._networkManager = new NetworkManager(client, ignoreHTTPSErrors, this); this._timeoutSettings = timeoutSettings; - this._client.on('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)); - this._client.on('Page.frameNavigated', event => this._onFrameNavigated(event.frame)); - this._client.on('Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)); - this._client.on('Page.frameDetached', event => this._onFrameDetached(event.frameId)); - this._client.on('Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)); - this._client.on('Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)); - this._client.on('Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId)); - this._client.on('Runtime.executionContextsCleared', () => this._onExecutionContextsCleared()); - this._client.on('Page.lifecycleEvent', event => this._onLifecycleEvent(event)); + this._client.on('Page.frameAttached', (event) => + this._onFrameAttached(event.frameId, event.parentFrameId) + ); + this._client.on('Page.frameNavigated', (event) => + this._onFrameNavigated(event.frame) + ); + this._client.on('Page.navigatedWithinDocument', (event) => + this._onFrameNavigatedWithinDocument(event.frameId, event.url) + ); + this._client.on('Page.frameDetached', (event) => + this._onFrameDetached(event.frameId) + ); + this._client.on('Page.frameStoppedLoading', (event) => + this._onFrameStoppedLoading(event.frameId) + ); + this._client.on('Runtime.executionContextCreated', (event) => + this._onExecutionContextCreated(event.context) + ); + this._client.on('Runtime.executionContextDestroyed', (event) => + this._onExecutionContextDestroyed(event.executionContextId) + ); + this._client.on('Runtime.executionContextsCleared', () => + this._onExecutionContextsCleared() + ); + this._client.on('Page.lifecycleEvent', (event) => + this._onLifecycleEvent(event) + ); } async initialize(): Promise { - const result = await Promise.all([ + const result = await Promise.all< + Protocol.Page.enableReturnValue, + Protocol.Page.getFrameTreeReturnValue + >([ this._client.send('Page.enable'), this._client.send('Page.getFrameTree'), ]); - const {frameTree} = result[1]; + const { frameTree } = result[1]; this._handleFrameTree(frameTree); await Promise.all([ - this._client.send('Page.setLifecycleEventsEnabled', {enabled: true}), - this._client.send('Runtime.enable', {}).then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)), + this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }), + this._client + .send('Runtime.enable', {}) + .then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)), this._networkManager.initialize(), ]); } @@ -75,7 +103,15 @@ export class FrameManager extends EventEmitter { return this._networkManager; } - async navigateFrame(frame: Frame, url: string, options: {referer?: string; timeout?: number; waitUntil?: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[]} = {}): Promise { + async navigateFrame( + frame: Frame, + url: string, + options: { + referer?: string; + timeout?: number; + waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; + } = {} + ): Promise { assertNoLegacyNavigationOptions(options); const { referer = this._networkManager.extraHTTPHeaders()['referer'], @@ -92,26 +128,44 @@ export class FrameManager extends EventEmitter { if (!error) { error = await Promise.race([ watcher.timeoutOrTerminationPromise(), - ensureNewDocumentNavigation ? watcher.newDocumentNavigationPromise() : watcher.sameDocumentNavigationPromise(), + ensureNewDocumentNavigation + ? watcher.newDocumentNavigationPromise() + : watcher.sameDocumentNavigationPromise(), ]); } watcher.dispose(); - if (error) - throw error; + if (error) throw error; return watcher.navigationResponse(); - async function navigate(client: CDPSession, url: string, referrer: string, frameId: string): Promise { + async function navigate( + client: CDPSession, + url: string, + referrer: string, + frameId: string + ): Promise { try { - const response = await client.send('Page.navigate', {url, referrer, frameId}); + const response = await client.send('Page.navigate', { + url, + referrer, + frameId, + }); ensureNewDocumentNavigation = !!response.loaderId; - return response.errorText ? new Error(`${response.errorText} at ${url}`) : null; + return response.errorText + ? new Error(`${response.errorText} at ${url}`) + : null; } catch (error) { return error; } } } - async waitForFrameNavigation(frame: Frame, options: {timeout?: number; waitUntil?: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[]} = {}): Promise { + async waitForFrameNavigation( + frame: Frame, + options: { + timeout?: number; + waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; + } = {} + ): Promise { assertNoLegacyNavigationOptions(options); const { waitUntil = ['load'], @@ -121,26 +175,23 @@ export class FrameManager extends EventEmitter { const error = await Promise.race([ watcher.timeoutOrTerminationPromise(), watcher.sameDocumentNavigationPromise(), - watcher.newDocumentNavigationPromise() + watcher.newDocumentNavigationPromise(), ]); watcher.dispose(); - if (error) - throw error; + if (error) throw error; return watcher.navigationResponse(); } _onLifecycleEvent(event: Protocol.Page.lifecycleEventPayload): void { const frame = this._frames.get(event.frameId); - if (!frame) - return; + if (!frame) return; frame._onLifecycleEvent(event.loaderId, event.name); this.emit(Events.FrameManager.LifecycleEvent, frame); } _onFrameStoppedLoading(frameId: string): void { const frame = this._frames.get(frameId); - if (!frame) - return; + if (!frame) return; frame._onLoadingStopped(); this.emit(Events.FrameManager.LifecycleEvent, frame); } @@ -149,11 +200,9 @@ export class FrameManager extends EventEmitter { if (frameTree.frame.parentId) this._onFrameAttached(frameTree.frame.id, frameTree.frame.parentId); this._onFrameNavigated(frameTree.frame); - if (!frameTree.childFrames) - return; + if (!frameTree.childFrames) return; - for (const child of frameTree.childFrames) - this._handleFrameTree(child); + for (const child of frameTree.childFrames) this._handleFrameTree(child); } page(): Page { @@ -173,8 +222,7 @@ export class FrameManager extends EventEmitter { } _onFrameAttached(frameId: string, parentFrameId?: string): void { - if (this._frames.has(frameId)) - return; + if (this._frames.has(frameId)) return; assert(parentFrameId); const parentFrame = this._frames.get(parentFrameId); const frame = new Frame(this, this._client, parentFrame, frameId); @@ -184,8 +232,13 @@ export class FrameManager extends EventEmitter { _onFrameNavigated(framePayload: Protocol.Page.Frame): void { const isMainFrame = !framePayload.parentId; - let frame = isMainFrame ? this._mainFrame : this._frames.get(framePayload.id); - assert(isMainFrame || frame, 'We either navigate top level or have old version of the navigated frame'); + let frame = isMainFrame + ? this._mainFrame + : this._frames.get(framePayload.id); + assert( + isMainFrame || frame, + 'We either navigate top level or have old version of the navigated frame' + ); // Detach all child frames first. if (frame) { @@ -214,24 +267,28 @@ export class FrameManager extends EventEmitter { } async _ensureIsolatedWorld(name: string): Promise { - if (this._isolatedWorlds.has(name)) - return; + if (this._isolatedWorlds.has(name)) return; this._isolatedWorlds.add(name); await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source: `//# sourceURL=${EVALUATION_SCRIPT_URL}`, worldName: name, }), - await Promise.all(this.frames().map(frame => this._client.send('Page.createIsolatedWorld', { - frameId: frame._id, - grantUniveralAccess: true, - worldName: name, - }).catch(debugError))); // frames might be removed before we send this + await Promise.all( + this.frames().map((frame) => + this._client + .send('Page.createIsolatedWorld', { + frameId: frame._id, + grantUniveralAccess: true, + worldName: name, + }) + .catch(debugError) + ) + ); // frames might be removed before we send this } _onFrameNavigatedWithinDocument(frameId: string, url: string): void { const frame = this._frames.get(frameId); - if (!frame) - return; + if (!frame) return; frame._navigatedWithinDocument(url); this.emit(Events.FrameManager.FrameNavigatedWithinDocument, frame); this.emit(Events.FrameManager.FrameNavigated, frame); @@ -239,19 +296,23 @@ export class FrameManager extends EventEmitter { _onFrameDetached(frameId: string): void { const frame = this._frames.get(frameId); - if (frame) - this._removeFramesRecursively(frame); + if (frame) this._removeFramesRecursively(frame); } - _onExecutionContextCreated(contextPayload: Protocol.Runtime.ExecutionContextDescription): void { - const auxData = contextPayload.auxData as { frameId?: string}; + _onExecutionContextCreated( + contextPayload: Protocol.Runtime.ExecutionContextDescription + ): void { + const auxData = contextPayload.auxData as { frameId?: string }; const frameId = auxData ? auxData.frameId : null; const frame = this._frames.get(frameId) || null; let world = null; if (frame) { if (contextPayload.auxData && !!contextPayload.auxData['isDefault']) { world = frame._mainWorld; - } else if (contextPayload.name === UTILITY_WORLD_NAME && !frame._secondaryWorld._hasContext()) { + } else if ( + contextPayload.name === UTILITY_WORLD_NAME && + !frame._secondaryWorld._hasContext() + ) { // In case of multiple sessions to the same target, there's a race between // connections so we might end up creating multiple isolated worlds. // We can use either. @@ -261,8 +322,7 @@ export class FrameManager extends EventEmitter { if (contextPayload.auxData && contextPayload.auxData['type'] === 'isolated') this._isolatedWorlds.add(contextPayload.name); const context = new ExecutionContext(this._client, contextPayload, world); - if (world) - world._setContext(context); + if (world) world._setContext(context); this._contextIdToContext.set(contextPayload.id, context); } @@ -271,17 +331,14 @@ export class FrameManager extends EventEmitter { */ _onExecutionContextDestroyed(executionContextId: number): void { const context = this._contextIdToContext.get(executionContextId); - if (!context) - return; + if (!context) return; this._contextIdToContext.delete(executionContextId); - if (context._world) - context._world._setContext(null); + if (context._world) context._world._setContext(null); } _onExecutionContextsCleared(): void { for (const context of this._contextIdToContext.values()) { - if (context._world) - context._world._setContext(null); + if (context._world) context._world._setContext(null); } this._contextIdToContext.clear(); } @@ -317,7 +374,12 @@ export class Frame { _secondaryWorld: DOMWorld; _childFrames: Set; - constructor(frameManager: FrameManager, client: CDPSession, parentFrame: Frame | null, frameId: string) { + constructor( + frameManager: FrameManager, + client: CDPSession, + parentFrame: Frame | null, + frameId: string + ) { this._frameManager = frameManager; this._client = client; this._parentFrame = parentFrame; @@ -326,19 +388,36 @@ export class Frame { this._detached = false; this._loaderId = ''; - this._mainWorld = new DOMWorld(frameManager, this, frameManager._timeoutSettings); - this._secondaryWorld = new DOMWorld(frameManager, this, frameManager._timeoutSettings); + this._mainWorld = new DOMWorld( + frameManager, + this, + frameManager._timeoutSettings + ); + this._secondaryWorld = new DOMWorld( + frameManager, + this, + frameManager._timeoutSettings + ); this._childFrames = new Set(); - if (this._parentFrame) - this._parentFrame._childFrames.add(this); + if (this._parentFrame) this._parentFrame._childFrames.add(this); } - async goto(url: string, options: {referer?: string; timeout?: number; waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]}): Promise { + async goto( + url: string, + options: { + referer?: string; + timeout?: number; + waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; + } + ): Promise { return await this._frameManager.navigateFrame(this, url, options); } - async waitForNavigation(options: {timeout?: number; waitUntil?: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[]}): Promise { + async waitForNavigation(options: { + timeout?: number; + waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; + }): Promise { return await this._frameManager.waitForFrameNavigation(this, options); } @@ -346,11 +425,17 @@ export class Frame { return this._mainWorld.executionContext(); } - async evaluateHandle(pageFunction: Function|string, ...args: unknown[]): Promise { + async evaluateHandle( + pageFunction: Function | string, + ...args: unknown[] + ): Promise { return this._mainWorld.evaluateHandle(pageFunction, ...args); } - async evaluate(pageFunction: Function|string, ...args: unknown[]): Promise { + async evaluate( + pageFunction: Function | string, + ...args: unknown[] + ): Promise { return this._mainWorld.evaluate(pageFunction, ...args); } @@ -362,11 +447,19 @@ export class Frame { return this._mainWorld.$x(expression); } - async $eval(selector: string, pageFunction: Function | string, ...args: unknown[]): Promise { + async $eval( + selector: string, + pageFunction: Function | string, + ...args: unknown[] + ): Promise { return this._mainWorld.$eval(selector, pageFunction, ...args); } - async $$eval(selector: string, pageFunction: Function | string, ...args: unknown[]): Promise { + async $$eval( + selector: string, + pageFunction: Function | string, + ...args: unknown[] + ): Promise { return this._mainWorld.$$eval(selector, pageFunction, ...args); } @@ -378,7 +471,13 @@ export class Frame { return this._secondaryWorld.content(); } - async setContent(html: string, options: {timeout?: number; waitUntil?: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[]} = {}): Promise { + async setContent( + html: string, + options: { + timeout?: number; + waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; + } = {} + ): Promise { return this._secondaryWorld.setContent(html, options); } @@ -402,15 +501,27 @@ export class Frame { return this._detached; } - async addScriptTag(options: {url?: string; path?: string; content?: string; type?: string}): Promise { + async addScriptTag(options: { + url?: string; + path?: string; + content?: string; + type?: string; + }): Promise { return this._mainWorld.addScriptTag(options); } - async addStyleTag(options: {url?: string; path?: string; content?: string}): Promise { + async addStyleTag(options: { + url?: string; + path?: string; + content?: string; + }): Promise { return this._mainWorld.addStyleTag(options); } - async click(selector: string, options: {delay?: number; button?: MouseButtonInput; clickCount?: number}): Promise { + async click( + selector: string, + options: { delay?: number; button?: MouseButtonInput; clickCount?: number } + ): Promise { return this._secondaryWorld.click(selector, options); } @@ -430,11 +541,19 @@ export class Frame { return this._secondaryWorld.tap(selector); } - async type(selector: string, text: string, options?: {delay: number}): Promise { + async type( + selector: string, + text: string, + options?: { delay: number } + ): Promise { return this._mainWorld.type(selector, text, options); } - waitFor(selectorOrFunctionOrTimeout: string|number|Function, options: {} = {}, ...args: unknown[]): Promise { + waitFor( + selectorOrFunctionOrTimeout: string | number | Function, + options: {} = {}, + ...args: unknown[] + ): Promise { const xPathPattern = '//'; if (helper.isString(selectorOrFunctionOrTimeout)) { @@ -444,33 +563,54 @@ export class Frame { return this.waitForSelector(string, options); } if (helper.isNumber(selectorOrFunctionOrTimeout)) - return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout)); + return new Promise((fulfill) => + setTimeout(fulfill, selectorOrFunctionOrTimeout) + ); if (typeof selectorOrFunctionOrTimeout === 'function') - return this.waitForFunction(selectorOrFunctionOrTimeout, options, ...args); - return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout))); - } - - async waitForSelector(selector: string, options: WaitForSelectorOptions): Promise { - const handle = await this._secondaryWorld.waitForSelector(selector, options); - if (!handle) - return null; + return this.waitForFunction( + selectorOrFunctionOrTimeout, + options, + ...args + ); + return Promise.reject( + new Error( + 'Unsupported target type: ' + typeof selectorOrFunctionOrTimeout + ) + ); + } + + async waitForSelector( + selector: string, + options: WaitForSelectorOptions + ): Promise { + const handle = await this._secondaryWorld.waitForSelector( + selector, + options + ); + if (!handle) return null; const mainExecutionContext = await this._mainWorld.executionContext(); const result = await mainExecutionContext._adoptElementHandle(handle); await handle.dispose(); return result; } - async waitForXPath(xpath: string, options: WaitForSelectorOptions): Promise { + async waitForXPath( + xpath: string, + options: WaitForSelectorOptions + ): Promise { const handle = await this._secondaryWorld.waitForXPath(xpath, options); - if (!handle) - return null; + if (!handle) return null; const mainExecutionContext = await this._mainWorld.executionContext(); const result = await mainExecutionContext._adoptElementHandle(handle); await handle.dispose(); return result; } - waitForFunction(pageFunction: Function|string, options: {polling?: string|number; timeout?: number} = {}, ...args: unknown[]): Promise { + waitForFunction( + pageFunction: Function | string, + options: { polling?: string | number; timeout?: number } = {}, + ...args: unknown[] + ): Promise { return this._mainWorld.waitForFunction(pageFunction, options, ...args); } @@ -504,14 +644,24 @@ export class Frame { this._detached = true; this._mainWorld._detach(); this._secondaryWorld._detach(); - if (this._parentFrame) - this._parentFrame._childFrames.delete(this); + if (this._parentFrame) this._parentFrame._childFrames.delete(this); this._parentFrame = null; } } -function assertNoLegacyNavigationOptions(options: {[optionName: string]: unknown}): void { - assert(options['networkIdleTimeout'] === undefined, 'ERROR: networkIdleTimeout option is no longer supported.'); - assert(options['networkIdleInflight'] === undefined, 'ERROR: networkIdleInflight option is no longer supported.'); - assert(options.waitUntil !== 'networkidle', 'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead'); +function assertNoLegacyNavigationOptions(options: { + [optionName: string]: unknown; +}): void { + assert( + options['networkIdleTimeout'] === undefined, + 'ERROR: networkIdleTimeout option is no longer supported.' + ); + assert( + options['networkIdleInflight'] === undefined, + 'ERROR: networkIdleInflight option is no longer supported.' + ); + assert( + options.waitUntil !== 'networkidle', + 'ERROR: "networkidle" option is no longer supported. Use "networkidle2" instead' + ); } diff --git a/src/Input.ts b/src/Input.ts index a2b87b17de968..0fef07d29339a 100644 --- a/src/Input.ts +++ b/src/Input.ts @@ -14,11 +14,13 @@ * limitations under the License. */ -import {assert} from './helper'; -import {CDPSession} from './Connection'; -import {keyDefinitions, KeyDefinition, KeyInput} from './USKeyboardLayout'; +import { assert } from './helper'; +import { CDPSession } from './Connection'; +import { keyDefinitions, KeyDefinition, KeyInput } from './USKeyboardLayout'; -type KeyDescription = Required>; +type KeyDescription = Required< + Pick +>; export class Keyboard { _client: CDPSession; @@ -29,7 +31,10 @@ export class Keyboard { this._client = client; } - async down(key: KeyInput, options: { text?: string } = {text: undefined}): Promise { + async down( + key: KeyInput, + options: { text?: string } = { text: undefined } + ): Promise { const description = this._keyDescriptionForString(key); const autoRepeat = this._pressedKeys.has(description.code); @@ -47,19 +52,15 @@ export class Keyboard { unmodifiedText: text, autoRepeat, location: description.location, - isKeypad: description.location === 3 + isKeypad: description.location === 3, }); } private _modifierBit(key: string): number { - if (key === 'Alt') - return 1; - if (key === 'Control') - return 2; - if (key === 'Meta') - return 4; - if (key === 'Shift') - return 8; + if (key === 'Alt') return 1; + if (key === 'Control') return 2; + if (key === 'Meta') return 4; + if (key === 'Shift') return 8; return 0; } @@ -70,39 +71,30 @@ export class Keyboard { keyCode: 0, code: '', text: '', - location: 0 + location: 0, }; const definition = keyDefinitions[keyString]; assert(definition, `Unknown key: "${keyString}"`); - if (definition.key) - description.key = definition.key; - if (shift && definition.shiftKey) - description.key = definition.shiftKey; + if (definition.key) description.key = definition.key; + if (shift && definition.shiftKey) description.key = definition.shiftKey; - if (definition.keyCode) - description.keyCode = definition.keyCode; + if (definition.keyCode) description.keyCode = definition.keyCode; if (shift && definition.shiftKeyCode) description.keyCode = definition.shiftKeyCode; - if (definition.code) - description.code = definition.code; + if (definition.code) description.code = definition.code; - if (definition.location) - description.location = definition.location; + if (definition.location) description.location = definition.location; - if (description.key.length === 1) - description.text = description.key; + if (description.key.length === 1) description.text = description.key; - if (definition.text) - description.text = definition.text; - if (shift && definition.shiftText) - description.text = definition.shiftText; + if (definition.text) description.text = definition.text; + if (shift && definition.shiftText) description.text = definition.shiftText; // if any modifiers besides shift are pressed, no text should be sent - if (this._modifiers & ~8) - description.text = ''; + if (this._modifiers & ~8) description.text = ''; return description; } @@ -118,36 +110,37 @@ export class Keyboard { key: description.key, windowsVirtualKeyCode: description.keyCode, code: description.code, - location: description.location + location: description.location, }); } async sendCharacter(char: string): Promise { - await this._client.send('Input.insertText', {text: char}); + await this._client.send('Input.insertText', { text: char }); } private charIsKey(char: string): char is KeyInput { return !!keyDefinitions[char]; } - async type(text: string, options: {delay?: number}): Promise { + async type(text: string, options: { delay?: number }): Promise { const delay = (options && options.delay) || null; for (const char of text) { if (this.charIsKey(char)) { - await this.press(char, {delay}); + await this.press(char, { delay }); } else { - if (delay) - await new Promise(f => setTimeout(f, delay)); + if (delay) await new Promise((f) => setTimeout(f, delay)); await this.sendCharacter(char); } } } - async press(key: KeyInput, options: {delay?: number; text?: string} = {}): Promise { - const {delay = null} = options; + async press( + key: KeyInput, + options: { delay?: number; text?: string } = {} + ): Promise { + const { delay = null } = options; await this.down(key, options); - if (delay) - await new Promise(f => setTimeout(f, options.delay)); + if (delay) await new Promise((f) => setTimeout(f, options.delay)); await this.up(key); } } @@ -175,9 +168,14 @@ export class Mouse { this._keyboard = keyboard; } - async move(x: number, y: number, options: {steps?: number} = {}): Promise { - const {steps = 1} = options; - const fromX = this._x, fromY = this._y; + async move( + x: number, + y: number, + options: { steps?: number } = {} + ): Promise { + const { steps = 1 } = options; + const fromX = this._x, + fromY = this._y; this._x = x; this._y = y; for (let i = 1; i <= steps; i++) { @@ -186,19 +184,20 @@ export class Mouse { button: this._button, x: fromX + (this._x - fromX) * (i / steps), y: fromY + (this._y - fromY) * (i / steps), - modifiers: this._keyboard._modifiers + modifiers: this._keyboard._modifiers, }); } } - async click(x: number, y: number, options: MouseOptions & {delay?: number} = {}): Promise { - const {delay = null} = options; + async click( + x: number, + y: number, + options: MouseOptions & { delay?: number } = {} + ): Promise { + const { delay = null } = options; if (delay !== null) { - await Promise.all([ - this.move(x, y), - this.down(options), - ]); - await new Promise(f => setTimeout(f, delay)); + await Promise.all([this.move(x, y), this.down(options)]); + await new Promise((f) => setTimeout(f, delay)); await this.up(options); } else { await Promise.all([ @@ -210,7 +209,7 @@ export class Mouse { } async down(options: MouseOptions = {}): Promise { - const {button = 'left', clickCount = 1} = options; + const { button = 'left', clickCount = 1 } = options; this._button = button; await this._client.send('Input.dispatchMouseEvent', { type: 'mousePressed', @@ -218,7 +217,7 @@ export class Mouse { x: this._x, y: this._y, modifiers: this._keyboard._modifiers, - clickCount + clickCount, }); } @@ -226,7 +225,7 @@ export class Mouse { * @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options */ async up(options: MouseOptions = {}): Promise { - const {button = 'left', clickCount = 1} = options; + const { button = 'left', clickCount = 1 } = options; this._button = 'none'; await this._client.send('Input.dispatchMouseEvent', { type: 'mouseReleased', @@ -234,7 +233,7 @@ export class Mouse { x: this._x, y: this._y, modifiers: this._keyboard._modifiers, - clickCount + clickCount, }); } } @@ -257,20 +256,21 @@ export class Touchscreen { // This waits a frame before sending the tap. // @see https://crbug.com/613219 await this._client.send('Runtime.evaluate', { - expression: 'new Promise(x => requestAnimationFrame(() => requestAnimationFrame(x)))', - awaitPromise: true + expression: + 'new Promise(x => requestAnimationFrame(() => requestAnimationFrame(x)))', + awaitPromise: true, }); - const touchPoints = [{x: Math.round(x), y: Math.round(y)}]; + const touchPoints = [{ x: Math.round(x), y: Math.round(y) }]; await this._client.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints, - modifiers: this._keyboard._modifiers + modifiers: this._keyboard._modifiers, }); await this._client.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [], - modifiers: this._keyboard._modifiers + modifiers: this._keyboard._modifiers, }); } } diff --git a/src/JSHandle.ts b/src/JSHandle.ts index 40833ea0d4ed3..71e15c8fcde3f 100644 --- a/src/JSHandle.ts +++ b/src/JSHandle.ts @@ -14,28 +14,37 @@ * limitations under the License. */ -import {helper, assert, debugError} from './helper'; -import {ExecutionContext} from './ExecutionContext'; -import {Page} from './Page'; -import {CDPSession} from './Connection'; -import {KeyInput} from './USKeyboardLayout'; -import {FrameManager, Frame} from './FrameManager'; -import {getQueryHandlerAndSelector} from './QueryHandler'; +import { helper, assert, debugError } from './helper'; +import { ExecutionContext } from './ExecutionContext'; +import { Page } from './Page'; +import { CDPSession } from './Connection'; +import { KeyInput } from './USKeyboardLayout'; +import { FrameManager, Frame } from './FrameManager'; +import { getQueryHandlerAndSelector } from './QueryHandler'; interface BoxModel { - content: Array<{x: number; y: number}>; - padding: Array<{x: number; y: number}>; - border: Array<{x: number; y: number}>; - margin: Array<{x: number; y: number}>; + content: Array<{ x: number; y: number }>; + padding: Array<{ x: number; y: number }>; + border: Array<{ x: number; y: number }>; + margin: Array<{ x: number; y: number }>; width: number; height: number; } -export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): JSHandle { +export function createJSHandle( + context: ExecutionContext, + remoteObject: Protocol.Runtime.RemoteObject +): JSHandle { const frame = context.frame(); if (remoteObject.subtype === 'node' && frame) { const frameManager = frame._frameManager; - return new ElementHandle(context, context._client, remoteObject, frameManager.page(), frameManager); + return new ElementHandle( + context, + context._client, + remoteObject, + frameManager.page(), + frameManager + ); } return new JSHandle(context, context._client, remoteObject); } @@ -46,7 +55,11 @@ export class JSHandle { _remoteObject: Protocol.Runtime.RemoteObject; _disposed = false; - constructor(context: ExecutionContext, client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject) { + constructor( + context: ExecutionContext, + client: CDPSession, + remoteObject: Protocol.Runtime.RemoteObject + ) { this._context = context; this._client = client; this._remoteObject = remoteObject; @@ -56,20 +69,37 @@ export class JSHandle { return this._context; } - async evaluate(pageFunction: Function | string, ...args: unknown[]): Promise { - return await this.executionContext().evaluate(pageFunction, this, ...args); + async evaluate( + pageFunction: Function | string, + ...args: unknown[] + ): Promise { + return await this.executionContext().evaluate( + pageFunction, + this, + ...args + ); } - async evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise { - return await this.executionContext().evaluateHandle(pageFunction, this, ...args); + async evaluateHandle( + pageFunction: Function | string, + ...args: unknown[] + ): Promise { + return await this.executionContext().evaluateHandle( + pageFunction, + this, + ...args + ); } async getProperty(propertyName: string): Promise { - const objectHandle = await this.evaluateHandle((object: HTMLElement, propertyName: string) => { - const result = {__proto__: null}; - result[propertyName] = object[propertyName]; - return result; - }, propertyName); + const objectHandle = await this.evaluateHandle( + (object: HTMLElement, propertyName: string) => { + const result = { __proto__: null }; + result[propertyName] = object[propertyName]; + return result; + }, + propertyName + ); const properties = await objectHandle.getProperties(); const result = properties.get(propertyName) || null; await objectHandle.dispose(); @@ -79,12 +109,11 @@ export class JSHandle { async getProperties(): Promise> { const response = await this._client.send('Runtime.getProperties', { objectId: this._remoteObject.objectId, - ownProperties: true + ownProperties: true, }); const result = new Map(); for (const property of response.result) { - if (!property.enumerable) - continue; + if (!property.enumerable) continue; result.set(property.name, createJSHandle(this._context, property.value)); } return result; @@ -109,15 +138,14 @@ export class JSHandle { } async dispose(): Promise { - if (this._disposed) - return; + if (this._disposed) return; this._disposed = true; await helper.releaseObject(this._client, this._remoteObject); } toString(): string { if (this._remoteObject.objectId) { - const type = this._remoteObject.subtype || this._remoteObject.type; + const type = this._remoteObject.subtype || this._remoteObject.type; return 'JSHandle@' + type; } return 'JSHandle:' + helper.valueFromRemoteObject(this._remoteObject); @@ -127,7 +155,13 @@ export class JSHandle { export class ElementHandle extends JSHandle { _page: Page; _frameManager: FrameManager; - constructor(context: ExecutionContext, client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject, page: Page, frameManager: FrameManager) { + constructor( + context: ExecutionContext, + client: CDPSession, + remoteObject: Protocol.Runtime.RemoteObject, + page: Page, + frameManager: FrameManager + ) { super(context, client, remoteObject); this._client = client; this._remoteObject = remoteObject; @@ -141,59 +175,74 @@ export class ElementHandle extends JSHandle { async contentFrame(): Promise { const nodeInfo = await this._client.send('DOM.describeNode', { - objectId: this._remoteObject.objectId + objectId: this._remoteObject.objectId, }); - if (typeof nodeInfo.node.frameId !== 'string') - return null; + if (typeof nodeInfo.node.frameId !== 'string') return null; return this._frameManager.frame(nodeInfo.node.frameId); } async _scrollIntoViewIfNeeded(): Promise { - const error = await this.evaluate>(async(element: HTMLElement, pageJavascriptEnabled: boolean) => { - if (!element.isConnected) - return 'Node is detached from document'; - if (element.nodeType !== Node.ELEMENT_NODE) - return 'Node is not of type HTMLElement'; - // force-scroll if page's javascript is disabled. - if (!pageJavascriptEnabled) { - // Chrome still supports behavior: instant but it's not in the spec so TS shouts - // We don't want to make this breaking change in Puppeteer yet so we'll ignore the line. - // @ts-ignore - element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'}); - return false; - } - const visibleRatio = await new Promise(resolve => { - const observer = new IntersectionObserver(entries => { - resolve(entries[0].intersectionRatio); - observer.disconnect(); + const error = await this.evaluate>( + async (element: HTMLElement, pageJavascriptEnabled: boolean) => { + if (!element.isConnected) return 'Node is detached from document'; + if (element.nodeType !== Node.ELEMENT_NODE) + return 'Node is not of type HTMLElement'; + // force-scroll if page's javascript is disabled. + if (!pageJavascriptEnabled) { + element.scrollIntoView({ + block: 'center', + inline: 'center', + // Chrome still supports behavior: instant but it's not in the spec so TS shouts + // We don't want to make this breaking change in Puppeteer yet so we'll ignore the line. + // @ts-ignore + behavior: 'instant', + }); + return false; + } + const visibleRatio = await new Promise((resolve) => { + const observer = new IntersectionObserver((entries) => { + resolve(entries[0].intersectionRatio); + observer.disconnect(); + }); + observer.observe(element); }); - observer.observe(element); - }); - if (visibleRatio !== 1.0) { - // Chrome still supports behavior: instant but it's not in the spec so TS shouts - // We don't want to make this breaking change in Puppeteer yet so we'll ignore the line. - // @ts-ignore - element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'}); - } - return false; - }, this._page._javascriptEnabled); + if (visibleRatio !== 1.0) { + element.scrollIntoView({ + block: 'center', + inline: 'center', + // Chrome still supports behavior: instant but it's not in the spec so TS shouts + // We don't want to make this breaking change in Puppeteer yet so we'll ignore the line. + // @ts-ignore + behavior: 'instant', + }); + } + return false; + }, + this._page._javascriptEnabled + ); - if (error) - throw new Error(error); + if (error) throw new Error(error); } - async _clickablePoint(): Promise<{x: number; y: number}> { + async _clickablePoint(): Promise<{ x: number; y: number }> { const [result, layoutMetrics] = await Promise.all([ - this._client.send('DOM.getContentQuads', { - objectId: this._remoteObject.objectId - }).catch(debugError), + this._client + .send('DOM.getContentQuads', { + objectId: this._remoteObject.objectId, + }) + .catch(debugError), this._client.send('Page.getLayoutMetrics'), ]); if (!result || !result.quads.length) throw new Error('Node is either not visible or not an HTMLElement'); // Filter out quads that have too small area to click into. - const {clientWidth, clientHeight} = layoutMetrics.layoutViewport; - const quads = result.quads.map(quad => this._fromProtocolQuad(quad)).map(quad => this._intersectQuadWithViewport(quad, clientWidth, clientHeight)).filter(quad => computeQuadArea(quad) > 1); + const { clientWidth, clientHeight } = layoutMetrics.layoutViewport; + const quads = result.quads + .map((quad) => this._fromProtocolQuad(quad)) + .map((quad) => + this._intersectQuadWithViewport(quad, clientWidth, clientHeight) + ) + .filter((quad) => computeQuadArea(quad) > 1); if (!quads.length) throw new Error('Node is either not visible or not an HTMLElement'); // Return the middle point of the first quad. @@ -206,27 +255,33 @@ export class ElementHandle extends JSHandle { } return { x: x / 4, - y: y / 4 + y: y / 4, }; } _getBoxModel(): Promise { - return this._client.send('DOM.getBoxModel', { - objectId: this._remoteObject.objectId - }).catch(error => debugError(error)); + return this._client + .send('DOM.getBoxModel', { + objectId: this._remoteObject.objectId, + }) + .catch((error) => debugError(error)); } - _fromProtocolQuad(quad: number[]): Array<{x: number; y: number}> { + _fromProtocolQuad(quad: number[]): Array<{ x: number; y: number }> { return [ - {x: quad[0], y: quad[1]}, - {x: quad[2], y: quad[3]}, - {x: quad[4], y: quad[5]}, - {x: quad[6], y: quad[7]} + { x: quad[0], y: quad[1] }, + { x: quad[2], y: quad[3] }, + { x: quad[4], y: quad[5] }, + { x: quad[6], y: quad[7] }, ]; } - _intersectQuadWithViewport(quad: Array<{x: number; y: number}>, width: number, height: number): Array<{x: number; y: number}> { - return quad.map(point => ({ + _intersectQuadWithViewport( + quad: Array<{ x: number; y: number }>, + width: number, + height: number + ): Array<{ x: number; y: number }> { + return quad.map((point) => ({ x: Math.min(Math.max(point.x, 0), width), y: Math.min(Math.max(point.y, 0), height), })); @@ -234,24 +289,35 @@ export class ElementHandle extends JSHandle { async hover(): Promise { await this._scrollIntoViewIfNeeded(); - const {x, y} = await this._clickablePoint(); + const { x, y } = await this._clickablePoint(); await this._page.mouse.move(x, y); } - async click(options: {delay?: number; button?: 'left'|'right'|'middle'; clickCount?: number}): Promise { + async click(options: { + delay?: number; + button?: 'left' | 'right' | 'middle'; + clickCount?: number; + }): Promise { await this._scrollIntoViewIfNeeded(); - const {x, y} = await this._clickablePoint(); + const { x, y } = await this._clickablePoint(); await this._page.mouse.click(x, y, options); } async select(...values: string[]): Promise { for (const value of values) - assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"'); + assert( + helper.isString(value), + 'Values must be strings. Found value "' + + value + + '" of type "' + + typeof value + + '"' + ); /* TODO(jacktfranklin@): once ExecutionContext is TypeScript, and - * its evaluate function is properly typed with generics we can - * return here and remove the typecasting - */ + * its evaluate function is properly typed with generics we can + * return here and remove the typecasting + */ return this.evaluate((element: HTMLSelectElement, values: string[]) => { if (element.nodeName.toLowerCase() !== 'select') throw new Error('Element is not a '); + const isMultiple = await this.evaluate( + (element: HTMLInputElement) => element.multiple + ); + assert( + filePaths.length <= 1 || isMultiple, + 'Multiple file uploads only work with ' + ); // This import is only needed for `uploadFile`, so keep it scoped here to avoid paying // the cost unnecessarily. @@ -280,24 +352,26 @@ export class ElementHandle extends JSHandle { // eslint-disable-next-line @typescript-eslint/no-var-requires const fs = require('fs'); // eslint-disable-next-line @typescript-eslint/no-var-requires - const {promisify} = require('util'); + const { promisify } = require('util'); const access = promisify(fs.access); // Locate all files and confirm that they exist. - const files = await Promise.all(filePaths.map(async filePath => { - const resolvedPath: string = path.resolve(filePath); - try { - await access(resolvedPath, fs.constants.R_OK); - } catch (error) { - if (error.code === 'ENOENT') - throw new Error(`${filePath} does not exist or is not readable`); - } - - return resolvedPath; - })); - const {objectId} = this._remoteObject; - const {node} = await this._client.send('DOM.describeNode', {objectId}); - const {backendNodeId} = node; + const files = await Promise.all( + filePaths.map(async (filePath) => { + const resolvedPath: string = path.resolve(filePath); + try { + await access(resolvedPath, fs.constants.R_OK); + } catch (error) { + if (error.code === 'ENOENT') + throw new Error(`${filePath} does not exist or is not readable`); + } + + return resolvedPath; + }) + ); + const { objectId } = this._remoteObject; + const { node } = await this._client.send('DOM.describeNode', { objectId }); + const { backendNodeId } = node; // The zero-length array is a special case, it seems that DOM.setFileInputFiles does // not actually update the files in that case, so the solution is to eval the element @@ -307,39 +381,50 @@ export class ElementHandle extends JSHandle { element.files = new DataTransfer().files; // Dispatch events for this case because it should behave akin to a user action. - element.dispatchEvent(new Event('input', {bubbles: true})); - element.dispatchEvent(new Event('change', {bubbles: true})); + element.dispatchEvent(new Event('input', { bubbles: true })); + element.dispatchEvent(new Event('change', { bubbles: true })); }); } else { - await this._client.send('DOM.setFileInputFiles', {objectId, files, backendNodeId}); + await this._client.send('DOM.setFileInputFiles', { + objectId, + files, + backendNodeId, + }); } } async tap(): Promise { await this._scrollIntoViewIfNeeded(); - const {x, y} = await this._clickablePoint(); + const { x, y } = await this._clickablePoint(); await this._page.touchscreen.tap(x, y); } async focus(): Promise { - await this.evaluate(element => element.focus()); + await this.evaluate((element) => element.focus()); } - async type(text: string, options?: {delay: number}): Promise { + async type(text: string, options?: { delay: number }): Promise { await this.focus(); await this._page.keyboard.type(text, options); } - async press(key: KeyInput, options?: {delay?: number; text?: string}): Promise { + async press( + key: KeyInput, + options?: { delay?: number; text?: string } + ): Promise { await this.focus(); await this._page.keyboard.press(key, options); } - async boundingBox(): Promise<{x: number; y: number; width: number; height: number}> { + async boundingBox(): Promise<{ + x: number; + y: number; + width: number; + height: number; + }> { const result = await this._getBoxModel(); - if (!result) - return null; + if (!result) return null; const quad = result.model.border; const x = Math.min(quad[0], quad[2], quad[4], quad[6]); @@ -347,7 +432,7 @@ export class ElementHandle extends JSHandle { const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x; const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y; - return {x, y, width, height}; + return { x, y, width, height }; } /** @@ -356,21 +441,20 @@ export class ElementHandle extends JSHandle { async boxModel(): Promise { const result = await this._getBoxModel(); - if (!result) - return null; + if (!result) return null; - const {content, padding, border, margin, width, height} = result.model; + const { content, padding, border, margin, width, height } = result.model; return { content: this._fromProtocolQuad(content), padding: this._fromProtocolQuad(padding), border: this._fromProtocolQuad(border), margin: this._fromProtocolQuad(margin), width, - height + height, }; } - async screenshot(options = {}): Promise { + async screenshot(options = {}): Promise { let needsViewportReset = false; let boundingBox = await this.boundingBox(); @@ -378,7 +462,11 @@ export class ElementHandle extends JSHandle { const viewport = this._page.viewport(); - if (viewport && (boundingBox.width > viewport.width || boundingBox.height > viewport.height)) { + if ( + viewport && + (boundingBox.width > viewport.width || + boundingBox.height > viewport.height) + ) { const newViewport = { width: Math.max(viewport.width, Math.ceil(boundingBox.width)), height: Math.max(viewport.height, Math.ceil(boundingBox.height)), @@ -395,97 +483,136 @@ export class ElementHandle extends JSHandle { assert(boundingBox.width !== 0, 'Node has 0 width.'); assert(boundingBox.height !== 0, 'Node has 0 height.'); - const {layoutViewport: {pageX, pageY}} = await this._client.send('Page.getLayoutMetrics'); + const { + layoutViewport: { pageX, pageY }, + } = await this._client.send('Page.getLayoutMetrics'); const clip = Object.assign({}, boundingBox); clip.x += pageX; clip.y += pageY; - const imageData = await this._page.screenshot(Object.assign({}, { - clip - }, options)); + const imageData = await this._page.screenshot( + Object.assign( + {}, + { + clip, + }, + options + ) + ); - if (needsViewportReset) - await this._page.setViewport(viewport); + if (needsViewportReset) await this._page.setViewport(viewport); return imageData; } async $(selector: string): Promise { - const defaultHandler = (element: Element, selector: string) => element.querySelector(selector); - const {updatedSelector, queryHandler} = getQueryHandlerAndSelector(selector, defaultHandler); + const defaultHandler = (element: Element, selector: string) => + element.querySelector(selector); + const { updatedSelector, queryHandler } = getQueryHandlerAndSelector( + selector, + defaultHandler + ); const handle = await this.evaluateHandle(queryHandler, updatedSelector); const element = handle.asElement(); - if (element) - return element; + if (element) return element; await handle.dispose(); return null; } async $$(selector: string): Promise { - const defaultHandler = (element: Element, selector: string) => element.querySelectorAll(selector); - const {updatedSelector, queryHandler} = getQueryHandlerAndSelector(selector, defaultHandler); + const defaultHandler = (element: Element, selector: string) => + element.querySelectorAll(selector); + const { updatedSelector, queryHandler } = getQueryHandlerAndSelector( + selector, + defaultHandler + ); - const arrayHandle = await this.evaluateHandle(queryHandler, updatedSelector); + const arrayHandle = await this.evaluateHandle( + queryHandler, + updatedSelector + ); const properties = await arrayHandle.getProperties(); await arrayHandle.dispose(); const result = []; for (const property of properties.values()) { const elementHandle = property.asElement(); - if (elementHandle) - result.push(elementHandle); + if (elementHandle) result.push(elementHandle); } return result; } - async $eval(selector: string, pageFunction: Function|string, ...args: unknown[]): Promise { + async $eval( + selector: string, + pageFunction: Function | string, + ...args: unknown[] + ): Promise { const elementHandle = await this.$(selector); if (!elementHandle) - throw new Error(`Error: failed to find element matching selector "${selector}"`); - const result = await elementHandle.evaluate(pageFunction, ...args); + throw new Error( + `Error: failed to find element matching selector "${selector}"` + ); + const result = await elementHandle.evaluate( + pageFunction, + ...args + ); await elementHandle.dispose(); return result; } - async $$eval(selector: string, pageFunction: Function | string, ...args: unknown[]): Promise { - const defaultHandler = (element: Element, selector: string) => Array.from(element.querySelectorAll(selector)); - const {updatedSelector, queryHandler} = getQueryHandlerAndSelector(selector, defaultHandler); + async $$eval( + selector: string, + pageFunction: Function | string, + ...args: unknown[] + ): Promise { + const defaultHandler = (element: Element, selector: string) => + Array.from(element.querySelectorAll(selector)); + const { updatedSelector, queryHandler } = getQueryHandlerAndSelector( + selector, + defaultHandler + ); - const arrayHandle = await this.evaluateHandle(queryHandler, updatedSelector); - const result = await arrayHandle.evaluate(pageFunction, ...args); + const arrayHandle = await this.evaluateHandle( + queryHandler, + updatedSelector + ); + const result = await arrayHandle.evaluate( + pageFunction, + ...args + ); await arrayHandle.dispose(); return result; } async $x(expression: string): Promise { - const arrayHandle = await this.evaluateHandle( - (element, expression) => { - const document = element.ownerDocument || element; - const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE); - const array = []; - let item; - while ((item = iterator.iterateNext())) - array.push(item); - return array; - }, - expression - ); + const arrayHandle = await this.evaluateHandle((element, expression) => { + const document = element.ownerDocument || element; + const iterator = document.evaluate( + expression, + element, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE + ); + const array = []; + let item; + while ((item = iterator.iterateNext())) array.push(item); + return array; + }, expression); const properties = await arrayHandle.getProperties(); await arrayHandle.dispose(); const result = []; for (const property of properties.values()) { const elementHandle = property.asElement(); - if (elementHandle) - result.push(elementHandle); + if (elementHandle) result.push(elementHandle); } return result; } async isIntersectingViewport(): Promise { - return await this.evaluate>(async element => { - const visibleRatio = await new Promise(resolve => { - const observer = new IntersectionObserver(entries => { + return await this.evaluate>(async (element) => { + const visibleRatio = await new Promise((resolve) => { + const observer = new IntersectionObserver((entries) => { resolve(entries[0].intersectionRatio); observer.disconnect(); }); @@ -496,7 +623,7 @@ export class ElementHandle extends JSHandle { } } -function computeQuadArea(quad: Array<{x: number; y: number}>): number { +function computeQuadArea(quad: Array<{ x: number; y: number }>): number { // Compute sum of all directed areas of adjacent triangles // https://en.wikipedia.org/wiki/Polygon#Simple_polygons let area = 0; diff --git a/src/Launcher.ts b/src/Launcher.ts index 4fa8998317eb5..49989911b5efc 100644 --- a/src/Launcher.ts +++ b/src/Launcher.ts @@ -25,15 +25,15 @@ import * as debug from 'debug'; import * as removeFolder from 'rimraf'; import * as childProcess from 'child_process'; -import {BrowserFetcher} from './BrowserFetcher'; -import {Connection} from './Connection'; -import {Browser} from './Browser'; -import {helper, assert, debugError} from './helper'; -import {TimeoutError} from './Errors'; -import type {ConnectionTransport} from './ConnectionTransport'; -import {WebSocketTransport} from './WebSocketTransport'; -import {PipeTransport} from './PipeTransport'; -import type {Viewport} from './PuppeteerViewport'; +import { BrowserFetcher } from './BrowserFetcher'; +import { Connection } from './Connection'; +import { Browser } from './Browser'; +import { helper, assert, debugError } from './helper'; +import { TimeoutError } from './Errors'; +import type { ConnectionTransport } from './ConnectionTransport'; +import { WebSocketTransport } from './WebSocketTransport'; +import { PipeTransport } from './PipeTransport'; +import type { Viewport } from './PuppeteerViewport'; const mkdtempAsync = helper.promisify(fs.mkdtemp); const removeFolderAsync = helper.promisify(removeFolder); @@ -49,22 +49,22 @@ export interface ProductLauncher { } export interface ChromeArgOptions { - headless?: boolean; - args?: string[]; - userDataDir?: string; - devtools?: boolean; + headless?: boolean; + args?: string[]; + userDataDir?: string; + devtools?: boolean; } export interface LaunchOptions { - executablePath?: string; - ignoreDefaultArgs?: boolean|string[]; - handleSIGINT?: boolean; - handleSIGTERM?: boolean; - handleSIGHUP?: boolean; - timeout?: number; - dumpio?: boolean; - env?: Record; - pipe?: boolean; + executablePath?: string; + ignoreDefaultArgs?: boolean | string[]; + handleSIGINT?: boolean; + handleSIGTERM?: boolean; + handleSIGHUP?: boolean; + timeout?: number; + dumpio?: boolean; + env?: Record; + pipe?: boolean; } export interface BrowserOptions { @@ -85,7 +85,11 @@ class BrowserRunner { _listeners = []; _processClosing: Promise; - constructor(executablePath: string, processArguments: string[], tempDirectory?: string) { + constructor( + executablePath: string, + processArguments: string[], + tempDirectory?: string + ) { this._executablePath = executablePath; this._processArguments = processArguments; this._tempDirectory = tempDirectory; @@ -98,65 +102,75 @@ class BrowserRunner { handleSIGHUP, dumpio, env, - pipe + pipe, } = options; - let stdio: Array<'ignore'|'pipe'> = ['pipe', 'pipe', 'pipe']; + let stdio: Array<'ignore' | 'pipe'> = ['pipe', 'pipe', 'pipe']; if (pipe) { - if (dumpio) - stdio = ['ignore', 'pipe', 'pipe', 'pipe', 'pipe']; - else - stdio = ['ignore', 'ignore', 'ignore', 'pipe', 'pipe']; + if (dumpio) stdio = ['ignore', 'pipe', 'pipe', 'pipe', 'pipe']; + else stdio = ['ignore', 'ignore', 'ignore', 'pipe', 'pipe']; } assert(!this.proc, 'This process has previously been started.'); - debugLauncher(`Calling ${this._executablePath} ${this._processArguments.join(' ')}`); + debugLauncher( + `Calling ${this._executablePath} ${this._processArguments.join(' ')}` + ); this.proc = childProcess.spawn( - this._executablePath, - this._processArguments, - { - // On non-windows platforms, `detached: true` makes child process a leader of a new - // process group, making it possible to kill child process tree with `.kill(-pid)` command. - // @see https://nodejs.org/api/child_process.html#child_process_options_detached - detached: process.platform !== 'win32', - env, - stdio - } + this._executablePath, + this._processArguments, + { + // On non-windows platforms, `detached: true` makes child process a leader of a new + // process group, making it possible to kill child process tree with `.kill(-pid)` command. + // @see https://nodejs.org/api/child_process.html#child_process_options_detached + detached: process.platform !== 'win32', + env, + stdio, + } ); if (dumpio) { this.proc.stderr.pipe(process.stderr); this.proc.stdout.pipe(process.stdout); } this._closed = false; - this._processClosing = new Promise(fulfill => { + this._processClosing = new Promise((fulfill) => { this.proc.once('exit', () => { this._closed = true; // Cleanup as processes exit. if (this._tempDirectory) { removeFolderAsync(this._tempDirectory) - .then(() => fulfill()) - .catch(error => console.error(error)); + .then(() => fulfill()) + .catch((error) => console.error(error)); } else { fulfill(); } }); }); - this._listeners = [ helper.addEventListener(process, 'exit', this.kill.bind(this)) ]; + this._listeners = [ + helper.addEventListener(process, 'exit', this.kill.bind(this)), + ]; if (handleSIGINT) - this._listeners.push(helper.addEventListener(process, 'SIGINT', () => { this.kill(); process.exit(130); })); + this._listeners.push( + helper.addEventListener(process, 'SIGINT', () => { + this.kill(); + process.exit(130); + }) + ); if (handleSIGTERM) - this._listeners.push(helper.addEventListener(process, 'SIGTERM', this.close.bind(this))); + this._listeners.push( + helper.addEventListener(process, 'SIGTERM', this.close.bind(this)) + ); if (handleSIGHUP) - this._listeners.push(helper.addEventListener(process, 'SIGHUP', this.close.bind(this))); + this._listeners.push( + helper.addEventListener(process, 'SIGHUP', this.close.bind(this)) + ); } close(): Promise { - if (this._closed) - return Promise.resolve(); + if (this._closed) return Promise.resolve(); helper.removeEventListeners(this._listeners); if (this._tempDirectory) { this.kill(); } else if (this.connection) { // Attempt to close the browser gracefully - this.connection.send('Browser.close').catch(error => { + this.connection.send('Browser.close').catch((error) => { debugError(error); this.kill(); }); @@ -170,8 +184,7 @@ class BrowserRunner { try { if (process.platform === 'win32') childProcess.execSync(`taskkill /pid ${this.proc.pid} /T /F`); - else - process.kill(-this.proc.pid, 'SIGKILL'); + else process.kill(-this.proc.pid, 'SIGKILL'); } catch (error) { // the process might have already stopped } @@ -179,7 +192,7 @@ class BrowserRunner { // Attempt to remove temporary profile directory to avoid littering. try { removeFolder.sync(this._tempDirectory); - } catch (error) { } + } catch (error) {} } /** @@ -193,20 +206,22 @@ class BrowserRunner { slowMo: number; preferredRevision: string; }): Promise { - const { - usePipe, - timeout, - slowMo, - preferredRevision - } = options; + const { usePipe, timeout, slowMo, preferredRevision } = options; if (!usePipe) { - const browserWSEndpoint = await waitForWSEndpoint(this.proc, timeout, preferredRevision); + const browserWSEndpoint = await waitForWSEndpoint( + this.proc, + timeout, + preferredRevision + ); const transport = await WebSocketTransport.create(browserWSEndpoint); this.connection = new Connection(browserWSEndpoint, transport, slowMo); } else { // stdio was assigned during start(), and the 'pipe' option there adds the 4th and 5th items to stdio array - const {3: pipeWrite, 4: pipeRead} = this.proc.stdio; - const transport = new PipeTransport(pipeWrite as NodeJS.WritableStream, pipeRead as NodeJS.ReadableStream); + const { 3: pipeWrite, 4: pipeRead } = this.proc.stdio; + const transport = new PipeTransport( + pipeWrite as NodeJS.WritableStream, + pipeRead as NodeJS.ReadableStream + ); this.connection = new Connection('', transport, slowMo); } return this.connection; @@ -218,13 +233,19 @@ class ChromeLauncher implements ProductLauncher { _preferredRevision: string; _isPuppeteerCore: boolean; - constructor(projectRoot: string, preferredRevision: string, isPuppeteerCore: boolean) { + constructor( + projectRoot: string, + preferredRevision: string, + isPuppeteerCore: boolean + ) { this._projectRoot = projectRoot; this._preferredRevision = preferredRevision; this._isPuppeteerCore = isPuppeteerCore; } - async launch(options: LaunchOptions & ChromeArgOptions & BrowserOptions = {}): Promise { + async launch( + options: LaunchOptions & ChromeArgOptions & BrowserOptions = {} + ): Promise { const { ignoreDefaultArgs = false, args = [], @@ -236,45 +257,75 @@ class ChromeLauncher implements ProductLauncher { handleSIGTERM = true, handleSIGHUP = true, ignoreHTTPSErrors = false, - defaultViewport = {width: 800, height: 600}, + defaultViewport = { width: 800, height: 600 }, slowMo = 0, - timeout = 30000 + timeout = 30000, } = options; const profilePath = path.join(os.tmpdir(), 'puppeteer_dev_chrome_profile-'); const chromeArguments = []; - if (!ignoreDefaultArgs) - chromeArguments.push(...this.defaultArgs(options)); + if (!ignoreDefaultArgs) chromeArguments.push(...this.defaultArgs(options)); else if (Array.isArray(ignoreDefaultArgs)) - chromeArguments.push(...this.defaultArgs(options).filter(arg => !ignoreDefaultArgs.includes(arg))); - else - chromeArguments.push(...args); + chromeArguments.push( + ...this.defaultArgs(options).filter( + (arg) => !ignoreDefaultArgs.includes(arg) + ) + ); + else chromeArguments.push(...args); let temporaryUserDataDir = null; - if (!chromeArguments.some(argument => argument.startsWith('--remote-debugging-'))) - chromeArguments.push(pipe ? '--remote-debugging-pipe' : '--remote-debugging-port=0'); - if (!chromeArguments.some(arg => arg.startsWith('--user-data-dir'))) { + if ( + !chromeArguments.some((argument) => + argument.startsWith('--remote-debugging-') + ) + ) + chromeArguments.push( + pipe ? '--remote-debugging-pipe' : '--remote-debugging-port=0' + ); + if (!chromeArguments.some((arg) => arg.startsWith('--user-data-dir'))) { temporaryUserDataDir = await mkdtempAsync(profilePath); chromeArguments.push(`--user-data-dir=${temporaryUserDataDir}`); } let chromeExecutable = executablePath; if (!executablePath) { - const {missingText, executablePath} = resolveExecutablePath(this); - if (missingText) - throw new Error(missingText); + const { missingText, executablePath } = resolveExecutablePath(this); + if (missingText) throw new Error(missingText); chromeExecutable = executablePath; } const usePipe = chromeArguments.includes('--remote-debugging-pipe'); - const runner = new BrowserRunner(chromeExecutable, chromeArguments, temporaryUserDataDir); - runner.start({handleSIGHUP, handleSIGTERM, handleSIGINT, dumpio, env, pipe: usePipe}); + const runner = new BrowserRunner( + chromeExecutable, + chromeArguments, + temporaryUserDataDir + ); + runner.start({ + handleSIGHUP, + handleSIGTERM, + handleSIGINT, + dumpio, + env, + pipe: usePipe, + }); try { - const connection = await runner.setupConnection({usePipe, timeout, slowMo, preferredRevision: this._preferredRevision}); - const browser = await Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, runner.proc, runner.close.bind(runner)); - await browser.waitForTarget(t => t.type() === 'page'); + const connection = await runner.setupConnection({ + usePipe, + timeout, + slowMo, + preferredRevision: this._preferredRevision, + }); + const browser = await Browser.create( + connection, + [], + ignoreHTTPSErrors, + defaultViewport, + runner.proc, + runner.close.bind(runner) + ); + await browser.waitForTarget((t) => t.type() === 'page'); return browser; } catch (error) { runner.kill(); @@ -316,20 +367,14 @@ class ChromeLauncher implements ProductLauncher { devtools = false, headless = !devtools, args = [], - userDataDir = null + userDataDir = null, } = options; - if (userDataDir) - chromeArguments.push(`--user-data-dir=${userDataDir}`); - if (devtools) - chromeArguments.push('--auto-open-devtools-for-tabs'); + if (userDataDir) chromeArguments.push(`--user-data-dir=${userDataDir}`); + if (devtools) chromeArguments.push('--auto-open-devtools-for-tabs'); if (headless) { - chromeArguments.push( - '--headless', - '--hide-scrollbars', - '--mute-audio' - ); + chromeArguments.push('--headless', '--hide-scrollbars', '--mute-audio'); } - if (args.every(arg => arg.startsWith('-'))) + if (args.every((arg) => arg.startsWith('-'))) chromeArguments.push('about:blank'); chromeArguments.push(...args); return chromeArguments; @@ -343,38 +388,62 @@ class ChromeLauncher implements ProductLauncher { return 'chrome'; } - async connect(options: BrowserOptions & { - browserWSEndpoint?: string; - browserURL?: string; - transport?: ConnectionTransport; - }): Promise { + async connect( + options: BrowserOptions & { + browserWSEndpoint?: string; + browserURL?: string; + transport?: ConnectionTransport; + } + ): Promise { const { browserWSEndpoint, browserURL, ignoreHTTPSErrors = false, - defaultViewport = {width: 800, height: 600}, + defaultViewport = { width: 800, height: 600 }, transport, slowMo = 0, } = options; - assert(Number(!!browserWSEndpoint) + Number(!!browserURL) + Number(!!transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect'); + assert( + Number(!!browserWSEndpoint) + + Number(!!browserURL) + + Number(!!transport) === + 1, + 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect' + ); let connection = null; if (transport) { connection = new Connection('', transport, slowMo); } else if (browserWSEndpoint) { - const connectionTransport = await WebSocketTransport.create(browserWSEndpoint); - connection = new Connection(browserWSEndpoint, connectionTransport, slowMo); + const connectionTransport = await WebSocketTransport.create( + browserWSEndpoint + ); + connection = new Connection( + browserWSEndpoint, + connectionTransport, + slowMo + ); } else if (browserURL) { const connectionURL = await getWSEndpoint(browserURL); - const connectionTransport = await WebSocketTransport.create(connectionURL); + const connectionTransport = await WebSocketTransport.create( + connectionURL + ); connection = new Connection(connectionURL, connectionTransport, slowMo); } - const {browserContextIds} = await connection.send('Target.getBrowserContexts'); - return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, () => connection.send('Browser.close').catch(debugError)); + const { browserContextIds } = await connection.send( + 'Target.getBrowserContexts' + ); + return Browser.create( + connection, + browserContextIds, + ignoreHTTPSErrors, + defaultViewport, + null, + () => connection.send('Browser.close').catch(debugError) + ); } - } class FirefoxLauncher implements ProductLauncher { @@ -382,15 +451,23 @@ class FirefoxLauncher implements ProductLauncher { _preferredRevision: string; _isPuppeteerCore: boolean; - constructor(projectRoot: string, preferredRevision: string, isPuppeteerCore: boolean) { + constructor( + projectRoot: string, + preferredRevision: string, + isPuppeteerCore: boolean + ) { this._projectRoot = projectRoot; this._preferredRevision = preferredRevision; this._isPuppeteerCore = isPuppeteerCore; } - async launch(options: LaunchOptions & ChromeArgOptions & BrowserOptions & { - extraPrefsFirefox?: {[x: string]: unknown}; - } = {}): Promise { + async launch( + options: LaunchOptions & + ChromeArgOptions & + BrowserOptions & { + extraPrefsFirefox?: { [x: string]: unknown }; + } = {} + ): Promise { const { ignoreDefaultArgs = false, args = [], @@ -402,26 +479,35 @@ class FirefoxLauncher implements ProductLauncher { handleSIGTERM = true, handleSIGHUP = true, ignoreHTTPSErrors = false, - defaultViewport = {width: 800, height: 600}, + defaultViewport = { width: 800, height: 600 }, slowMo = 0, timeout = 30000, - extraPrefsFirefox = {} + extraPrefsFirefox = {}, } = options; const firefoxArguments = []; - if (!ignoreDefaultArgs) - firefoxArguments.push(...this.defaultArgs(options)); + if (!ignoreDefaultArgs) firefoxArguments.push(...this.defaultArgs(options)); else if (Array.isArray(ignoreDefaultArgs)) - firefoxArguments.push(...this.defaultArgs(options).filter(arg => !ignoreDefaultArgs.includes(arg))); - else - firefoxArguments.push(...args); + firefoxArguments.push( + ...this.defaultArgs(options).filter( + (arg) => !ignoreDefaultArgs.includes(arg) + ) + ); + else firefoxArguments.push(...args); - if (!firefoxArguments.some(argument => argument.startsWith('--remote-debugging-'))) + if ( + !firefoxArguments.some((argument) => + argument.startsWith('--remote-debugging-') + ) + ) firefoxArguments.push('--remote-debugging-port=0'); let temporaryUserDataDir = null; - if (!firefoxArguments.includes('-profile') && !firefoxArguments.includes('--profile')) { + if ( + !firefoxArguments.includes('-profile') && + !firefoxArguments.includes('--profile') + ) { temporaryUserDataDir = await this._createProfile(extraPrefsFirefox); firefoxArguments.push('--profile'); firefoxArguments.push(temporaryUserDataDir); @@ -430,19 +516,41 @@ class FirefoxLauncher implements ProductLauncher { await this._updateRevision(); let firefoxExecutable = executablePath; if (!executablePath) { - const {missingText, executablePath} = resolveExecutablePath(this); - if (missingText) - throw new Error(missingText); + const { missingText, executablePath } = resolveExecutablePath(this); + if (missingText) throw new Error(missingText); firefoxExecutable = executablePath; } - const runner = new BrowserRunner(firefoxExecutable, firefoxArguments, temporaryUserDataDir); - runner.start({handleSIGHUP, handleSIGTERM, handleSIGINT, dumpio, env, pipe}); + const runner = new BrowserRunner( + firefoxExecutable, + firefoxArguments, + temporaryUserDataDir + ); + runner.start({ + handleSIGHUP, + handleSIGTERM, + handleSIGINT, + dumpio, + env, + pipe, + }); try { - const connection = await runner.setupConnection({usePipe: pipe, timeout, slowMo, preferredRevision: this._preferredRevision}); - const browser = await Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, runner.proc, runner.close.bind(runner)); - await browser.waitForTarget(t => t.type() === 'page'); + const connection = await runner.setupConnection({ + usePipe: pipe, + timeout, + slowMo, + preferredRevision: this._preferredRevision, + }); + const browser = await Browser.create( + connection, + [], + ignoreHTTPSErrors, + defaultViewport, + runner.proc, + runner.close.bind(runner) + ); + await browser.waitForTarget((t) => t.type() === 'page'); return browser; } catch (error) { runner.kill(); @@ -450,36 +558,61 @@ class FirefoxLauncher implements ProductLauncher { } } - async connect(options: BrowserOptions & { - browserWSEndpoint?: string; - browserURL?: string; - transport?: ConnectionTransport; - }): Promise { + async connect( + options: BrowserOptions & { + browserWSEndpoint?: string; + browserURL?: string; + transport?: ConnectionTransport; + } + ): Promise { const { browserWSEndpoint, browserURL, ignoreHTTPSErrors = false, - defaultViewport = {width: 800, height: 600}, + defaultViewport = { width: 800, height: 600 }, transport, slowMo = 0, } = options; - assert(Number(!!browserWSEndpoint) + Number(!!browserURL) + Number(!!transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect'); + assert( + Number(!!browserWSEndpoint) + + Number(!!browserURL) + + Number(!!transport) === + 1, + 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect' + ); let connection = null; if (transport) { connection = new Connection('', transport, slowMo); } else if (browserWSEndpoint) { - const connectionTransport = await WebSocketTransport.create(browserWSEndpoint); - connection = new Connection(browserWSEndpoint, connectionTransport, slowMo); + const connectionTransport = await WebSocketTransport.create( + browserWSEndpoint + ); + connection = new Connection( + browserWSEndpoint, + connectionTransport, + slowMo + ); } else if (browserURL) { const connectionURL = await getWSEndpoint(browserURL); - const connectionTransport = await WebSocketTransport.create(connectionURL); + const connectionTransport = await WebSocketTransport.create( + connectionURL + ); connection = new Connection(connectionURL, connectionTransport, slowMo); } - const {browserContextIds} = await connection.send('Target.getBrowserContexts'); - return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, () => connection.send('Browser.close').catch(debugError)); + const { browserContextIds } = await connection.send( + 'Target.getBrowserContexts' + ); + return Browser.create( + connection, + browserContextIds, + ignoreHTTPSErrors, + defaultViewport, + null, + () => connection.send('Browser.close').catch(debugError) + ); } executablePath(): string { @@ -489,10 +622,11 @@ class FirefoxLauncher implements ProductLauncher { async _updateRevision(): Promise { // replace 'latest' placeholder with actual downloaded revision if (this._preferredRevision === 'latest') { - const browserFetcher = new BrowserFetcher(this._projectRoot, {product: this.product}); + const browserFetcher = new BrowserFetcher(this._projectRoot, { + product: this.product, + }); const localRevisions = await browserFetcher.localRevisions(); - if (localRevisions[0]) - this._preferredRevision = localRevisions[0]; + if (localRevisions[0]) this._preferredRevision = localRevisions[0]; } } @@ -501,32 +635,29 @@ class FirefoxLauncher implements ProductLauncher { } defaultArgs(options: ChromeArgOptions = {}): string[] { - const firefoxArguments = [ - '--no-remote', - '--foreground', - ]; + const firefoxArguments = ['--no-remote', '--foreground']; const { devtools = false, headless = !devtools, args = [], - userDataDir = null + userDataDir = null, } = options; if (userDataDir) { firefoxArguments.push('--profile'); firefoxArguments.push(userDataDir); } - if (headless) - firefoxArguments.push('--headless'); - if (devtools) - firefoxArguments.push('--devtools'); - if (args.every(arg => arg.startsWith('-'))) + if (headless) firefoxArguments.push('--headless'); + if (devtools) firefoxArguments.push('--devtools'); + if (args.every((arg) => arg.startsWith('-'))) firefoxArguments.push('about:blank'); firefoxArguments.push(...args); return firefoxArguments; } - async _createProfile(extraPrefs: { [x: string]: unknown}): Promise { - const profilePath = await mkdtempAsync(path.join(os.tmpdir(), 'puppeteer_dev_firefox_profile-')); + async _createProfile(extraPrefs: { [x: string]: unknown }): Promise { + const profilePath = await mkdtempAsync( + path.join(os.tmpdir(), 'puppeteer_dev_firefox_profile-') + ); const prefsJS = []; const userJS = []; const server = 'dummy.test'; @@ -543,8 +674,8 @@ class FirefoxLauncher implements ProductLauncher { // Prevent various error message on the console // jest-puppeteer asserts that no error message is emitted by the console - 'browser.contentblocking.features.standard': '-tp,tpPrivate,cookieBehavior0,-cm,-fp', - + 'browser.contentblocking.features.standard': + '-tp,tpPrivate,cookieBehavior0,-cm,-fp', // Enable the dump function: which sends messages to the system // console @@ -724,28 +855,37 @@ class FirefoxLauncher implements ProductLauncher { 'toolkit.telemetry.server': `https://${server}/dummy/telemetry/`, // Prevent starting into safe mode after application crashes 'toolkit.startup.max_resumed_crashes': -1, - }; Object.assign(defaultPreferences, extraPrefs); for (const [key, value] of Object.entries(defaultPreferences)) - userJS.push(`user_pref(${JSON.stringify(key)}, ${JSON.stringify(value)});`); + userJS.push( + `user_pref(${JSON.stringify(key)}, ${JSON.stringify(value)});` + ); await writeFileAsync(path.join(profilePath, 'user.js'), userJS.join('\n')); - await writeFileAsync(path.join(profilePath, 'prefs.js'), prefsJS.join('\n')); + await writeFileAsync( + path.join(profilePath, 'prefs.js'), + prefsJS.join('\n') + ); return profilePath; } } - -function waitForWSEndpoint(browserProcess: childProcess.ChildProcess, timeout: number, preferredRevision: string): Promise { +function waitForWSEndpoint( + browserProcess: childProcess.ChildProcess, + timeout: number, + preferredRevision: string +): Promise { return new Promise((resolve, reject) => { - const rl = readline.createInterface({input: browserProcess.stderr}); + const rl = readline.createInterface({ input: browserProcess.stderr }); let stderr = ''; const listeners = [ helper.addEventListener(rl, 'line', onLine), helper.addEventListener(rl, 'close', () => onClose()), helper.addEventListener(browserProcess, 'exit', () => onClose()), - helper.addEventListener(browserProcess, 'error', error => onClose(error)) + helper.addEventListener(browserProcess, 'error', (error) => + onClose(error) + ), ]; const timeoutId = timeout ? setTimeout(onTimeout, timeout) : 0; @@ -754,32 +894,39 @@ function waitForWSEndpoint(browserProcess: childProcess.ChildProcess, timeout: n */ function onClose(error?: Error): void { cleanup(); - reject(new Error([ - 'Failed to launch the browser process!' + (error ? ' ' + error.message : ''), - stderr, - '', - 'TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md', - '', - ].join('\n'))); + reject( + new Error( + [ + 'Failed to launch the browser process!' + + (error ? ' ' + error.message : ''), + stderr, + '', + 'TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md', + '', + ].join('\n') + ) + ); } function onTimeout(): void { cleanup(); - reject(new TimeoutError(`Timed out after ${timeout} ms while trying to connect to the browser! Only Chrome at revision r${preferredRevision} is guaranteed to work.`)); + reject( + new TimeoutError( + `Timed out after ${timeout} ms while trying to connect to the browser! Only Chrome at revision r${preferredRevision} is guaranteed to work.` + ) + ); } function onLine(line: string): void { stderr += line + '\n'; const match = line.match(/^DevTools listening on (ws:\/\/.*)$/); - if (!match) - return; + if (!match) return; cleanup(); resolve(match[1]); } function cleanup(): void { - if (timeoutId) - clearTimeout(timeoutId); + if (timeoutId) clearTimeout(timeoutId); helper.removeEventListeners(listeners); } }); @@ -787,12 +934,17 @@ function waitForWSEndpoint(browserProcess: childProcess.ChildProcess, timeout: n function getWSEndpoint(browserURL: string): Promise { let resolve, reject; - const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); const endpointURL = URL.resolve(browserURL, '/json/version'); const protocol = endpointURL.startsWith('https') ? https : http; - const requestOptions = Object.assign(URL.parse(endpointURL), {method: 'GET'}); - const request = protocol.request(requestOptions, res => { + const requestOptions = Object.assign(URL.parse(endpointURL), { + method: 'GET', + }); + const request = protocol.request(requestOptions, (res) => { let data = ''; if (res.statusCode !== 200) { // Consume response data to free up memory. @@ -801,52 +953,85 @@ function getWSEndpoint(browserURL: string): Promise { return; } res.setEncoding('utf8'); - res.on('data', chunk => data += chunk); + res.on('data', (chunk) => (data += chunk)); res.on('end', () => resolve(JSON.parse(data).webSocketDebuggerUrl)); }); request.on('error', reject); request.end(); - return promise.catch(error => { - error.message = `Failed to fetch browser webSocket url from ${endpointURL}: ` + error.message; + return promise.catch((error) => { + error.message = + `Failed to fetch browser webSocket url from ${endpointURL}: ` + + error.message; throw error; }); } -function resolveExecutablePath(launcher: ChromeLauncher | FirefoxLauncher): {executablePath: string; missingText?: string} { +function resolveExecutablePath( + launcher: ChromeLauncher | FirefoxLauncher +): { executablePath: string; missingText?: string } { // puppeteer-core doesn't take into account PUPPETEER_* env variables. if (!launcher._isPuppeteerCore) { - const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.npm_config_puppeteer_executable_path || process.env.npm_package_config_puppeteer_executable_path; + const executablePath = + process.env.PUPPETEER_EXECUTABLE_PATH || + process.env.npm_config_puppeteer_executable_path || + process.env.npm_package_config_puppeteer_executable_path; if (executablePath) { - const missingText = !fs.existsSync(executablePath) ? 'Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: ' + executablePath : null; - return {executablePath, missingText}; + const missingText = !fs.existsSync(executablePath) + ? 'Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: ' + + executablePath + : null; + return { executablePath, missingText }; } } - const browserFetcher = new BrowserFetcher(launcher._projectRoot, {product: launcher.product}); + const browserFetcher = new BrowserFetcher(launcher._projectRoot, { + product: launcher.product, + }); if (!launcher._isPuppeteerCore && launcher.product === 'chrome') { const revision = process.env['PUPPETEER_CHROMIUM_REVISION']; if (revision) { const revisionInfo = browserFetcher.revisionInfo(revision); - const missingText = !revisionInfo.local ? 'Tried to use PUPPETEER_CHROMIUM_REVISION env variable to launch browser but did not find executable at: ' + revisionInfo.executablePath : null; - return {executablePath: revisionInfo.executablePath, missingText}; + const missingText = !revisionInfo.local + ? 'Tried to use PUPPETEER_CHROMIUM_REVISION env variable to launch browser but did not find executable at: ' + + revisionInfo.executablePath + : null; + return { executablePath: revisionInfo.executablePath, missingText }; } } const revisionInfo = browserFetcher.revisionInfo(launcher._preferredRevision); - const missingText = !revisionInfo.local ? `Could not find browser revision ${launcher._preferredRevision}. Run "npm install" or "yarn install" to download a browser binary.` : null; - return {executablePath: revisionInfo.executablePath, missingText}; + const missingText = !revisionInfo.local + ? `Could not find browser revision ${launcher._preferredRevision}. Run "npm install" or "yarn install" to download a browser binary.` + : null; + return { executablePath: revisionInfo.executablePath, missingText }; } -function Launcher(projectRoot: string, preferredRevision: string, isPuppeteerCore: boolean, product?: string): ProductLauncher { +function Launcher( + projectRoot: string, + preferredRevision: string, + isPuppeteerCore: boolean, + product?: string +): ProductLauncher { // puppeteer-core doesn't take into account PUPPETEER_* env variables. if (!product && !isPuppeteerCore) - product = process.env.PUPPETEER_PRODUCT || process.env.npm_config_puppeteer_product || process.env.npm_package_config_puppeteer_product; + product = + process.env.PUPPETEER_PRODUCT || + process.env.npm_config_puppeteer_product || + process.env.npm_package_config_puppeteer_product; switch (product) { case 'firefox': - return new FirefoxLauncher(projectRoot, preferredRevision, isPuppeteerCore); + return new FirefoxLauncher( + projectRoot, + preferredRevision, + isPuppeteerCore + ); case 'chrome': default: - return new ChromeLauncher(projectRoot, preferredRevision, isPuppeteerCore); + return new ChromeLauncher( + projectRoot, + preferredRevision, + isPuppeteerCore + ); } } diff --git a/src/LifecycleWatcher.ts b/src/LifecycleWatcher.ts index fc35484c94165..7de9f85c66fe4 100644 --- a/src/LifecycleWatcher.ts +++ b/src/LifecycleWatcher.ts @@ -14,23 +14,33 @@ * limitations under the License. */ -import {helper, assert, PuppeteerEventListener} from './helper'; -import {Events} from './Events'; -import {TimeoutError} from './Errors'; -import {FrameManager, Frame} from './FrameManager'; -import {Request, Response} from './NetworkManager'; - -export type PuppeteerLifeCycleEvent = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'; -type ProtocolLifeCycleEvent = 'load' | 'DOMContentLoaded' | 'networkIdle' | 'networkAlmostIdle'; - -const puppeteerToProtocolLifecycle = new Map([ +import { helper, assert, PuppeteerEventListener } from './helper'; +import { Events } from './Events'; +import { TimeoutError } from './Errors'; +import { FrameManager, Frame } from './FrameManager'; +import { Request, Response } from './NetworkManager'; + +export type PuppeteerLifeCycleEvent = + | 'load' + | 'domcontentloaded' + | 'networkidle0' + | 'networkidle2'; +type ProtocolLifeCycleEvent = + | 'load' + | 'DOMContentLoaded' + | 'networkIdle' + | 'networkAlmostIdle'; + +const puppeteerToProtocolLifecycle = new Map< + PuppeteerLifeCycleEvent, + ProtocolLifeCycleEvent +>([ ['load', 'load'], ['domcontentloaded', 'DOMContentLoaded'], ['networkidle0', 'networkIdle'], ['networkidle2', 'networkAlmostIdle'], ]); - export class LifecycleWatcher { _expectedLifecycle: ProtocolLifeCycleEvent[]; _frameManager: FrameManager; @@ -40,7 +50,6 @@ export class LifecycleWatcher { _eventListeners: PuppeteerEventListener[]; _initialLoaderId: string; - _sameDocumentNavigationPromise: Promise; _sameDocumentNavigationCompleteCallback: (x?: Error) => void; @@ -58,12 +67,15 @@ export class LifecycleWatcher { _maximumTimer?: NodeJS.Timeout; _hasSameDocumentNavigation?: boolean; - constructor(frameManager: FrameManager, frame: Frame, waitUntil: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[], timeout: number) { - if (Array.isArray(waitUntil)) - waitUntil = waitUntil.slice(); - else if (typeof waitUntil === 'string') - waitUntil = [waitUntil]; - this._expectedLifecycle = waitUntil.map(value => { + constructor( + frameManager: FrameManager, + frame: Frame, + waitUntil: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[], + timeout: number + ) { + if (Array.isArray(waitUntil)) waitUntil = waitUntil.slice(); + else if (typeof waitUntil === 'string') waitUntil = [waitUntil]; + this._expectedLifecycle = waitUntil.map((value) => { const protocolEvent = puppeteerToProtocolLifecycle.get(value); assert(protocolEvent, 'Unknown value for options.waitUntil: ' + value); return protocolEvent; @@ -75,27 +87,52 @@ export class LifecycleWatcher { this._timeout = timeout; this._navigationRequest = null; this._eventListeners = [ - helper.addEventListener(frameManager._client, Events.CDPSession.Disconnected, () => this._terminate(new Error('Navigation failed because browser has disconnected!'))), - helper.addEventListener(this._frameManager, Events.FrameManager.LifecycleEvent, this._checkLifecycleComplete.bind(this)), - helper.addEventListener(this._frameManager, Events.FrameManager.FrameNavigatedWithinDocument, this._navigatedWithinDocument.bind(this)), - helper.addEventListener(this._frameManager, Events.FrameManager.FrameDetached, this._onFrameDetached.bind(this)), - helper.addEventListener(this._frameManager.networkManager(), Events.NetworkManager.Request, this._onRequest.bind(this)), + helper.addEventListener( + frameManager._client, + Events.CDPSession.Disconnected, + () => + this._terminate( + new Error('Navigation failed because browser has disconnected!') + ) + ), + helper.addEventListener( + this._frameManager, + Events.FrameManager.LifecycleEvent, + this._checkLifecycleComplete.bind(this) + ), + helper.addEventListener( + this._frameManager, + Events.FrameManager.FrameNavigatedWithinDocument, + this._navigatedWithinDocument.bind(this) + ), + helper.addEventListener( + this._frameManager, + Events.FrameManager.FrameDetached, + this._onFrameDetached.bind(this) + ), + helper.addEventListener( + this._frameManager.networkManager(), + Events.NetworkManager.Request, + this._onRequest.bind(this) + ), ]; - this._sameDocumentNavigationPromise = new Promise(fulfill => { - this._sameDocumentNavigationCompleteCallback = fulfill; - }); + this._sameDocumentNavigationPromise = new Promise( + (fulfill) => { + this._sameDocumentNavigationCompleteCallback = fulfill; + } + ); - this._lifecyclePromise = new Promise(fulfill => { + this._lifecyclePromise = new Promise((fulfill) => { this._lifecycleCallback = fulfill; }); - this._newDocumentNavigationPromise = new Promise(fulfill => { + this._newDocumentNavigationPromise = new Promise((fulfill) => { this._newDocumentNavigationCompleteCallback = fulfill; }); this._timeoutPromise = this._createTimeoutPromise(); - this._terminationPromise = new Promise(fulfill => { + this._terminationPromise = new Promise((fulfill) => { this._terminationCallback = fulfill; }); this._checkLifecycleComplete(); @@ -109,7 +146,10 @@ export class LifecycleWatcher { _onFrameDetached(frame: Frame): void { if (this._frame === frame) { - this._terminationCallback.call(null, new Error('Navigating frame was detached')); + this._terminationCallback.call( + null, + new Error('Navigating frame was detached') + ); return; } this._checkLifecycleComplete(); @@ -140,26 +180,28 @@ export class LifecycleWatcher { } _createTimeoutPromise(): Promise { - if (!this._timeout) - return new Promise(() => {}); - const errorMessage = 'Navigation timeout of ' + this._timeout + ' ms exceeded'; - return new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._timeout)) - .then(() => new TimeoutError(errorMessage)); + if (!this._timeout) return new Promise(() => {}); + const errorMessage = + 'Navigation timeout of ' + this._timeout + ' ms exceeded'; + return new Promise( + (fulfill) => (this._maximumTimer = setTimeout(fulfill, this._timeout)) + ).then(() => new TimeoutError(errorMessage)); } _navigatedWithinDocument(frame: Frame): void { - if (frame !== this._frame) - return; + if (frame !== this._frame) return; this._hasSameDocumentNavigation = true; this._checkLifecycleComplete(); } _checkLifecycleComplete(): void { // We expect navigation to commit. - if (!checkLifecycle(this._frame, this._expectedLifecycle)) - return; + if (!checkLifecycle(this._frame, this._expectedLifecycle)) return; this._lifecycleCallback(); - if (this._frame._loaderId === this._initialLoaderId && !this._hasSameDocumentNavigation) + if ( + this._frame._loaderId === this._initialLoaderId && + !this._hasSameDocumentNavigation + ) return; if (this._hasSameDocumentNavigation) this._sameDocumentNavigationCompleteCallback(); @@ -171,14 +213,15 @@ export class LifecycleWatcher { * @param {!Array} expectedLifecycle * @return {boolean} */ - function checkLifecycle(frame: Frame, expectedLifecycle: ProtocolLifeCycleEvent[]): boolean { + function checkLifecycle( + frame: Frame, + expectedLifecycle: ProtocolLifeCycleEvent[] + ): boolean { for (const event of expectedLifecycle) { - if (!frame._lifecycleEvents.has(event)) - return false; + if (!frame._lifecycleEvents.has(event)) return false; } for (const child of frame.childFrames()) { - if (!checkLifecycle(child, expectedLifecycle)) - return false; + if (!checkLifecycle(child, expectedLifecycle)) return false; } return true; } diff --git a/src/NetworkManager.ts b/src/NetworkManager.ts index 4f40196974265..dd8c5c00edb38 100644 --- a/src/NetworkManager.ts +++ b/src/NetworkManager.ts @@ -14,10 +14,10 @@ * limitations under the License. */ import * as EventEmitter from 'events'; -import {helper, assert, debugError} from './helper'; -import {Events} from './Events'; -import {CDPSession} from './Connection'; -import {FrameManager, Frame} from './FrameManager'; +import { helper, assert, debugError } from './helper'; +import { Events } from './Events'; +import { CDPSession } from './Connection'; +import { FrameManager, Frame } from './FrameManager'; export interface Credentials { username: string; @@ -29,7 +29,10 @@ export class NetworkManager extends EventEmitter { _ignoreHTTPSErrors: boolean; _frameManager: FrameManager; _requestIdToRequest = new Map(); - _requestIdToRequestWillBeSentEvent = new Map(); + _requestIdToRequestWillBeSentEvent = new Map< + string, + Protocol.Network.requestWillBeSentPayload + >(); _extraHTTPHeaders: Record = {}; _offline = false; _credentials?: Credentials = null; @@ -39,7 +42,11 @@ export class NetworkManager extends EventEmitter { _userCacheDisabled = false; _requestIdToInterceptionId = new Map(); - constructor(client: CDPSession, ignoreHTTPSErrors: boolean, frameManager: FrameManager) { + constructor( + client: CDPSession, + ignoreHTTPSErrors: boolean, + frameManager: FrameManager + ) { super(); this._client = client; this._ignoreHTTPSErrors = ignoreHTTPSErrors; @@ -47,17 +54,31 @@ export class NetworkManager extends EventEmitter { this._client.on('Fetch.requestPaused', this._onRequestPaused.bind(this)); this._client.on('Fetch.authRequired', this._onAuthRequired.bind(this)); - this._client.on('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)); - this._client.on('Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)); - this._client.on('Network.responseReceived', this._onResponseReceived.bind(this)); - this._client.on('Network.loadingFinished', this._onLoadingFinished.bind(this)); + this._client.on( + 'Network.requestWillBeSent', + this._onRequestWillBeSent.bind(this) + ); + this._client.on( + 'Network.requestServedFromCache', + this._onRequestServedFromCache.bind(this) + ); + this._client.on( + 'Network.responseReceived', + this._onResponseReceived.bind(this) + ); + this._client.on( + 'Network.loadingFinished', + this._onLoadingFinished.bind(this) + ); this._client.on('Network.loadingFailed', this._onLoadingFailed.bind(this)); } async initialize(): Promise { await this._client.send('Network.enable'); if (this._ignoreHTTPSErrors) - await this._client.send('Security.setIgnoreCertificateErrors', {ignore: true}); + await this._client.send('Security.setIgnoreCertificateErrors', { + ignore: true, + }); } async authenticate(credentials?: Credentials): Promise { @@ -65,14 +86,21 @@ export class NetworkManager extends EventEmitter { await this._updateProtocolRequestInterception(); } - async setExtraHTTPHeaders(extraHTTPHeaders: Record): Promise { + async setExtraHTTPHeaders( + extraHTTPHeaders: Record + ): Promise { this._extraHTTPHeaders = {}; for (const key of Object.keys(extraHTTPHeaders)) { const value = extraHTTPHeaders[key]; - assert(helper.isString(value), `Expected value of header "${key}" to be String, but "${typeof value}" is found.`); + assert( + helper.isString(value), + `Expected value of header "${key}" to be String, but "${typeof value}" is found.` + ); this._extraHTTPHeaders[key.toLowerCase()] = value; } - await this._client.send('Network.setExtraHTTPHeaders', {headers: this._extraHTTPHeaders}); + await this._client.send('Network.setExtraHTTPHeaders', { + headers: this._extraHTTPHeaders, + }); } extraHTTPHeaders(): Record { @@ -80,20 +108,19 @@ export class NetworkManager extends EventEmitter { } async setOfflineMode(value: boolean): Promise { - if (this._offline === value) - return; + if (this._offline === value) return; this._offline = value; await this._client.send('Network.emulateNetworkConditions', { offline: this._offline, // values of 0 remove any active throttling. crbug.com/456324#c9 latency: 0, downloadThroughput: -1, - uploadThroughput: -1 + uploadThroughput: -1, }); } async setUserAgent(userAgent: string): Promise { - await this._client.send('Network.setUserAgentOverride', {userAgent}); + await this._client.send('Network.setUserAgentOverride', { userAgent }); } async setCacheEnabled(enabled: boolean): Promise { @@ -108,34 +135,37 @@ export class NetworkManager extends EventEmitter { async _updateProtocolRequestInterception(): Promise { const enabled = this._userRequestInterceptionEnabled || !!this._credentials; - if (enabled === this._protocolRequestInterceptionEnabled) - return; + if (enabled === this._protocolRequestInterceptionEnabled) return; this._protocolRequestInterceptionEnabled = enabled; if (enabled) { await Promise.all([ this._updateProtocolCacheDisabled(), this._client.send('Fetch.enable', { handleAuthRequests: true, - patterns: [{urlPattern: '*'}], + patterns: [{ urlPattern: '*' }], }), ]); } else { await Promise.all([ this._updateProtocolCacheDisabled(), - this._client.send('Fetch.disable') + this._client.send('Fetch.disable'), ]); } } async _updateProtocolCacheDisabled(): Promise { await this._client.send('Network.setCacheDisabled', { - cacheDisabled: this._userCacheDisabled || this._protocolRequestInterceptionEnabled + cacheDisabled: + this._userCacheDisabled || this._protocolRequestInterceptionEnabled, }); } _onRequestWillBeSent(event: Protocol.Network.requestWillBeSentPayload): void { // Request interception doesn't happen for data URLs with Network Service. - if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) { + if ( + this._protocolRequestInterceptionEnabled && + !event.request.url.startsWith('data:') + ) { const requestId = event.requestId; const interceptionId = this._requestIdToInterceptionId.get(requestId); if (interceptionId) { @@ -154,9 +184,9 @@ export class NetworkManager extends EventEmitter { */ _onAuthRequired(event: Protocol.Fetch.authRequiredPayload): void { /* TODO(jacktfranklin): This is defined in protocol.d.ts but not - * in an easily referrable way - we should look at exposing it. - */ - type AuthResponse = 'Default'|'CancelAuth'|'ProvideCredentials'; + * in an easily referrable way - we should look at exposing it. + */ + type AuthResponse = 'Default' | 'CancelAuth' | 'ProvideCredentials'; let response: AuthResponse = 'Default'; if (this._attemptedAuthentications.has(event.requestId)) { response = 'CancelAuth'; @@ -164,24 +194,36 @@ export class NetworkManager extends EventEmitter { response = 'ProvideCredentials'; this._attemptedAuthentications.add(event.requestId); } - const {username, password} = this._credentials || {username: undefined, password: undefined}; - this._client.send('Fetch.continueWithAuth', { - requestId: event.requestId, - authChallengeResponse: {response, username, password}, - }).catch(debugError); + const { username, password } = this._credentials || { + username: undefined, + password: undefined, + }; + this._client + .send('Fetch.continueWithAuth', { + requestId: event.requestId, + authChallengeResponse: { response, username, password }, + }) + .catch(debugError); } _onRequestPaused(event: Protocol.Fetch.requestPausedPayload): void { - if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) { - this._client.send('Fetch.continueRequest', { - requestId: event.requestId - }).catch(debugError); + if ( + !this._userRequestInterceptionEnabled && + this._protocolRequestInterceptionEnabled + ) { + this._client + .send('Fetch.continueRequest', { + requestId: event.requestId, + }) + .catch(debugError); } const requestId = event.networkId; const interceptionId = event.requestId; if (requestId && this._requestIdToRequestWillBeSentEvent.has(requestId)) { - const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId); + const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get( + requestId + ); this._onRequest(requestWillBeSentEvent, interceptionId); this._requestIdToRequestWillBeSentEvent.delete(requestId); } else { @@ -189,7 +231,10 @@ export class NetworkManager extends EventEmitter { } } - _onRequest(event: Protocol.Network.requestWillBeSentPayload, interceptionId?: string): void { + _onRequest( + event: Protocol.Network.requestWillBeSentPayload, + interceptionId?: string + ): void { let redirectChain = []; if (event.redirectResponse) { const request = this._requestIdToRequest.get(event.requestId); @@ -199,23 +244,39 @@ export class NetworkManager extends EventEmitter { redirectChain = request._redirectChain; } } - const frame = event.frameId ? this._frameManager.frame(event.frameId) : null; - const request = new Request(this._client, frame, interceptionId, this._userRequestInterceptionEnabled, event, redirectChain); + const frame = event.frameId + ? this._frameManager.frame(event.frameId) + : null; + const request = new Request( + this._client, + frame, + interceptionId, + this._userRequestInterceptionEnabled, + event, + redirectChain + ); this._requestIdToRequest.set(event.requestId, request); this.emit(Events.NetworkManager.Request, request); } - _onRequestServedFromCache(event: Protocol.Network.requestServedFromCachePayload): void { + _onRequestServedFromCache( + event: Protocol.Network.requestServedFromCachePayload + ): void { const request = this._requestIdToRequest.get(event.requestId); - if (request) - request._fromMemoryCache = true; + if (request) request._fromMemoryCache = true; } - _handleRequestRedirect(request: Request, responsePayload: Protocol.Network.Response): void { + _handleRequestRedirect( + request: Request, + responsePayload: Protocol.Network.Response + ): void { const response = new Response(this._client, request, responsePayload); request._response = response; request._redirectChain.push(request); - response._bodyLoadedPromiseFulfill.call(null, new Error('Response body is unavailable for redirect responses')); + response._bodyLoadedPromiseFulfill.call( + null, + new Error('Response body is unavailable for redirect responses') + ); this._requestIdToRequest.delete(request._requestId); this._attemptedAuthentications.delete(request._interceptionId); this.emit(Events.NetworkManager.Response, response); @@ -225,8 +286,7 @@ export class NetworkManager extends EventEmitter { _onResponseReceived(event: Protocol.Network.responseReceivedPayload): void { const request = this._requestIdToRequest.get(event.requestId); // FileUpload sends a response without a matching request. - if (!request) - return; + if (!request) return; const response = new Response(this._client, request, event.response); request._response = response; this.emit(Events.NetworkManager.Response, response); @@ -236,8 +296,7 @@ export class NetworkManager extends EventEmitter { const request = this._requestIdToRequest.get(event.requestId); // For certain requestIds we never receive requestWillBeSent event. // @see https://crbug.com/750469 - if (!request) - return; + if (!request) return; // Under certain conditions we never get the Network.responseReceived // event from protocol. @see https://crbug.com/883475 @@ -252,12 +311,10 @@ export class NetworkManager extends EventEmitter { const request = this._requestIdToRequest.get(event.requestId); // For certain requestIds we never receive requestWillBeSent event. // @see https://crbug.com/750469 - if (!request) - return; + if (!request) return; request._failureText = event.errorText; const response = request.response(); - if (response) - response._bodyLoadedPromiseFulfill.call(null); + if (response) response._bodyLoadedPromiseFulfill.call(null); this._requestIdToRequest.delete(request._requestId); this._attemptedAuthentications.delete(request._interceptionId); this.emit(Events.NetworkManager.RequestFailed, request); @@ -282,12 +339,20 @@ export class Request { _frame: Frame; _redirectChain: Request[]; - _fromMemoryCache = false; - - constructor(client: CDPSession, frame: Frame, interceptionId: string, allowInterception: boolean, event: Protocol.Network.requestWillBeSentPayload, redirectChain: Request[]) { + _fromMemoryCache = false; + + constructor( + client: CDPSession, + frame: Frame, + interceptionId: string, + allowInterception: boolean, + event: Protocol.Network.requestWillBeSentPayload, + redirectChain: Request[] + ) { this._client = client; this._requestId = event.requestId; - this._isNavigationRequest = event.requestId === event.loaderId && event.type === 'Document'; + this._isNavigationRequest = + event.requestId === event.loaderId && event.type === 'Document'; this._interceptionId = interceptionId; this._allowInterception = allowInterception; this._url = event.request.url; @@ -340,54 +405,58 @@ export class Request { /** * @return {?{errorText: string}} */ - failure(): {errorText: string} | null { - if (!this._failureText) - return null; + failure(): { errorText: string } | null { + if (!this._failureText) return null; return { - errorText: this._failureText + errorText: this._failureText, }; } - async continue(overrides: {url?: string; method?: string; postData?: string; headers?: Record} = {}): Promise { + async continue( + overrides: { + url?: string; + method?: string; + postData?: string; + headers?: Record; + } = {} + ): Promise { // Request interception is not supported for data: urls. - if (this._url.startsWith('data:')) - return; + if (this._url.startsWith('data:')) return; assert(this._allowInterception, 'Request Interception is not enabled!'); assert(!this._interceptionHandled, 'Request is already handled!'); - const { - url, - method, - postData, - headers - } = overrides; + const { url, method, postData, headers } = overrides; this._interceptionHandled = true; - await this._client.send('Fetch.continueRequest', { - requestId: this._interceptionId, - url, - method, - postData, - headers: headers ? headersArray(headers) : undefined, - }).catch(error => { - // In certain cases, protocol will return error if the request was already canceled - // or the page was closed. We should tolerate these errors. - debugError(error); - }); + await this._client + .send('Fetch.continueRequest', { + requestId: this._interceptionId, + url, + method, + postData, + headers: headers ? headersArray(headers) : undefined, + }) + .catch((error) => { + // In certain cases, protocol will return error if the request was already canceled + // or the page was closed. We should tolerate these errors. + debugError(error); + }); } async respond(response: { status: number; headers: Record; contentType: string; - body: string|Buffer; + body: string | Buffer; }): Promise { // Mocking responses for dataURL requests is not currently supported. - if (this._url.startsWith('data:')) - return; + if (this._url.startsWith('data:')) return; assert(this._allowInterception, 'Request Interception is not enabled!'); assert(!this._interceptionHandled, 'Request is already handled!'); this._interceptionHandled = true; - const responseBody: Buffer | null = response.body && helper.isString(response.body) ? Buffer.from(response.body) : response.body as Buffer || null; + const responseBody: Buffer | null = + response.body && helper.isString(response.body) + ? Buffer.from(response.body) + : (response.body as Buffer) || null; const responseHeaders: Record = {}; if (response.headers) { @@ -397,63 +466,82 @@ export class Request { if (response.contentType) responseHeaders['content-type'] = response.contentType; if (responseBody && !('content-length' in responseHeaders)) - responseHeaders['content-length'] = String(Buffer.byteLength(responseBody)); - - await this._client.send('Fetch.fulfillRequest', { - requestId: this._interceptionId, - responseCode: response.status || 200, - responsePhrase: STATUS_TEXTS[response.status || 200], - responseHeaders: headersArray(responseHeaders), - body: responseBody ? responseBody.toString('base64') : undefined, - }).catch(error => { - // In certain cases, protocol will return error if the request was already canceled - // or the page was closed. We should tolerate these errors. - debugError(error); - }); + responseHeaders['content-length'] = String( + Buffer.byteLength(responseBody) + ); + + await this._client + .send('Fetch.fulfillRequest', { + requestId: this._interceptionId, + responseCode: response.status || 200, + responsePhrase: STATUS_TEXTS[response.status || 200], + responseHeaders: headersArray(responseHeaders), + body: responseBody ? responseBody.toString('base64') : undefined, + }) + .catch((error) => { + // In certain cases, protocol will return error if the request was already canceled + // or the page was closed. We should tolerate these errors. + debugError(error); + }); } async abort(errorCode: ErrorCode = 'failed'): Promise { // Request interception is not supported for data: urls. - if (this._url.startsWith('data:')) - return; + if (this._url.startsWith('data:')) return; const errorReason = errorReasons[errorCode]; assert(errorReason, 'Unknown error code: ' + errorCode); assert(this._allowInterception, 'Request Interception is not enabled!'); assert(!this._interceptionHandled, 'Request is already handled!'); this._interceptionHandled = true; - await this._client.send('Fetch.failRequest', { - requestId: this._interceptionId, - errorReason - }).catch(error => { - // In certain cases, protocol will return error if the request was already canceled - // or the page was closed. We should tolerate these errors. - debugError(error); - }); + await this._client + .send('Fetch.failRequest', { + requestId: this._interceptionId, + errorReason, + }) + .catch((error) => { + // In certain cases, protocol will return error if the request was already canceled + // or the page was closed. We should tolerate these errors. + debugError(error); + }); } } -type ErrorCode = 'aborted' | 'accessdenied' | 'addressunreachable' | 'blockedbyclient' | 'blockedbyresponse' | 'connectionaborted' | 'connectionclosed' | 'connectionfailed' | 'connectionrefused' | 'connectionreset' | 'internetdisconnected' | 'namenotresolved' | 'timedout' | 'failed'; +type ErrorCode = + | 'aborted' + | 'accessdenied' + | 'addressunreachable' + | 'blockedbyclient' + | 'blockedbyresponse' + | 'connectionaborted' + | 'connectionclosed' + | 'connectionfailed' + | 'connectionrefused' + | 'connectionreset' + | 'internetdisconnected' + | 'namenotresolved' + | 'timedout' + | 'failed'; const errorReasons: Record = { - 'aborted': 'Aborted', - 'accessdenied': 'AccessDenied', - 'addressunreachable': 'AddressUnreachable', - 'blockedbyclient': 'BlockedByClient', - 'blockedbyresponse': 'BlockedByResponse', - 'connectionaborted': 'ConnectionAborted', - 'connectionclosed': 'ConnectionClosed', - 'connectionfailed': 'ConnectionFailed', - 'connectionrefused': 'ConnectionRefused', - 'connectionreset': 'ConnectionReset', - 'internetdisconnected': 'InternetDisconnected', - 'namenotresolved': 'NameNotResolved', - 'timedout': 'TimedOut', - 'failed': 'Failed', + aborted: 'Aborted', + accessdenied: 'AccessDenied', + addressunreachable: 'AddressUnreachable', + blockedbyclient: 'BlockedByClient', + blockedbyresponse: 'BlockedByResponse', + connectionaborted: 'ConnectionAborted', + connectionclosed: 'ConnectionClosed', + connectionfailed: 'ConnectionFailed', + connectionrefused: 'ConnectionRefused', + connectionreset: 'ConnectionReset', + internetdisconnected: 'InternetDisconnected', + namenotresolved: 'NameNotResolved', + timedout: 'TimedOut', + failed: 'Failed', } as const; interface RemoteAddress { - ip: string; - port: number; + ip: string; + port: number; } export class Response { @@ -471,11 +559,15 @@ export class Response { _headers: Record = {}; _securityDetails: SecurityDetails | null; - constructor(client: CDPSession, request: Request, responsePayload: Protocol.Network.Response) { + constructor( + client: CDPSession, + request: Request, + responsePayload: Protocol.Network.Response + ) { this._client = client; this._request = request; - this._bodyLoadedPromise = new Promise(fulfill => { + this._bodyLoadedPromise = new Promise((fulfill) => { this._bodyLoadedPromiseFulfill = fulfill; }); @@ -490,7 +582,9 @@ export class Response { this._fromServiceWorker = !!responsePayload.fromServiceWorker; for (const key of Object.keys(responsePayload.headers)) this._headers[key.toLowerCase()] = responsePayload.headers[key]; - this._securityDetails = responsePayload.securityDetails ? new SecurityDetails(responsePayload.securityDetails) : null; + this._securityDetails = responsePayload.securityDetails + ? new SecurityDetails(responsePayload.securityDetails) + : null; } remoteAddress(): RemoteAddress { @@ -523,13 +617,15 @@ export class Response { buffer(): Promise { if (!this._contentPromise) { - this._contentPromise = this._bodyLoadedPromise.then(async error => { - if (error) - throw error; + this._contentPromise = this._bodyLoadedPromise.then(async (error) => { + if (error) throw error; const response = await this._client.send('Network.getResponseBody', { - requestId: this._request._requestId + requestId: this._request._requestId, }); - return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8'); + return Buffer.from( + response.body, + response.base64Encoded ? 'base64' : 'utf8' + ); }); } return this._contentPromise; @@ -598,11 +694,13 @@ export class SecurityDetails { } } -function headersArray(headers: Record): Array<{name: string; value: string}> { +function headersArray( + headers: Record +): Array<{ name: string; value: string }> { const result = []; for (const name in headers) { if (!Object.is(headers[name], undefined)) - result.push({name, value: headers[name] + ''}); + result.push({ name, value: headers[name] + '' }); } return result; } @@ -650,7 +748,7 @@ const STATUS_TEXTS = { '415': 'Unsupported Media Type', '416': 'Range Not Satisfiable', '417': 'Expectation Failed', - '418': 'I\'m a teapot', + '418': "I'm a teapot", '421': 'Misdirected Request', '422': 'Unprocessable Entity', '423': 'Locked', diff --git a/src/Page.ts b/src/Page.ts index 529918a0ca4f9..7e370ade00689 100644 --- a/src/Page.ts +++ b/src/Page.ts @@ -17,47 +17,51 @@ import * as fs from 'fs'; import * as EventEmitter from 'events'; import * as mime from 'mime'; -import {Events} from './Events'; -import {Connection, CDPSession} from './Connection'; -import {Dialog} from './Dialog'; -import {EmulationManager} from './EmulationManager'; -import {Frame, FrameManager} from './FrameManager'; -import {Keyboard, Mouse, Touchscreen, MouseButtonInput} from './Input'; -import {Tracing} from './Tracing'; -import {helper, debugError, assert} from './helper'; -import {Coverage} from './Coverage'; -import {Worker as PuppeteerWorker} from './Worker'; -import {Browser, BrowserContext} from './Browser'; -import {Target} from './Target'; -import {createJSHandle, JSHandle, ElementHandle} from './JSHandle'; -import type {Viewport} from './PuppeteerViewport'; -import {Request as PuppeteerRequest, Response as PuppeteerResponse, Credentials} from './NetworkManager'; -import {Accessibility} from './Accessibility'; -import {TimeoutSettings} from './TimeoutSettings'; -import {PuppeteerLifeCycleEvent} from './LifecycleWatcher'; -import {TaskQueue} from './TaskQueue'; +import { Events } from './Events'; +import { Connection, CDPSession } from './Connection'; +import { Dialog } from './Dialog'; +import { EmulationManager } from './EmulationManager'; +import { Frame, FrameManager } from './FrameManager'; +import { Keyboard, Mouse, Touchscreen, MouseButtonInput } from './Input'; +import { Tracing } from './Tracing'; +import { helper, debugError, assert } from './helper'; +import { Coverage } from './Coverage'; +import { Worker as PuppeteerWorker } from './Worker'; +import { Browser, BrowserContext } from './Browser'; +import { Target } from './Target'; +import { createJSHandle, JSHandle, ElementHandle } from './JSHandle'; +import type { Viewport } from './PuppeteerViewport'; +import { + Request as PuppeteerRequest, + Response as PuppeteerResponse, + Credentials, +} from './NetworkManager'; +import { Accessibility } from './Accessibility'; +import { TimeoutSettings } from './TimeoutSettings'; +import { PuppeteerLifeCycleEvent } from './LifecycleWatcher'; +import { TaskQueue } from './TaskQueue'; const writeFileAsync = helper.promisify(fs.writeFile); interface Metrics { - Timestamp?: number; - Documents?: number; - Frames?: number; - JSEventListeners?: number; - Nodes?: number; - LayoutCount?: number; - RecalcStyleCount?: number; - LayoutDuration?: number; - RecalcStyleDuration?: number; - ScriptDuration?: number; - TaskDuration?: number; - JSHeapUsedSize?: number; - JSHeapTotalSize?: number; + Timestamp?: number; + Documents?: number; + Frames?: number; + JSEventListeners?: number; + Nodes?: number; + LayoutCount?: number; + RecalcStyleCount?: number; + LayoutDuration?: number; + RecalcStyleDuration?: number; + ScriptDuration?: number; + TaskDuration?: number; + JSHeapUsedSize?: number; + JSHeapTotalSize?: number; } interface WaitForOptions { timeout?: number; - waitUntil?: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[]; + waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; } interface MediaFeature { @@ -83,10 +87,10 @@ interface ScreenshotOptions { } interface PDFMargin { - top?: string|number; - bottom?: string|number; - left?: string|number; - right?: string|number; + top?: string | number; + bottom?: string | number; + left?: string | number; + right?: string | number; } interface PDFOptions { @@ -98,8 +102,8 @@ interface PDFOptions { landscape?: boolean; pageRanges?: string; format?: string; - width?: string|number; - height?: string|number; + width?: string | number; + height?: string | number; preferCSSPageSize?: boolean; margin?: PDFMargin; path?: string; @@ -111,25 +115,35 @@ interface PaperFormat { } const paperFormats: Record = { - letter: {width: 8.5, height: 11}, - legal: {width: 8.5, height: 14}, - tabloid: {width: 11, height: 17}, - ledger: {width: 17, height: 11}, - a0: {width: 33.1, height: 46.8}, - a1: {width: 23.4, height: 33.1}, - a2: {width: 16.54, height: 23.4}, - a3: {width: 11.7, height: 16.54}, - a4: {width: 8.27, height: 11.7}, - a5: {width: 5.83, height: 8.27}, - a6: {width: 4.13, height: 5.83}, + letter: { width: 8.5, height: 11 }, + legal: { width: 8.5, height: 14 }, + tabloid: { width: 11, height: 17 }, + ledger: { width: 17, height: 11 }, + a0: { width: 33.1, height: 46.8 }, + a1: { width: 23.4, height: 33.1 }, + a2: { width: 16.54, height: 23.4 }, + a3: { width: 11.7, height: 16.54 }, + a4: { width: 8.27, height: 11.7 }, + a5: { width: 5.83, height: 8.27 }, + a6: { width: 4.13, height: 5.83 }, } as const; export class Page extends EventEmitter { - static async create(client: CDPSession, target: Target, ignoreHTTPSErrors: boolean, defaultViewport: Viewport | null, screenshotTaskQueue: TaskQueue): Promise { - const page = new Page(client, target, ignoreHTTPSErrors, screenshotTaskQueue); + static async create( + client: CDPSession, + target: Target, + ignoreHTTPSErrors: boolean, + defaultViewport: Viewport | null, + screenshotTaskQueue: TaskQueue + ): Promise { + const page = new Page( + client, + target, + ignoreHTTPSErrors, + screenshotTaskQueue + ); await page._initialize(); - if (defaultViewport) - await page.setViewport(defaultViewport); + if (defaultViewport) await page.setViewport(defaultViewport); return page; } @@ -155,7 +169,12 @@ export class Page extends EventEmitter { _disconnectPromise?: Promise; - constructor(client: CDPSession, target: Target, ignoreHTTPSErrors: boolean, screenshotTaskQueue: TaskQueue) { + constructor( + client: CDPSession, + target: Target, + ignoreHTTPSErrors: boolean, + screenshotTaskQueue: TaskQueue + ) { super(); this._client = client; this._target = target; @@ -163,55 +182,84 @@ export class Page extends EventEmitter { this._mouse = new Mouse(client, this._keyboard); this._touchscreen = new Touchscreen(client, this._keyboard); this._accessibility = new Accessibility(client); - this._frameManager = new FrameManager(client, this, ignoreHTTPSErrors, this._timeoutSettings); + this._frameManager = new FrameManager( + client, + this, + ignoreHTTPSErrors, + this._timeoutSettings + ); this._emulationManager = new EmulationManager(client); this._tracing = new Tracing(client); this._coverage = new Coverage(client); this._screenshotTaskQueue = screenshotTaskQueue; this._viewport = null; - client.on('Target.attachedToTarget', event => { + client.on('Target.attachedToTarget', (event) => { if (event.targetInfo.type !== 'worker') { // If we don't detach from service workers, they will never die. - client.send('Target.detachFromTarget', { - sessionId: event.sessionId - }).catch(debugError); + client + .send('Target.detachFromTarget', { + sessionId: event.sessionId, + }) + .catch(debugError); return; } const session = Connection.fromSession(client).session(event.sessionId); - const worker = new PuppeteerWorker(session, event.targetInfo.url, this._addConsoleMessage.bind(this), this._handleException.bind(this)); + const worker = new PuppeteerWorker( + session, + event.targetInfo.url, + this._addConsoleMessage.bind(this), + this._handleException.bind(this) + ); this._workers.set(event.sessionId, worker); this.emit(Events.Page.WorkerCreated, worker); }); - client.on('Target.detachedFromTarget', event => { + client.on('Target.detachedFromTarget', (event) => { const worker = this._workers.get(event.sessionId); - if (!worker) - return; + if (!worker) return; this.emit(Events.Page.WorkerDestroyed, worker); this._workers.delete(event.sessionId); }); - this._frameManager.on(Events.FrameManager.FrameAttached, event => this.emit(Events.Page.FrameAttached, event)); - this._frameManager.on(Events.FrameManager.FrameDetached, event => this.emit(Events.Page.FrameDetached, event)); - this._frameManager.on(Events.FrameManager.FrameNavigated, event => this.emit(Events.Page.FrameNavigated, event)); + this._frameManager.on(Events.FrameManager.FrameAttached, (event) => + this.emit(Events.Page.FrameAttached, event) + ); + this._frameManager.on(Events.FrameManager.FrameDetached, (event) => + this.emit(Events.Page.FrameDetached, event) + ); + this._frameManager.on(Events.FrameManager.FrameNavigated, (event) => + this.emit(Events.Page.FrameNavigated, event) + ); const networkManager = this._frameManager.networkManager(); - networkManager.on(Events.NetworkManager.Request, event => this.emit(Events.Page.Request, event)); - networkManager.on(Events.NetworkManager.Response, event => this.emit(Events.Page.Response, event)); - networkManager.on(Events.NetworkManager.RequestFailed, event => this.emit(Events.Page.RequestFailed, event)); - networkManager.on(Events.NetworkManager.RequestFinished, event => this.emit(Events.Page.RequestFinished, event)); + networkManager.on(Events.NetworkManager.Request, (event) => + this.emit(Events.Page.Request, event) + ); + networkManager.on(Events.NetworkManager.Response, (event) => + this.emit(Events.Page.Response, event) + ); + networkManager.on(Events.NetworkManager.RequestFailed, (event) => + this.emit(Events.Page.RequestFailed, event) + ); + networkManager.on(Events.NetworkManager.RequestFinished, (event) => + this.emit(Events.Page.RequestFinished, event) + ); this._fileChooserInterceptors = new Set(); - client.on('Page.domContentEventFired', () => this.emit(Events.Page.DOMContentLoaded)); + client.on('Page.domContentEventFired', () => + this.emit(Events.Page.DOMContentLoaded) + ); client.on('Page.loadEventFired', () => this.emit(Events.Page.Load)); - client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event)); - client.on('Runtime.bindingCalled', event => this._onBindingCalled(event)); - client.on('Page.javascriptDialogOpening', event => this._onDialog(event)); - client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)); + client.on('Runtime.consoleAPICalled', (event) => this._onConsoleAPI(event)); + client.on('Runtime.bindingCalled', (event) => this._onBindingCalled(event)); + client.on('Page.javascriptDialogOpening', (event) => this._onDialog(event)); + client.on('Runtime.exceptionThrown', (exception) => + this._handleException(exception.exceptionDetails) + ); client.on('Inspector.targetCrashed', () => this._onTargetCrashed()); - client.on('Performance.metrics', event => this._emitMetrics(event)); - client.on('Log.entryAdded', event => this._onLogEntryAdded(event)); - client.on('Page.fileChooserOpened', event => this._onFileChooser(event)); + client.on('Performance.metrics', (event) => this._emitMetrics(event)); + client.on('Log.entryAdded', (event) => this._onLogEntryAdded(event)); + client.on('Page.fileChooserOpened', (event) => this._onFileChooser(event)); this._target._isClosedPromise.then(() => { this.emit(Events.Page.Close); this._closed = true; @@ -221,50 +269,76 @@ export class Page extends EventEmitter { async _initialize(): Promise { await Promise.all([ this._frameManager.initialize(), - this._client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}), + this._client.send('Target.setAutoAttach', { + autoAttach: true, + waitForDebuggerOnStart: false, + flatten: true, + }), this._client.send('Performance.enable', {}), this._client.send('Log.enable', {}), ]); } - async _onFileChooser(event: Protocol.Page.fileChooserOpenedPayload): Promise { - if (!this._fileChooserInterceptors.size) - return; + async _onFileChooser( + event: Protocol.Page.fileChooserOpenedPayload + ): Promise { + if (!this._fileChooserInterceptors.size) return; const frame = this._frameManager.frame(event.frameId); const context = await frame.executionContext(); const element = await context._adoptBackendNodeId(event.backendNodeId); const interceptors = Array.from(this._fileChooserInterceptors); this._fileChooserInterceptors.clear(); const fileChooser = new FileChooser(this._client, element, event); - for (const interceptor of interceptors) - interceptor.call(null, fileChooser); + for (const interceptor of interceptors) interceptor.call(null, fileChooser); } - async waitForFileChooser(options: {timeout?: number} = {}): Promise { + async waitForFileChooser( + options: { timeout?: number } = {} + ): Promise { if (!this._fileChooserInterceptors.size) - await this._client.send('Page.setInterceptFileChooserDialog', {enabled: true}); + await this._client.send('Page.setInterceptFileChooserDialog', { + enabled: true, + }); - const { - timeout = this._timeoutSettings.timeout(), - } = options; + const { timeout = this._timeoutSettings.timeout() } = options; let callback; - const promise = new Promise(x => callback = x); + const promise = new Promise((x) => (callback = x)); this._fileChooserInterceptors.add(callback); - return helper.waitWithTimeout(promise, 'waiting for file chooser', timeout).catch(error => { - this._fileChooserInterceptors.delete(callback); - throw error; - }); + return helper + .waitWithTimeout( + promise, + 'waiting for file chooser', + timeout + ) + .catch((error) => { + this._fileChooserInterceptors.delete(callback); + throw error; + }); } - async setGeolocation(options: {longitude: number; latitude: number; accuracy?: number}): Promise { - const {longitude, latitude, accuracy = 0} = options; + async setGeolocation(options: { + longitude: number; + latitude: number; + accuracy?: number; + }): Promise { + const { longitude, latitude, accuracy = 0 } = options; if (longitude < -180 || longitude > 180) - throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`); + throw new Error( + `Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.` + ); if (latitude < -90 || latitude > 90) - throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`); + throw new Error( + `Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.` + ); if (accuracy < 0) - throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`); - await this._client.send('Emulation.setGeolocationOverride', {longitude, latitude, accuracy}); + throw new Error( + `Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.` + ); + await this._client.send('Emulation.setGeolocationOverride', { + longitude, + latitude, + accuracy, + }); } target(): Target { @@ -284,11 +358,13 @@ export class Page extends EventEmitter { } _onLogEntryAdded(event: Protocol.Log.entryAddedPayload): void { - const {level, text, args, source, url, lineNumber} = event.entry; - if (args) - args.map(arg => helper.releaseObject(this._client, arg)); + const { level, text, args, source, url, lineNumber } = event.entry; + if (args) args.map((arg) => helper.releaseObject(this._client, arg)); if (source !== 'worker') - this.emit(Events.Page.Console, new ConsoleMessage(level, text, [], {url, lineNumber})); + this.emit( + Events.Page.Console, + new ConsoleMessage(level, text, [], { url, lineNumber }) + ); } mainFrame(): Frame { @@ -343,7 +419,10 @@ export class Page extends EventEmitter { return this.mainFrame().$(selector); } - async evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise { + async evaluateHandle( + pageFunction: Function | string, + ...args: unknown[] + ): Promise { const context = await this.mainFrame().executionContext(); return context.evaluateHandle(pageFunction, ...args); } @@ -353,11 +432,19 @@ export class Page extends EventEmitter { return context.queryObjects(prototypeHandle); } - async $eval(selector: string, pageFunction: Function | string, ...args: unknown[]): Promise { + async $eval( + selector: string, + pageFunction: Function | string, + ...args: unknown[] + ): Promise { return this.mainFrame().$eval(selector, pageFunction, ...args); } - async $$eval(selector: string, pageFunction: Function | string, ...args: unknown[]): Promise { + async $$eval( + selector: string, + pageFunction: Function | string, + ...args: unknown[] + ): Promise { return this.mainFrame().$$eval(selector, pageFunction, ...args); } @@ -370,25 +457,29 @@ export class Page extends EventEmitter { } async cookies(...urls: string[]): Promise { - const originalCookies = (await this._client.send('Network.getCookies', { - urls: urls.length ? urls : [this.url()] - })).cookies; + const originalCookies = ( + await this._client.send('Network.getCookies', { + urls: urls.length ? urls : [this.url()], + }) + ).cookies; const unsupportedCookieAttributes = ['priority']; - const filterUnsupportedAttributes = (cookie: Protocol.Network.Cookie): Protocol.Network.Cookie => { - for (const attr of unsupportedCookieAttributes) - delete cookie[attr]; + const filterUnsupportedAttributes = ( + cookie: Protocol.Network.Cookie + ): Protocol.Network.Cookie => { + for (const attr of unsupportedCookieAttributes) delete cookie[attr]; return cookie; }; return originalCookies.map(filterUnsupportedAttributes); } - async deleteCookie(...cookies: Protocol.Network.deleteCookiesParameters[]): Promise { + async deleteCookie( + ...cookies: Protocol.Network.deleteCookiesParameters[] + ): Promise { const pageURL = this.url(); for (const cookie of cookies) { const item = Object.assign({}, cookie); - if (!cookie.url && pageURL.startsWith('http')) - item.url = pageURL; + if (!cookie.url && pageURL.startsWith('http')) item.url = pageURL; await this._client.send('Network.deleteCookies', item); } } @@ -396,36 +487,59 @@ export class Page extends EventEmitter { async setCookie(...cookies: Protocol.Network.CookieParam[]): Promise { const pageURL = this.url(); const startsWithHTTP = pageURL.startsWith('http'); - const items = cookies.map(cookie => { + const items = cookies.map((cookie) => { const item = Object.assign({}, cookie); - if (!item.url && startsWithHTTP) - item.url = pageURL; - assert(item.url !== 'about:blank', `Blank page can not have cookie "${item.name}"`); - assert(!String.prototype.startsWith.call(item.url || '', 'data:'), `Data URL page can not have cookie "${item.name}"`); + if (!item.url && startsWithHTTP) item.url = pageURL; + assert( + item.url !== 'about:blank', + `Blank page can not have cookie "${item.name}"` + ); + assert( + !String.prototype.startsWith.call(item.url || '', 'data:'), + `Data URL page can not have cookie "${item.name}"` + ); return item; }); await this.deleteCookie(...items); if (items.length) - await this._client.send('Network.setCookies', {cookies: items}); + await this._client.send('Network.setCookies', { cookies: items }); } - async addScriptTag(options: {url?: string; path?: string; content?: string; type?: string}): Promise { + async addScriptTag(options: { + url?: string; + path?: string; + content?: string; + type?: string; + }): Promise { return this.mainFrame().addScriptTag(options); } - async addStyleTag(options: {url?: string; path?: string; content?: string}): Promise { + async addStyleTag(options: { + url?: string; + path?: string; + content?: string; + }): Promise { return this.mainFrame().addStyleTag(options); } - async exposeFunction(name: string, puppeteerFunction: Function): Promise { + async exposeFunction( + name: string, + puppeteerFunction: Function + ): Promise { if (this._pageBindings.has(name)) - throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`); + throw new Error( + `Failed to add page binding with name ${name}: window['${name}'] already exists!` + ); this._pageBindings.set(name, puppeteerFunction); const expression = helper.evaluationString(addPageBinding, name); - await this._client.send('Runtime.addBinding', {name: name}); - await this._client.send('Page.addScriptToEvaluateOnNewDocument', {source: expression}); - await Promise.all(this.frames().map(frame => frame.evaluate(expression).catch(debugError))); + await this._client.send('Runtime.addBinding', { name: name }); + await this._client.send('Page.addScriptToEvaluateOnNewDocument', { + source: expression, + }); + await Promise.all( + this.frames().map((frame) => frame.evaluate(expression).catch(debugError)) + ); function addPageBinding(bindingName): void { /* Cast window to any here as we're about to add properties to it @@ -443,8 +557,10 @@ export class Page extends EventEmitter { } const seq = (me['lastSeq'] || 0) + 1; me['lastSeq'] = seq; - const promise = new Promise((resolve, reject) => callbacks.set(seq, {resolve, reject})); - binding(JSON.stringify({name: bindingName, seq, args})); + const promise = new Promise((resolve, reject) => + callbacks.set(seq, { resolve, reject }) + ); + binding(JSON.stringify({ name: bindingName, seq, args })); return promise; }; } @@ -470,15 +586,14 @@ export class Page extends EventEmitter { _emitMetrics(event: Protocol.Performance.metricsPayload): void { this.emit(Events.Page.Metrics, { title: event.title, - metrics: this._buildMetricsObject(event.metrics) + metrics: this._buildMetricsObject(event.metrics), }); } _buildMetricsObject(metrics?: Protocol.Performance.Metric[]): Metrics { const result = {}; for (const metric of metrics || []) { - if (supportedMetrics.has(metric.name)) - result[metric.name] = metric.value; + if (supportedMetrics.has(metric.name)) result[metric.name] = metric.value; } return result; } @@ -490,7 +605,9 @@ export class Page extends EventEmitter { this.emit(Events.Page.PageError, err); } - async _onConsoleAPI(event: Protocol.Runtime.consoleAPICalledPayload): Promise { + async _onConsoleAPI( + event: Protocol.Runtime.consoleAPICalledPayload + ): Promise { if (event.executionContextId === 0) { // DevTools protocol stores the last 1000 console messages. These // messages are always reported even for removed execution contexts. In @@ -507,78 +624,119 @@ export class Page extends EventEmitter { // @see https://github.com/puppeteer/puppeteer/issues/3865 return; } - const context = this._frameManager.executionContextById(event.executionContextId); - const values = event.args.map(arg => createJSHandle(context, arg)); + const context = this._frameManager.executionContextById( + event.executionContextId + ); + const values = event.args.map((arg) => createJSHandle(context, arg)); this._addConsoleMessage(event.type, values, event.stackTrace); } - async _onBindingCalled(event: Protocol.Runtime.bindingCalledPayload): Promise { - const {name, seq, args} = JSON.parse(event.payload); + async _onBindingCalled( + event: Protocol.Runtime.bindingCalledPayload + ): Promise { + const { name, seq, args } = JSON.parse(event.payload); let expression = null; try { const result = await this._pageBindings.get(name)(...args); expression = helper.evaluationString(deliverResult, name, seq, result); } catch (error) { if (error instanceof Error) - expression = helper.evaluationString(deliverError, name, seq, error.message, error.stack); + expression = helper.evaluationString( + deliverError, + name, + seq, + error.message, + error.stack + ); else - expression = helper.evaluationString(deliverErrorValue, name, seq, error); + expression = helper.evaluationString( + deliverErrorValue, + name, + seq, + error + ); } - this._client.send('Runtime.evaluate', {expression, contextId: event.executionContextId}).catch(debugError); + this._client + .send('Runtime.evaluate', { + expression, + contextId: event.executionContextId, + }) + .catch(debugError); function deliverResult(name: string, seq: number, result: unknown): void { window[name]['callbacks'].get(seq).resolve(result); window[name]['callbacks'].delete(seq); } - function deliverError(name: string, seq: number, message: string, stack: string): void { + function deliverError( + name: string, + seq: number, + message: string, + stack: string + ): void { const error = new Error(message); error.stack = stack; window[name]['callbacks'].get(seq).reject(error); window[name]['callbacks'].delete(seq); } - function deliverErrorValue(name: string, seq: number, value: unknown): void { + function deliverErrorValue( + name: string, + seq: number, + value: unknown + ): void { window[name]['callbacks'].get(seq).reject(value); window[name]['callbacks'].delete(seq); } } - _addConsoleMessage(type: string, args: JSHandle[], stackTrace?: Protocol.Runtime.StackTrace): void { + _addConsoleMessage( + type: string, + args: JSHandle[], + stackTrace?: Protocol.Runtime.StackTrace + ): void { if (!this.listenerCount(Events.Page.Console)) { - args.forEach(arg => arg.dispose()); + args.forEach((arg) => arg.dispose()); return; } const textTokens = []; for (const arg of args) { const remoteObject = arg._remoteObject; - if (remoteObject.objectId) - textTokens.push(arg.toString()); - else - textTokens.push(helper.valueFromRemoteObject(remoteObject)); + if (remoteObject.objectId) textTokens.push(arg.toString()); + else textTokens.push(helper.valueFromRemoteObject(remoteObject)); } - const location = stackTrace && stackTrace.callFrames.length ? { - url: stackTrace.callFrames[0].url, - lineNumber: stackTrace.callFrames[0].lineNumber, - columnNumber: stackTrace.callFrames[0].columnNumber, - } : {}; - const message = new ConsoleMessage(type, textTokens.join(' '), args, location); + const location = + stackTrace && stackTrace.callFrames.length + ? { + url: stackTrace.callFrames[0].url, + lineNumber: stackTrace.callFrames[0].lineNumber, + columnNumber: stackTrace.callFrames[0].columnNumber, + } + : {}; + const message = new ConsoleMessage( + type, + textTokens.join(' '), + args, + location + ); this.emit(Events.Page.Console, message); } _onDialog(event: Protocol.Page.javascriptDialogOpeningPayload): void { let dialogType = null; - if (event.type === 'alert') - dialogType = Dialog.Type.Alert; - else if (event.type === 'confirm') - dialogType = Dialog.Type.Confirm; - else if (event.type === 'prompt') - dialogType = Dialog.Type.Prompt; + if (event.type === 'alert') dialogType = Dialog.Type.Alert; + else if (event.type === 'confirm') dialogType = Dialog.Type.Confirm; + else if (event.type === 'prompt') dialogType = Dialog.Type.Prompt; else if (event.type === 'beforeunload') dialogType = Dialog.Type.BeforeUnload; assert(dialogType, 'Unknown javascript dialog type: ' + event.type); - const dialog = new Dialog(this._client, dialogType, event.message, event.defaultPrompt); + const dialog = new Dialog( + this._client, + dialogType, + event.message, + event.defaultPrompt + ); this.emit(Events.Page.Dialog, dialog); } @@ -594,53 +752,76 @@ export class Page extends EventEmitter { await this._frameManager.mainFrame().setContent(html, options); } - async goto(url: string, options: WaitForOptions & { referer?: string }): Promise { + async goto( + url: string, + options: WaitForOptions & { referer?: string } + ): Promise { return await this._frameManager.mainFrame().goto(url, options); } async reload(options?: WaitForOptions): Promise { - const result = await Promise.all([ - this.waitForNavigation(options), - this._client.send('Page.reload') - ]); + const result = await Promise.all< + PuppeteerResponse, + Protocol.Page.reloadReturnValue + >([this.waitForNavigation(options), this._client.send('Page.reload')]); return result[0]; } - async waitForNavigation(options: WaitForOptions = {}): Promise { + async waitForNavigation( + options: WaitForOptions = {} + ): Promise { return await this._frameManager.mainFrame().waitForNavigation(options); } _sessionClosePromise(): Promise { if (!this._disconnectPromise) - this._disconnectPromise = new Promise(fulfill => this._client.once(Events.CDPSession.Disconnected, () => fulfill(new Error('Target closed')))); + this._disconnectPromise = new Promise((fulfill) => + this._client.once(Events.CDPSession.Disconnected, () => + fulfill(new Error('Target closed')) + ) + ); return this._disconnectPromise; } - async waitForRequest(urlOrPredicate: string | Function, options: {timeout?: number} = {}): Promise { - const { - timeout = this._timeoutSettings.timeout(), - } = options; - return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Request, request => { - if (helper.isString(urlOrPredicate)) - return (urlOrPredicate === request.url()); - if (typeof urlOrPredicate === 'function') - return !!(urlOrPredicate(request)); - return false; - }, timeout, this._sessionClosePromise()); - } - - async waitForResponse(urlOrPredicate: string | Function, options: {timeout?: number} = {}): Promise { - const { - timeout = this._timeoutSettings.timeout(), - } = options; - return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Response, response => { - if (helper.isString(urlOrPredicate)) - return (urlOrPredicate === response.url()); - if (typeof urlOrPredicate === 'function') - return !!(urlOrPredicate(response)); - return false; - }, timeout, this._sessionClosePromise()); + async waitForRequest( + urlOrPredicate: string | Function, + options: { timeout?: number } = {} + ): Promise { + const { timeout = this._timeoutSettings.timeout() } = options; + return helper.waitForEvent( + this._frameManager.networkManager(), + Events.NetworkManager.Request, + (request) => { + if (helper.isString(urlOrPredicate)) + return urlOrPredicate === request.url(); + if (typeof urlOrPredicate === 'function') + return !!urlOrPredicate(request); + return false; + }, + timeout, + this._sessionClosePromise() + ); + } + + async waitForResponse( + urlOrPredicate: string | Function, + options: { timeout?: number } = {} + ): Promise { + const { timeout = this._timeoutSettings.timeout() } = options; + return helper.waitForEvent( + this._frameManager.networkManager(), + Events.NetworkManager.Response, + (response) => { + if (helper.isString(urlOrPredicate)) + return urlOrPredicate === response.url(); + if (typeof urlOrPredicate === 'function') + return !!urlOrPredicate(response); + return false; + }, + timeout, + this._sessionClosePromise() + ); } async goBack(options: WaitForOptions): Promise { @@ -651,14 +832,19 @@ export class Page extends EventEmitter { return this._go(+1, options); } - async _go(delta: number, options: WaitForOptions): Promise { + async _go( + delta: number, + options: WaitForOptions + ): Promise { const history = await this._client.send('Page.getNavigationHistory'); const entry = history.entries[history.currentIndex + delta]; - if (!entry) - return null; - const result = await Promise.all([ + if (!entry) return null; + const result = await Promise.all< + PuppeteerResponse, + Protocol.Page.navigateToHistoryEntryReturnValue + >([ this.waitForNavigation(options), - this._client.send('Page.navigateToHistoryEntry', {entryId: entry.id}), + this._client.send('Page.navigateToHistoryEntry', { entryId: entry.id }), ]); return result[0]; } @@ -667,45 +853,61 @@ export class Page extends EventEmitter { await this._client.send('Page.bringToFront'); } - async emulate(options: {viewport: Viewport; userAgent: string}): Promise { + async emulate(options: { + viewport: Viewport; + userAgent: string; + }): Promise { await Promise.all([ this.setViewport(options.viewport), - this.setUserAgent(options.userAgent) + this.setUserAgent(options.userAgent), ]); } async setJavaScriptEnabled(enabled: boolean): Promise { - if (this._javascriptEnabled === enabled) - return; + if (this._javascriptEnabled === enabled) return; this._javascriptEnabled = enabled; - await this._client.send('Emulation.setScriptExecutionDisabled', {value: !enabled}); + await this._client.send('Emulation.setScriptExecutionDisabled', { + value: !enabled, + }); } async setBypassCSP(enabled: boolean): Promise { - await this._client.send('Page.setBypassCSP', {enabled}); + await this._client.send('Page.setBypassCSP', { enabled }); } async emulateMediaType(type?: string): Promise { - assert(type === 'screen' || type === 'print' || type === null, 'Unsupported media type: ' + type); - await this._client.send('Emulation.setEmulatedMedia', {media: type || ''}); + assert( + type === 'screen' || type === 'print' || type === null, + 'Unsupported media type: ' + type + ); + await this._client.send('Emulation.setEmulatedMedia', { + media: type || '', + }); } async emulateMediaFeatures(features?: MediaFeature[]): Promise { if (features === null) - await this._client.send('Emulation.setEmulatedMedia', {features: null}); + await this._client.send('Emulation.setEmulatedMedia', { features: null }); if (Array.isArray(features)) { - features.every(mediaFeature => { + features.every((mediaFeature) => { const name = mediaFeature.name; - assert(/^prefers-(?:color-scheme|reduced-motion)$/.test(name), 'Unsupported media feature: ' + name); + assert( + /^prefers-(?:color-scheme|reduced-motion)$/.test(name), + 'Unsupported media feature: ' + name + ); return true; }); - await this._client.send('Emulation.setEmulatedMedia', {features: features}); + await this._client.send('Emulation.setEmulatedMedia', { + features: features, + }); } } async emulateTimezone(timezoneId?: string): Promise { try { - await this._client.send('Emulation.setTimezoneOverride', {timezoneId: timezoneId || ''}); + await this._client.send('Emulation.setTimezoneOverride', { + timezoneId: timezoneId || '', + }); } catch (error) { if (error.message.includes('Invalid timezone')) throw new Error(`Invalid timezone ID: ${timezoneId}`); @@ -716,21 +918,30 @@ export class Page extends EventEmitter { async setViewport(viewport: Viewport): Promise { const needsReload = await this._emulationManager.emulateViewport(viewport); this._viewport = viewport; - if (needsReload) - await this.reload(); + if (needsReload) await this.reload(); } viewport(): Viewport | null { return this._viewport; } - async evaluate(pageFunction: Function | string, ...args: unknown[]): Promise { - return this._frameManager.mainFrame().evaluate(pageFunction, ...args); + async evaluate( + pageFunction: Function | string, + ...args: unknown[] + ): Promise { + return this._frameManager + .mainFrame() + .evaluate(pageFunction, ...args); } - async evaluateOnNewDocument(pageFunction: Function | string, ...args: unknown[]): Promise { + async evaluateOnNewDocument( + pageFunction: Function | string, + ...args: unknown[] + ): Promise { const source = helper.evaluationString(pageFunction, ...args); - await this._client.send('Page.addScriptToEvaluateOnNewDocument', {source}); + await this._client.send('Page.addScriptToEvaluateOnNewDocument', { + source, + }); } async setCacheEnabled(enabled = true): Promise { @@ -742,40 +953,88 @@ export class Page extends EventEmitter { // options.type takes precedence over inferring the type from options.path // because it may be a 0-length file with no extension created beforehand (i.e. as a temp file). if (options.type) { - assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type); + assert( + options.type === 'png' || options.type === 'jpeg', + 'Unknown options.type value: ' + options.type + ); screenshotType = options.type; } else if (options.path) { const mimeType = mime.getType(options.path); - if (mimeType === 'image/png') - screenshotType = 'png'; - else if (mimeType === 'image/jpeg') - screenshotType = 'jpeg'; + if (mimeType === 'image/png') screenshotType = 'png'; + else if (mimeType === 'image/jpeg') screenshotType = 'jpeg'; assert(screenshotType, 'Unsupported screenshot mime type: ' + mimeType); } - if (!screenshotType) - screenshotType = 'png'; + if (!screenshotType) screenshotType = 'png'; if (options.quality) { - assert(screenshotType === 'jpeg', 'options.quality is unsupported for the ' + screenshotType + ' screenshots'); - assert(typeof options.quality === 'number', 'Expected options.quality to be a number but found ' + (typeof options.quality)); - assert(Number.isInteger(options.quality), 'Expected options.quality to be an integer'); - assert(options.quality >= 0 && options.quality <= 100, 'Expected options.quality to be between 0 and 100 (inclusive), got ' + options.quality); + assert( + screenshotType === 'jpeg', + 'options.quality is unsupported for the ' + + screenshotType + + ' screenshots' + ); + assert( + typeof options.quality === 'number', + 'Expected options.quality to be a number but found ' + + typeof options.quality + ); + assert( + Number.isInteger(options.quality), + 'Expected options.quality to be an integer' + ); + assert( + options.quality >= 0 && options.quality <= 100, + 'Expected options.quality to be between 0 and 100 (inclusive), got ' + + options.quality + ); } - assert(!options.clip || !options.fullPage, 'options.clip and options.fullPage are exclusive'); + assert( + !options.clip || !options.fullPage, + 'options.clip and options.fullPage are exclusive' + ); if (options.clip) { - assert(typeof options.clip.x === 'number', 'Expected options.clip.x to be a number but found ' + (typeof options.clip.x)); - assert(typeof options.clip.y === 'number', 'Expected options.clip.y to be a number but found ' + (typeof options.clip.y)); - assert(typeof options.clip.width === 'number', 'Expected options.clip.width to be a number but found ' + (typeof options.clip.width)); - assert(typeof options.clip.height === 'number', 'Expected options.clip.height to be a number but found ' + (typeof options.clip.height)); - assert(options.clip.width !== 0, 'Expected options.clip.width not to be 0.'); - assert(options.clip.height !== 0, 'Expected options.clip.height not to be 0.'); + assert( + typeof options.clip.x === 'number', + 'Expected options.clip.x to be a number but found ' + + typeof options.clip.x + ); + assert( + typeof options.clip.y === 'number', + 'Expected options.clip.y to be a number but found ' + + typeof options.clip.y + ); + assert( + typeof options.clip.width === 'number', + 'Expected options.clip.width to be a number but found ' + + typeof options.clip.width + ); + assert( + typeof options.clip.height === 'number', + 'Expected options.clip.height to be a number but found ' + + typeof options.clip.height + ); + assert( + options.clip.width !== 0, + 'Expected options.clip.width not to be 0.' + ); + assert( + options.clip.height !== 0, + 'Expected options.clip.height not to be 0.' + ); } - return this._screenshotTaskQueue.postTask(this._screenshotTask.bind(this, screenshotType, options)); + return this._screenshotTaskQueue.postTask( + this._screenshotTask.bind(this, screenshotType, options) + ); } - async _screenshotTask(format: 'png' | 'jpeg', options?: ScreenshotOptions): Promise { - await this._client.send('Target.activateTarget', {targetId: this._target._targetId}); + async _screenshotTask( + format: 'png' | 'jpeg', + options?: ScreenshotOptions + ): Promise { + await this._client.send('Target.activateTarget', { + targetId: this._target._targetId, + }); let clip = options.clip ? processClip(options.clip) : undefined; if (options.fullPage) { @@ -784,36 +1043,52 @@ export class Page extends EventEmitter { const height = Math.ceil(metrics.contentSize.height); // Overwrite clip for full page at all times. - clip = {x: 0, y: 0, width, height, scale: 1}; - const { - isMobile = false, - deviceScaleFactor = 1, - isLandscape = false - } = this._viewport || {}; - const screenOrientation: Protocol.Emulation.ScreenOrientation = isLandscape ? {angle: 90, type: 'landscapePrimary'} : {angle: 0, type: 'portraitPrimary'}; - await this._client.send('Emulation.setDeviceMetricsOverride', {mobile: isMobile, width, height, deviceScaleFactor, screenOrientation}); + clip = { x: 0, y: 0, width, height, scale: 1 }; + const { isMobile = false, deviceScaleFactor = 1, isLandscape = false } = + this._viewport || {}; + const screenOrientation: Protocol.Emulation.ScreenOrientation = isLandscape + ? { angle: 90, type: 'landscapePrimary' } + : { angle: 0, type: 'portraitPrimary' }; + await this._client.send('Emulation.setDeviceMetricsOverride', { + mobile: isMobile, + width, + height, + deviceScaleFactor, + screenOrientation, + }); } - const shouldSetDefaultBackground = options.omitBackground && format === 'png'; + const shouldSetDefaultBackground = + options.omitBackground && format === 'png'; if (shouldSetDefaultBackground) - await this._client.send('Emulation.setDefaultBackgroundColorOverride', {color: {r: 0, g: 0, b: 0, a: 0}}); - const result = await this._client.send('Page.captureScreenshot', {format, quality: options.quality, clip}); + await this._client.send('Emulation.setDefaultBackgroundColorOverride', { + color: { r: 0, g: 0, b: 0, a: 0 }, + }); + const result = await this._client.send('Page.captureScreenshot', { + format, + quality: options.quality, + clip, + }); if (shouldSetDefaultBackground) await this._client.send('Emulation.setDefaultBackgroundColorOverride'); if (options.fullPage && this._viewport) await this.setViewport(this._viewport); - const buffer = options.encoding === 'base64' ? result.data : Buffer.from(result.data, 'base64'); - if (options.path) - await writeFileAsync(options.path, buffer); + const buffer = + options.encoding === 'base64' + ? result.data + : Buffer.from(result.data, 'base64'); + if (options.path) await writeFileAsync(options.path, buffer); return buffer; - function processClip(clip: ScreenshotClip): ScreenshotClip & { scale: number } { + function processClip( + clip: ScreenshotClip + ): ScreenshotClip & { scale: number } { const x = Math.round(clip.x); const y = Math.round(clip.y); const width = Math.round(clip.width + clip.x - x); const height = Math.round(clip.height + clip.y - y); - return {x, y, width, height, scale: 1}; + return { x, y, width, height, scale: 1 }; } } @@ -828,7 +1103,7 @@ export class Page extends EventEmitter { pageRanges = '', preferCSSPageSize = false, margin = {}, - path = null + path = null, } = options; let paperWidth = 8.5; @@ -840,7 +1115,8 @@ export class Page extends EventEmitter { paperHeight = format.height; } else { paperWidth = convertPrintParameterToInches(options.width) || paperWidth; - paperHeight = convertPrintParameterToInches(options.height) || paperHeight; + paperHeight = + convertPrintParameterToInches(options.height) || paperHeight; } const marginTop = convertPrintParameterToInches(margin.top) || 0; @@ -863,7 +1139,7 @@ export class Page extends EventEmitter { marginLeft, marginRight, pageRanges, - preferCSSPageSize + preferCSSPageSize, }); return await helper.readProtocolStream(this._client, result.stream, path); } @@ -872,13 +1148,20 @@ export class Page extends EventEmitter { return this.mainFrame().title(); } - async close(options: {runBeforeUnload?: boolean} = {runBeforeUnload: undefined}): Promise { - assert(!!this._client._connection, 'Protocol error: Connection closed. Most likely the page has been closed.'); + async close( + options: { runBeforeUnload?: boolean } = { runBeforeUnload: undefined } + ): Promise { + assert( + !!this._client._connection, + 'Protocol error: Connection closed. Most likely the page has been closed.' + ); const runBeforeUnload = !!options.runBeforeUnload; if (runBeforeUnload) { await this._client.send('Page.close'); } else { - await this._client._connection.send('Target.closeTarget', {targetId: this._target._targetId}); + await this._client._connection.send('Target.closeTarget', { + targetId: this._target._targetId, + }); await this._target._isClosedPromise; } } @@ -891,11 +1174,14 @@ export class Page extends EventEmitter { return this._mouse; } - click(selector: string, options: { - delay?: number; - button?: MouseButtonInput; - clickCount?: number; - } = {}): Promise { + click( + selector: string, + options: { + delay?: number; + button?: MouseButtonInput; + clickCount?: number; + } = {} + ): Promise { return this.mainFrame().click(selector, options); } @@ -915,39 +1201,61 @@ export class Page extends EventEmitter { return this.mainFrame().tap(selector); } - type(selector: string, text: string, options?: {delay: number}): Promise { + type( + selector: string, + text: string, + options?: { delay: number } + ): Promise { return this.mainFrame().type(selector, text, options); } - waitFor(selectorOrFunctionOrTimeout: string | number | Function, options: { - visible?: boolean; - hidden?: boolean; - timeout?: number; - polling?: string|number; - } = {}, ...args: unknown[]): Promise { - return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args); - } - - waitForSelector(selector: string, options: { - visible?: boolean; - hidden?: boolean; - timeout?: number; - } = {}): Promise { + waitFor( + selectorOrFunctionOrTimeout: string | number | Function, + options: { + visible?: boolean; + hidden?: boolean; + timeout?: number; + polling?: string | number; + } = {}, + ...args: unknown[] + ): Promise { + return this.mainFrame().waitFor( + selectorOrFunctionOrTimeout, + options, + ...args + ); + } + + waitForSelector( + selector: string, + options: { + visible?: boolean; + hidden?: boolean; + timeout?: number; + } = {} + ): Promise { return this.mainFrame().waitForSelector(selector, options); } - waitForXPath(xpath: string, options: { - visible?: boolean; - hidden?: boolean; - timeout?: number; - } = {}): Promise { + waitForXPath( + xpath: string, + options: { + visible?: boolean; + hidden?: boolean; + timeout?: number; + } = {} + ): Promise { return this.mainFrame().waitForXPath(xpath, options); } - waitForFunction(pageFunction: Function, options: { - timeout?: number; - polling?: string|number; - } = {}, ...args: unknown[]): Promise { + waitForFunction( + pageFunction: Function, + options: { + timeout?: number; + polling?: string | number; + } = {}, + ...args: unknown[] + ): Promise { return this.mainFrame().waitForFunction(pageFunction, options, ...args); } } @@ -968,23 +1276,23 @@ const supportedMetrics = new Set([ 'JSHeapTotalSize', ]); - const unitToPixels = { - 'px': 1, - 'in': 96, - 'cm': 37.8, - 'mm': 3.78 + px: 1, + in: 96, + cm: 37.8, + mm: 3.78, }; -function convertPrintParameterToInches(parameter?: string|number): number | undefined { - if (typeof parameter === 'undefined') - return undefined; +function convertPrintParameterToInches( + parameter?: string | number +): number | undefined { + if (typeof parameter === 'undefined') return undefined; let pixels; if (helper.isNumber(parameter)) { // Treat numbers as pixel values to be aligned with phantom's paperSize. - pixels = /** @type {number} */ (parameter); + pixels = /** @type {number} */ parameter; } else if (helper.isString(parameter)) { - const text = /** @type {string} */ (parameter); + const text = /** @type {string} */ parameter; let unit = text.substring(text.length - 2).toLowerCase(); let valueText = ''; if (unitToPixels.hasOwnProperty(unit)) { @@ -999,7 +1307,9 @@ function convertPrintParameterToInches(parameter?: string|number): number | unde assert(!isNaN(value), 'Failed to parse parameter value: ' + text); pixels = value * unitToPixels[unit]; } else { - throw new Error('page.pdf() Cannot handle parameter type: ' + (typeof parameter)); + throw new Error( + 'page.pdf() Cannot handle parameter type: ' + typeof parameter + ); } return pixels / 96; } @@ -1016,7 +1326,12 @@ export class ConsoleMessage { _args: JSHandle[]; _location: ConsoleMessageLocation; - constructor(type: string, text: string, args: JSHandle[], location: ConsoleMessageLocation = {}) { + constructor( + type: string, + text: string, + args: JSHandle[], + location: ConsoleMessageLocation = {} + ) { this._type = type; this._text = text; this._args = args; @@ -1046,7 +1361,11 @@ export class FileChooser { _multiple: boolean; _handled = false; - constructor(client: CDPSession, element: ElementHandle, event: Protocol.Page.fileChooserOpenedPayload) { + constructor( + client: CDPSession, + element: ElementHandle, + event: Protocol.Page.fileChooserOpenedPayload + ) { this._client = client; this._element = element; this._multiple = event.mode !== 'selectSingle'; @@ -1057,13 +1376,19 @@ export class FileChooser { } async accept(filePaths: string[]): Promise { - assert(!this._handled, 'Cannot accept FileChooser which is already handled!'); + assert( + !this._handled, + 'Cannot accept FileChooser which is already handled!' + ); this._handled = true; await this._element.uploadFile(...filePaths); } async cancel(): Promise { - assert(!this._handled, 'Cannot cancel FileChooser which is already handled!'); + assert( + !this._handled, + 'Cannot cancel FileChooser which is already handled!' + ); this._handled = true; } } diff --git a/src/PipeTransport.ts b/src/PipeTransport.ts index a3bebb0bc6c4d..7adf79a333f35 100644 --- a/src/PipeTransport.ts +++ b/src/PipeTransport.ts @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {helper, debugError, PuppeteerEventListener} from './helper'; -import type {ConnectionTransport} from './ConnectionTransport'; +import { helper, debugError, PuppeteerEventListener } from './helper'; +import type { ConnectionTransport } from './ConnectionTransport'; export class PipeTransport implements ConnectionTransport { _pipeWrite: NodeJS.WritableStream; @@ -24,14 +24,18 @@ export class PipeTransport implements ConnectionTransport { onclose?: () => void; onmessage?: () => void; - constructor(pipeWrite: NodeJS.WritableStream, pipeRead: NodeJS.ReadableStream) { + constructor( + pipeWrite: NodeJS.WritableStream, + pipeRead: NodeJS.ReadableStream + ) { this._pipeWrite = pipeWrite; this._pendingMessage = ''; this._eventListeners = [ - helper.addEventListener(pipeRead, 'data', buffer => this._dispatch(buffer)), + helper.addEventListener(pipeRead, 'data', (buffer) => + this._dispatch(buffer) + ), helper.addEventListener(pipeRead, 'close', () => { - if (this.onclose) - this.onclose.call(null); + if (this.onclose) this.onclose.call(null); }), helper.addEventListener(pipeRead, 'error', debugError), helper.addEventListener(pipeWrite, 'error', debugError), @@ -52,8 +56,7 @@ export class PipeTransport implements ConnectionTransport { return; } const message = this._pendingMessage + buffer.toString(undefined, 0, end); - if (this.onmessage) - this.onmessage.call(null, message); + if (this.onmessage) this.onmessage.call(null, message); let start = end + 1; end = buffer.indexOf('\0', start); diff --git a/src/Puppeteer.ts b/src/Puppeteer.ts index dbcbd99c0da9b..5a059c43d49d8 100644 --- a/src/Puppeteer.ts +++ b/src/Puppeteer.ts @@ -14,14 +14,19 @@ * limitations under the License. */ import Launcher from './Launcher'; -import type {LaunchOptions, ChromeArgOptions, BrowserOptions, ProductLauncher} from './Launcher'; -import {BrowserFetcher, BrowserFetcherOptions} from './BrowserFetcher'; -import {puppeteerErrors, PuppeteerErrors} from './Errors'; -import type {ConnectionTransport} from './ConnectionTransport'; - -import {devicesMap} from './DeviceDescriptors'; -import type {DevicesMap} from './/DeviceDescriptors'; -import {Browser} from './Browser'; +import type { + LaunchOptions, + ChromeArgOptions, + BrowserOptions, + ProductLauncher, +} from './Launcher'; +import { BrowserFetcher, BrowserFetcherOptions } from './BrowserFetcher'; +import { puppeteerErrors, PuppeteerErrors } from './Errors'; +import type { ConnectionTransport } from './ConnectionTransport'; + +import { devicesMap } from './DeviceDescriptors'; +import type { DevicesMap } from './/DeviceDescriptors'; +import { Browser } from './Browser'; import * as QueryHandler from './QueryHandler'; export class Puppeteer { @@ -32,7 +37,12 @@ export class Puppeteer { __productName: string; _lazyLauncher: ProductLauncher; - constructor(projectRoot: string, preferredRevision: string, isPuppeteerCore: boolean, productName: string) { + constructor( + projectRoot: string, + preferredRevision: string, + isPuppeteerCore: boolean, + productName: string + ) { this._projectRoot = projectRoot; this._preferredRevision = preferredRevision; this._isPuppeteerCore = isPuppeteerCore; @@ -40,26 +50,29 @@ export class Puppeteer { this.__productName = productName; } - launch(options: LaunchOptions & ChromeArgOptions & BrowserOptions & {product?: string; extraPrefsFirefox?: {}} = {}): Promise { - if (options.product) - this._productName = options.product; + launch( + options: LaunchOptions & + ChromeArgOptions & + BrowserOptions & { product?: string; extraPrefsFirefox?: {} } = {} + ): Promise { + if (options.product) this._productName = options.product; return this._launcher.launch(options); } - connect(options: BrowserOptions & { - browserWSEndpoint?: string; - browserURL?: string; - transport?: ConnectionTransport; - product?: string; - }): Promise { - if (options.product) - this._productName = options.product; + connect( + options: BrowserOptions & { + browserWSEndpoint?: string; + browserURL?: string; + transport?: ConnectionTransport; + product?: string; + } + ): Promise { + if (options.product) this._productName = options.product; return this._launcher.connect(options); } set _productName(name: string) { - if (this.__productName !== name) - this._changedProduct = true; + if (this.__productName !== name) this._changedProduct = true; this.__productName = name; } @@ -72,7 +85,11 @@ export class Puppeteer { } get _launcher(): ProductLauncher { - if (!this._lazyLauncher || this._lazyLauncher.product !== this._productName || this._changedProduct) { + if ( + !this._lazyLauncher || + this._lazyLauncher.product !== this._productName || + this._changedProduct + ) { // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-var-requires const packageJson = require('../package.json'); @@ -85,7 +102,12 @@ export class Puppeteer { this._preferredRevision = packageJson.puppeteer.chromium_revision; } this._changedProduct = false; - this._lazyLauncher = Launcher(this._projectRoot, this._preferredRevision, this._isPuppeteerCore, this._productName); + this._lazyLauncher = Launcher( + this._projectRoot, + this._preferredRevision, + this._isPuppeteerCore, + this._productName + ); } return this._lazyLauncher; } @@ -111,7 +133,10 @@ export class Puppeteer { } // eslint-disable-next-line @typescript-eslint/camelcase - __experimental_registerCustomQueryHandler(name: string, queryHandler: QueryHandler.QueryHandler): void { + __experimental_registerCustomQueryHandler( + name: string, + queryHandler: QueryHandler.QueryHandler + ): void { QueryHandler.registerCustomQueryHandler(name, queryHandler); } diff --git a/src/QueryHandler.ts b/src/QueryHandler.ts index 98f6b1e896c35..ca8cc6b4d1497 100644 --- a/src/QueryHandler.ts +++ b/src/QueryHandler.ts @@ -15,12 +15,18 @@ */ export interface QueryHandler { - (element: Element | Document, selector: string): Element | Element[] | NodeListOf; + (element: Element | Document, selector: string): + | Element + | Element[] + | NodeListOf; } const _customQueryHandlers = new Map(); -export function registerCustomQueryHandler(name: string, handler: Function): void { +export function registerCustomQueryHandler( + name: string, + handler: Function +): void { if (_customQueryHandlers.get(name)) throw new Error(`A custom query handler named "${name}" already exists`); @@ -46,22 +52,26 @@ export function clearQueryHandlers(): void { _customQueryHandlers.clear(); } -export function getQueryHandlerAndSelector(selector: string, defaultQueryHandler: QueryHandler): - { updatedSelector: string; queryHandler: QueryHandler} { +export function getQueryHandlerAndSelector( + selector: string, + defaultQueryHandler: QueryHandler +): { updatedSelector: string; queryHandler: QueryHandler } { const hasCustomQueryHandler = /^[a-zA-Z]+\//.test(selector); if (!hasCustomQueryHandler) - return {updatedSelector: selector, queryHandler: defaultQueryHandler}; + return { updatedSelector: selector, queryHandler: defaultQueryHandler }; const index = selector.indexOf('/'); const name = selector.slice(0, index); const updatedSelector = selector.slice(index + 1); const queryHandler = customQueryHandlers().get(name); if (!queryHandler) - throw new Error(`Query set to use "${name}", but no query handler of that name was found`); + throw new Error( + `Query set to use "${name}", but no query handler of that name was found` + ); return { updatedSelector, - queryHandler + queryHandler, }; } @@ -70,5 +80,5 @@ module.exports = { unregisterCustomQueryHandler, customQueryHandlers, getQueryHandlerAndSelector, - clearQueryHandlers + clearQueryHandlers, }; diff --git a/src/Target.ts b/src/Target.ts index 6475d0fb6d31b..894d3fa8f1301 100644 --- a/src/Target.ts +++ b/src/Target.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import {Events} from './Events'; -import {Page} from './Page'; -import {Worker as PuppeteerWorker} from './Worker'; -import {CDPSession} from './Connection'; -import {TaskQueue} from './TaskQueue'; -import {Browser, BrowserContext} from './Browser'; -import type {Viewport} from './PuppeteerViewport'; +import { Events } from './Events'; +import { Page } from './Page'; +import { Worker as PuppeteerWorker } from './Worker'; +import { CDPSession } from './Connection'; +import { TaskQueue } from './TaskQueue'; +import { Browser, BrowserContext } from './Browser'; +import type { Viewport } from './PuppeteerViewport'; export class Target { _targetInfo: Protocol.Target.TargetInfo; @@ -38,7 +38,14 @@ export class Target { _closedCallback: () => void; _isInitialized: boolean; - constructor(targetInfo: Protocol.Target.TargetInfo, browserContext: BrowserContext, sessionFactory: () => Promise, ignoreHTTPSErrors: boolean, defaultViewport: Viewport | null, screenshotTaskQueue: TaskQueue) { + constructor( + targetInfo: Protocol.Target.TargetInfo, + browserContext: BrowserContext, + sessionFactory: () => Promise, + ignoreHTTPSErrors: boolean, + defaultViewport: Viewport | null, + screenshotTaskQueue: TaskQueue + ) { this._targetInfo = targetInfo; this._browserContext = browserContext; this._targetId = targetInfo.targetId; @@ -50,23 +57,25 @@ export class Target { this._pagePromise = null; /** @type {?Promise} */ this._workerPromise = null; - this._initializedPromise = new Promise(fulfill => this._initializedCallback = fulfill).then(async success => { - if (!success) - return false; + this._initializedPromise = new Promise( + (fulfill) => (this._initializedCallback = fulfill) + ).then(async (success) => { + if (!success) return false; const opener = this.opener(); if (!opener || !opener._pagePromise || this.type() !== 'page') return true; const openerPage = await opener._pagePromise; - if (!openerPage.listenerCount(Events.Page.Popup)) - return true; + if (!openerPage.listenerCount(Events.Page.Popup)) return true; const popupPage = await this.page(); openerPage.emit(Events.Page.Popup, popupPage); return true; }); - this._isClosedPromise = new Promise(fulfill => this._closedCallback = fulfill); - this._isInitialized = this._targetInfo.type !== 'page' || this._targetInfo.url !== ''; - if (this._isInitialized) - this._initializedCallback(true); + this._isClosedPromise = new Promise( + (fulfill) => (this._closedCallback = fulfill) + ); + this._isInitialized = + this._targetInfo.type !== 'page' || this._targetInfo.url !== ''; + if (this._isInitialized) this._initializedCallback(true); } createCDPSession(): Promise { @@ -74,20 +83,41 @@ export class Target { } async page(): Promise { - if ((this._targetInfo.type === 'page' || this._targetInfo.type === 'background_page') && !this._pagePromise) { - this._pagePromise = this._sessionFactory() - .then(client => Page.create(client, this, this._ignoreHTTPSErrors, this._defaultViewport, this._screenshotTaskQueue)); + if ( + (this._targetInfo.type === 'page' || + this._targetInfo.type === 'background_page') && + !this._pagePromise + ) { + this._pagePromise = this._sessionFactory().then((client) => + Page.create( + client, + this, + this._ignoreHTTPSErrors, + this._defaultViewport, + this._screenshotTaskQueue + ) + ); } return this._pagePromise; } async worker(): Promise { - if (this._targetInfo.type !== 'service_worker' && this._targetInfo.type !== 'shared_worker') + if ( + this._targetInfo.type !== 'service_worker' && + this._targetInfo.type !== 'shared_worker' + ) return null; if (!this._workerPromise) { // TODO(einbinder): Make workers send their console logs. - this._workerPromise = this._sessionFactory() - .then(client => new PuppeteerWorker(client, this._targetInfo.url, () => {} /* consoleAPICalled */, () => {} /* exceptionThrown */)); + this._workerPromise = this._sessionFactory().then( + (client) => + new PuppeteerWorker( + client, + this._targetInfo.url, + () => {} /* consoleAPICalled */, + () => {} /* exceptionThrown */ + ) + ); } return this._workerPromise; } @@ -96,9 +126,21 @@ export class Target { return this._targetInfo.url; } - type(): 'page'|'background_page'|'service_worker'|'shared_worker'|'other'|'browser'{ + type(): + | 'page' + | 'background_page' + | 'service_worker' + | 'shared_worker' + | 'other' + | 'browser' { const type = this._targetInfo.type; - if (type === 'page' || type === 'background_page' || type === 'service_worker' || type === 'shared_worker' || type === 'browser') + if ( + type === 'page' || + type === 'background_page' || + type === 'service_worker' || + type === 'shared_worker' || + type === 'browser' + ) return type; return 'other'; } @@ -112,16 +154,18 @@ export class Target { } opener(): Target | null { - const {openerId} = this._targetInfo; - if (!openerId) - return null; + const { openerId } = this._targetInfo; + if (!openerId) return null; return this.browser()._targets.get(openerId); } _targetInfoChanged(targetInfo: Protocol.Target.TargetInfo): void { this._targetInfo = targetInfo; - if (!this._isInitialized && (this._targetInfo.type !== 'page' || this._targetInfo.url !== '')) { + if ( + !this._isInitialized && + (this._targetInfo.type !== 'page' || this._targetInfo.url !== '') + ) { this._isInitialized = true; this._initializedCallback(true); return; diff --git a/src/TimeoutSettings.ts b/src/TimeoutSettings.ts index 7113cb6d4c1e5..bb07751f92aff 100644 --- a/src/TimeoutSettings.ts +++ b/src/TimeoutSettings.ts @@ -39,14 +39,12 @@ export class TimeoutSettings { navigationTimeout(): number { if (this._defaultNavigationTimeout !== null) return this._defaultNavigationTimeout; - if (this._defaultTimeout !== null) - return this._defaultTimeout; + if (this._defaultTimeout !== null) return this._defaultTimeout; return DEFAULT_TIMEOUT; } timeout(): number { - if (this._defaultTimeout !== null) - return this._defaultTimeout; + if (this._defaultTimeout !== null) return this._defaultTimeout; return DEFAULT_TIMEOUT; } } diff --git a/src/Tracing.ts b/src/Tracing.ts index aaa48a0374517..8f19840a54aea 100644 --- a/src/Tracing.ts +++ b/src/Tracing.ts @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {helper, assert} from './helper'; -import {CDPSession} from './Connection'; +import { helper, assert } from './helper'; +import { CDPSession } from './Connection'; interface TracingOptions { path?: string; @@ -25,20 +25,31 @@ interface TracingOptions { export class Tracing { _client: CDPSession; _recording = false; - _path = ''; + _path = ''; constructor(client: CDPSession) { this._client = client; } async start(options: TracingOptions = {}): Promise { - assert(!this._recording, 'Cannot start recording trace while already recording trace.'); + assert( + !this._recording, + 'Cannot start recording trace while already recording trace.' + ); const defaultCategories = [ - '-*', 'devtools.timeline', 'v8.execute', 'disabled-by-default-devtools.timeline', - 'disabled-by-default-devtools.timeline.frame', 'toplevel', - 'blink.console', 'blink.user_timing', 'latencyInfo', 'disabled-by-default-devtools.timeline.stack', - 'disabled-by-default-v8.cpu_profiler', 'disabled-by-default-v8.cpu_profiler.hires' + '-*', + 'devtools.timeline', + 'v8.execute', + 'disabled-by-default-devtools.timeline', + 'disabled-by-default-devtools.timeline.frame', + 'toplevel', + 'blink.console', + 'blink.user_timing', + 'latencyInfo', + 'disabled-by-default-devtools.timeline.stack', + 'disabled-by-default-v8.cpu_profiler', + 'disabled-by-default-v8.cpu_profiler.hires', ]; const { path = null, @@ -46,22 +57,23 @@ export class Tracing { categories = defaultCategories, } = options; - if (screenshots) - categories.push('disabled-by-default-devtools.screenshot'); + if (screenshots) categories.push('disabled-by-default-devtools.screenshot'); this._path = path; this._recording = true; await this._client.send('Tracing.start', { transferMode: 'ReturnAsStream', - categories: categories.join(',') + categories: categories.join(','), }); } async stop(): Promise { let fulfill: (value: Buffer) => void; - const contentPromise = new Promise(x => fulfill = x); - this._client.once('Tracing.tracingComplete', event => { - helper.readProtocolStream(this._client, event.stream, this._path).then(fulfill); + const contentPromise = new Promise((x) => (fulfill = x)); + this._client.once('Tracing.tracingComplete', (event) => { + helper + .readProtocolStream(this._client, event.stream, this._path) + .then(fulfill); }); await this._client.send('Tracing.end'); this._recording = false; diff --git a/src/USKeyboardLayout.ts b/src/USKeyboardLayout.ts index 52a7f2a58f96c..69da035557646 100644 --- a/src/USKeyboardLayout.ts +++ b/src/USKeyboardLayout.ts @@ -14,274 +14,656 @@ * limitations under the License. */ - export interface KeyDefinition { - keyCode?: number; - shiftKeyCode?: number; - key?: string; - shiftKey?: string; - code?: string; - text?: string; - shiftText?: string; - location?: number; + keyCode?: number; + shiftKeyCode?: number; + key?: string; + shiftKey?: string; + code?: string; + text?: string; + shiftText?: string; + location?: number; } -export type KeyInput = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'Power'|'Eject'|'Abort'|'Help'|'Backspace'|'Tab'|'Numpad5'|'NumpadEnter'|'Enter'|'\r'|'\n'|'ShiftLeft'|'ShiftRight'|'ControlLeft'|'ControlRight'|'AltLeft'|'AltRight'|'Pause'|'CapsLock'|'Escape'|'Convert'|'NonConvert'|'Space'|'Numpad9'|'PageUp'|'Numpad3'|'PageDown'|'End'|'Numpad1'|'Home'|'Numpad7'|'ArrowLeft'|'Numpad4'|'Numpad8'|'ArrowUp'|'ArrowRight'|'Numpad6'|'Numpad2'|'ArrowDown'|'Select'|'Open'|'PrintScreen'|'Insert'|'Numpad0'|'Delete'|'NumpadDecimal'|'Digit0'|'Digit1'|'Digit2'|'Digit3'|'Digit4'|'Digit5'|'Digit6'|'Digit7'|'Digit8'|'Digit9'|'KeyA'|'KeyB'|'KeyC'|'KeyD'|'KeyE'|'KeyF'|'KeyG'|'KeyH'|'KeyI'|'KeyJ'|'KeyK'|'KeyL'|'KeyM'|'KeyN'|'KeyO'|'KeyP'|'KeyQ'|'KeyR'|'KeyS'|'KeyT'|'KeyU'|'KeyV'|'KeyW'|'KeyX'|'KeyY'|'KeyZ'|'MetaLeft'|'MetaRight'|'ContextMenu'|'NumpadMultiply'|'NumpadAdd'|'NumpadSubtract'|'NumpadDivide'|'F1'|'F2'|'F3'|'F4'|'F5'|'F6'|'F7'|'F8'|'F9'|'F10'|'F11'|'F12'|'F13'|'F14'|'F15'|'F16'|'F17'|'F18'|'F19'|'F20'|'F21'|'F22'|'F23'|'F24'|'NumLock'|'ScrollLock'|'AudioVolumeMute'|'AudioVolumeDown'|'AudioVolumeUp'|'MediaTrackNext'|'MediaTrackPrevious'|'MediaStop'|'MediaPlayPause'|'Semicolon'|'Equal'|'NumpadEqual'|'Comma'|'Minus'|'Period'|'Slash'|'Backquote'|'BracketLeft'|'Backslash'|'BracketRight'|'Quote'|'AltGraph'|'Props'|'Cancel'|'Clear'|'Shift'|'Control'|'Alt'|'Accept'|'ModeChange'|' '|'Print'|'Execute'|'\u0000'|'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|'n'|'o'|'p'|'q'|'r'|'s'|'t'|'u'|'v'|'w'|'x'|'y'|'z'|'Meta'|'*'|'+'|'-'|'/'|';'|'='|','|'.'|'`'|'['|'\\'|']'|'\''|'Attn'|'CrSel'|'ExSel'|'EraseEof'|'Play'|'ZoomOut'|')'|'!'|'@'|'#'|'$'|'%'|'^'|'&'|'('|'A'|'B'|'C'|'D'|'E'|'F'|'G'|'H'|'I'|'J'|'K'|'L'|'M'|'N'|'O'|'P'|'Q'|'R'|'S'|'T'|'U'|'V'|'W'|'X'|'Y'|'Z'|':'|'<'|'_'|'>'|'?'|'~'|'{'|'|'|'}'|'"'|'SoftLeft'|'SoftRight'|'Camera'|'Call'|'EndCall'|'VolumeDown'|'VolumeUp'; +export type KeyInput = + | '0' + | '1' + | '2' + | '3' + | '4' + | '5' + | '6' + | '7' + | '8' + | '9' + | 'Power' + | 'Eject' + | 'Abort' + | 'Help' + | 'Backspace' + | 'Tab' + | 'Numpad5' + | 'NumpadEnter' + | 'Enter' + | '\r' + | '\n' + | 'ShiftLeft' + | 'ShiftRight' + | 'ControlLeft' + | 'ControlRight' + | 'AltLeft' + | 'AltRight' + | 'Pause' + | 'CapsLock' + | 'Escape' + | 'Convert' + | 'NonConvert' + | 'Space' + | 'Numpad9' + | 'PageUp' + | 'Numpad3' + | 'PageDown' + | 'End' + | 'Numpad1' + | 'Home' + | 'Numpad7' + | 'ArrowLeft' + | 'Numpad4' + | 'Numpad8' + | 'ArrowUp' + | 'ArrowRight' + | 'Numpad6' + | 'Numpad2' + | 'ArrowDown' + | 'Select' + | 'Open' + | 'PrintScreen' + | 'Insert' + | 'Numpad0' + | 'Delete' + | 'NumpadDecimal' + | 'Digit0' + | 'Digit1' + | 'Digit2' + | 'Digit3' + | 'Digit4' + | 'Digit5' + | 'Digit6' + | 'Digit7' + | 'Digit8' + | 'Digit9' + | 'KeyA' + | 'KeyB' + | 'KeyC' + | 'KeyD' + | 'KeyE' + | 'KeyF' + | 'KeyG' + | 'KeyH' + | 'KeyI' + | 'KeyJ' + | 'KeyK' + | 'KeyL' + | 'KeyM' + | 'KeyN' + | 'KeyO' + | 'KeyP' + | 'KeyQ' + | 'KeyR' + | 'KeyS' + | 'KeyT' + | 'KeyU' + | 'KeyV' + | 'KeyW' + | 'KeyX' + | 'KeyY' + | 'KeyZ' + | 'MetaLeft' + | 'MetaRight' + | 'ContextMenu' + | 'NumpadMultiply' + | 'NumpadAdd' + | 'NumpadSubtract' + | 'NumpadDivide' + | 'F1' + | 'F2' + | 'F3' + | 'F4' + | 'F5' + | 'F6' + | 'F7' + | 'F8' + | 'F9' + | 'F10' + | 'F11' + | 'F12' + | 'F13' + | 'F14' + | 'F15' + | 'F16' + | 'F17' + | 'F18' + | 'F19' + | 'F20' + | 'F21' + | 'F22' + | 'F23' + | 'F24' + | 'NumLock' + | 'ScrollLock' + | 'AudioVolumeMute' + | 'AudioVolumeDown' + | 'AudioVolumeUp' + | 'MediaTrackNext' + | 'MediaTrackPrevious' + | 'MediaStop' + | 'MediaPlayPause' + | 'Semicolon' + | 'Equal' + | 'NumpadEqual' + | 'Comma' + | 'Minus' + | 'Period' + | 'Slash' + | 'Backquote' + | 'BracketLeft' + | 'Backslash' + | 'BracketRight' + | 'Quote' + | 'AltGraph' + | 'Props' + | 'Cancel' + | 'Clear' + | 'Shift' + | 'Control' + | 'Alt' + | 'Accept' + | 'ModeChange' + | ' ' + | 'Print' + | 'Execute' + | '\u0000' + | 'a' + | 'b' + | 'c' + | 'd' + | 'e' + | 'f' + | 'g' + | 'h' + | 'i' + | 'j' + | 'k' + | 'l' + | 'm' + | 'n' + | 'o' + | 'p' + | 'q' + | 'r' + | 's' + | 't' + | 'u' + | 'v' + | 'w' + | 'x' + | 'y' + | 'z' + | 'Meta' + | '*' + | '+' + | '-' + | '/' + | ';' + | '=' + | ',' + | '.' + | '`' + | '[' + | '\\' + | ']' + | "'" + | 'Attn' + | 'CrSel' + | 'ExSel' + | 'EraseEof' + | 'Play' + | 'ZoomOut' + | ')' + | '!' + | '@' + | '#' + | '$' + | '%' + | '^' + | '&' + | '(' + | 'A' + | 'B' + | 'C' + | 'D' + | 'E' + | 'F' + | 'G' + | 'H' + | 'I' + | 'J' + | 'K' + | 'L' + | 'M' + | 'N' + | 'O' + | 'P' + | 'Q' + | 'R' + | 'S' + | 'T' + | 'U' + | 'V' + | 'W' + | 'X' + | 'Y' + | 'Z' + | ':' + | '<' + | '_' + | '>' + | '?' + | '~' + | '{' + | '|' + | '}' + | '"' + | 'SoftLeft' + | 'SoftRight' + | 'Camera' + | 'Call' + | 'EndCall' + | 'VolumeDown' + | 'VolumeUp'; export const keyDefinitions: Readonly> = { - '0': {'keyCode': 48, 'key': '0', 'code': 'Digit0'}, - '1': {'keyCode': 49, 'key': '1', 'code': 'Digit1'}, - '2': {'keyCode': 50, 'key': '2', 'code': 'Digit2'}, - '3': {'keyCode': 51, 'key': '3', 'code': 'Digit3'}, - '4': {'keyCode': 52, 'key': '4', 'code': 'Digit4'}, - '5': {'keyCode': 53, 'key': '5', 'code': 'Digit5'}, - '6': {'keyCode': 54, 'key': '6', 'code': 'Digit6'}, - '7': {'keyCode': 55, 'key': '7', 'code': 'Digit7'}, - '8': {'keyCode': 56, 'key': '8', 'code': 'Digit8'}, - '9': {'keyCode': 57, 'key': '9', 'code': 'Digit9'}, - 'Power': {'key': 'Power', 'code': 'Power'}, - 'Eject': {'key': 'Eject', 'code': 'Eject'}, - 'Abort': {'keyCode': 3, 'code': 'Abort', 'key': 'Cancel'}, - 'Help': {'keyCode': 6, 'code': 'Help', 'key': 'Help'}, - 'Backspace': {'keyCode': 8, 'code': 'Backspace', 'key': 'Backspace'}, - 'Tab': {'keyCode': 9, 'code': 'Tab', 'key': 'Tab'}, - 'Numpad5': {'keyCode': 12, 'shiftKeyCode': 101, 'key': 'Clear', 'code': 'Numpad5', 'shiftKey': '5', 'location': 3}, - 'NumpadEnter': {'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': '\r', 'location': 3}, - 'Enter': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r'}, - '\r': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r'}, - '\n': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r'}, - 'ShiftLeft': {'keyCode': 16, 'code': 'ShiftLeft', 'key': 'Shift', 'location': 1}, - 'ShiftRight': {'keyCode': 16, 'code': 'ShiftRight', 'key': 'Shift', 'location': 2}, - 'ControlLeft': {'keyCode': 17, 'code': 'ControlLeft', 'key': 'Control', 'location': 1}, - 'ControlRight': {'keyCode': 17, 'code': 'ControlRight', 'key': 'Control', 'location': 2}, - 'AltLeft': {'keyCode': 18, 'code': 'AltLeft', 'key': 'Alt', 'location': 1}, - 'AltRight': {'keyCode': 18, 'code': 'AltRight', 'key': 'Alt', 'location': 2}, - 'Pause': {'keyCode': 19, 'code': 'Pause', 'key': 'Pause'}, - 'CapsLock': {'keyCode': 20, 'code': 'CapsLock', 'key': 'CapsLock'}, - 'Escape': {'keyCode': 27, 'code': 'Escape', 'key': 'Escape'}, - 'Convert': {'keyCode': 28, 'code': 'Convert', 'key': 'Convert'}, - 'NonConvert': {'keyCode': 29, 'code': 'NonConvert', 'key': 'NonConvert'}, - 'Space': {'keyCode': 32, 'code': 'Space', 'key': ' '}, - 'Numpad9': {'keyCode': 33, 'shiftKeyCode': 105, 'key': 'PageUp', 'code': 'Numpad9', 'shiftKey': '9', 'location': 3}, - 'PageUp': {'keyCode': 33, 'code': 'PageUp', 'key': 'PageUp'}, - 'Numpad3': {'keyCode': 34, 'shiftKeyCode': 99, 'key': 'PageDown', 'code': 'Numpad3', 'shiftKey': '3', 'location': 3}, - 'PageDown': {'keyCode': 34, 'code': 'PageDown', 'key': 'PageDown'}, - 'End': {'keyCode': 35, 'code': 'End', 'key': 'End'}, - 'Numpad1': {'keyCode': 35, 'shiftKeyCode': 97, 'key': 'End', 'code': 'Numpad1', 'shiftKey': '1', 'location': 3}, - 'Home': {'keyCode': 36, 'code': 'Home', 'key': 'Home'}, - 'Numpad7': {'keyCode': 36, 'shiftKeyCode': 103, 'key': 'Home', 'code': 'Numpad7', 'shiftKey': '7', 'location': 3}, - 'ArrowLeft': {'keyCode': 37, 'code': 'ArrowLeft', 'key': 'ArrowLeft'}, - 'Numpad4': {'keyCode': 37, 'shiftKeyCode': 100, 'key': 'ArrowLeft', 'code': 'Numpad4', 'shiftKey': '4', 'location': 3}, - 'Numpad8': {'keyCode': 38, 'shiftKeyCode': 104, 'key': 'ArrowUp', 'code': 'Numpad8', 'shiftKey': '8', 'location': 3}, - 'ArrowUp': {'keyCode': 38, 'code': 'ArrowUp', 'key': 'ArrowUp'}, - 'ArrowRight': {'keyCode': 39, 'code': 'ArrowRight', 'key': 'ArrowRight'}, - 'Numpad6': {'keyCode': 39, 'shiftKeyCode': 102, 'key': 'ArrowRight', 'code': 'Numpad6', 'shiftKey': '6', 'location': 3}, - 'Numpad2': {'keyCode': 40, 'shiftKeyCode': 98, 'key': 'ArrowDown', 'code': 'Numpad2', 'shiftKey': '2', 'location': 3}, - 'ArrowDown': {'keyCode': 40, 'code': 'ArrowDown', 'key': 'ArrowDown'}, - 'Select': {'keyCode': 41, 'code': 'Select', 'key': 'Select'}, - 'Open': {'keyCode': 43, 'code': 'Open', 'key': 'Execute'}, - 'PrintScreen': {'keyCode': 44, 'code': 'PrintScreen', 'key': 'PrintScreen'}, - 'Insert': {'keyCode': 45, 'code': 'Insert', 'key': 'Insert'}, - 'Numpad0': {'keyCode': 45, 'shiftKeyCode': 96, 'key': 'Insert', 'code': 'Numpad0', 'shiftKey': '0', 'location': 3}, - 'Delete': {'keyCode': 46, 'code': 'Delete', 'key': 'Delete'}, - 'NumpadDecimal': {'keyCode': 46, 'shiftKeyCode': 110, 'code': 'NumpadDecimal', 'key': '\u0000', 'shiftKey': '.', 'location': 3}, - 'Digit0': {'keyCode': 48, 'code': 'Digit0', 'shiftKey': ')', 'key': '0'}, - 'Digit1': {'keyCode': 49, 'code': 'Digit1', 'shiftKey': '!', 'key': '1'}, - 'Digit2': {'keyCode': 50, 'code': 'Digit2', 'shiftKey': '@', 'key': '2'}, - 'Digit3': {'keyCode': 51, 'code': 'Digit3', 'shiftKey': '#', 'key': '3'}, - 'Digit4': {'keyCode': 52, 'code': 'Digit4', 'shiftKey': '$', 'key': '4'}, - 'Digit5': {'keyCode': 53, 'code': 'Digit5', 'shiftKey': '%', 'key': '5'}, - 'Digit6': {'keyCode': 54, 'code': 'Digit6', 'shiftKey': '^', 'key': '6'}, - 'Digit7': {'keyCode': 55, 'code': 'Digit7', 'shiftKey': '&', 'key': '7'}, - 'Digit8': {'keyCode': 56, 'code': 'Digit8', 'shiftKey': '*', 'key': '8'}, - 'Digit9': {'keyCode': 57, 'code': 'Digit9', 'shiftKey': '\(', 'key': '9'}, - 'KeyA': {'keyCode': 65, 'code': 'KeyA', 'shiftKey': 'A', 'key': 'a'}, - 'KeyB': {'keyCode': 66, 'code': 'KeyB', 'shiftKey': 'B', 'key': 'b'}, - 'KeyC': {'keyCode': 67, 'code': 'KeyC', 'shiftKey': 'C', 'key': 'c'}, - 'KeyD': {'keyCode': 68, 'code': 'KeyD', 'shiftKey': 'D', 'key': 'd'}, - 'KeyE': {'keyCode': 69, 'code': 'KeyE', 'shiftKey': 'E', 'key': 'e'}, - 'KeyF': {'keyCode': 70, 'code': 'KeyF', 'shiftKey': 'F', 'key': 'f'}, - 'KeyG': {'keyCode': 71, 'code': 'KeyG', 'shiftKey': 'G', 'key': 'g'}, - 'KeyH': {'keyCode': 72, 'code': 'KeyH', 'shiftKey': 'H', 'key': 'h'}, - 'KeyI': {'keyCode': 73, 'code': 'KeyI', 'shiftKey': 'I', 'key': 'i'}, - 'KeyJ': {'keyCode': 74, 'code': 'KeyJ', 'shiftKey': 'J', 'key': 'j'}, - 'KeyK': {'keyCode': 75, 'code': 'KeyK', 'shiftKey': 'K', 'key': 'k'}, - 'KeyL': {'keyCode': 76, 'code': 'KeyL', 'shiftKey': 'L', 'key': 'l'}, - 'KeyM': {'keyCode': 77, 'code': 'KeyM', 'shiftKey': 'M', 'key': 'm'}, - 'KeyN': {'keyCode': 78, 'code': 'KeyN', 'shiftKey': 'N', 'key': 'n'}, - 'KeyO': {'keyCode': 79, 'code': 'KeyO', 'shiftKey': 'O', 'key': 'o'}, - 'KeyP': {'keyCode': 80, 'code': 'KeyP', 'shiftKey': 'P', 'key': 'p'}, - 'KeyQ': {'keyCode': 81, 'code': 'KeyQ', 'shiftKey': 'Q', 'key': 'q'}, - 'KeyR': {'keyCode': 82, 'code': 'KeyR', 'shiftKey': 'R', 'key': 'r'}, - 'KeyS': {'keyCode': 83, 'code': 'KeyS', 'shiftKey': 'S', 'key': 's'}, - 'KeyT': {'keyCode': 84, 'code': 'KeyT', 'shiftKey': 'T', 'key': 't'}, - 'KeyU': {'keyCode': 85, 'code': 'KeyU', 'shiftKey': 'U', 'key': 'u'}, - 'KeyV': {'keyCode': 86, 'code': 'KeyV', 'shiftKey': 'V', 'key': 'v'}, - 'KeyW': {'keyCode': 87, 'code': 'KeyW', 'shiftKey': 'W', 'key': 'w'}, - 'KeyX': {'keyCode': 88, 'code': 'KeyX', 'shiftKey': 'X', 'key': 'x'}, - 'KeyY': {'keyCode': 89, 'code': 'KeyY', 'shiftKey': 'Y', 'key': 'y'}, - 'KeyZ': {'keyCode': 90, 'code': 'KeyZ', 'shiftKey': 'Z', 'key': 'z'}, - 'MetaLeft': {'keyCode': 91, 'code': 'MetaLeft', 'key': 'Meta', 'location': 1}, - 'MetaRight': {'keyCode': 92, 'code': 'MetaRight', 'key': 'Meta', 'location': 2}, - 'ContextMenu': {'keyCode': 93, 'code': 'ContextMenu', 'key': 'ContextMenu'}, - 'NumpadMultiply': {'keyCode': 106, 'code': 'NumpadMultiply', 'key': '*', 'location': 3}, - 'NumpadAdd': {'keyCode': 107, 'code': 'NumpadAdd', 'key': '+', 'location': 3}, - 'NumpadSubtract': {'keyCode': 109, 'code': 'NumpadSubtract', 'key': '-', 'location': 3}, - 'NumpadDivide': {'keyCode': 111, 'code': 'NumpadDivide', 'key': '/', 'location': 3}, - 'F1': {'keyCode': 112, 'code': 'F1', 'key': 'F1'}, - 'F2': {'keyCode': 113, 'code': 'F2', 'key': 'F2'}, - 'F3': {'keyCode': 114, 'code': 'F3', 'key': 'F3'}, - 'F4': {'keyCode': 115, 'code': 'F4', 'key': 'F4'}, - 'F5': {'keyCode': 116, 'code': 'F5', 'key': 'F5'}, - 'F6': {'keyCode': 117, 'code': 'F6', 'key': 'F6'}, - 'F7': {'keyCode': 118, 'code': 'F7', 'key': 'F7'}, - 'F8': {'keyCode': 119, 'code': 'F8', 'key': 'F8'}, - 'F9': {'keyCode': 120, 'code': 'F9', 'key': 'F9'}, - 'F10': {'keyCode': 121, 'code': 'F10', 'key': 'F10'}, - 'F11': {'keyCode': 122, 'code': 'F11', 'key': 'F11'}, - 'F12': {'keyCode': 123, 'code': 'F12', 'key': 'F12'}, - 'F13': {'keyCode': 124, 'code': 'F13', 'key': 'F13'}, - 'F14': {'keyCode': 125, 'code': 'F14', 'key': 'F14'}, - 'F15': {'keyCode': 126, 'code': 'F15', 'key': 'F15'}, - 'F16': {'keyCode': 127, 'code': 'F16', 'key': 'F16'}, - 'F17': {'keyCode': 128, 'code': 'F17', 'key': 'F17'}, - 'F18': {'keyCode': 129, 'code': 'F18', 'key': 'F18'}, - 'F19': {'keyCode': 130, 'code': 'F19', 'key': 'F19'}, - 'F20': {'keyCode': 131, 'code': 'F20', 'key': 'F20'}, - 'F21': {'keyCode': 132, 'code': 'F21', 'key': 'F21'}, - 'F22': {'keyCode': 133, 'code': 'F22', 'key': 'F22'}, - 'F23': {'keyCode': 134, 'code': 'F23', 'key': 'F23'}, - 'F24': {'keyCode': 135, 'code': 'F24', 'key': 'F24'}, - 'NumLock': {'keyCode': 144, 'code': 'NumLock', 'key': 'NumLock'}, - 'ScrollLock': {'keyCode': 145, 'code': 'ScrollLock', 'key': 'ScrollLock'}, - 'AudioVolumeMute': {'keyCode': 173, 'code': 'AudioVolumeMute', 'key': 'AudioVolumeMute'}, - 'AudioVolumeDown': {'keyCode': 174, 'code': 'AudioVolumeDown', 'key': 'AudioVolumeDown'}, - 'AudioVolumeUp': {'keyCode': 175, 'code': 'AudioVolumeUp', 'key': 'AudioVolumeUp'}, - 'MediaTrackNext': {'keyCode': 176, 'code': 'MediaTrackNext', 'key': 'MediaTrackNext'}, - 'MediaTrackPrevious': {'keyCode': 177, 'code': 'MediaTrackPrevious', 'key': 'MediaTrackPrevious'}, - 'MediaStop': {'keyCode': 178, 'code': 'MediaStop', 'key': 'MediaStop'}, - 'MediaPlayPause': {'keyCode': 179, 'code': 'MediaPlayPause', 'key': 'MediaPlayPause'}, - 'Semicolon': {'keyCode': 186, 'code': 'Semicolon', 'shiftKey': ':', 'key': ';'}, - 'Equal': {'keyCode': 187, 'code': 'Equal', 'shiftKey': '+', 'key': '='}, - 'NumpadEqual': {'keyCode': 187, 'code': 'NumpadEqual', 'key': '=', 'location': 3}, - 'Comma': {'keyCode': 188, 'code': 'Comma', 'shiftKey': '\<', 'key': ','}, - 'Minus': {'keyCode': 189, 'code': 'Minus', 'shiftKey': '_', 'key': '-'}, - 'Period': {'keyCode': 190, 'code': 'Period', 'shiftKey': '>', 'key': '.'}, - 'Slash': {'keyCode': 191, 'code': 'Slash', 'shiftKey': '?', 'key': '/'}, - 'Backquote': {'keyCode': 192, 'code': 'Backquote', 'shiftKey': '~', 'key': '`'}, - 'BracketLeft': {'keyCode': 219, 'code': 'BracketLeft', 'shiftKey': '{', 'key': '['}, - 'Backslash': {'keyCode': 220, 'code': 'Backslash', 'shiftKey': '|', 'key': '\\'}, - 'BracketRight': {'keyCode': 221, 'code': 'BracketRight', 'shiftKey': '}', 'key': ']'}, - 'Quote': {'keyCode': 222, 'code': 'Quote', 'shiftKey': '"', 'key': '\''}, - 'AltGraph': {'keyCode': 225, 'code': 'AltGraph', 'key': 'AltGraph'}, - 'Props': {'keyCode': 247, 'code': 'Props', 'key': 'CrSel'}, - 'Cancel': {'keyCode': 3, 'key': 'Cancel', 'code': 'Abort'}, - 'Clear': {'keyCode': 12, 'key': 'Clear', 'code': 'Numpad5', 'location': 3}, - 'Shift': {'keyCode': 16, 'key': 'Shift', 'code': 'ShiftLeft', 'location': 1}, - 'Control': {'keyCode': 17, 'key': 'Control', 'code': 'ControlLeft', 'location': 1}, - 'Alt': {'keyCode': 18, 'key': 'Alt', 'code': 'AltLeft', 'location': 1}, - 'Accept': {'keyCode': 30, 'key': 'Accept'}, - 'ModeChange': {'keyCode': 31, 'key': 'ModeChange'}, - ' ': {'keyCode': 32, 'key': ' ', 'code': 'Space'}, - 'Print': {'keyCode': 42, 'key': 'Print'}, - 'Execute': {'keyCode': 43, 'key': 'Execute', 'code': 'Open'}, - '\u0000': {'keyCode': 46, 'key': '\u0000', 'code': 'NumpadDecimal', 'location': 3}, - 'a': {'keyCode': 65, 'key': 'a', 'code': 'KeyA'}, - 'b': {'keyCode': 66, 'key': 'b', 'code': 'KeyB'}, - 'c': {'keyCode': 67, 'key': 'c', 'code': 'KeyC'}, - 'd': {'keyCode': 68, 'key': 'd', 'code': 'KeyD'}, - 'e': {'keyCode': 69, 'key': 'e', 'code': 'KeyE'}, - 'f': {'keyCode': 70, 'key': 'f', 'code': 'KeyF'}, - 'g': {'keyCode': 71, 'key': 'g', 'code': 'KeyG'}, - 'h': {'keyCode': 72, 'key': 'h', 'code': 'KeyH'}, - 'i': {'keyCode': 73, 'key': 'i', 'code': 'KeyI'}, - 'j': {'keyCode': 74, 'key': 'j', 'code': 'KeyJ'}, - 'k': {'keyCode': 75, 'key': 'k', 'code': 'KeyK'}, - 'l': {'keyCode': 76, 'key': 'l', 'code': 'KeyL'}, - 'm': {'keyCode': 77, 'key': 'm', 'code': 'KeyM'}, - 'n': {'keyCode': 78, 'key': 'n', 'code': 'KeyN'}, - 'o': {'keyCode': 79, 'key': 'o', 'code': 'KeyO'}, - 'p': {'keyCode': 80, 'key': 'p', 'code': 'KeyP'}, - 'q': {'keyCode': 81, 'key': 'q', 'code': 'KeyQ'}, - 'r': {'keyCode': 82, 'key': 'r', 'code': 'KeyR'}, - 's': {'keyCode': 83, 'key': 's', 'code': 'KeyS'}, - 't': {'keyCode': 84, 'key': 't', 'code': 'KeyT'}, - 'u': {'keyCode': 85, 'key': 'u', 'code': 'KeyU'}, - 'v': {'keyCode': 86, 'key': 'v', 'code': 'KeyV'}, - 'w': {'keyCode': 87, 'key': 'w', 'code': 'KeyW'}, - 'x': {'keyCode': 88, 'key': 'x', 'code': 'KeyX'}, - 'y': {'keyCode': 89, 'key': 'y', 'code': 'KeyY'}, - 'z': {'keyCode': 90, 'key': 'z', 'code': 'KeyZ'}, - 'Meta': {'keyCode': 91, 'key': 'Meta', 'code': 'MetaLeft', 'location': 1}, - '*': {'keyCode': 106, 'key': '*', 'code': 'NumpadMultiply', 'location': 3}, - '+': {'keyCode': 107, 'key': '+', 'code': 'NumpadAdd', 'location': 3}, - '-': {'keyCode': 109, 'key': '-', 'code': 'NumpadSubtract', 'location': 3}, - '/': {'keyCode': 111, 'key': '/', 'code': 'NumpadDivide', 'location': 3}, - ';': {'keyCode': 186, 'key': ';', 'code': 'Semicolon'}, - '=': {'keyCode': 187, 'key': '=', 'code': 'Equal'}, - ',': {'keyCode': 188, 'key': ',', 'code': 'Comma'}, - '.': {'keyCode': 190, 'key': '.', 'code': 'Period'}, - '`': {'keyCode': 192, 'key': '`', 'code': 'Backquote'}, - '[': {'keyCode': 219, 'key': '[', 'code': 'BracketLeft'}, - '\\': {'keyCode': 220, 'key': '\\', 'code': 'Backslash'}, - ']': {'keyCode': 221, 'key': ']', 'code': 'BracketRight'}, - '\'': {'keyCode': 222, 'key': '\'', 'code': 'Quote'}, - 'Attn': {'keyCode': 246, 'key': 'Attn'}, - 'CrSel': {'keyCode': 247, 'key': 'CrSel', 'code': 'Props'}, - 'ExSel': {'keyCode': 248, 'key': 'ExSel'}, - 'EraseEof': {'keyCode': 249, 'key': 'EraseEof'}, - 'Play': {'keyCode': 250, 'key': 'Play'}, - 'ZoomOut': {'keyCode': 251, 'key': 'ZoomOut'}, - ')': {'keyCode': 48, 'key': ')', 'code': 'Digit0'}, - '!': {'keyCode': 49, 'key': '!', 'code': 'Digit1'}, - '@': {'keyCode': 50, 'key': '@', 'code': 'Digit2'}, - '#': {'keyCode': 51, 'key': '#', 'code': 'Digit3'}, - '$': {'keyCode': 52, 'key': '$', 'code': 'Digit4'}, - '%': {'keyCode': 53, 'key': '%', 'code': 'Digit5'}, - '^': {'keyCode': 54, 'key': '^', 'code': 'Digit6'}, - '&': {'keyCode': 55, 'key': '&', 'code': 'Digit7'}, - '(': {'keyCode': 57, 'key': '\(', 'code': 'Digit9'}, - 'A': {'keyCode': 65, 'key': 'A', 'code': 'KeyA'}, - 'B': {'keyCode': 66, 'key': 'B', 'code': 'KeyB'}, - 'C': {'keyCode': 67, 'key': 'C', 'code': 'KeyC'}, - 'D': {'keyCode': 68, 'key': 'D', 'code': 'KeyD'}, - 'E': {'keyCode': 69, 'key': 'E', 'code': 'KeyE'}, - 'F': {'keyCode': 70, 'key': 'F', 'code': 'KeyF'}, - 'G': {'keyCode': 71, 'key': 'G', 'code': 'KeyG'}, - 'H': {'keyCode': 72, 'key': 'H', 'code': 'KeyH'}, - 'I': {'keyCode': 73, 'key': 'I', 'code': 'KeyI'}, - 'J': {'keyCode': 74, 'key': 'J', 'code': 'KeyJ'}, - 'K': {'keyCode': 75, 'key': 'K', 'code': 'KeyK'}, - 'L': {'keyCode': 76, 'key': 'L', 'code': 'KeyL'}, - 'M': {'keyCode': 77, 'key': 'M', 'code': 'KeyM'}, - 'N': {'keyCode': 78, 'key': 'N', 'code': 'KeyN'}, - 'O': {'keyCode': 79, 'key': 'O', 'code': 'KeyO'}, - 'P': {'keyCode': 80, 'key': 'P', 'code': 'KeyP'}, - 'Q': {'keyCode': 81, 'key': 'Q', 'code': 'KeyQ'}, - 'R': {'keyCode': 82, 'key': 'R', 'code': 'KeyR'}, - 'S': {'keyCode': 83, 'key': 'S', 'code': 'KeyS'}, - 'T': {'keyCode': 84, 'key': 'T', 'code': 'KeyT'}, - 'U': {'keyCode': 85, 'key': 'U', 'code': 'KeyU'}, - 'V': {'keyCode': 86, 'key': 'V', 'code': 'KeyV'}, - 'W': {'keyCode': 87, 'key': 'W', 'code': 'KeyW'}, - 'X': {'keyCode': 88, 'key': 'X', 'code': 'KeyX'}, - 'Y': {'keyCode': 89, 'key': 'Y', 'code': 'KeyY'}, - 'Z': {'keyCode': 90, 'key': 'Z', 'code': 'KeyZ'}, - ':': {'keyCode': 186, 'key': ':', 'code': 'Semicolon'}, - '<': {'keyCode': 188, 'key': '\<', 'code': 'Comma'}, - '_': {'keyCode': 189, 'key': '_', 'code': 'Minus'}, - '>': {'keyCode': 190, 'key': '>', 'code': 'Period'}, - '?': {'keyCode': 191, 'key': '?', 'code': 'Slash'}, - '~': {'keyCode': 192, 'key': '~', 'code': 'Backquote'}, - '{': {'keyCode': 219, 'key': '{', 'code': 'BracketLeft'}, - '|': {'keyCode': 220, 'key': '|', 'code': 'Backslash'}, - '}': {'keyCode': 221, 'key': '}', 'code': 'BracketRight'}, - '"': {'keyCode': 222, 'key': '"', 'code': 'Quote'}, - 'SoftLeft': {'key': 'SoftLeft', 'code': 'SoftLeft', 'location': 4}, - 'SoftRight': {'key': 'SoftRight', 'code': 'SoftRight', 'location': 4}, - 'Camera': {'keyCode': 44, 'key': 'Camera', 'code': 'Camera', 'location': 4}, - 'Call': {'key': 'Call', 'code': 'Call', 'location': 4}, - 'EndCall': {'keyCode': 95, 'key': 'EndCall', 'code': 'EndCall', 'location': 4}, - 'VolumeDown': {'keyCode': 182, 'key': 'VolumeDown', 'code': 'VolumeDown', 'location': 4}, - 'VolumeUp': {'keyCode': 183, 'key': 'VolumeUp', 'code': 'VolumeUp', 'location': 4}, + '0': { keyCode: 48, key: '0', code: 'Digit0' }, + '1': { keyCode: 49, key: '1', code: 'Digit1' }, + '2': { keyCode: 50, key: '2', code: 'Digit2' }, + '3': { keyCode: 51, key: '3', code: 'Digit3' }, + '4': { keyCode: 52, key: '4', code: 'Digit4' }, + '5': { keyCode: 53, key: '5', code: 'Digit5' }, + '6': { keyCode: 54, key: '6', code: 'Digit6' }, + '7': { keyCode: 55, key: '7', code: 'Digit7' }, + '8': { keyCode: 56, key: '8', code: 'Digit8' }, + '9': { keyCode: 57, key: '9', code: 'Digit9' }, + Power: { key: 'Power', code: 'Power' }, + Eject: { key: 'Eject', code: 'Eject' }, + Abort: { keyCode: 3, code: 'Abort', key: 'Cancel' }, + Help: { keyCode: 6, code: 'Help', key: 'Help' }, + Backspace: { keyCode: 8, code: 'Backspace', key: 'Backspace' }, + Tab: { keyCode: 9, code: 'Tab', key: 'Tab' }, + Numpad5: { + keyCode: 12, + shiftKeyCode: 101, + key: 'Clear', + code: 'Numpad5', + shiftKey: '5', + location: 3, + }, + NumpadEnter: { + keyCode: 13, + code: 'NumpadEnter', + key: 'Enter', + text: '\r', + location: 3, + }, + Enter: { keyCode: 13, code: 'Enter', key: 'Enter', text: '\r' }, + '\r': { keyCode: 13, code: 'Enter', key: 'Enter', text: '\r' }, + '\n': { keyCode: 13, code: 'Enter', key: 'Enter', text: '\r' }, + ShiftLeft: { keyCode: 16, code: 'ShiftLeft', key: 'Shift', location: 1 }, + ShiftRight: { keyCode: 16, code: 'ShiftRight', key: 'Shift', location: 2 }, + ControlLeft: { + keyCode: 17, + code: 'ControlLeft', + key: 'Control', + location: 1, + }, + ControlRight: { + keyCode: 17, + code: 'ControlRight', + key: 'Control', + location: 2, + }, + AltLeft: { keyCode: 18, code: 'AltLeft', key: 'Alt', location: 1 }, + AltRight: { keyCode: 18, code: 'AltRight', key: 'Alt', location: 2 }, + Pause: { keyCode: 19, code: 'Pause', key: 'Pause' }, + CapsLock: { keyCode: 20, code: 'CapsLock', key: 'CapsLock' }, + Escape: { keyCode: 27, code: 'Escape', key: 'Escape' }, + Convert: { keyCode: 28, code: 'Convert', key: 'Convert' }, + NonConvert: { keyCode: 29, code: 'NonConvert', key: 'NonConvert' }, + Space: { keyCode: 32, code: 'Space', key: ' ' }, + Numpad9: { + keyCode: 33, + shiftKeyCode: 105, + key: 'PageUp', + code: 'Numpad9', + shiftKey: '9', + location: 3, + }, + PageUp: { keyCode: 33, code: 'PageUp', key: 'PageUp' }, + Numpad3: { + keyCode: 34, + shiftKeyCode: 99, + key: 'PageDown', + code: 'Numpad3', + shiftKey: '3', + location: 3, + }, + PageDown: { keyCode: 34, code: 'PageDown', key: 'PageDown' }, + End: { keyCode: 35, code: 'End', key: 'End' }, + Numpad1: { + keyCode: 35, + shiftKeyCode: 97, + key: 'End', + code: 'Numpad1', + shiftKey: '1', + location: 3, + }, + Home: { keyCode: 36, code: 'Home', key: 'Home' }, + Numpad7: { + keyCode: 36, + shiftKeyCode: 103, + key: 'Home', + code: 'Numpad7', + shiftKey: '7', + location: 3, + }, + ArrowLeft: { keyCode: 37, code: 'ArrowLeft', key: 'ArrowLeft' }, + Numpad4: { + keyCode: 37, + shiftKeyCode: 100, + key: 'ArrowLeft', + code: 'Numpad4', + shiftKey: '4', + location: 3, + }, + Numpad8: { + keyCode: 38, + shiftKeyCode: 104, + key: 'ArrowUp', + code: 'Numpad8', + shiftKey: '8', + location: 3, + }, + ArrowUp: { keyCode: 38, code: 'ArrowUp', key: 'ArrowUp' }, + ArrowRight: { keyCode: 39, code: 'ArrowRight', key: 'ArrowRight' }, + Numpad6: { + keyCode: 39, + shiftKeyCode: 102, + key: 'ArrowRight', + code: 'Numpad6', + shiftKey: '6', + location: 3, + }, + Numpad2: { + keyCode: 40, + shiftKeyCode: 98, + key: 'ArrowDown', + code: 'Numpad2', + shiftKey: '2', + location: 3, + }, + ArrowDown: { keyCode: 40, code: 'ArrowDown', key: 'ArrowDown' }, + Select: { keyCode: 41, code: 'Select', key: 'Select' }, + Open: { keyCode: 43, code: 'Open', key: 'Execute' }, + PrintScreen: { keyCode: 44, code: 'PrintScreen', key: 'PrintScreen' }, + Insert: { keyCode: 45, code: 'Insert', key: 'Insert' }, + Numpad0: { + keyCode: 45, + shiftKeyCode: 96, + key: 'Insert', + code: 'Numpad0', + shiftKey: '0', + location: 3, + }, + Delete: { keyCode: 46, code: 'Delete', key: 'Delete' }, + NumpadDecimal: { + keyCode: 46, + shiftKeyCode: 110, + code: 'NumpadDecimal', + key: '\u0000', + shiftKey: '.', + location: 3, + }, + Digit0: { keyCode: 48, code: 'Digit0', shiftKey: ')', key: '0' }, + Digit1: { keyCode: 49, code: 'Digit1', shiftKey: '!', key: '1' }, + Digit2: { keyCode: 50, code: 'Digit2', shiftKey: '@', key: '2' }, + Digit3: { keyCode: 51, code: 'Digit3', shiftKey: '#', key: '3' }, + Digit4: { keyCode: 52, code: 'Digit4', shiftKey: '$', key: '4' }, + Digit5: { keyCode: 53, code: 'Digit5', shiftKey: '%', key: '5' }, + Digit6: { keyCode: 54, code: 'Digit6', shiftKey: '^', key: '6' }, + Digit7: { keyCode: 55, code: 'Digit7', shiftKey: '&', key: '7' }, + Digit8: { keyCode: 56, code: 'Digit8', shiftKey: '*', key: '8' }, + Digit9: { keyCode: 57, code: 'Digit9', shiftKey: '(', key: '9' }, + KeyA: { keyCode: 65, code: 'KeyA', shiftKey: 'A', key: 'a' }, + KeyB: { keyCode: 66, code: 'KeyB', shiftKey: 'B', key: 'b' }, + KeyC: { keyCode: 67, code: 'KeyC', shiftKey: 'C', key: 'c' }, + KeyD: { keyCode: 68, code: 'KeyD', shiftKey: 'D', key: 'd' }, + KeyE: { keyCode: 69, code: 'KeyE', shiftKey: 'E', key: 'e' }, + KeyF: { keyCode: 70, code: 'KeyF', shiftKey: 'F', key: 'f' }, + KeyG: { keyCode: 71, code: 'KeyG', shiftKey: 'G', key: 'g' }, + KeyH: { keyCode: 72, code: 'KeyH', shiftKey: 'H', key: 'h' }, + KeyI: { keyCode: 73, code: 'KeyI', shiftKey: 'I', key: 'i' }, + KeyJ: { keyCode: 74, code: 'KeyJ', shiftKey: 'J', key: 'j' }, + KeyK: { keyCode: 75, code: 'KeyK', shiftKey: 'K', key: 'k' }, + KeyL: { keyCode: 76, code: 'KeyL', shiftKey: 'L', key: 'l' }, + KeyM: { keyCode: 77, code: 'KeyM', shiftKey: 'M', key: 'm' }, + KeyN: { keyCode: 78, code: 'KeyN', shiftKey: 'N', key: 'n' }, + KeyO: { keyCode: 79, code: 'KeyO', shiftKey: 'O', key: 'o' }, + KeyP: { keyCode: 80, code: 'KeyP', shiftKey: 'P', key: 'p' }, + KeyQ: { keyCode: 81, code: 'KeyQ', shiftKey: 'Q', key: 'q' }, + KeyR: { keyCode: 82, code: 'KeyR', shiftKey: 'R', key: 'r' }, + KeyS: { keyCode: 83, code: 'KeyS', shiftKey: 'S', key: 's' }, + KeyT: { keyCode: 84, code: 'KeyT', shiftKey: 'T', key: 't' }, + KeyU: { keyCode: 85, code: 'KeyU', shiftKey: 'U', key: 'u' }, + KeyV: { keyCode: 86, code: 'KeyV', shiftKey: 'V', key: 'v' }, + KeyW: { keyCode: 87, code: 'KeyW', shiftKey: 'W', key: 'w' }, + KeyX: { keyCode: 88, code: 'KeyX', shiftKey: 'X', key: 'x' }, + KeyY: { keyCode: 89, code: 'KeyY', shiftKey: 'Y', key: 'y' }, + KeyZ: { keyCode: 90, code: 'KeyZ', shiftKey: 'Z', key: 'z' }, + MetaLeft: { keyCode: 91, code: 'MetaLeft', key: 'Meta', location: 1 }, + MetaRight: { keyCode: 92, code: 'MetaRight', key: 'Meta', location: 2 }, + ContextMenu: { keyCode: 93, code: 'ContextMenu', key: 'ContextMenu' }, + NumpadMultiply: { + keyCode: 106, + code: 'NumpadMultiply', + key: '*', + location: 3, + }, + NumpadAdd: { keyCode: 107, code: 'NumpadAdd', key: '+', location: 3 }, + NumpadSubtract: { + keyCode: 109, + code: 'NumpadSubtract', + key: '-', + location: 3, + }, + NumpadDivide: { keyCode: 111, code: 'NumpadDivide', key: '/', location: 3 }, + F1: { keyCode: 112, code: 'F1', key: 'F1' }, + F2: { keyCode: 113, code: 'F2', key: 'F2' }, + F3: { keyCode: 114, code: 'F3', key: 'F3' }, + F4: { keyCode: 115, code: 'F4', key: 'F4' }, + F5: { keyCode: 116, code: 'F5', key: 'F5' }, + F6: { keyCode: 117, code: 'F6', key: 'F6' }, + F7: { keyCode: 118, code: 'F7', key: 'F7' }, + F8: { keyCode: 119, code: 'F8', key: 'F8' }, + F9: { keyCode: 120, code: 'F9', key: 'F9' }, + F10: { keyCode: 121, code: 'F10', key: 'F10' }, + F11: { keyCode: 122, code: 'F11', key: 'F11' }, + F12: { keyCode: 123, code: 'F12', key: 'F12' }, + F13: { keyCode: 124, code: 'F13', key: 'F13' }, + F14: { keyCode: 125, code: 'F14', key: 'F14' }, + F15: { keyCode: 126, code: 'F15', key: 'F15' }, + F16: { keyCode: 127, code: 'F16', key: 'F16' }, + F17: { keyCode: 128, code: 'F17', key: 'F17' }, + F18: { keyCode: 129, code: 'F18', key: 'F18' }, + F19: { keyCode: 130, code: 'F19', key: 'F19' }, + F20: { keyCode: 131, code: 'F20', key: 'F20' }, + F21: { keyCode: 132, code: 'F21', key: 'F21' }, + F22: { keyCode: 133, code: 'F22', key: 'F22' }, + F23: { keyCode: 134, code: 'F23', key: 'F23' }, + F24: { keyCode: 135, code: 'F24', key: 'F24' }, + NumLock: { keyCode: 144, code: 'NumLock', key: 'NumLock' }, + ScrollLock: { keyCode: 145, code: 'ScrollLock', key: 'ScrollLock' }, + AudioVolumeMute: { + keyCode: 173, + code: 'AudioVolumeMute', + key: 'AudioVolumeMute', + }, + AudioVolumeDown: { + keyCode: 174, + code: 'AudioVolumeDown', + key: 'AudioVolumeDown', + }, + AudioVolumeUp: { keyCode: 175, code: 'AudioVolumeUp', key: 'AudioVolumeUp' }, + MediaTrackNext: { + keyCode: 176, + code: 'MediaTrackNext', + key: 'MediaTrackNext', + }, + MediaTrackPrevious: { + keyCode: 177, + code: 'MediaTrackPrevious', + key: 'MediaTrackPrevious', + }, + MediaStop: { keyCode: 178, code: 'MediaStop', key: 'MediaStop' }, + MediaPlayPause: { + keyCode: 179, + code: 'MediaPlayPause', + key: 'MediaPlayPause', + }, + Semicolon: { keyCode: 186, code: 'Semicolon', shiftKey: ':', key: ';' }, + Equal: { keyCode: 187, code: 'Equal', shiftKey: '+', key: '=' }, + NumpadEqual: { keyCode: 187, code: 'NumpadEqual', key: '=', location: 3 }, + Comma: { keyCode: 188, code: 'Comma', shiftKey: '<', key: ',' }, + Minus: { keyCode: 189, code: 'Minus', shiftKey: '_', key: '-' }, + Period: { keyCode: 190, code: 'Period', shiftKey: '>', key: '.' }, + Slash: { keyCode: 191, code: 'Slash', shiftKey: '?', key: '/' }, + Backquote: { keyCode: 192, code: 'Backquote', shiftKey: '~', key: '`' }, + BracketLeft: { keyCode: 219, code: 'BracketLeft', shiftKey: '{', key: '[' }, + Backslash: { keyCode: 220, code: 'Backslash', shiftKey: '|', key: '\\' }, + BracketRight: { keyCode: 221, code: 'BracketRight', shiftKey: '}', key: ']' }, + Quote: { keyCode: 222, code: 'Quote', shiftKey: '"', key: "'" }, + AltGraph: { keyCode: 225, code: 'AltGraph', key: 'AltGraph' }, + Props: { keyCode: 247, code: 'Props', key: 'CrSel' }, + Cancel: { keyCode: 3, key: 'Cancel', code: 'Abort' }, + Clear: { keyCode: 12, key: 'Clear', code: 'Numpad5', location: 3 }, + Shift: { keyCode: 16, key: 'Shift', code: 'ShiftLeft', location: 1 }, + Control: { keyCode: 17, key: 'Control', code: 'ControlLeft', location: 1 }, + Alt: { keyCode: 18, key: 'Alt', code: 'AltLeft', location: 1 }, + Accept: { keyCode: 30, key: 'Accept' }, + ModeChange: { keyCode: 31, key: 'ModeChange' }, + ' ': { keyCode: 32, key: ' ', code: 'Space' }, + Print: { keyCode: 42, key: 'Print' }, + Execute: { keyCode: 43, key: 'Execute', code: 'Open' }, + '\u0000': { keyCode: 46, key: '\u0000', code: 'NumpadDecimal', location: 3 }, + a: { keyCode: 65, key: 'a', code: 'KeyA' }, + b: { keyCode: 66, key: 'b', code: 'KeyB' }, + c: { keyCode: 67, key: 'c', code: 'KeyC' }, + d: { keyCode: 68, key: 'd', code: 'KeyD' }, + e: { keyCode: 69, key: 'e', code: 'KeyE' }, + f: { keyCode: 70, key: 'f', code: 'KeyF' }, + g: { keyCode: 71, key: 'g', code: 'KeyG' }, + h: { keyCode: 72, key: 'h', code: 'KeyH' }, + i: { keyCode: 73, key: 'i', code: 'KeyI' }, + j: { keyCode: 74, key: 'j', code: 'KeyJ' }, + k: { keyCode: 75, key: 'k', code: 'KeyK' }, + l: { keyCode: 76, key: 'l', code: 'KeyL' }, + m: { keyCode: 77, key: 'm', code: 'KeyM' }, + n: { keyCode: 78, key: 'n', code: 'KeyN' }, + o: { keyCode: 79, key: 'o', code: 'KeyO' }, + p: { keyCode: 80, key: 'p', code: 'KeyP' }, + q: { keyCode: 81, key: 'q', code: 'KeyQ' }, + r: { keyCode: 82, key: 'r', code: 'KeyR' }, + s: { keyCode: 83, key: 's', code: 'KeyS' }, + t: { keyCode: 84, key: 't', code: 'KeyT' }, + u: { keyCode: 85, key: 'u', code: 'KeyU' }, + v: { keyCode: 86, key: 'v', code: 'KeyV' }, + w: { keyCode: 87, key: 'w', code: 'KeyW' }, + x: { keyCode: 88, key: 'x', code: 'KeyX' }, + y: { keyCode: 89, key: 'y', code: 'KeyY' }, + z: { keyCode: 90, key: 'z', code: 'KeyZ' }, + Meta: { keyCode: 91, key: 'Meta', code: 'MetaLeft', location: 1 }, + '*': { keyCode: 106, key: '*', code: 'NumpadMultiply', location: 3 }, + '+': { keyCode: 107, key: '+', code: 'NumpadAdd', location: 3 }, + '-': { keyCode: 109, key: '-', code: 'NumpadSubtract', location: 3 }, + '/': { keyCode: 111, key: '/', code: 'NumpadDivide', location: 3 }, + ';': { keyCode: 186, key: ';', code: 'Semicolon' }, + '=': { keyCode: 187, key: '=', code: 'Equal' }, + ',': { keyCode: 188, key: ',', code: 'Comma' }, + '.': { keyCode: 190, key: '.', code: 'Period' }, + '`': { keyCode: 192, key: '`', code: 'Backquote' }, + '[': { keyCode: 219, key: '[', code: 'BracketLeft' }, + '\\': { keyCode: 220, key: '\\', code: 'Backslash' }, + ']': { keyCode: 221, key: ']', code: 'BracketRight' }, + "'": { keyCode: 222, key: "'", code: 'Quote' }, + Attn: { keyCode: 246, key: 'Attn' }, + CrSel: { keyCode: 247, key: 'CrSel', code: 'Props' }, + ExSel: { keyCode: 248, key: 'ExSel' }, + EraseEof: { keyCode: 249, key: 'EraseEof' }, + Play: { keyCode: 250, key: 'Play' }, + ZoomOut: { keyCode: 251, key: 'ZoomOut' }, + ')': { keyCode: 48, key: ')', code: 'Digit0' }, + '!': { keyCode: 49, key: '!', code: 'Digit1' }, + '@': { keyCode: 50, key: '@', code: 'Digit2' }, + '#': { keyCode: 51, key: '#', code: 'Digit3' }, + $: { keyCode: 52, key: '$', code: 'Digit4' }, + '%': { keyCode: 53, key: '%', code: 'Digit5' }, + '^': { keyCode: 54, key: '^', code: 'Digit6' }, + '&': { keyCode: 55, key: '&', code: 'Digit7' }, + '(': { keyCode: 57, key: '(', code: 'Digit9' }, + A: { keyCode: 65, key: 'A', code: 'KeyA' }, + B: { keyCode: 66, key: 'B', code: 'KeyB' }, + C: { keyCode: 67, key: 'C', code: 'KeyC' }, + D: { keyCode: 68, key: 'D', code: 'KeyD' }, + E: { keyCode: 69, key: 'E', code: 'KeyE' }, + F: { keyCode: 70, key: 'F', code: 'KeyF' }, + G: { keyCode: 71, key: 'G', code: 'KeyG' }, + H: { keyCode: 72, key: 'H', code: 'KeyH' }, + I: { keyCode: 73, key: 'I', code: 'KeyI' }, + J: { keyCode: 74, key: 'J', code: 'KeyJ' }, + K: { keyCode: 75, key: 'K', code: 'KeyK' }, + L: { keyCode: 76, key: 'L', code: 'KeyL' }, + M: { keyCode: 77, key: 'M', code: 'KeyM' }, + N: { keyCode: 78, key: 'N', code: 'KeyN' }, + O: { keyCode: 79, key: 'O', code: 'KeyO' }, + P: { keyCode: 80, key: 'P', code: 'KeyP' }, + Q: { keyCode: 81, key: 'Q', code: 'KeyQ' }, + R: { keyCode: 82, key: 'R', code: 'KeyR' }, + S: { keyCode: 83, key: 'S', code: 'KeyS' }, + T: { keyCode: 84, key: 'T', code: 'KeyT' }, + U: { keyCode: 85, key: 'U', code: 'KeyU' }, + V: { keyCode: 86, key: 'V', code: 'KeyV' }, + W: { keyCode: 87, key: 'W', code: 'KeyW' }, + X: { keyCode: 88, key: 'X', code: 'KeyX' }, + Y: { keyCode: 89, key: 'Y', code: 'KeyY' }, + Z: { keyCode: 90, key: 'Z', code: 'KeyZ' }, + ':': { keyCode: 186, key: ':', code: 'Semicolon' }, + '<': { keyCode: 188, key: '<', code: 'Comma' }, + _: { keyCode: 189, key: '_', code: 'Minus' }, + '>': { keyCode: 190, key: '>', code: 'Period' }, + '?': { keyCode: 191, key: '?', code: 'Slash' }, + '~': { keyCode: 192, key: '~', code: 'Backquote' }, + '{': { keyCode: 219, key: '{', code: 'BracketLeft' }, + '|': { keyCode: 220, key: '|', code: 'Backslash' }, + '}': { keyCode: 221, key: '}', code: 'BracketRight' }, + '"': { keyCode: 222, key: '"', code: 'Quote' }, + SoftLeft: { key: 'SoftLeft', code: 'SoftLeft', location: 4 }, + SoftRight: { key: 'SoftRight', code: 'SoftRight', location: 4 }, + Camera: { keyCode: 44, key: 'Camera', code: 'Camera', location: 4 }, + Call: { key: 'Call', code: 'Call', location: 4 }, + EndCall: { keyCode: 95, key: 'EndCall', code: 'EndCall', location: 4 }, + VolumeDown: { + keyCode: 182, + key: 'VolumeDown', + code: 'VolumeDown', + location: 4, + }, + VolumeUp: { keyCode: 183, key: 'VolumeUp', code: 'VolumeUp', location: 4 }, }; diff --git a/src/WebSocketTransport.ts b/src/WebSocketTransport.ts index 5e6e90c0b1481..f1305599803e6 100644 --- a/src/WebSocketTransport.ts +++ b/src/WebSocketTransport.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import * as NodeWebSocket from 'ws'; -import type {ConnectionTransport} from './ConnectionTransport'; +import type { ConnectionTransport } from './ConnectionTransport'; export class WebSocketTransport implements ConnectionTransport { static create(url: string): Promise { @@ -35,13 +35,11 @@ export class WebSocketTransport implements ConnectionTransport { constructor(ws: NodeWebSocket) { this._ws = ws; - this._ws.addEventListener('message', event => { - if (this.onmessage) - this.onmessage.call(null, event.data); + this._ws.addEventListener('message', (event) => { + if (this.onmessage) this.onmessage.call(null, event.data); }); this._ws.addEventListener('close', () => { - if (this.onclose) - this.onclose.call(null); + if (this.onclose) this.onclose.call(null); }); // Silently ignore all errors - we don't know what to do with them. this._ws.addEventListener('error', () => {}); @@ -57,4 +55,3 @@ export class WebSocketTransport implements ConnectionTransport { this._ws.close(); } } - diff --git a/src/Worker.ts b/src/Worker.ts index b075370bcefa5..cad92e5424f3f 100644 --- a/src/Worker.ts +++ b/src/Worker.ts @@ -13,14 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {EventEmitter} from 'events'; -import {debugError} from './helper'; -import {ExecutionContext} from './ExecutionContext'; -import {JSHandle} from './JSHandle'; -import {CDPSession} from './Connection'; +import { EventEmitter } from 'events'; +import { debugError } from './helper'; +import { ExecutionContext } from './ExecutionContext'; +import { JSHandle } from './JSHandle'; +import { CDPSession } from './Connection'; -type ConsoleAPICalledCallback = (eventType: string, handles: JSHandle[], trace: Protocol.Runtime.StackTrace) => void; -type ExceptionThrownCallback = (details: Protocol.Runtime.ExceptionDetails) => void; +type ConsoleAPICalledCallback = ( + eventType: string, + handles: JSHandle[], + trace: Protocol.Runtime.StackTrace +) => void; +type ExceptionThrownCallback = ( + details: Protocol.Runtime.ExceptionDetails +) => void; type JSHandleFactory = (obj: Protocol.Runtime.RemoteObject) => JSHandle; export class Worker extends EventEmitter { @@ -29,24 +35,44 @@ export class Worker extends EventEmitter { _executionContextPromise: Promise; _executionContextCallback: (value: ExecutionContext) => void; - constructor(client: CDPSession, url: string, consoleAPICalled: ConsoleAPICalledCallback, exceptionThrown: ExceptionThrownCallback) { + constructor( + client: CDPSession, + url: string, + consoleAPICalled: ConsoleAPICalledCallback, + exceptionThrown: ExceptionThrownCallback + ) { super(); this._client = client; this._url = url; - this._executionContextPromise = new Promise(x => this._executionContextCallback = x); + this._executionContextPromise = new Promise( + (x) => (this._executionContextCallback = x) + ); let jsHandleFactory: JSHandleFactory; - this._client.once('Runtime.executionContextCreated', async event => { + this._client.once('Runtime.executionContextCreated', async (event) => { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - jsHandleFactory = remoteObject => new JSHandle(executionContext, client, remoteObject); - const executionContext = new ExecutionContext(client, event.context, null); + jsHandleFactory = (remoteObject) => + new JSHandle(executionContext, client, remoteObject); + const executionContext = new ExecutionContext( + client, + event.context, + null + ); this._executionContextCallback(executionContext); }); // This might fail if the target is closed before we recieve all execution contexts. this._client.send('Runtime.enable', {}).catch(debugError); - this._client.on('Runtime.consoleAPICalled', event => consoleAPICalled(event.type, event.args.map(jsHandleFactory), event.stackTrace)); - this._client.on('Runtime.exceptionThrown', exception => exceptionThrown(exception.exceptionDetails)); + this._client.on('Runtime.consoleAPICalled', (event) => + consoleAPICalled( + event.type, + event.args.map(jsHandleFactory), + event.stackTrace + ) + ); + this._client.on('Runtime.exceptionThrown', (exception) => + exceptionThrown(exception.exceptionDetails) + ); } url(): string { @@ -57,11 +83,23 @@ export class Worker extends EventEmitter { return this._executionContextPromise; } - async evaluate(pageFunction: Function|string, ...args: any[]): Promise { - return (await this._executionContextPromise).evaluate(pageFunction, ...args); + async evaluate( + pageFunction: Function | string, + ...args: any[] + ): Promise { + return (await this._executionContextPromise).evaluate( + pageFunction, + ...args + ); } - async evaluateHandle(pageFunction: Function|string, ...args: any[]): Promise { - return (await this._executionContextPromise).evaluateHandle(pageFunction, ...args); + async evaluateHandle( + pageFunction: Function | string, + ...args: any[] + ): Promise { + return (await this._executionContextPromise).evaluateHandle( + pageFunction, + ...args + ); } } diff --git a/src/helper.ts b/src/helper.ts index 52beda63b8dc6..1b3d2f65df8af 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {TimeoutError} from './Errors'; +import { TimeoutError } from './Errors'; import * as debug from 'debug'; import * as fs from 'fs'; -import {CDPSession} from './Connection'; -import {promisify} from 'util'; +import { CDPSession } from './Connection'; +import { promisify } from 'util'; const openAsync = promisify(fs.open); const writeAsync = promisify(fs.write); @@ -26,22 +26,29 @@ const closeAsync = promisify(fs.close); export const debugError = debug('puppeteer:error'); export function assert(value: unknown, message?: string): void { - if (!value) - throw new Error(message); + if (!value) throw new Error(message); } interface AnyClass { prototype: object; } - -function getExceptionMessage(exceptionDetails: Protocol.Runtime.ExceptionDetails): string { +function getExceptionMessage( + exceptionDetails: Protocol.Runtime.ExceptionDetails +): string { if (exceptionDetails.exception) - return exceptionDetails.exception.description || exceptionDetails.exception.value; + return ( + exceptionDetails.exception.description || exceptionDetails.exception.value + ); let message = exceptionDetails.text; if (exceptionDetails.stackTrace) { for (const callframe of exceptionDetails.stackTrace.callFrames) { - const location = callframe.url + ':' + callframe.lineNumber + ':' + callframe.columnNumber; + const location = + callframe.url + + ':' + + callframe.lineNumber + + ':' + + callframe.columnNumber; const functionName = callframe.functionName || ''; message += `\n at ${functionName} (${location})`; } @@ -49,7 +56,9 @@ function getExceptionMessage(exceptionDetails: Protocol.Runtime.ExceptionDetails return message; } -function valueFromRemoteObject(remoteObject: Protocol.Runtime.RemoteObject): any { +function valueFromRemoteObject( + remoteObject: Protocol.Runtime.RemoteObject +): any { assert(!remoteObject.objectId, 'Cannot extract value when objectId is given'); if (remoteObject.unserializableValue) { if (remoteObject.type === 'bigint' && typeof BigInt !== 'undefined') @@ -64,36 +73,55 @@ function valueFromRemoteObject(remoteObject: Protocol.Runtime.RemoteObject): any case '-Infinity': return -Infinity; default: - throw new Error('Unsupported unserializable value: ' + remoteObject.unserializableValue); + throw new Error( + 'Unsupported unserializable value: ' + + remoteObject.unserializableValue + ); } } return remoteObject.value; } -async function releaseObject(client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject): Promise { - if (!remoteObject.objectId) - return; - await client.send('Runtime.releaseObject', {objectId: remoteObject.objectId}).catch(error => { - // Exceptions might happen in case of a page been navigated or closed. - // Swallow these since they are harmless and we don't leak anything in this case. - debugError(error); - }); +async function releaseObject( + client: CDPSession, + remoteObject: Protocol.Runtime.RemoteObject +): Promise { + if (!remoteObject.objectId) return; + await client + .send('Runtime.releaseObject', { objectId: remoteObject.objectId }) + .catch((error) => { + // Exceptions might happen in case of a page been navigated or closed. + // Swallow these since they are harmless and we don't leak anything in this case. + debugError(error); + }); } function installAsyncStackHooks(classType: AnyClass): void { for (const methodName of Reflect.ownKeys(classType.prototype)) { const method = Reflect.get(classType.prototype, methodName); - if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function' || method.constructor.name !== 'AsyncFunction') + if ( + methodName === 'constructor' || + typeof methodName !== 'string' || + methodName.startsWith('_') || + typeof method !== 'function' || + method.constructor.name !== 'AsyncFunction' + ) continue; - Reflect.set(classType.prototype, methodName, function(...args) { + Reflect.set(classType.prototype, methodName, function (...args) { const syncStack = { - stack: '' + stack: '', }; Error.captureStackTrace(syncStack); - return method.call(this, ...args).catch(error => { - const stack = syncStack.stack.substring(syncStack.stack.indexOf('\n') + 1); + return method.call(this, ...args).catch((error) => { + const stack = syncStack.stack.substring( + syncStack.stack.indexOf('\n') + 1 + ); const clientStack = stack.substring(stack.indexOf('\n')); - if (error instanceof Error && error.stack && !error.stack.includes(clientStack)) + if ( + error instanceof Error && + error.stack && + !error.stack.includes(clientStack) + ) error.stack += '\n -- ASYNC --\n' + stack; throw error; }); @@ -107,12 +135,22 @@ export interface PuppeteerEventListener { handler: (...args: any[]) => void; } -function addEventListener(emitter: NodeJS.EventEmitter, eventName: string|symbol, handler: (...args: any[]) => void): PuppeteerEventListener { +function addEventListener( + emitter: NodeJS.EventEmitter, + eventName: string | symbol, + handler: (...args: any[]) => void +): PuppeteerEventListener { emitter.on(eventName, handler); - return {emitter, eventName, handler}; + return { emitter, eventName, handler }; } -function removeEventListeners(listeners: Array<{emitter: NodeJS.EventEmitter; eventName: string|symbol; handler: (...args: any[]) => void}>): void { +function removeEventListeners( + listeners: Array<{ + emitter: NodeJS.EventEmitter; + eventName: string | symbol; + handler: (...args: any[]) => void; + }> +): void { for (const listener of listeners) listener.emitter.removeListener(listener.eventName, listener.handler); listeners.length = 0; @@ -126,35 +164,44 @@ function isNumber(obj: unknown): obj is number { return typeof obj === 'number' || obj instanceof Number; } -async function waitForEvent(emitter: NodeJS.EventEmitter, eventName: string|symbol, predicate: (event: T) => boolean, timeout: number, abortPromise: Promise): Promise { +async function waitForEvent( + emitter: NodeJS.EventEmitter, + eventName: string | symbol, + predicate: (event: T) => boolean, + timeout: number, + abortPromise: Promise +): Promise { let eventTimeout, resolveCallback, rejectCallback; const promise = new Promise((resolve, reject) => { resolveCallback = resolve; rejectCallback = reject; }); - const listener = addEventListener(emitter, eventName, event => { - if (!predicate(event)) - return; + const listener = addEventListener(emitter, eventName, (event) => { + if (!predicate(event)) return; resolveCallback(event); }); if (timeout) { eventTimeout = setTimeout(() => { - rejectCallback(new TimeoutError('Timeout exceeded while waiting for event')); + rejectCallback( + new TimeoutError('Timeout exceeded while waiting for event') + ); }, timeout); } function cleanup(): void { removeEventListeners([listener]); clearTimeout(eventTimeout); } - const result = await Promise.race([promise, abortPromise]).then(r => { - cleanup(); - return r; - }, error => { - cleanup(); - throw error; - }); - if (result instanceof Error) - throw result; + const result = await Promise.race([promise, abortPromise]).then( + (r) => { + cleanup(); + return r; + }, + (error) => { + cleanup(); + throw error; + } + ); + if (result instanceof Error) throw result; return result; } @@ -166,46 +213,53 @@ function evaluationString(fun: Function | string, ...args: unknown[]): string { } function serializeArgument(arg: unknown): string { - if (Object.is(arg, undefined)) - return 'undefined'; + if (Object.is(arg, undefined)) return 'undefined'; return JSON.stringify(arg); } return `(${fun})(${args.map(serializeArgument).join(',')})`; } -async function waitWithTimeout(promise: Promise, taskName: string, timeout: number): Promise { +async function waitWithTimeout( + promise: Promise, + taskName: string, + timeout: number +): Promise { let reject; - const timeoutError = new TimeoutError(`waiting for ${taskName} failed: timeout ${timeout}ms exceeded`); - const timeoutPromise = new Promise((resolve, x) => reject = x); + const timeoutError = new TimeoutError( + `waiting for ${taskName} failed: timeout ${timeout}ms exceeded` + ); + const timeoutPromise = new Promise((resolve, x) => (reject = x)); let timeoutTimer = null; - if (timeout) - timeoutTimer = setTimeout(() => reject(timeoutError), timeout); + if (timeout) timeoutTimer = setTimeout(() => reject(timeoutError), timeout); try { return await Promise.race([promise, timeoutPromise]); } finally { - if (timeoutTimer) - clearTimeout(timeoutTimer); + if (timeoutTimer) clearTimeout(timeoutTimer); } } -async function readProtocolStream(client: CDPSession, handle: string, path?: string): Promise { +async function readProtocolStream( + client: CDPSession, + handle: string, + path?: string +): Promise { let eof = false; let file; - if (path) - file = await openAsync(path, 'w'); + if (path) file = await openAsync(path, 'w'); const bufs = []; while (!eof) { - const response = await client.send('IO.read', {handle}); + const response = await client.send('IO.read', { handle }); eof = response.eof; - const buf = Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined); + const buf = Buffer.from( + response.data, + response.base64Encoded ? 'base64' : undefined + ); bufs.push(buf); - if (path) - await writeAsync(file, buf); + if (path) await writeAsync(file, buf); } - if (path) - await closeAsync(file); - await client.send('IO.close', {handle}); + if (path) await closeAsync(file); + await client.send('IO.close', { handle }); let resultBuffer = null; try { resultBuffer = Buffer.concat(bufs); @@ -214,7 +268,6 @@ async function readProtocolStream(client: CDPSession, handle: string, path?: str } } - export const helper = { promisify, evaluationString, diff --git a/test/CDPSession.spec.js b/test/CDPSession.spec.js index e2682c2f93ad6..43d0aad381e42 100644 --- a/test/CDPSession.spec.js +++ b/test/CDPSession.spec.js @@ -14,38 +14,42 @@ * limitations under the License. */ -const {waitEvent} = require('./utils'); +const { waitEvent } = require('./utils'); const expect = require('expect'); -const {getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); -describeChromeOnly('Target.createCDPSession', function() { +describeChromeOnly('Target.createCDPSession', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - it('should work', async() => { - const {page} = getTestState(); + it('should work', async () => { + const { page } = getTestState(); const client = await page.target().createCDPSession(); await Promise.all([ client.send('Runtime.enable'), - client.send('Runtime.evaluate', {expression: 'window.foo = "bar"'}) + client.send('Runtime.evaluate', { expression: 'window.foo = "bar"' }), ]); const foo = await page.evaluate(() => window.foo); expect(foo).toBe('bar'); }); - it('should send events', async() => { - const {page, server} = getTestState(); + it('should send events', async () => { + const { page, server } = getTestState(); const client = await page.target().createCDPSession(); await client.send('Network.enable'); const events = []; - client.on('Network.requestWillBeSent', event => events.push(event)); + 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() => { - const {page} = getTestState(); + it('should enable and disable domains independently', async () => { + const { page } = getTestState(); const client = await page.target().createCDPSession(); await client.send('Runtime.enable'); @@ -56,32 +60,38 @@ describeChromeOnly('Target.createCDPSession', function() { // generate a script in page and wait for the event. const [event] = await Promise.all([ waitEvent(client, 'Debugger.scriptParsed'), - page.evaluate('//# sourceURL=foo.js') + page.evaluate('//# sourceURL=foo.js'), ]); - // expect events to be dispatched. + // expect events to be dispatched. expect(event.url).toBe('foo.js'); }); - it('should be able to detach session', async() => { - const {page} = getTestState(); + it('should be able to detach session', async () => { + const { page } = getTestState(); const client = await page.target().createCDPSession(); await client.send('Runtime.enable'); - const evalResponse = await client.send('Runtime.evaluate', {expression: '1 + 2', returnByValue: true}); + 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}); + await client.send('Runtime.evaluate', { + expression: '3 + 1', + returnByValue: true, + }); } catch (error_) { error = error_; } expect(error.message).toContain('Session closed.'); }); - it('should throw nice errors', async() => { - const {page} = getTestState(); + it('should throw nice errors', async () => { + const { page } = getTestState(); const client = await page.target().createCDPSession(); - const error = await theSourceOfTheProblems().catch(error => error); + const error = await theSourceOfTheProblems().catch((error) => error); expect(error.stack).toContain('theSourceOfTheProblems'); expect(error.message).toContain('ThisCommand.DoesNotExist'); diff --git a/test/accessibility.spec.js b/test/accessibility.spec.js index a62a624efc281..5f223ecfe6393 100644 --- a/test/accessibility.spec.js +++ b/test/accessibility.spec.js @@ -15,14 +15,18 @@ */ const expect = require('expect'); -const {getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); -describeFailsFirefox('Accessibility', function() { +describeFailsFirefox('Accessibility', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - it('should work', async() => { - const {page, isFirefox} = getTestState(); + it('should work', async () => { + const { page, isFirefox } = getTestState(); await page.setContent(` @@ -46,210 +50,283 @@ describeFailsFirefox('Accessibility', function() { `); await page.focus('[placeholder="Empty input"]'); - const golden = isFirefox ? { - role: 'document', - name: 'Accessibility Test', - children: [ - {role: 'text leaf', name: 'Hello World'}, - {role: 'heading', name: 'Inputs', level: 1}, - {role: 'entry', name: 'Empty input', focused: true}, - {role: 'entry', name: 'readonly input', readonly: true}, - {role: 'entry', name: 'disabled input', disabled: true}, - {role: 'entry', name: 'Input with whitespace', value: ' '}, - {role: 'entry', name: '', value: 'value only'}, - {role: 'entry', name: '', value: 'and a value'}, // firefox doesn't use aria-placeholder for the name - {role: 'entry', name: '', value: 'and a value', description: 'This is a description!'}, // and here - {role: 'combobox', name: '', value: 'First Option', haspopup: true, children: [ - {role: 'combobox option', name: 'First Option', selected: true}, - {role: 'combobox option', name: 'Second Option'}] - }] - } : { - role: 'WebArea', - name: 'Accessibility Test', - children: [ - {role: 'text', name: 'Hello World'}, - {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: 'combobox', name: '', value: 'First Option', children: [ - {role: 'menuitem', name: 'First Option', selected: true}, - {role: 'menuitem', name: 'Second Option'}] - }] - }; + const golden = isFirefox + ? { + role: 'document', + name: 'Accessibility Test', + children: [ + { role: 'text leaf', name: 'Hello World' }, + { role: 'heading', name: 'Inputs', level: 1 }, + { role: 'entry', name: 'Empty input', focused: true }, + { role: 'entry', name: 'readonly input', readonly: true }, + { role: 'entry', name: 'disabled input', disabled: true }, + { role: 'entry', name: 'Input with whitespace', value: ' ' }, + { role: 'entry', name: '', value: 'value only' }, + { role: 'entry', name: '', value: 'and a value' }, // firefox doesn't use aria-placeholder for the name + { + role: 'entry', + name: '', + value: 'and a value', + description: 'This is a description!', + }, // and here + { + role: 'combobox', + name: '', + value: 'First Option', + haspopup: true, + children: [ + { + role: 'combobox option', + name: 'First Option', + selected: true, + }, + { role: 'combobox option', name: 'Second Option' }, + ], + }, + ], + } + : { + role: 'WebArea', + name: 'Accessibility Test', + children: [ + { role: 'text', name: 'Hello World' }, + { 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: 'combobox', + name: '', + value: 'First Option', + children: [ + { role: 'menuitem', name: 'First Option', selected: true }, + { role: 'menuitem', name: 'Second Option' }, + ], + }, + ], + }; expect(await page.accessibility.snapshot()).toEqual(golden); }); - it('should report uninteresting nodes', async() => { - const {page, isFirefox} = getTestState(); + it('should report uninteresting nodes', async () => { + const { page, isFirefox } = getTestState(); await page.setContent(``); await page.focus('textarea'); - const golden = isFirefox ? { - role: 'entry', - name: '', - value: 'hi', - focused: true, - multiline: true, - children: [{ - role: 'text leaf', - name: 'hi' - }] - } : { - role: 'textbox', - name: '', - value: 'hi', - focused: true, - multiline: true, - children: [{ - role: 'generic', - name: '', - children: [{ - role: 'text', name: 'hi' - }] - }] - }; - expect(findFocusedNode(await page.accessibility.snapshot({interestingOnly: false}))).toEqual(golden); + const golden = isFirefox + ? { + role: 'entry', + name: '', + value: 'hi', + focused: true, + multiline: true, + children: [ + { + role: 'text leaf', + name: 'hi', + }, + ], + } + : { + role: 'textbox', + name: '', + value: 'hi', + focused: true, + multiline: true, + children: [ + { + role: 'generic', + name: '', + children: [ + { + role: 'text', + name: 'hi', + }, + ], + }, + ], + }; + expect( + findFocusedNode( + await page.accessibility.snapshot({ interestingOnly: false }) + ) + ).toEqual(golden); }); - it('roledescription', async() => { - const {page} = getTestState(); + it('roledescription', async () => { + const { page } = getTestState(); - await page.setContent('
Hi
'); + await page.setContent( + '
Hi
' + ); const snapshot = await page.accessibility.snapshot(); expect(snapshot.children[0].roledescription).toEqual('foo'); }); - it('orientation', async() => { - const {page} = getTestState(); + it('orientation', async () => { + const { page } = getTestState(); - await page.setContent('11'); + await page.setContent( + '11' + ); const snapshot = await page.accessibility.snapshot(); expect(snapshot.children[0].orientation).toEqual('vertical'); }); - it('autocomplete', async() => { - const {page} = getTestState(); + it('autocomplete', async () => { + const { page } = getTestState(); await page.setContent(''); const snapshot = await page.accessibility.snapshot(); expect(snapshot.children[0].autocomplete).toEqual('list'); }); - it('multiselectable', async() => { - const {page} = getTestState(); + it('multiselectable', async () => { + const { page } = getTestState(); - await page.setContent('
hey
'); + await page.setContent( + '
hey
' + ); const snapshot = await page.accessibility.snapshot(); expect(snapshot.children[0].multiselectable).toEqual(true); }); - it('keyshortcuts', async() => { - const {page} = getTestState(); + it('keyshortcuts', async () => { + const { page } = getTestState(); - await page.setContent('
hey
'); + 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() => { - const {page, isFirefox} = getTestState(); + describe('filtering children of leaf nodes', function () { + it('should not report text nodes inside controls', async () => { + const { page, isFirefox } = getTestState(); await page.setContent(`
Tab1
Tab2
`); - const golden = isFirefox ? { - role: 'document', - name: '', - children: [{ - role: 'pagetab', - name: 'Tab1', - selected: true - }, { - role: 'pagetab', - name: 'Tab2' - }] - } : { - role: 'WebArea', - name: '', - children: [{ - role: 'tab', - name: 'Tab1', - selected: true - }, { - role: 'tab', - name: 'Tab2' - }] - }; + const golden = isFirefox + ? { + role: 'document', + name: '', + children: [ + { + role: 'pagetab', + name: 'Tab1', + selected: true, + }, + { + role: 'pagetab', + name: 'Tab2', + }, + ], + } + : { + role: 'WebArea', + name: '', + children: [ + { + role: 'tab', + name: 'Tab1', + selected: true, + }, + { + role: 'tab', + name: 'Tab2', + }, + ], + }; expect(await page.accessibility.snapshot()).toEqual(golden); }); - it('rich text editable fields should have children', async() => { - const {page, isFirefox} = getTestState(); + it('rich text editable fields should have children', async () => { + const { page, isFirefox } = getTestState(); await page.setContent(`
Edit this image: my fake image
`); - const golden = isFirefox ? { - 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 golden = isFirefox + ? { + role: 'section', + name: '', + children: [ + { + role: 'text leaf', + name: 'Edit this image: ', + }, + { + role: 'text', + name: 'my fake image', + }, + ], + } + : { + role: 'generic', + name: '', + value: 'Edit this image: ', + children: [ + { + role: 'text', + name: 'Edit this image:', + }, + { + role: 'img', + name: 'my fake image', + }, + ], + }; const snapshot = await page.accessibility.snapshot(); expect(snapshot.children[0]).toEqual(golden); }); - it('rich text editable fields with role should have children', async() => { - const {page, isFirefox} = getTestState(); + it('rich text editable fields with role should have children', async () => { + const { page, isFirefox } = getTestState(); await page.setContent(`
Edit this image: my fake image
`); - const golden = isFirefox ? { - role: 'entry', - 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 golden = isFirefox + ? { + role: 'entry', + name: '', + value: 'Edit this image: my fake image', + children: [ + { + role: 'text', + name: 'my fake image', + }, + ], + } + : { + role: 'textbox', + name: '', + value: 'Edit this image: ', + children: [ + { + role: 'text', + name: 'Edit this image:', + }, + { + role: 'img', + name: 'my fake image', + }, + ], + }; const snapshot = await page.accessibility.snapshot(); expect(snapshot.children[0]).toEqual(golden); }); // Firefox does not support contenteditable="plaintext-only". - describeFailsFirefox('plaintext contenteditable', function() { - it('plain text field with role should not have children', async() => { - const {page} = getTestState(); + describeFailsFirefox('plaintext contenteditable', function () { + it('plain text field with role should not have children', async () => { + const { page } = getTestState(); await page.setContent(`
Edit this image:my fake image
`); @@ -257,119 +334,125 @@ describeFailsFirefox('Accessibility', function() { expect(snapshot.children[0]).toEqual({ role: 'textbox', name: '', - value: 'Edit this image:' + value: 'Edit this image:', }); }); - it('plain text field without role should not have content', async() => { - const {page} = getTestState(); + it('plain text field without role should not have content', async () => { + const { page } = getTestState(); await page.setContent(`
Edit this image:my fake image
`); const snapshot = await page.accessibility.snapshot(); expect(snapshot.children[0]).toEqual({ role: 'generic', - name: '' + name: '', }); }); - it('plain text field with tabindex and without role should not have content', async() => { - const {page} = getTestState(); + it('plain text field with tabindex and without role should not have content', async () => { + const { page } = getTestState(); await page.setContent(`
Edit this image:my fake image
`); const snapshot = await page.accessibility.snapshot(); expect(snapshot.children[0]).toEqual({ role: 'generic', - name: '' + name: '', }); }); }); - it('non editable textbox with role and tabIndex and label should not have children', async() => { - const {page, isFirefox} = getTestState(); + it('non editable textbox with role and tabIndex and label should not have children', async () => { + const { page, isFirefox } = getTestState(); await page.setContent(`
this is the inner content yo
`); - const golden = isFirefox ? { - role: 'entry', - name: 'my favorite textbox', - value: 'this is the inner content yo' - } : { - role: 'textbox', - name: 'my favorite textbox', - value: 'this is the inner content ' - }; + const golden = isFirefox + ? { + role: 'entry', + name: 'my favorite textbox', + value: 'this is the inner content yo', + } + : { + 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() => { - const {page, isFirefox} = getTestState(); + it('checkbox with and tabIndex and label should not have children', async () => { + const { page, isFirefox } = getTestState(); await page.setContent(`
this is the inner content yo
`); - const golden = isFirefox ? { - role: 'checkbutton', - name: 'my favorite checkbox', - checked: true - } : { - role: 'checkbox', - name: 'my favorite checkbox', - checked: true - }; + const golden = isFirefox + ? { + role: 'checkbutton', + name: 'my favorite checkbox', + checked: true, + } + : { + 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() => { - const {page, isFirefox} = getTestState(); + it('checkbox without label should not have children', async () => { + const { page, isFirefox } = getTestState(); await page.setContent(`
this is the inner content yo
`); - const golden = isFirefox ? { - role: 'checkbutton', - name: 'this is the inner content yo', - checked: true - } : { - role: 'checkbox', - name: 'this is the inner content yo', - checked: true - }; + const golden = isFirefox + ? { + role: 'checkbutton', + 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() => { - const {page} = getTestState(); + describe('root option', function () { + it('should work a button', async () => { + const { page } = getTestState(); await page.setContent(``); const button = await page.$('button'); - expect(await page.accessibility.snapshot({root: button})).toEqual({ + expect(await page.accessibility.snapshot({ root: button })).toEqual({ role: 'button', - name: 'My Button' + name: 'My Button', }); }); - it('should work an input', async() => { - const {page} = getTestState(); + it('should work an input', async () => { + const { page } = getTestState(); await page.setContent(``); const input = await page.$('input'); - expect(await page.accessibility.snapshot({root: input})).toEqual({ + expect(await page.accessibility.snapshot({ root: input })).toEqual({ role: 'textbox', name: 'My Input', - value: 'My Value' + value: 'My Value', }); }); - it('should work a menu', async() => { - const {page} = getTestState(); + it('should work a menu', async () => { + const { page } = getTestState(); await page.setContent(`
@@ -380,39 +463,45 @@ describeFailsFirefox('Accessibility', function() { `); const menu = await page.$('div[role="menu"]'); - expect(await page.accessibility.snapshot({root: menu})).toEqual({ + 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'} ] + children: [ + { role: 'menuitem', name: 'First Item' }, + { role: 'menuitem', name: 'Second Item' }, + { role: 'menuitem', name: 'Third Item' }, + ], }); }); - it('should return null when the element is no longer in DOM', async() => { - const {page} = getTestState(); + it('should return null when the element is no longer in DOM', async () => { + const { page } = getTestState(); await page.setContent(``); const button = await page.$('button'); - await page.$eval('button', button => button.remove()); - expect(await page.accessibility.snapshot({root: button})).toEqual(null); + await page.$eval('button', (button) => button.remove()); + expect(await page.accessibility.snapshot({ root: button })).toEqual( + null + ); }); - it('should support the interestingOnly option', async() => { - const {page} = getTestState(); + it('should support the interestingOnly option', async () => { + const { page } = getTestState(); await page.setContent(`
`); const div = await page.$('div'); - expect(await page.accessibility.snapshot({root: div})).toEqual(null); - expect(await page.accessibility.snapshot({root: div, interestingOnly: false})).toEqual({ + expect(await page.accessibility.snapshot({ root: div })).toEqual(null); + expect( + await page.accessibility.snapshot({ + root: div, + interestingOnly: false, + }) + ).toEqual({ role: 'generic', name: '', children: [ { role: 'button', name: 'My Button', - children: [ - {role: 'text', name: 'My Button'}, - ], + children: [{ role: 'text', name: 'My Button' }], }, ], }); @@ -420,12 +509,10 @@ describeFailsFirefox('Accessibility', function() { }); }); function findFocusedNode(node) { - if (node.focused) - return node; + if (node.focused) return node; for (const child of node.children || []) { const focusedChild = findFocusedNode(child); - if (focusedChild) - return focusedChild; + if (focusedChild) return focusedChild; } return null; } diff --git a/test/assert-coverage-test.js b/test/assert-coverage-test.js index 39b7b535c6e03..6e26a1121adc3 100644 --- a/test/assert-coverage-test.js +++ b/test/assert-coverage-test.js @@ -1,5 +1,5 @@ -const {describe, it} = require('mocha'); -const {getCoverageResults} = require('./coverage-utils'); +const { describe, it } = require('mocha'); +const { getCoverageResults } = require('./coverage-utils'); const expect = require('expect'); describe('API coverage test', () => { @@ -9,11 +9,12 @@ describe('API coverage test', () => { const coverageMap = getCoverageResults(); const missingMethods = []; for (const method of coverageMap.keys()) { - if (!coverageMap.get(method)) - missingMethods.push(method); + if (!coverageMap.get(method)) missingMethods.push(method); } if (missingMethods.length) { - console.error('\nCoverage check failed: not all API methods called. See above output for list of missing methods.'); + console.error( + '\nCoverage check failed: not all API methods called. See above output for list of missing methods.' + ); console.error(missingMethods.join('\n')); } diff --git a/test/assets/es6/es6import.js b/test/assets/es6/es6import.js index 9a0a1095d1542..9aac2d4d64e0f 100644 --- a/test/assets/es6/es6import.js +++ b/test/assets/es6/es6import.js @@ -1,2 +1,2 @@ import num from './es6module.js'; -window.__es6injected = num; \ No newline at end of file +window.__es6injected = num; diff --git a/test/assets/es6/es6module.js b/test/assets/es6/es6module.js index a4012bff06c1b..7a4e8a723a407 100644 --- a/test/assets/es6/es6module.js +++ b/test/assets/es6/es6module.js @@ -1 +1 @@ -export default 42; \ No newline at end of file +export default 42; diff --git a/test/assets/es6/es6pathimport.js b/test/assets/es6/es6pathimport.js index 99919621a82d4..eb17a9a3d108f 100644 --- a/test/assets/es6/es6pathimport.js +++ b/test/assets/es6/es6pathimport.js @@ -1,2 +1,2 @@ import num from './es6/es6module.js'; -window.__es6injected = num; \ No newline at end of file +window.__es6injected = num; diff --git a/test/assets/injectedfile.js b/test/assets/injectedfile.js index 6cb04f1bba081..c211b62c167a3 100644 --- a/test/assets/injectedfile.js +++ b/test/assets/injectedfile.js @@ -1,2 +1,2 @@ window.__injected = 42; -window.__injectedError = new Error('hi'); \ No newline at end of file +window.__injectedError = new Error('hi'); diff --git a/test/assets/input/mouse-helper.js b/test/assets/input/mouse-helper.js index 3c4d57033c7f2..4f2824dceb021 100644 --- a/test/assets/input/mouse-helper.js +++ b/test/assets/input/mouse-helper.js @@ -1,6 +1,6 @@ // This injects a box into the page that moves with the mouse; // Useful for debugging -(function(){ +(function () { const box = document.createElement('div'); box.classList.add('mouse-helper'); const styleElement = document.createElement('style'); @@ -42,19 +42,31 @@ `; document.head.appendChild(styleElement); document.body.appendChild(box); - document.addEventListener('mousemove', event => { - box.style.left = event.pageX + 'px'; - box.style.top = event.pageY + 'px'; - updateButtons(event.buttons); - }, true); - document.addEventListener('mousedown', event => { - updateButtons(event.buttons); - box.classList.add('button-' + event.which); - }, true); - document.addEventListener('mouseup', event => { - updateButtons(event.buttons); - box.classList.remove('button-' + event.which); - }, true); + document.addEventListener( + 'mousemove', + (event) => { + box.style.left = event.pageX + 'px'; + box.style.top = event.pageY + 'px'; + updateButtons(event.buttons); + }, + true + ); + document.addEventListener( + 'mousedown', + (event) => { + updateButtons(event.buttons); + box.classList.add('button-' + event.which); + }, + true + ); + document.addEventListener( + 'mouseup', + (event) => { + updateButtons(event.buttons); + box.classList.remove('button-' + event.which); + }, + true + ); function updateButtons(buttons) { for (let i = 0; i < 5; i++) box.classList.toggle('button-' + i, buttons & (1 << i)); diff --git a/test/assets/serviceworkers/fetch/sw.js b/test/assets/serviceworkers/fetch/sw.js index d44c7eab9465b..21381484b63f7 100644 --- a/test/assets/serviceworkers/fetch/sw.js +++ b/test/assets/serviceworkers/fetch/sw.js @@ -1,7 +1,7 @@ -self.addEventListener('fetch', event => { +self.addEventListener('fetch', (event) => { event.respondWith(fetch(event.request)); }); -self.addEventListener('activate', event => { +self.addEventListener('activate', (event) => { event.waitUntil(clients.claim()); }); diff --git a/test/assets/simple-extension/content-script.js b/test/assets/simple-extension/content-script.js index 965f99fd3d3da..0fd83b90f1eb5 100644 --- a/test/assets/simple-extension/content-script.js +++ b/test/assets/simple-extension/content-script.js @@ -1,3 +1,2 @@ console.log('hey from the content-script'); self.thisIsTheContentScript = true; - diff --git a/test/assets/worker/worker.js b/test/assets/worker/worker.js index d0d229a192ac6..0626f13e58d70 100644 --- a/test/assets/worker/worker.js +++ b/test/assets/worker/worker.js @@ -4,13 +4,13 @@ function workerFunction() { return 'worker function result'; } -self.addEventListener('message', event => { +self.addEventListener('message', (event) => { console.log('got this data: ' + event.data); }); -(async function() { +(async function () { while (true) { self.postMessage(workerFunction.toString()); - await new Promise(x => setTimeout(x, 100)); + await new Promise((x) => setTimeout(x, 100)); } -})(); \ No newline at end of file +})(); diff --git a/test/browser.spec.js b/test/browser.spec.js index 17ebc31894678..f20db1f657319 100644 --- a/test/browser.spec.js +++ b/test/browser.spec.js @@ -15,14 +15,14 @@ */ const expect = require('expect'); -const {getTestState, setupTestBrowserHooks} = require('./mocha-utils'); +const { getTestState, setupTestBrowserHooks } = require('./mocha-utils'); -describe('Browser specs', function() { +describe('Browser specs', function () { setupTestBrowserHooks(); - describe('Browser.version', function() { - it('should return whether we are in headless', async() => { - const {browser, isHeadless} = getTestState(); + describe('Browser.version', function () { + it('should return whether we are in headless', async () => { + const { browser, isHeadless } = getTestState(); const version = await browser.version(); expect(version.length).toBeGreaterThan(0); @@ -30,51 +30,49 @@ describe('Browser specs', function() { }); }); - describe('Browser.userAgent', function() { - it('should include WebKit', async() => { - const {browser, isChrome} = getTestState(); + describe('Browser.userAgent', function () { + it('should include WebKit', async () => { + const { browser, isChrome } = getTestState(); const userAgent = await browser.userAgent(); expect(userAgent.length).toBeGreaterThan(0); - if (isChrome) - expect(userAgent).toContain('WebKit'); - else - expect(userAgent).toContain('Gecko'); + if (isChrome) expect(userAgent).toContain('WebKit'); + else expect(userAgent).toContain('Gecko'); }); }); - describe('Browser.target', function() { - it('should return browser target', async() => { - const {browser} = getTestState(); + describe('Browser.target', function () { + it('should return browser target', async () => { + const { browser } = getTestState(); const target = browser.target(); expect(target.type()).toBe('browser'); }); }); - describe('Browser.process', function() { - it('should return child_process instance', async() => { - const {browser} = getTestState(); + describe('Browser.process', function () { + it('should return child_process instance', async () => { + const { browser } = getTestState(); const process = await browser.process(); expect(process.pid).toBeGreaterThan(0); }); - it('should not return child_process for remote browser', async() => { - const {browser, puppeteer} = getTestState(); + it('should not return child_process for remote browser', async () => { + const { browser, puppeteer } = getTestState(); const browserWSEndpoint = browser.wsEndpoint(); - const remoteBrowser = await puppeteer.connect({browserWSEndpoint}); + const remoteBrowser = await puppeteer.connect({ browserWSEndpoint }); expect(remoteBrowser.process()).toBe(null); remoteBrowser.disconnect(); }); }); describe('Browser.isConnected', () => { - it('should set the browser connected state', async() => { - const {browser, puppeteer} = getTestState(); + it('should set the browser connected state', async () => { + const { browser, puppeteer } = getTestState(); const browserWSEndpoint = browser.wsEndpoint(); - const newBrowser = await puppeteer.connect({browserWSEndpoint}); + const newBrowser = await puppeteer.connect({ browserWSEndpoint }); expect(newBrowser.isConnected()).toBe(true); newBrowser.disconnect(); expect(newBrowser.isConnected()).toBe(false); diff --git a/test/browsercontext.spec.js b/test/browsercontext.spec.js index c394a08459d8a..130870af9b456 100644 --- a/test/browsercontext.spec.js +++ b/test/browsercontext.spec.js @@ -15,23 +15,23 @@ */ const expect = require('expect'); -const {getTestState, setupTestBrowserHooks} = require('./mocha-utils'); +const { getTestState, setupTestBrowserHooks } = require('./mocha-utils'); const utils = require('./utils'); -describe('BrowserContext', function() { +describe('BrowserContext', function () { setupTestBrowserHooks(); - itFailsFirefox('should have default context', async() => { - const {browser} = getTestState(); + itFailsFirefox('should have default context', async () => { + const { browser } = getTestState(); expect(browser.browserContexts().length).toEqual(1); const defaultContext = browser.browserContexts()[0]; expect(defaultContext.isIncognito()).toBe(false); let error = null; - await defaultContext.close().catch(error_ => error = error_); + await defaultContext.close().catch((error_) => (error = error_)); expect(browser.defaultBrowserContext()).toBe(defaultContext); expect(error.message).toContain('cannot be closed'); }); - itFailsFirefox('should create new incognito context', async() => { - const {browser} = getTestState(); + itFailsFirefox('should create new incognito context', async () => { + const { browser } = getTestState(); expect(browser.browserContexts().length).toBe(1); const context = await browser.createIncognitoBrowserContext(); @@ -41,57 +41,68 @@ describe('BrowserContext', function() { await context.close(); expect(browser.browserContexts().length).toBe(1); }); - itFailsFirefox('should close all belonging targets once closing context', async() => { - const {browser} = getTestState(); + itFailsFirefox( + 'should close all belonging targets once closing context', + async () => { + const { browser } = getTestState(); - expect((await browser.pages()).length).toBe(1); + expect((await browser.pages()).length).toBe(1); - const context = await browser.createIncognitoBrowserContext(); - await context.newPage(); - expect((await browser.pages()).length).toBe(2); - expect((await context.pages()).length).toBe(1); + const context = await browser.createIncognitoBrowserContext(); + await context.newPage(); + expect((await browser.pages()).length).toBe(2); + expect((await context.pages()).length).toBe(1); - await context.close(); - expect((await browser.pages()).length).toBe(1); - }); - itFailsFirefox('window.open should use parent tab context', async() => { - const {browser, server} = getTestState(); + await context.close(); + expect((await browser.pages()).length).toBe(1); + } + ); + itFailsFirefox('window.open should use parent tab context', async () => { + const { browser, server } = getTestState(); const context = await browser.createIncognitoBrowserContext(); const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); const [popupTarget] = await Promise.all([ utils.waitEvent(browser, 'targetcreated'), - page.evaluate(url => window.open(url), server.EMPTY_PAGE) + page.evaluate((url) => window.open(url), server.EMPTY_PAGE), ]); expect(popupTarget.browserContext()).toBe(context); await context.close(); }); - itFailsFirefox('should fire target events', async() => { - const {browser, server} = getTestState(); + itFailsFirefox('should fire target events', async () => { + const { browser, server } = getTestState(); const context = await browser.createIncognitoBrowserContext(); const events = []; - context.on('targetcreated', target => events.push('CREATED: ' + target.url())); - context.on('targetchanged', target => events.push('CHANGED: ' + target.url())); - context.on('targetdestroyed', target => events.push('DESTROYED: ' + target.url())); + context.on('targetcreated', (target) => + events.push('CREATED: ' + target.url()) + ); + context.on('targetchanged', (target) => + events.push('CHANGED: ' + target.url()) + ); + context.on('targetdestroyed', (target) => + events.push('DESTROYED: ' + target.url()) + ); const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); await page.close(); expect(events).toEqual([ 'CREATED: about:blank', `CHANGED: ${server.EMPTY_PAGE}`, - `DESTROYED: ${server.EMPTY_PAGE}` + `DESTROYED: ${server.EMPTY_PAGE}`, ]); await context.close(); }); - itFailsFirefox('should wait for a target', async() => { - const {browser, server} = getTestState(); + itFailsFirefox('should wait for a target', async () => { + const { browser, server } = getTestState(); const context = await browser.createIncognitoBrowserContext(); let resolved = false; - const targetPromise = context.waitForTarget(target => target.url() === server.EMPTY_PAGE); - targetPromise.then(() => resolved = true); + const targetPromise = context.waitForTarget( + (target) => target.url() === server.EMPTY_PAGE + ); + targetPromise.then(() => (resolved = true)); const page = await context.newPage(); expect(resolved).toBe(false); await page.goto(server.EMPTY_PAGE); @@ -100,17 +111,21 @@ describe('BrowserContext', function() { await context.close(); }); - it('should timeout waiting for a non-existent target', async() => { - const {browser, server, puppeteer} = getTestState(); + it('should timeout waiting for a non-existent target', async () => { + const { browser, server, puppeteer } = getTestState(); const context = await browser.createIncognitoBrowserContext(); - const error = await context.waitForTarget(target => target.url() === server.EMPTY_PAGE, {timeout: 1}).catch(error_ => error_); + const error = await context + .waitForTarget((target) => target.url() === server.EMPTY_PAGE, { + timeout: 1, + }) + .catch((error_) => error_); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); await context.close(); }); - itFailsFirefox('should isolate localStorage and cookies', async() => { - const {browser, server} = getTestState(); + itFailsFirefox('should isolate localStorage and cookies', async () => { + const { browser, server } = getTestState(); // Create two incognito contexts. const context1 = await browser.createIncognitoBrowserContext(); @@ -143,27 +158,28 @@ describe('BrowserContext', function() { expect(context2.targets()[0]).toBe(page2.target()); // Make sure pages don't share localstorage or cookies. - expect(await page1.evaluate(() => localStorage.getItem('name'))).toBe('page1'); + 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(() => localStorage.getItem('name'))).toBe( + 'page2' + ); expect(await page2.evaluate(() => document.cookie)).toBe('name=page2'); // Cleanup contexts. - await Promise.all([ - context1.close(), - context2.close() - ]); + await Promise.all([context1.close(), context2.close()]); expect(browser.browserContexts().length).toBe(1); }); - itFailsFirefox('should work across sessions', async() => { - const {browser, puppeteer} = getTestState(); + itFailsFirefox('should work across sessions', async () => { + const { browser, puppeteer } = getTestState(); expect(browser.browserContexts().length).toBe(1); const context = await browser.createIncognitoBrowserContext(); expect(browser.browserContexts().length).toBe(2); const remoteBrowser = await puppeteer.connect({ - browserWSEndpoint: browser.wsEndpoint() + browserWSEndpoint: browser.wsEndpoint(), }); const contexts = remoteBrowser.browserContexts(); expect(contexts.length).toBe(2); diff --git a/test/chromiumonly.spec.js b/test/chromiumonly.spec.js index b14d25d4c27fb..1a46c4b719e34 100644 --- a/test/chromiumonly.spec.js +++ b/test/chromiumonly.spec.js @@ -14,62 +14,85 @@ * limitations under the License. */ const expect = require('expect'); -const {getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); -describeChromeOnly('Chromium-Specific Launcher tests', function() { - describe('Puppeteer.launch |browserURL| option', function() { - it('should be able to connect using browserUrl, with and without trailing slash', async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); +describeChromeOnly('Chromium-Specific Launcher tests', function () { + describe('Puppeteer.launch |browserURL| option', function () { + it('should be able to connect using browserUrl, with and without trailing slash', async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); - const originalBrowser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { - args: ['--remote-debugging-port=21222'] - })); + const originalBrowser = await puppeteer.launch( + Object.assign({}, defaultBrowserOptions, { + args: ['--remote-debugging-port=21222'], + }) + ); const browserURL = 'http://127.0.0.1:21222'; - const browser1 = await puppeteer.connect({browserURL}); + const browser1 = await puppeteer.connect({ browserURL }); const page1 = await browser1.newPage(); expect(await page1.evaluate(() => 7 * 8)).toBe(56); browser1.disconnect(); - const browser2 = await puppeteer.connect({browserURL: browserURL + '/'}); + const browser2 = await puppeteer.connect({ + browserURL: browserURL + '/', + }); const page2 = await browser2.newPage(); expect(await page2.evaluate(() => 8 * 7)).toBe(56); browser2.disconnect(); originalBrowser.close(); }); - it('should throw when using both browserWSEndpoint and browserURL', async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); + it('should throw when using both browserWSEndpoint and browserURL', async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); - const originalBrowser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { - args: ['--remote-debugging-port=21222'] - })); + const originalBrowser = await puppeteer.launch( + Object.assign({}, defaultBrowserOptions, { + args: ['--remote-debugging-port=21222'], + }) + ); const browserURL = 'http://127.0.0.1:21222'; let error = null; - await puppeteer.connect({browserURL, browserWSEndpoint: originalBrowser.wsEndpoint()}).catch(error_ => error = error_); - expect(error.message).toContain('Exactly one of browserWSEndpoint, browserURL or transport'); + await puppeteer + .connect({ + browserURL, + browserWSEndpoint: originalBrowser.wsEndpoint(), + }) + .catch((error_) => (error = error_)); + expect(error.message).toContain( + 'Exactly one of browserWSEndpoint, browserURL or transport' + ); originalBrowser.close(); }); - it('should throw when trying to connect to non-existing browser', async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); + it('should throw when trying to connect to non-existing browser', async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); - const originalBrowser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { - args: ['--remote-debugging-port=21222'] - })); + const originalBrowser = await puppeteer.launch( + Object.assign({}, defaultBrowserOptions, { + args: ['--remote-debugging-port=21222'], + }) + ); const browserURL = 'http://127.0.0.1:32333'; let error = null; - await puppeteer.connect({browserURL}).catch(error_ => error = error_); - expect(error.message).toContain('Failed to fetch browser webSocket url from'); + await puppeteer + .connect({ browserURL }) + .catch((error_) => (error = error_)); + expect(error.message).toContain( + 'Failed to fetch browser webSocket url from' + ); originalBrowser.close(); }); }); - describe('Puppeteer.launch |pipe| option', function() { - it('should support the pipe option', async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); - const options = Object.assign({pipe: true}, defaultBrowserOptions); + describe('Puppeteer.launch |pipe| option', function () { + it('should support the pipe option', async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); + const options = Object.assign({ pipe: true }, defaultBrowserOptions); const browser = await puppeteer.launch(options); expect((await browser.pages()).length).toBe(1); expect(browser.wsEndpoint()).toBe(''); @@ -78,8 +101,8 @@ describeChromeOnly('Chromium-Specific Launcher tests', function() { await page.close(); await browser.close(); }); - it('should support the pipe argument', async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); + it('should support the pipe argument', async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); const options = Object.assign({}, defaultBrowserOptions); options.args = ['--remote-debugging-pipe'].concat(options.args || []); const browser = await puppeteer.launch(options); @@ -89,30 +112,33 @@ describeChromeOnly('Chromium-Specific Launcher tests', function() { await page.close(); await browser.close(); }); - it('should fire "disconnected" when closing with pipe', async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); - const options = Object.assign({pipe: true}, defaultBrowserOptions); + it('should fire "disconnected" when closing with pipe', async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); + const options = Object.assign({ pipe: true }, defaultBrowserOptions); const browser = await puppeteer.launch(options); - const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve)); + const disconnectedEventPromise = new Promise((resolve) => + browser.once('disconnected', resolve) + ); // Emulate user exiting browser. browser.process().kill(); await disconnectedEventPromise; }); }); - }); -describeChromeOnly('Chromium-Specific Page Tests', function() { +describeChromeOnly('Chromium-Specific Page Tests', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - it('Page.setRequestInterception should work with intervention headers', async() => { - const {server, page} = getTestState(); + it('Page.setRequestInterception should work with intervention headers', async () => { + const { server, page } = getTestState(); - server.setRoute('/intervention', (req, res) => res.end(` + server.setRoute('/intervention', (req, res) => + res.end(` - `)); + `) + ); server.setRedirect('/intervention.js', '/redirect.js'); let serverRequest = null; server.setRoute('/redirect.js', (req, res) => { @@ -121,10 +147,12 @@ describeChromeOnly('Chromium-Specific Page Tests', function() { }); await page.setRequestInterception(true); - page.on('request', request => request.continue()); + page.on('request', (request) => request.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'); + expect(serverRequest.headers.intervention).toContain( + 'feature/5718547946799104' + ); }); }); diff --git a/test/click.spec.js b/test/click.spec.js index f2ac1e9af12d1..85f3bfc5c9a96 100644 --- a/test/click.spec.js +++ b/test/click.spec.js @@ -15,21 +15,25 @@ */ const expect = require('expect'); -const {getTestState,setupTestPageAndContextHooks, setupTestBrowserHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestPageAndContextHooks, + setupTestBrowserHooks, +} = require('./mocha-utils'); const utils = require('./utils'); -describe('Page.click', function() { +describe('Page.click', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - it('should click the button', async() => { - const {page, server} = getTestState(); + it('should click the button', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/button.html'); await page.click('button'); expect(await page.evaluate(() => result)).toBe('Clicked'); }); - itFailsFirefox('should click svg', async() => { - const {page} = getTestState(); + itFailsFirefox('should click svg', async () => { + const { page } = getTestState(); await page.setContent(` @@ -39,19 +43,24 @@ describe('Page.click', function() { await page.click('circle'); expect(await page.evaluate(() => window.__CLICKED)).toBe(42); }); - itFailsFirefox('should click the button if window.Node is removed', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should click the button if window.Node is removed', + async () => { + const { page, server } = getTestState(); - 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'); - }); + 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/puppeteer/puppeteer/issues/4281 - itFailsFirefox('should click on a span with an inline element inside', async() => { - const {page} = getTestState(); + itFailsFirefox( + 'should click on a span with an inline element inside', + async () => { + const { page } = getTestState(); - await page.setContent(` + await page.setContent(` `); - await page.click('span'); - expect(await page.evaluate(() => window.CLICKED)).toBe(42); - }); - it('should not throw UnhandledPromiseRejection when page closes', async() => { - const {page} = getTestState(); + await page.click('span'); + expect(await page.evaluate(() => window.CLICKED)).toBe(42); + } + ); + it('should not throw UnhandledPromiseRejection when page closes', async () => { + const { page } = getTestState(); const newPage = await page.browser().newPage(); await Promise.all([ newPage.close(), newPage.mouse.click(1, 2), - ]).catch(error => {}); + ]).catch((error) => {}); }); - it('should click the button after navigation ', async() => { - const {page, server} = getTestState(); + it('should click the button after navigation ', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/button.html'); await page.click('button'); @@ -80,21 +90,20 @@ describe('Page.click', function() { await page.click('button'); expect(await page.evaluate(() => result)).toBe('Clicked'); }); - itFailsFirefox('should click with disabled javascript', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should click with disabled javascript', async () => { + const { page, server } = getTestState(); await page.setJavaScriptEnabled(false); await page.goto(server.PREFIX + '/wrappedlink.html'); - await Promise.all([ - page.click('a'), - page.waitForNavigation() - ]); + await Promise.all([page.click('a'), page.waitForNavigation()]); expect(page.url()).toBe(server.PREFIX + '/wrappedlink.html#clicked'); }); - itFailsFirefox('should click when one of inline box children is outside of viewport', async() => { - const {page} = getTestState(); + itFailsFirefox( + 'should click when one of inline box children is outside of viewport', + async () => { + const { page } = getTestState(); - await page.setContent(` + 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() => { - const {page, server} = getTestState(); + await page.click('span'); + expect(await page.evaluate(() => window.CLICKED)).toBe(42); + } + ); + it('should select the text by triple clicking', async () => { + const { page, server } = getTestState(); 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.'; + 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); await page.click('textarea'); - await page.click('textarea', {clickCount: 2}); - 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); + await page.click('textarea', { clickCount: 2 }); + 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); }); - itFailsFirefox('should click offscreen buttons', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should click offscreen buttons', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/offscreenbuttons.html'); const messages = []; - page.on('console', msg => messages.push(msg.text())); + 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)); @@ -143,20 +159,20 @@ describe('Page.click', function() { 'button #7 clicked', 'button #8 clicked', 'button #9 clicked', - 'button #10 clicked' + 'button #10 clicked', ]); }); - it('should click wrapped links', async() => { - const {page, server} = getTestState(); + it('should click wrapped links', async () => { + const { page, server } = getTestState(); 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() => { - const {page, server} = getTestState(); + it('should click on checkbox input and toggle', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/checkbox.html'); expect(await page.evaluate(() => result.check)).toBe(null); @@ -176,8 +192,8 @@ describe('Page.click', function() { expect(await page.evaluate(() => result.check)).toBe(false); }); - itFailsFirefox('should click on checkbox label and toggle', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should click on checkbox label and toggle', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/checkbox.html'); expect(await page.evaluate(() => result.check)).toBe(null); @@ -192,50 +208,60 @@ describe('Page.click', function() { expect(await page.evaluate(() => result.check)).toBe(false); }); - it('should fail to click a missing button', async() => { - const {page, server} = getTestState(); + it('should fail to click a missing button', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/button.html'); let error = null; - await page.click('button.does-not-exist').catch(error_ => error = error_); - expect(error.message).toBe('No node found for selector: button.does-not-exist'); + await page + .click('button.does-not-exist') + .catch((error_) => (error = error_)); + expect(error.message).toBe( + 'No node found for selector: button.does-not-exist' + ); }); // @see https://github.com/puppeteer/puppeteer/issues/161 - it('should not hang with touch-enabled viewports', async() => { - const {page, puppeteer} = getTestState(); + it('should not hang with touch-enabled viewports', async () => { + const { page, puppeteer } = getTestState(); await page.setViewport(puppeteer.devices['iPhone 6'].viewport); await page.mouse.down(); await page.mouse.move(100, 10); await page.mouse.up(); }); - it('should scroll and click the button', async() => { - const {page, server} = getTestState(); + it('should scroll and click the button', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/scrollable.html'); await page.click('#button-5'); - expect(await page.evaluate(() => document.querySelector('#button-5').textContent)).toBe('clicked'); + 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'); + expect( + await page.evaluate( + () => document.querySelector('#button-80').textContent + ) + ).toBe('clicked'); }); - itFailsFirefox('should double click the button', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should double click the button', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/button.html'); await page.evaluate(() => { window.double = false; const button = document.querySelector('button'); - button.addEventListener('dblclick', event => { + button.addEventListener('dblclick', (event) => { window.double = true; }); }); const button = await page.$('button'); - await button.click({clickCount: 2}); + await button.click({ clickCount: 2 }); expect(await page.evaluate('double')).toBe(true); expect(await page.evaluate('result')).toBe('Clicked'); }); - it('should click a partially obscured button', async() => { - const {page, server} = getTestState(); + it('should click a partially obscured button', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/button.html'); await page.evaluate(() => { @@ -247,62 +273,85 @@ describe('Page.click', function() { await page.click('button'); expect(await page.evaluate(() => window.result)).toBe('Clicked'); }); - it('should click a rotated button', async() => { - const {page, server} = getTestState(); + it('should click a rotated button', async () => { + const { page, server } = getTestState(); 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() => { - const {page, server} = getTestState(); + it('should fire contextmenu event on right click', async () => { + const { page, server } = getTestState(); 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'); + await page.click('#button-8', { button: 'right' }); + expect( + await page.evaluate(() => document.querySelector('#button-8').textContent) + ).toBe('context menu'); }); // @see https://github.com/puppeteer/puppeteer/issues/206 - itFailsFirefox('should click links which cause navigation', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should click links which cause navigation', async () => { + const { page, server } = getTestState(); await page.setContent(`empty.html`); // This await should not hang. await page.click('a'); }); - itFailsFirefox('should click the button inside an iframe', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should click the button inside an iframe', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.setContent('
spacer
'); - await utils.attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); + 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/puppeteer/puppeteer/issues/4110 - xit('should click the button with fixed position inside an iframe', async() => { - const {page, server} = getTestState(); + xit('should click the button with fixed position inside an iframe', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.setViewport({width: 500, height: 500}); - await page.setContent('
spacer
'); - await utils.attachFrame(page, 'button-test', server.CROSS_PROCESS_PREFIX + '/input/button.html'); + await page.setViewport({ 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.$eval('button', (button) => + button.style.setProperty('position', 'fixed') + ); await frame.click('button'); expect(await frame.evaluate(() => window.result)).toBe('Clicked'); }); - itFailsFirefox('should click the button with deviceScaleFactor set', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should click the button with deviceScaleFactor set', + async () => { + const { page, server } = getTestState(); - await page.setViewport({width: 400, height: 400, deviceScaleFactor: 5}); - 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 page.setViewport({ width: 400, height: 400, deviceScaleFactor: 5 }); + 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'); + } + ); }); diff --git a/test/cookies.spec.js b/test/cookies.spec.js index 16a67328ee9a2..844ff8b978c77 100644 --- a/test/cookies.spec.js +++ b/test/cookies.spec.js @@ -14,38 +14,44 @@ * limitations under the License. */ const expect = require('expect'); -const {getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); describe('Cookie specs', () => { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - describe('Page.cookies', function() { - it('should return no cookies in pristine browser context', async() => { - const {page, server} = getTestState(); + describe('Page.cookies', function () { + it('should return no cookies in pristine browser context', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); expect(await page.cookies()).toEqual([]); }); - itFailsFirefox('should get a cookie', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should get a cookie', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.evaluate(() => { document.cookie = 'username=John Doe'; }); - expect(await page.cookies()).toEqual([{ - name: 'username', - value: 'John Doe', - domain: 'localhost', - path: '/', - expires: -1, - size: 16, - httpOnly: false, - secure: false, - session: true, - }]); + expect(await page.cookies()).toEqual([ + { + name: 'username', + value: 'John Doe', + domain: 'localhost', + path: '/', + expires: -1, + size: 16, + httpOnly: false, + secure: false, + session: true, + }, + ]); }); - it('should properly report httpOnly cookie', async() => { - const {page, server} = getTestState(); + it('should properly report httpOnly cookie', async () => { + const { page, server } = getTestState(); server.setRoute('/empty.html', (req, res) => { res.setHeader('Set-Cookie', 'a=b; HttpOnly; Path=/'); res.end(); @@ -55,8 +61,8 @@ describe('Cookie specs', () => { expect(cookies.length).toBe(1); expect(cookies[0].httpOnly).toBe(true); }); - it('should properly report "Strict" sameSite cookie', async() => { - const {page, server} = getTestState(); + it('should properly report "Strict" sameSite cookie', async () => { + const { page, server } = getTestState(); server.setRoute('/empty.html', (req, res) => { res.setHeader('Set-Cookie', 'a=b; SameSite=Strict'); res.end(); @@ -66,8 +72,8 @@ describe('Cookie specs', () => { expect(cookies.length).toBe(1); expect(cookies[0].sameSite).toBe('Strict'); }); - it('should properly report "Lax" sameSite cookie', async() => { - const {page, server} = getTestState(); + it('should properly report "Lax" sameSite cookie', async () => { + const { page, server } = getTestState(); server.setRoute('/empty.html', (req, res) => { res.setHeader('Set-Cookie', 'a=b; SameSite=Lax'); res.end(); @@ -77,8 +83,8 @@ describe('Cookie specs', () => { expect(cookies.length).toBe(1); expect(cookies[0].sameSite).toBe('Lax'); }); - itFailsFirefox('should get multiple cookies', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should get multiple cookies', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.evaluate(() => { document.cookie = 'username=John Doe'; @@ -111,59 +117,68 @@ describe('Cookie specs', () => { }, ]); }); - itFailsFirefox('should get cookies from multiple urls', async() => { - const {page} = getTestState(); - await page.setCookie({ - url: 'https://foo.com', - name: 'doggo', - value: 'woofs', - }, { - url: 'https://bar.com', - name: 'catto', - value: 'purrs', - }, { - url: 'https://baz.com', - name: 'birdo', - value: 'tweets', - }); + itFailsFirefox('should get cookies from multiple urls', async () => { + const { page } = getTestState(); + await page.setCookie( + { + 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 page.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, - size: 11, - httpOnly: false, - secure: true, - session: true, - }, { - name: 'doggo', - value: 'woofs', - domain: 'foo.com', - path: '/', - expires: -1, - size: 10, - httpOnly: false, - secure: true, - session: true, - }]); + expect(cookies).toEqual([ + { + name: 'birdo', + value: 'tweets', + domain: 'baz.com', + path: '/', + expires: -1, + size: 11, + httpOnly: false, + secure: true, + session: true, + }, + { + name: 'doggo', + value: 'woofs', + domain: 'foo.com', + path: '/', + expires: -1, + size: 10, + httpOnly: false, + secure: true, + session: true, + }, + ]); }); }); - describe('Page.setCookie', function() { - itFailsFirefox('should work', async() => { - const {page, server} = getTestState(); + describe('Page.setCookie', function () { + itFailsFirefox('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.setCookie({ name: 'password', - value: '123456' + value: '123456', }); - expect(await page.evaluate(() => document.cookie)).toEqual('password=123456'); + expect(await page.evaluate(() => document.cookie)).toEqual( + 'password=123456' + ); }); - itFailsFirefox('should isolate cookies in browser contexts', async() => { - const {page, server, browser} = getTestState(); + itFailsFirefox('should isolate cookies in browser contexts', async () => { + const { page, server, browser } = getTestState(); const anotherContext = await browser.createIncognitoBrowserContext(); const anotherPage = await anotherContext.newPage(); @@ -171,8 +186,8 @@ describe('Cookie specs', () => { await page.goto(server.EMPTY_PAGE); await anotherPage.goto(server.EMPTY_PAGE); - await page.setCookie({name: 'page1cookie', value: 'page1value'}); - await anotherPage.setCookie({name: 'page2cookie', value: 'page2value'}); + await page.setCookie({ name: 'page1cookie', value: 'page1value' }); + await anotherPage.setCookie({ name: 'page2cookie', value: 'page2value' }); const cookies1 = await page.cookies(); const cookies2 = await anotherPage.cookies(); @@ -184,78 +199,87 @@ describe('Cookie specs', () => { expect(cookies2[0].value).toBe('page2value'); await anotherContext.close(); }); - itFailsFirefox('should set multiple cookies', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should set multiple cookies', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.setCookie({ - name: 'password', - value: '123456' - }, { - name: 'foo', - value: 'bar' - }); - expect(await page.evaluate(() => { - const cookies = document.cookie.split(';'); - return cookies.map(cookie => cookie.trim()).sort(); - })).toEqual([ - 'foo=bar', - 'password=123456', - ]); + await page.setCookie( + { + name: 'password', + value: '123456', + }, + { + name: 'foo', + value: 'bar', + } + ); + expect( + await page.evaluate(() => { + const cookies = document.cookie.split(';'); + return cookies.map((cookie) => cookie.trim()).sort(); + }) + ).toEqual(['foo=bar', 'password=123456']); }); - itFailsFirefox('should have |expires| set to |-1| for session cookies', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should have |expires| set to |-1| for session cookies', + async () => { + const { page, server } = getTestState(); - await page.goto(server.EMPTY_PAGE); - await page.setCookie({ - name: 'password', - value: '123456' - }); - const cookies = await page.cookies(); - expect(cookies[0].session).toBe(true); - expect(cookies[0].expires).toBe(-1); - }); - itFailsFirefox('should set cookie with reasonable defaults', async() => { - const {page, server} = getTestState(); + await page.goto(server.EMPTY_PAGE); + await page.setCookie({ + name: 'password', + value: '123456', + }); + const cookies = await page.cookies(); + expect(cookies[0].session).toBe(true); + expect(cookies[0].expires).toBe(-1); + } + ); + itFailsFirefox('should set cookie with reasonable defaults', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.setCookie({ name: 'password', - value: '123456' + value: '123456', }); const cookies = await page.cookies(); - expect(cookies.sort((a, b) => a.name.localeCompare(b.name))).toEqual([{ - name: 'password', - value: '123456', - domain: 'localhost', - path: '/', - expires: -1, - size: 14, - httpOnly: false, - secure: false, - session: true, - }]); + expect(cookies.sort((a, b) => a.name.localeCompare(b.name))).toEqual([ + { + name: 'password', + value: '123456', + domain: 'localhost', + path: '/', + expires: -1, + size: 14, + httpOnly: false, + secure: false, + session: true, + }, + ]); }); - itFailsFirefox('should set a cookie with a path', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should set a cookie with a path', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/grid.html'); await page.setCookie({ name: 'gridcookie', value: 'GRID', - path: '/grid.html' - }); - expect(await page.cookies()).toEqual([{ - name: 'gridcookie', - value: 'GRID', - domain: 'localhost', path: '/grid.html', - expires: -1, - size: 14, - httpOnly: false, - secure: false, - session: true, - }]); + }); + expect(await page.cookies()).toEqual([ + { + name: 'gridcookie', + value: 'GRID', + domain: 'localhost', + path: '/grid.html', + expires: -1, + size: 14, + httpOnly: false, + secure: false, + session: true, + }, + ]); expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID'); await page.goto(server.EMPTY_PAGE); expect(await page.cookies()).toEqual([]); @@ -263,75 +287,85 @@ describe('Cookie specs', () => { await page.goto(server.PREFIX + '/grid.html'); expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID'); }); - it('should not set a cookie on a blank page', async() => { - const {page} = getTestState(); + it('should not set a cookie on a blank page', async () => { + const { page } = getTestState(); await page.goto('about:blank'); let error = null; try { - await page.setCookie({name: 'example-cookie', value: 'best'}); + await page.setCookie({ name: 'example-cookie', value: 'best' }); } catch (error_) { error = error_; } - expect(error.message).toContain('At least one of the url and domain needs to be specified'); + expect(error.message).toContain( + 'At least one of the url and domain needs to be specified' + ); }); - it('should not set a cookie with blank page URL', async() => { - const {page, server} = getTestState(); + it('should not set a cookie with blank page URL', async () => { + const { page, server } = getTestState(); let error = null; await page.goto(server.EMPTY_PAGE); try { await page.setCookie( - {name: 'example-cookie', value: 'best'}, - {url: 'about:blank', name: 'example-cookie-blank', value: 'best'} + { name: 'example-cookie', value: 'best' }, + { url: 'about:blank', name: 'example-cookie-blank', value: 'best' } ); } catch (error_) { error = error_; } expect(error.message).toEqual( - `Blank page can not have cookie "example-cookie-blank"` + `Blank page can not have cookie "example-cookie-blank"` ); }); - it('should not set a cookie on a data URL page', async() => { - const {page} = getTestState(); + it('should not set a cookie on a data URL page', async () => { + const { page } = getTestState(); let error = null; await page.goto('data:,Hello%2C%20World!'); try { - await page.setCookie({name: 'example-cookie', value: 'best'}); + await page.setCookie({ name: 'example-cookie', value: 'best' }); } catch (error_) { error = error_; } - expect(error.message).toContain('At least one of the url and domain needs to be specified'); + expect(error.message).toContain( + 'At least one of the url and domain needs to be specified' + ); }); - itFailsFirefox('should default to setting secure cookie for HTTPS websites', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should default to setting secure cookie for HTTPS websites', + async () => { + const { page, server } = getTestState(); - await page.goto(server.EMPTY_PAGE); - const SECURE_URL = 'https://example.com'; - await page.setCookie({ - url: SECURE_URL, - name: 'foo', - value: 'bar', - }); - const [cookie] = await page.cookies(SECURE_URL); - expect(cookie.secure).toBe(true); - }); - itFailsFirefox('should be able to set unsecure cookie for HTTP website', async() => { - const {page, server} = getTestState(); + await page.goto(server.EMPTY_PAGE); + const SECURE_URL = 'https://example.com'; + await page.setCookie({ + url: SECURE_URL, + name: 'foo', + value: 'bar', + }); + const [cookie] = await page.cookies(SECURE_URL); + expect(cookie.secure).toBe(true); + } + ); + itFailsFirefox( + 'should be able to set unsecure cookie for HTTP website', + async () => { + const { page, server } = getTestState(); - await page.goto(server.EMPTY_PAGE); - const HTTP_URL = 'http://example.com'; - await page.setCookie({ - url: HTTP_URL, - name: 'foo', - value: 'bar', - }); - const [cookie] = await page.cookies(HTTP_URL); - expect(cookie.secure).toBe(false); - }); - itFailsFirefox('should set a cookie on a different domain', async() => { - const {page, server} = getTestState(); + await page.goto(server.EMPTY_PAGE); + const HTTP_URL = 'http://example.com'; + await page.setCookie({ + url: HTTP_URL, + name: 'foo', + value: 'bar', + }); + const [cookie] = await page.cookies(HTTP_URL); + expect(cookie.secure).toBe(false); + } + ); + itFailsFirefox('should set a cookie on a different domain', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.setCookie({ @@ -341,80 +375,102 @@ describe('Cookie specs', () => { }); expect(await page.evaluate('document.cookie')).toBe(''); expect(await page.cookies()).toEqual([]); - expect(await page.cookies('https://www.example.com')).toEqual([{ - name: 'example-cookie', - value: 'best', - domain: 'www.example.com', - path: '/', - expires: -1, - size: 18, - httpOnly: false, - secure: true, - session: true, - }]); + expect(await page.cookies('https://www.example.com')).toEqual([ + { + name: 'example-cookie', + value: 'best', + domain: 'www.example.com', + path: '/', + expires: -1, + size: 18, + httpOnly: false, + secure: true, + session: true, + }, + ]); }); - itFailsFirefox('should set cookies from a frame', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should set cookies from a frame', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/grid.html'); - await page.setCookie({name: 'localhost-cookie', value: 'best'}); - await page.evaluate(src => { + await page.setCookie({ name: 'localhost-cookie', value: 'best' }); + await page.evaluate((src) => { let fulfill; - const promise = new Promise(x => fulfill = x); + 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.CROSS_PROCESS_PREFIX); - await page.setCookie({name: '127-cookie', value: 'worst', url: server.CROSS_PROCESS_PREFIX}); - expect(await page.evaluate('document.cookie')).toBe('localhost-cookie=best'); - expect(await page.frames()[1].evaluate('document.cookie')).toBe('127-cookie=worst'); - - expect(await page.cookies()).toEqual([{ - name: 'localhost-cookie', - value: 'best', - domain: 'localhost', - path: '/', - expires: -1, - size: 20, - httpOnly: false, - secure: false, - session: true, - }]); - - expect(await page.cookies(server.CROSS_PROCESS_PREFIX)).toEqual([{ + await page.setCookie({ name: '127-cookie', value: 'worst', - domain: '127.0.0.1', - path: '/', - expires: -1, - size: 15, - httpOnly: false, - secure: false, - session: true, - }]); + url: server.CROSS_PROCESS_PREFIX, + }); + expect(await page.evaluate('document.cookie')).toBe( + 'localhost-cookie=best' + ); + expect(await page.frames()[1].evaluate('document.cookie')).toBe( + '127-cookie=worst' + ); + + expect(await page.cookies()).toEqual([ + { + name: 'localhost-cookie', + value: 'best', + domain: 'localhost', + path: '/', + expires: -1, + size: 20, + httpOnly: false, + secure: false, + session: true, + }, + ]); + + expect(await page.cookies(server.CROSS_PROCESS_PREFIX)).toEqual([ + { + name: '127-cookie', + value: 'worst', + domain: '127.0.0.1', + path: '/', + expires: -1, + size: 15, + httpOnly: false, + secure: false, + session: true, + }, + ]); }); }); - describe('Page.deleteCookie', function() { - itFailsFirefox('should work', async() => { - const {page, server} = getTestState(); + describe('Page.deleteCookie', function () { + itFailsFirefox('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.setCookie({ - name: 'cookie1', - value: '1' - }, { - name: 'cookie2', - value: '2' - }, { - name: 'cookie3', - value: '3' - }); - expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie2=2; cookie3=3'); - await page.deleteCookie({name: 'cookie2'}); - expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie3=3'); + await page.setCookie( + { + name: 'cookie1', + value: '1', + }, + { + name: 'cookie2', + value: '2', + }, + { + name: 'cookie3', + value: '3', + } + ); + expect(await page.evaluate('document.cookie')).toBe( + 'cookie1=1; cookie2=2; cookie3=3' + ); + await page.deleteCookie({ name: 'cookie2' }); + expect(await page.evaluate('document.cookie')).toBe( + 'cookie1=1; cookie3=3' + ); }); }); }); diff --git a/test/coverage-utils.js b/test/coverage-utils.js index 4f73a9e725a3c..c1799f261c924 100644 --- a/test/coverage-utils.js +++ b/test/coverage-utils.js @@ -39,10 +39,15 @@ function traceAPICoverage(apiCoverage, events, className, classType) { className = className.substring(0, 1).toLowerCase() + className.substring(1); for (const methodName of Reflect.ownKeys(classType.prototype)) { const method = Reflect.get(classType.prototype, methodName); - if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function') + if ( + methodName === 'constructor' || + typeof methodName !== 'string' || + methodName.startsWith('_') || + typeof method !== 'function' + ) continue; apiCoverage.set(`${className}.${methodName}`, false); - Reflect.set(classType.prototype, methodName, function(...args) { + Reflect.set(classType.prototype, methodName, function (...args) { apiCoverage.set(`${className}.${methodName}`, true); return method.call(this, ...args); }); @@ -54,7 +59,7 @@ function traceAPICoverage(apiCoverage, events, className, classType) { apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, false); } const method = Reflect.get(classType.prototype, 'emit'); - Reflect.set(classType.prototype, 'emit', function(event, ...args) { + Reflect.set(classType.prototype, 'emit', function (event, ...args) { if (typeof event !== 'symbol' && this.listenerCount(event)) apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, true); return method.call(this, event, ...args); @@ -71,14 +76,14 @@ const clearOldCoverage = () => { // do nothing, the file didn't exist } }; -const writeCoverage = coverage => { +const writeCoverage = (coverage) => { fs.writeFileSync(coverageLocation, JSON.stringify([...coverage.entries()])); }; const getCoverageResults = () => { let contents; try { - contents = fs.readFileSync(coverageLocation, {encoding: 'utf8'}); + contents = fs.readFileSync(coverageLocation, { encoding: 'utf8' }); } catch (error) { console.error('Warning: coverage file does not exist or is not readable.'); } @@ -92,7 +97,6 @@ const trackCoverage = () => { const coverageMap = new Map(); before(() => { - const api = require('../lib/api'); const events = require('../lib/Events'); for (const [className, classType] of Object.entries(api)) @@ -106,5 +110,5 @@ const trackCoverage = () => { module.exports = { trackCoverage, - getCoverageResults + getCoverageResults, }; diff --git a/test/coverage.spec.js b/test/coverage.spec.js index 334363e7bfaa1..df5fe21a60a09 100644 --- a/test/coverage.spec.js +++ b/test/coverage.spec.js @@ -15,27 +15,33 @@ */ const expect = require('expect'); -const {getTestState, setupTestPageAndContextHooks, setupTestBrowserHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestPageAndContextHooks, + setupTestBrowserHooks, +} = require('./mocha-utils'); -describe('Coverage specs', function() { - describeChromeOnly('JSCoverage', function() { +describe('Coverage specs', function () { + describeChromeOnly('JSCoverage', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - it('should work', async() => { - const {page, server} = getTestState(); + it('should work', async () => { + const { page, server } = getTestState(); await page.coverage.startJSCoverage(); - await page.goto(server.PREFIX + '/jscoverage/simple.html', {waitUntil: 'networkidle0'}); + await page.goto(server.PREFIX + '/jscoverage/simple.html', { + waitUntil: 'networkidle0', + }); const coverage = await page.coverage.stopJSCoverage(); expect(coverage.length).toBe(1); expect(coverage[0].url).toContain('/jscoverage/simple.html'); expect(coverage[0].ranges).toEqual([ - {start: 0, end: 17}, - {start: 35, end: 61}, + { start: 0, end: 17 }, + { start: 35, end: 61 }, ]); }); - it('should report sourceURLs', async() => { - const {page, server} = getTestState(); + it('should report sourceURLs', async () => { + const { page, server } = getTestState(); await page.coverage.startJSCoverage(); await page.goto(server.PREFIX + '/jscoverage/sourceurl.html'); @@ -43,35 +49,37 @@ describe('Coverage specs', function() { expect(coverage.length).toBe(1); expect(coverage[0].url).toBe('nicename.js'); }); - it('should ignore eval() scripts by default', async() => { - const {page, server} = getTestState(); + it('should ignore eval() scripts by default', async () => { + const { page, server } = getTestState(); 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() => { - const {page, server} = getTestState(); + it("shouldn't ignore eval() scripts if reportAnonymousScripts is true", async () => { + const { page, server } = getTestState(); - await page.coverage.startJSCoverage({reportAnonymousScripts: true}); + 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.find((entry) => entry.url.startsWith('debugger://')) + ).not.toBe(null); expect(coverage.length).toBe(2); }); - it('should ignore pptr internal scripts if reportAnonymousScripts is true', async() => { - const {page, server} = getTestState(); + it('should ignore pptr internal scripts if reportAnonymousScripts is true', async () => { + const { page, server } = getTestState(); - await page.coverage.startJSCoverage({reportAnonymousScripts: true}); + 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() => { - const {page, server} = getTestState(); + it('should report multiple scripts', async () => { + const { page, server } = getTestState(); await page.coverage.startJSCoverage(); await page.goto(server.PREFIX + '/jscoverage/multiple.html'); @@ -81,8 +89,8 @@ describe('Coverage specs', function() { expect(coverage[0].url).toContain('/jscoverage/script1.js'); expect(coverage[1].url).toContain('/jscoverage/script2.js'); }); - it('should report right ranges', async() => { - const {page, server} = getTestState(); + it('should report right ranges', async () => { + const { page, server } = getTestState(); await page.coverage.startJSCoverage(); await page.goto(server.PREFIX + '/jscoverage/ranges.html'); @@ -91,10 +99,12 @@ describe('Coverage specs', function() { const entry = coverage[0]; expect(entry.ranges.length).toBe(1); const range = entry.ranges[0]; - expect(entry.text.substring(range.start, range.end)).toBe(`console.log('used!');`); + expect(entry.text.substring(range.start, range.end)).toBe( + `console.log('used!');` + ); }); - it('should report scripts that have no coverage', async() => { - const {page, server} = getTestState(); + it('should report scripts that have no coverage', async () => { + const { page, server } = getTestState(); await page.coverage.startJSCoverage(); await page.goto(server.PREFIX + '/jscoverage/unused.html'); @@ -104,27 +114,29 @@ describe('Coverage specs', function() { expect(entry.url).toContain('unused.html'); expect(entry.ranges.length).toBe(0); }); - it('should work with conditionals', async() => { - const {page, server} = getTestState(); + it('should work with conditionals', async () => { + const { page, server } = getTestState(); await page.coverage.startJSCoverage(); await page.goto(server.PREFIX + '/jscoverage/involved.html'); const coverage = await page.coverage.stopJSCoverage(); - expect(JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':/')).toBeGolden('jscoverage-involved.txt'); + expect( + JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':/') + ).toBeGolden('jscoverage-involved.txt'); }); - describe('resetOnNavigation', function() { - it('should report scripts across navigations when disabled', async() => { - const {page, server} = getTestState(); + describe('resetOnNavigation', function () { + it('should report scripts across navigations when disabled', async () => { + const { page, server } = getTestState(); - await page.coverage.startJSCoverage({resetOnNavigation: false}); + 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() => { - const {page, server} = getTestState(); + it('should NOT report scripts across navigations when enabled', async () => { + const { page, server } = getTestState(); await page.coverage.startJSCoverage(); // Enabled by default. await page.goto(server.PREFIX + '/jscoverage/multiple.html'); @@ -134,8 +146,8 @@ describe('Coverage specs', function() { }); }); // @see https://crbug.com/990945 - xit('should not hang when there is a debugger statement', async() => { - const {page, server} = getTestState(); + xit('should not hang when there is a debugger statement', async () => { + const { page, server } = getTestState(); await page.coverage.startJSCoverage(); await page.goto(server.EMPTY_PAGE); @@ -146,26 +158,26 @@ describe('Coverage specs', function() { }); }); - describeChromeOnly('CSSCoverage', function() { + describeChromeOnly('CSSCoverage', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - it('should work', async() => { - const {page, server} = getTestState(); + it('should work', async () => { + const { page, server } = getTestState(); 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} - ]); + 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; }'); + expect(coverage[0].text.substring(range.start, range.end)).toBe( + 'div { color: green; }' + ); }); - it('should report sourceURLs', async() => { - const {page, server} = getTestState(); + it('should report sourceURLs', async () => { + const { page, server } = getTestState(); await page.coverage.startCSSCoverage(); await page.goto(server.PREFIX + '/csscoverage/sourceurl.html'); @@ -173,8 +185,8 @@ describe('Coverage specs', function() { expect(coverage.length).toBe(1); expect(coverage[0].url).toBe('nicename.css'); }); - it('should report multiple stylesheets', async() => { - const {page, server} = getTestState(); + it('should report multiple stylesheets', async () => { + const { page, server } = getTestState(); await page.coverage.startCSSCoverage(); await page.goto(server.PREFIX + '/csscoverage/multiple.html'); @@ -184,8 +196,8 @@ describe('Coverage specs', function() { 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() => { - const {page, server} = getTestState(); + it('should report stylesheets that have no coverage', async () => { + const { page, server } = getTestState(); await page.coverage.startCSSCoverage(); await page.goto(server.PREFIX + '/csscoverage/unused.html'); @@ -194,49 +206,51 @@ describe('Coverage specs', function() { expect(coverage[0].url).toBe('unused.css'); expect(coverage[0].ranges.length).toBe(0); }); - it('should work with media queries', async() => { - const {page, server} = getTestState(); + it('should work with media queries', async () => { + const { page, server } = getTestState(); 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} - ]); + expect(coverage[0].ranges).toEqual([{ start: 17, end: 38 }]); }); - it('should work with complicated usecases', async() => { - const {page, server} = getTestState(); + it('should work with complicated usecases', async () => { + const { page, server } = getTestState(); 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'); + expect( + JSON.stringify(coverage, null, 2).replace(/:\d{4}\//g, ':/') + ).toBeGolden('csscoverage-involved.txt'); }); - it('should ignore injected stylesheets', async() => { - const {page} = getTestState(); + it('should ignore injected stylesheets', async () => { + const { page } = getTestState(); await page.coverage.startCSSCoverage(); - await page.addStyleTag({content: 'body { margin: 10px;}'}); + await page.addStyleTag({ content: 'body { margin: 10px;}' }); // trigger style recalc - const margin = await page.evaluate(() => window.getComputedStyle(document.body).margin); + 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() => { - const {page, server} = getTestState(); + describe('resetOnNavigation', function () { + it('should report stylesheets across navigations', async () => { + const { page, server } = getTestState(); - await page.coverage.startCSSCoverage({resetOnNavigation: false}); + 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() => { - const {page, server} = getTestState(); + it('should NOT report scripts across navigations', async () => { + const { page, server } = getTestState(); await page.coverage.startCSSCoverage(); // Enabled by default. await page.goto(server.PREFIX + '/csscoverage/multiple.html'); @@ -245,18 +259,18 @@ describe('Coverage specs', function() { expect(coverage.length).toBe(0); }); }); - it('should work with a recently loaded stylesheet', async() => { - const {page, server} = getTestState(); + it('should work with a recently loaded stylesheet', async () => { + const { page, server } = getTestState(); await page.coverage.startCSSCoverage(); - await page.evaluate(async url => { + 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((x) => (link.onload = x)); }, server.PREFIX + '/csscoverage/stylesheet1.css'); const coverage = await page.coverage.stopCSSCoverage(); expect(coverage.length).toBe(1); diff --git a/test/defaultbrowsercontext.spec.js b/test/defaultbrowsercontext.spec.js index c424b75d6ce54..67862e1f714e2 100644 --- a/test/defaultbrowsercontext.spec.js +++ b/test/defaultbrowsercontext.spec.js @@ -14,75 +14,90 @@ * limitations under the License. */ const expect = require('expect'); -const {getTestState,setupTestBrowserHooks, setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); -describe('DefaultBrowserContext', function() { +describe('DefaultBrowserContext', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - itFailsFirefox('page.cookies() should work', async() => { - const {page, server} = getTestState(); + itFailsFirefox('page.cookies() should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.evaluate(() => { document.cookie = 'username=John Doe'; }); - expect(await page.cookies()).toEqual([{ - name: 'username', - value: 'John Doe', - domain: 'localhost', - path: '/', - expires: -1, - size: 16, - httpOnly: false, - secure: false, - session: true, - }]); + expect(await page.cookies()).toEqual([ + { + name: 'username', + value: 'John Doe', + domain: 'localhost', + path: '/', + expires: -1, + size: 16, + httpOnly: false, + secure: false, + session: true, + }, + ]); }); - itFailsFirefox('page.setCookie() should work', async() => { - const {page, server} = getTestState(); + itFailsFirefox('page.setCookie() should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.setCookie({ - name: 'username', - value: 'John Doe' - }); - expect(await page.evaluate(() => document.cookie)).toBe('username=John Doe'); - expect(await page.cookies()).toEqual([{ name: 'username', value: 'John Doe', - domain: 'localhost', - path: '/', - expires: -1, - size: 16, - httpOnly: false, - secure: false, - session: true, - }]); + }); + expect(await page.evaluate(() => document.cookie)).toBe( + 'username=John Doe' + ); + expect(await page.cookies()).toEqual([ + { + name: 'username', + value: 'John Doe', + domain: 'localhost', + path: '/', + expires: -1, + size: 16, + httpOnly: false, + secure: false, + session: true, + }, + ]); }); - itFailsFirefox('page.deleteCookie() should work', async() => { - const {page, server} = getTestState(); + itFailsFirefox('page.deleteCookie() should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.setCookie({ - name: 'cookie1', - value: '1' - }, { - name: 'cookie2', - value: '2' - }); + await page.setCookie( + { + name: 'cookie1', + value: '1', + }, + { + name: 'cookie2', + value: '2', + } + ); expect(await page.evaluate('document.cookie')).toBe('cookie1=1; cookie2=2'); - await page.deleteCookie({name: 'cookie2'}); + await page.deleteCookie({ name: 'cookie2' }); expect(await page.evaluate('document.cookie')).toBe('cookie1=1'); - expect(await page.cookies()).toEqual([{ - name: 'cookie1', - value: '1', - domain: 'localhost', - path: '/', - expires: -1, - size: 8, - httpOnly: false, - secure: false, - session: true, - }]); + expect(await page.cookies()).toEqual([ + { + name: 'cookie1', + value: '1', + domain: 'localhost', + path: '/', + expires: -1, + size: 8, + httpOnly: false, + secure: false, + session: true, + }, + ]); }); }); diff --git a/test/dialog.spec.js b/test/dialog.spec.js index 4cf309373e802..4d7efcb0f6db4 100644 --- a/test/dialog.spec.js +++ b/test/dialog.spec.js @@ -14,15 +14,19 @@ * limitations under the License. */ const expect = require('expect'); -const {getTestState,setupTestPageAndContextHooks, setupTestBrowserHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestPageAndContextHooks, + setupTestBrowserHooks, +} = require('./mocha-utils'); -describe('Page.Events.Dialog', function() { +describe('Page.Events.Dialog', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - it('should fire', async() => { - const {page} = getTestState(); + it('should fire', async () => { + const { page } = getTestState(); - page.on('dialog', dialog => { + page.on('dialog', (dialog) => { expect(dialog.type()).toBe('alert'); expect(dialog.defaultValue()).toBe(''); expect(dialog.message()).toBe('yo'); @@ -30,10 +34,10 @@ describe('Page.Events.Dialog', function() { }); await page.evaluate(() => alert('yo')); }); - itFailsFirefox('should allow accepting prompts', async() => { - const {page} = getTestState(); + itFailsFirefox('should allow accepting prompts', async () => { + const { page } = getTestState(); - page.on('dialog', dialog => { + page.on('dialog', (dialog) => { expect(dialog.type()).toBe('prompt'); expect(dialog.defaultValue()).toBe('yes.'); expect(dialog.message()).toBe('question?'); @@ -42,10 +46,10 @@ describe('Page.Events.Dialog', function() { const result = await page.evaluate(() => prompt('question?', 'yes.')); expect(result).toBe('answer!'); }); - it('should dismiss the prompt', async() => { - const {page} = getTestState(); + it('should dismiss the prompt', async () => { + const { page } = getTestState(); - page.on('dialog', dialog => { + page.on('dialog', (dialog) => { dialog.dismiss(); }); const result = await page.evaluate(() => prompt('question?')); diff --git a/test/elementhandle.spec.js b/test/elementhandle.spec.js index 7b53f30a5d0f8..881846e6ba9c4 100644 --- a/test/elementhandle.spec.js +++ b/test/elementhandle.spec.js @@ -15,56 +15,64 @@ */ const expect = require('expect'); -const {getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); const utils = require('./utils'); -describe('ElementHandle specs', function() { +describe('ElementHandle specs', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - describeFailsFirefox('ElementHandle.boundingBox', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('ElementHandle.boundingBox', function () { + it('should work', async () => { + const { page, server } = getTestState(); - await page.setViewport({width: 500, height: 500}); + await page.setViewport({ 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}); + expect(box).toEqual({ x: 100, y: 50, width: 50, height: 50 }); }); - it('should handle nested frames', async() => { - const {page, server, isChrome} = getTestState(); + it('should handle nested frames', async () => { + const { page, server, isChrome } = getTestState(); - await page.setViewport({width: 500, height: 500}); + await page.setViewport({ width: 500, height: 500 }); await page.goto(server.PREFIX + '/frames/nested-frames.html'); const nestedFrame = page.frames()[1].childFrames()[1]; const elementHandle = await nestedFrame.$('div'); const box = await elementHandle.boundingBox(); if (isChrome) - expect(box).toEqual({x: 28, y: 260, width: 264, height: 18}); - else - expect(box).toEqual({x: 28, y: 182, width: 254, height: 18}); + expect(box).toEqual({ x: 28, y: 260, width: 264, height: 18 }); + else expect(box).toEqual({ x: 28, y: 182, width: 254, height: 18 }); }); - it('should return null for invisible elements', async() => { - const {page} = getTestState(); + it('should return null for invisible elements', async () => { + const { page } = getTestState(); await page.setContent('
hi
'); const element = await page.$('div'); expect(await element.boundingBox()).toBe(null); }); - it('should force a layout', async() => { - const {page} = getTestState(); + it('should force a layout', async () => { + const { page } = getTestState(); - await page.setViewport({width: 500, height: 500}); - await page.setContent('
hello
'); + await page.setViewport({ width: 500, height: 500 }); + await page.setContent( + '
hello
' + ); const elementHandle = await page.$('div'); - await page.evaluate(element => element.style.height = '200px', elementHandle); + 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}); + expect(box).toEqual({ x: 8, y: 8, width: 100, height: 200 }); }); - it('should work with SVG nodes', async() => { - const {page} = getTestState(); + it('should work with SVG nodes', async () => { + const { page } = getTestState(); await page.setContent(` @@ -73,17 +81,17 @@ describe('ElementHandle specs', function() { `); const element = await page.$('#therect'); const pptrBoundingBox = await element.boundingBox(); - const webBoundingBox = await page.evaluate(e => { + const webBoundingBox = await page.evaluate((e) => { const rect = e.getBoundingClientRect(); - return {x: rect.x, y: rect.y, width: rect.width, height: rect.height}; + return { x: rect.x, y: rect.y, width: rect.width, height: rect.height }; }, element); expect(pptrBoundingBox).toEqual(webBoundingBox); }); }); - describeFailsFirefox('ElementHandle.boxModel', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('ElementHandle.boxModel', function () { + it('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/resetcss.html'); @@ -100,10 +108,11 @@ describe('ElementHandle specs', function() { // Step 2: Add div and position it absolutely inside frame. const frame = page.frames()[1]; - const divHandle = (await frame.evaluateHandle(() => { - const div = document.createElement('div'); - document.body.appendChild(div); - div.style = ` + const divHandle = ( + await frame.evaluateHandle(() => { + const div = document.createElement('div'); + document.body.appendChild(div); + div.style = ` box-sizing: border-box; position: absolute; border-left: 1px solid black; @@ -114,8 +123,9 @@ describe('ElementHandle specs', function() { width: 6px; height: 7px; `; - return div; - })).asElement(); + return div; + }) + ).asElement(); // Step 3: query div's boxModel and assert box values. const box = await divHandle.boxModel(); @@ -139,8 +149,8 @@ describe('ElementHandle specs', function() { }); }); - it('should return null for invisible elements', async() => { - const {page} = getTestState(); + it('should return null for invisible elements', async () => { + const { page } = getTestState(); await page.setContent('
hi
'); const element = await page.$('div'); @@ -148,9 +158,9 @@ describe('ElementHandle specs', function() { }); }); - describe('ElementHandle.contentFrame', function() { - itFailsFirefox('should work', async() => { - const {page,server} = getTestState(); + describe('ElementHandle.contentFrame', function () { + itFailsFirefox('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); @@ -160,84 +170,97 @@ describe('ElementHandle specs', function() { }); }); - describe('ElementHandle.click', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describe('ElementHandle.click', function () { + it('should work', async () => { + const { page, server } = getTestState(); 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 for Shadow DOM v1', async() => { - const {page, server} = getTestState(); + it('should work for Shadow DOM v1', async () => { + const { page, server } = getTestState(); 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() => { - const {page, server} = getTestState(); + it('should work for TextNodes', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/button.html'); - const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild); + const buttonTextNode = await page.evaluateHandle( + () => document.querySelector('button').firstChild + ); let error = null; - await buttonTextNode.click().catch(error_ => error = error_); + await buttonTextNode.click().catch((error_) => (error = error_)); expect(error.message).toBe('Node is not of type HTMLElement'); }); - it('should throw for detached nodes', async() => { - const {page, server} = getTestState(); + it('should throw for detached nodes', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/button.html'); const button = await page.$('button'); - await page.evaluate(button => button.remove(), button); + await page.evaluate((button) => button.remove(), button); let error = null; - await button.click().catch(error_ => error = error_); + await button.click().catch((error_) => (error = error_)); expect(error.message).toBe('Node is detached from document'); }); - it('should throw for hidden nodes', async() => { - const {page, server} = getTestState(); + it('should throw for hidden nodes', async () => { + const { page, server } = getTestState(); 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().catch(error_ => error_); - expect(error.message).toBe('Node is either not visible or not an HTMLElement'); + await page.evaluate((button) => (button.style.display = 'none'), button); + const error = await button.click().catch((error_) => error_); + expect(error.message).toBe( + 'Node is either not visible or not an HTMLElement' + ); }); - it('should throw for recursively hidden nodes', async() => { - const {page, server} = getTestState(); + it('should throw for recursively hidden nodes', async () => { + const { page, server } = getTestState(); 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().catch(error_ => error_); - expect(error.message).toBe('Node is either not visible or not an HTMLElement'); + await page.evaluate( + (button) => (button.parentElement.style.display = 'none'), + button + ); + const error = await button.click().catch((error_) => error_); + expect(error.message).toBe( + 'Node is either not visible or not an HTMLElement' + ); }); - itFailsFirefox('should throw for
elements', async() => { - const {page} = getTestState(); + itFailsFirefox('should throw for
elements', async () => { + const { page } = getTestState(); await page.setContent('hello
goodbye'); const br = await page.$('br'); - const error = await br.click().catch(error_ => error_); - expect(error.message).toBe('Node is either not visible or not an HTMLElement'); + const error = await br.click().catch((error_) => error_); + expect(error.message).toBe( + 'Node is either not visible or not an HTMLElement' + ); }); }); - describe('ElementHandle.hover', function() { - itFailsFirefox('should work', async() => { - const {page, server} = getTestState(); + describe('ElementHandle.hover', function () { + itFailsFirefox('should work', async () => { + const { page, server } = getTestState(); 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'); + expect( + await page.evaluate(() => document.querySelector('button:hover').id) + ).toBe('button-6'); }); }); - describe('ElementHandle.isIntersectingViewport', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describe('ElementHandle.isIntersectingViewport', function () { + it('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/offscreenbuttons.html'); for (let i = 0; i < 11; ++i) { @@ -249,19 +272,22 @@ describe('ElementHandle specs', function() { }); }); - describe('Custom queries', function() { + describe('Custom queries', function () { this.afterEach(() => { - const {puppeteer} = getTestState(); + const { puppeteer } = getTestState(); puppeteer.__experimental_clearQueryHandlers(); }); - it('should register and unregister', async() => { - const {page, puppeteer} = getTestState(); + it('should register and unregister', async () => { + const { page, puppeteer } = getTestState(); await page.setContent('
'); // Register. - puppeteer.__experimental_registerCustomQueryHandler('getById', (element, selector) => document.querySelector(`[id="${selector}"]`)); + puppeteer.__experimental_registerCustomQueryHandler( + 'getById', + (element, selector) => document.querySelector(`[id="${selector}"]`) + ); const element = await page.$('getById/foo'); - expect(await page.evaluate(element => element.id, element)).toBe('foo'); + expect(await page.evaluate((element) => element.id, element)).toBe('foo'); // Unregister. puppeteer.__experimental_unregisterCustomQueryHandler('getById'); @@ -269,53 +295,90 @@ describe('ElementHandle specs', function() { await page.$('getById/foo'); expect.fail('Custom query handler not set - throw expected'); } catch (error) { - expect(error).toStrictEqual(new Error('Query set to use "getById", but no query handler of that name was found')); + expect(error).toStrictEqual( + new Error( + 'Query set to use "getById", but no query handler of that name was found' + ) + ); } }); it('should throw with invalid query names', () => { try { - const {puppeteer} = getTestState(); - puppeteer.__experimental_registerCustomQueryHandler('1/2/3', (element, selector) => {}); + const { puppeteer } = getTestState(); + puppeteer.__experimental_registerCustomQueryHandler( + '1/2/3', + (element, selector) => {} + ); expect.fail('Custom query handler name was invalid - throw expected'); } catch (error) { - expect(error).toStrictEqual(new Error('Custom query handler names may only contain [a-zA-Z]')); + expect(error).toStrictEqual( + new Error('Custom query handler names may only contain [a-zA-Z]') + ); } }); - it('should work for multiple elements', async() => { - const {page, puppeteer} = getTestState(); - await page.setContent('
Foo1
Foo2
'); - puppeteer.__experimental_registerCustomQueryHandler('getByClass', (element, selector) => document.querySelectorAll(`.${selector}`)); + it('should work for multiple elements', async () => { + const { page, puppeteer } = getTestState(); + await page.setContent( + '
Foo1
Foo2
' + ); + puppeteer.__experimental_registerCustomQueryHandler( + 'getByClass', + (element, selector) => document.querySelectorAll(`.${selector}`) + ); const elements = await page.$$('getByClass/foo'); - const classNames = await Promise.all(elements.map(async element => await page.evaluate(element => element.className, element))); + const classNames = await Promise.all( + elements.map( + async (element) => + await page.evaluate((element) => element.className, element) + ) + ); expect(classNames).toStrictEqual(['foo', 'foo baz']); }); - it('should eval correctly', async() => { - const {page, puppeteer} = getTestState(); - await page.setContent('
Foo1
Foo2
'); - puppeteer.__experimental_registerCustomQueryHandler('getByClass', (element, selector) => document.querySelectorAll(`.${selector}`)); - const elements = await page.$$eval('getByClass/foo', divs => divs.length); + it('should eval correctly', async () => { + const { page, puppeteer } = getTestState(); + await page.setContent( + '
Foo1
Foo2
' + ); + puppeteer.__experimental_registerCustomQueryHandler( + 'getByClass', + (element, selector) => document.querySelectorAll(`.${selector}`) + ); + const elements = await page.$$eval( + 'getByClass/foo', + (divs) => divs.length + ); expect(elements).toBe(2); }); - it('should wait correctly with waitForSelector', async() => { - const {page, puppeteer} = getTestState(); - puppeteer.__experimental_registerCustomQueryHandler('getByClass', (element, selector) => element.querySelector(`.${selector}`)); + it('should wait correctly with waitForSelector', async () => { + const { page, puppeteer } = getTestState(); + puppeteer.__experimental_registerCustomQueryHandler( + 'getByClass', + (element, selector) => element.querySelector(`.${selector}`) + ); const waitFor = page.waitForSelector('getByClass/foo'); // Set the page content after the waitFor has been started. - await page.setContent('
Foo1
'); + await page.setContent( + '
Foo1
' + ); const element = await waitFor; expect(element).toBeDefined(); }); - it('should wait correctly with waitFor', async() => { - const {page, puppeteer} = getTestState(); - puppeteer.__experimental_registerCustomQueryHandler('getByClass', (element, selector) => element.querySelector(`.${selector}`)); + it('should wait correctly with waitFor', async () => { + const { page, puppeteer } = getTestState(); + puppeteer.__experimental_registerCustomQueryHandler( + 'getByClass', + (element, selector) => element.querySelector(`.${selector}`) + ); const waitFor = page.waitFor('getByClass/foo'); // Set the page content after the waitFor has been started. - await page.setContent('
Foo1
'); + await page.setContent( + '
Foo1
' + ); const element = await waitFor; expect(element).toBeDefined(); diff --git a/test/emulation.spec.js b/test/emulation.spec.js index e25eee21a96de..a458224e0a47b 100644 --- a/test/emulation.spec.js +++ b/test/emulation.spec.js @@ -15,7 +15,11 @@ */ const expect = require('expect'); -const {getTestState,setupTestBrowserHooks, setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); describe('Emulation', () => { setupTestBrowserHooks(); @@ -24,45 +28,44 @@ describe('Emulation', () => { let iPhoneLandscape; before(() => { - const {puppeteer} = getTestState(); + const { puppeteer } = getTestState(); iPhone = puppeteer.devices['iPhone 6']; iPhoneLandscape = puppeteer.devices['iPhone 6 landscape']; }); - describe('Page.viewport', function() { + describe('Page.viewport', function () { + it('should get the proper viewport size', async () => { + const { page } = getTestState(); - it('should get the proper viewport size', async() => { - const {page} = getTestState(); - - expect(page.viewport()).toEqual({width: 800, height: 600}); - await page.setViewport({width: 123, height: 456}); - expect(page.viewport()).toEqual({width: 123, height: 456}); + expect(page.viewport()).toEqual({ width: 800, height: 600 }); + await page.setViewport({ width: 123, height: 456 }); + expect(page.viewport()).toEqual({ width: 123, height: 456 }); }); - it('should support mobile emulation', async() => { - const {page, server} = getTestState(); + it('should support mobile emulation', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/mobile.html'); expect(await page.evaluate(() => window.innerWidth)).toBe(800); await page.setViewport(iPhone.viewport); expect(await page.evaluate(() => window.innerWidth)).toBe(375); - await page.setViewport({width: 400, height: 300}); + await page.setViewport({ width: 400, height: 300 }); expect(await page.evaluate(() => window.innerWidth)).toBe(400); }); - itFailsFirefox('should support touch emulation', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should support touch emulation', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/mobile.html'); expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false); await page.setViewport(iPhone.viewport); expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(true); expect(await page.evaluate(dispatchTouch)).toBe('Received touch'); - await page.setViewport({width: 100, height: 100}); + await page.setViewport({ width: 100, height: 100 }); expect(await page.evaluate(() => 'ontouchstart' in window)).toBe(false); function dispatchTouch() { let fulfill; - const promise = new Promise(x => fulfill = x); - window.ontouchstart = function(e) { + const promise = new Promise((x) => (fulfill = x)); + window.ontouchstart = function (e) { fulfill('Received touch'); }; window.dispatchEvent(new Event('touchstart')); @@ -72,57 +75,75 @@ describe('Emulation', () => { return promise; } }); - itFailsFirefox('should be detectable by Modernizr', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should be detectable by Modernizr', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/detect-touch.html'); - expect(await page.evaluate(() => document.body.textContent.trim())).toBe('NO'); + expect(await page.evaluate(() => document.body.textContent.trim())).toBe( + 'NO' + ); await page.setViewport(iPhone.viewport); await page.goto(server.PREFIX + '/detect-touch.html'); - expect(await page.evaluate(() => document.body.textContent.trim())).toBe('YES'); - }); - itFailsFirefox('should detect touch when applying viewport with touches', async() => { - const {page, server} = getTestState(); - - await page.setViewport({width: 800, height: 600, hasTouch: true}); - await page.addScriptTag({url: server.PREFIX + '/modernizr.js'}); - expect(await page.evaluate(() => Modernizr.touchevents)).toBe(true); + expect(await page.evaluate(() => document.body.textContent.trim())).toBe( + 'YES' + ); }); - itFailsFirefox('should support landscape emulation', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should detect touch when applying viewport with touches', + async () => { + const { page, server } = getTestState(); + + await page.setViewport({ width: 800, height: 600, hasTouch: true }); + await page.addScriptTag({ url: server.PREFIX + '/modernizr.js' }); + expect(await page.evaluate(() => Modernizr.touchevents)).toBe(true); + } + ); + itFailsFirefox('should support landscape emulation', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => screen.orientation.type)).toBe('portrait-primary'); + expect(await page.evaluate(() => screen.orientation.type)).toBe( + 'portrait-primary' + ); await page.setViewport(iPhoneLandscape.viewport); - expect(await page.evaluate(() => screen.orientation.type)).toBe('landscape-primary'); - await page.setViewport({width: 100, height: 100}); - expect(await page.evaluate(() => screen.orientation.type)).toBe('portrait-primary'); + expect(await page.evaluate(() => screen.orientation.type)).toBe( + 'landscape-primary' + ); + await page.setViewport({ width: 100, height: 100 }); + expect(await page.evaluate(() => screen.orientation.type)).toBe( + 'portrait-primary' + ); }); }); - describe('Page.emulate', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describe('Page.emulate', function () { + it('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/mobile.html'); await page.emulate(iPhone); expect(await page.evaluate(() => window.innerWidth)).toBe(375); - expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone'); + expect(await page.evaluate(() => navigator.userAgent)).toContain( + 'iPhone' + ); }); - it('should support clicking', async() => { - const {page, server} = getTestState(); + it('should support clicking', async () => { + const { page, server } = getTestState(); await page.emulate(iPhone); await page.goto(server.PREFIX + '/input/button.html'); const button = await page.$('button'); - await page.evaluate(button => button.style.marginTop = '200px', button); + await page.evaluate( + (button) => (button.style.marginTop = '200px'), + button + ); await button.click(); expect(await page.evaluate(() => result)).toBe('Clicked'); }); }); - describe('Page.emulateMedia [deprecated]', function() { - /* emulateMedia is deprecated in favour of emulateMediaType but we + describe('Page.emulateMedia [deprecated]', function () { + /* emulateMedia is deprecated in favour of emulateMediaType but we * don't want to remove it from Puppeteer just yet. We can't check * that emulateMedia === emulateMediaType because when running tests * with COVERAGE=1 the methods get rewritten. So instead we @@ -132,121 +153,202 @@ describe('Emulation', () => { * If you update these tests, you should update emulateMediaType's * tests, and vice-versa. */ - itFailsFirefox('should work', async() => { - const {page} = getTestState(); - - expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false); + itFailsFirefox('should work', async () => { + const { page } = getTestState(); + + expect(await page.evaluate(() => matchMedia('screen').matches)).toBe( + true + ); + expect(await page.evaluate(() => matchMedia('print').matches)).toBe( + false + ); await page.emulateMedia('print'); - expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('screen').matches)).toBe( + false + ); expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true); await page.emulateMedia(null); - expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false); + 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 argument', async() => { - const {page} = getTestState(); + it('should throw in case of bad argument', async () => { + const { page } = getTestState(); let error = null; - await page.emulateMedia('bad').catch(error_ => error = error_); + await page.emulateMedia('bad').catch((error_) => (error = error_)); expect(error.message).toBe('Unsupported media type: bad'); }); }); - describe('Page.emulateMediaType', function() { - /* NOTE! Updating these tests? Update the emulateMedia tests above + describe('Page.emulateMediaType', function () { + /* NOTE! Updating these tests? Update the emulateMedia tests above * too (and see the big comment for why we have these duplicated). */ - itFailsFirefox('should work', async() => { - const {page} = getTestState(); - - expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false); + itFailsFirefox('should work', async () => { + const { page } = getTestState(); + + expect(await page.evaluate(() => matchMedia('screen').matches)).toBe( + true + ); + expect(await page.evaluate(() => matchMedia('print').matches)).toBe( + false + ); await page.emulateMediaType('print'); - expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(false); + expect(await page.evaluate(() => matchMedia('screen').matches)).toBe( + false + ); expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true); await page.emulateMediaType(null); - expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('print').matches)).toBe(false); + 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 argument', async() => { - const {page} = getTestState(); + it('should throw in case of bad argument', async () => { + const { page } = getTestState(); let error = null; - await page.emulateMediaType('bad').catch(error_ => error = error_); + await page.emulateMediaType('bad').catch((error_) => (error = error_)); expect(error.message).toBe('Unsupported media type: bad'); }); }); - describe('Page.emulateMediaFeatures', function() { - itFailsFirefox('should work', async() => { - const {page} = getTestState(); + describe('Page.emulateMediaFeatures', function () { + itFailsFirefox('should work', async () => { + const { page } = getTestState(); await page.emulateMediaFeatures([ - {name: 'prefers-reduced-motion', value: 'reduce'}, + { name: 'prefers-reduced-motion', value: 'reduce' }, ]); - expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: no-preference)').matches)).toBe(false); + expect( + await page.evaluate( + () => matchMedia('(prefers-reduced-motion: reduce)').matches + ) + ).toBe(true); + expect( + await page.evaluate( + () => matchMedia('(prefers-reduced-motion: no-preference)').matches + ) + ).toBe(false); await page.emulateMediaFeatures([ - {name: 'prefers-color-scheme', value: 'light'}, + { name: 'prefers-color-scheme', value: '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); + 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.emulateMediaFeatures([ - {name: 'prefers-color-scheme', value: 'dark'}, + { name: 'prefers-color-scheme', value: '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); + 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.emulateMediaFeatures([ - {name: 'prefers-reduced-motion', value: 'reduce'}, - {name: 'prefers-color-scheme', value: 'light'}, + { name: 'prefers-reduced-motion', value: 'reduce' }, + { name: 'prefers-color-scheme', value: 'light' }, ]); - expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: reduce)').matches)).toBe(true); - expect(await page.evaluate(() => matchMedia('(prefers-reduced-motion: no-preference)').matches)).toBe(false); - 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); + expect( + await page.evaluate( + () => matchMedia('(prefers-reduced-motion: reduce)').matches + ) + ).toBe(true); + expect( + await page.evaluate( + () => matchMedia('(prefers-reduced-motion: no-preference)').matches + ) + ).toBe(false); + 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); }); - it('should throw in case of bad argument', async() => { - const {page} = getTestState(); + it('should throw in case of bad argument', async () => { + const { page } = getTestState(); let error = null; - await page.emulateMediaFeatures([{name: 'bad', value: ''}]).catch(error_ => error = error_); + await page + .emulateMediaFeatures([{ name: 'bad', value: '' }]) + .catch((error_) => (error = error_)); expect(error.message).toBe('Unsupported media feature: bad'); }); }); - describeFailsFirefox('Page.emulateTimezone', function() { - it('should work', async() => { - const {page} = getTestState(); + describeFailsFirefox('Page.emulateTimezone', function () { + it('should work', async () => { + const { page } = getTestState(); page.evaluate(() => { globalThis.date = new Date(1479579154987); }); await page.emulateTimezone('America/Jamaica'); - expect(await page.evaluate(() => date.toString())).toBe('Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)'); + expect(await page.evaluate(() => date.toString())).toBe( + 'Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)' + ); await page.emulateTimezone('Pacific/Honolulu'); - expect(await page.evaluate(() => date.toString())).toBe('Sat Nov 19 2016 08:12:34 GMT-1000 (Hawaii-Aleutian Standard Time)'); + expect(await page.evaluate(() => date.toString())).toBe( + 'Sat Nov 19 2016 08:12:34 GMT-1000 (Hawaii-Aleutian Standard Time)' + ); await page.emulateTimezone('America/Buenos_Aires'); - expect(await page.evaluate(() => date.toString())).toBe('Sat Nov 19 2016 15:12:34 GMT-0300 (Argentina Standard Time)'); + expect(await page.evaluate(() => date.toString())).toBe( + 'Sat Nov 19 2016 15:12:34 GMT-0300 (Argentina Standard Time)' + ); await page.emulateTimezone('Europe/Berlin'); - expect(await page.evaluate(() => date.toString())).toBe('Sat Nov 19 2016 19:12:34 GMT+0100 (Central European Standard Time)'); + expect(await page.evaluate(() => date.toString())).toBe( + 'Sat Nov 19 2016 19:12:34 GMT+0100 (Central European Standard Time)' + ); }); - it('should throw for invalid timezone IDs', async() => { - const {page} = getTestState(); + it('should throw for invalid timezone IDs', async () => { + const { page } = getTestState(); let error = null; - await page.emulateTimezone('Foo/Bar').catch(error_ => error = error_); + await page.emulateTimezone('Foo/Bar').catch((error_) => (error = error_)); expect(error.message).toBe('Invalid timezone ID: Foo/Bar'); - await page.emulateTimezone('Baz/Qux').catch(error_ => error = error_); + await page.emulateTimezone('Baz/Qux').catch((error_) => (error = error_)); expect(error.message).toBe('Invalid timezone ID: Baz/Qux'); }); }); - }); diff --git a/test/evaluation.spec.js b/test/evaluation.spec.js index fedf248c68284..686d620516aa9 100644 --- a/test/evaluation.spec.js +++ b/test/evaluation.spec.js @@ -16,284 +16,331 @@ const utils = require('./utils'); const expect = require('expect'); -const {getTestState,setupTestBrowserHooks, setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); const bigint = typeof BigInt !== 'undefined'; -describe('Evaluation specs', function() { +describe('Evaluation specs', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - describe('Page.evaluate', function() { - - it('should work', async() => { - const {page} = getTestState(); + describe('Page.evaluate', function () { + it('should work', async () => { + const { page } = getTestState(); const result = await page.evaluate(() => 7 * 3); expect(result).toBe(21); }); - (bigint ? itFailsFirefox : xit)('should transfer BigInt', async() => { - const {page} = getTestState(); + (bigint ? itFailsFirefox : xit)('should transfer BigInt', async () => { + const { page } = getTestState(); - const result = await page.evaluate(a => a, BigInt(42)); + const result = await page.evaluate((a) => a, BigInt(42)); expect(result).toBe(BigInt(42)); }); - itFailsFirefox('should transfer NaN', async() => { - const {page} = getTestState(); + itFailsFirefox('should transfer NaN', async () => { + const { page } = getTestState(); - const result = await page.evaluate(a => a, NaN); + const result = await page.evaluate((a) => a, NaN); expect(Object.is(result, NaN)).toBe(true); }); - itFailsFirefox('should transfer -0', async() => { - const {page} = getTestState(); + itFailsFirefox('should transfer -0', async () => { + const { page } = getTestState(); - const result = await page.evaluate(a => a, -0); + const result = await page.evaluate((a) => a, -0); expect(Object.is(result, -0)).toBe(true); }); - itFailsFirefox('should transfer Infinity', async() => { - const {page} = getTestState(); + itFailsFirefox('should transfer Infinity', async () => { + const { page } = getTestState(); - const result = await page.evaluate(a => a, Infinity); + const result = await page.evaluate((a) => a, Infinity); expect(Object.is(result, Infinity)).toBe(true); }); - itFailsFirefox('should transfer -Infinity', async() => { - const {page} = getTestState(); + itFailsFirefox('should transfer -Infinity', async () => { + const { page } = getTestState(); - const result = await page.evaluate(a => a, -Infinity); + const result = await page.evaluate((a) => a, -Infinity); expect(Object.is(result, -Infinity)).toBe(true); }); - it('should transfer arrays', async() => { - const {page} = getTestState(); + it('should transfer arrays', async () => { + const { page } = getTestState(); - const result = await page.evaluate(a => a, [1, 2, 3]); - expect(result).toEqual([1,2,3]); + 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() => { - const {page} = getTestState(); + it('should transfer arrays as arrays, not objects', async () => { + const { page } = getTestState(); - const result = await page.evaluate(a => Array.isArray(a), [1, 2, 3]); + const result = await page.evaluate((a) => Array.isArray(a), [1, 2, 3]); expect(result).toBe(true); }); - it('should modify global environment', async() => { - const {page} = getTestState(); + it('should modify global environment', async () => { + const { page } = getTestState(); - await page.evaluate(() => window.globalVar = 123); + await page.evaluate(() => (window.globalVar = 123)); expect(await page.evaluate('globalVar')).toBe(123); }); - it('should evaluate in the page context', async() => { - const {page, server} = getTestState(); + it('should evaluate in the page context', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/global-var.html'); expect(await page.evaluate('globalVar')).toBe(123); }); - itFailsFirefox('should return undefined for objects with symbols', async() => { - const {page} = getTestState(); + itFailsFirefox( + 'should return undefined for objects with symbols', + async () => { + const { page } = getTestState(); - expect(await page.evaluate(() => [Symbol('foo4')])).toBe(undefined); - }); - it('should work with function shorthands', async() => { - const {page} = getTestState(); + expect(await page.evaluate(() => [Symbol('foo4')])).toBe(undefined); + } + ); + it('should work with function shorthands', async () => { + const { page } = getTestState(); const a = { - sum(a, b) { return a + b; }, + sum(a, b) { + return a + b; + }, - async mult(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() => { - const {page} = getTestState(); + it('should work with unicode chars', async () => { + const { page } = getTestState(); - const result = await page.evaluate(a => a['中文字符'], {'中文字符': 42}); + const result = await page.evaluate((a) => a['中文字符'], { + 中文字符: 42, + }); expect(result).toBe(42); }); - itFailsFirefox('should throw when evaluation triggers reload', async() => { - const {page} = getTestState(); + itFailsFirefox('should throw when evaluation triggers reload', async () => { + const { page } = getTestState(); let error = null; - await page.evaluate(() => { - location.reload(); - return new Promise(() => {}); - }).catch(error_ => error = error_); + await page + .evaluate(() => { + location.reload(); + return new Promise(() => {}); + }) + .catch((error_) => (error = error_)); expect(error.message).toContain('Protocol error'); }); - it('should await promise', async() => { - const {page} = getTestState(); + it('should await promise', async () => { + const { page } = getTestState(); const result = await page.evaluate(() => Promise.resolve(8 * 7)); expect(result).toBe(56); }); - it('should work right after framenavigated', async() => { - const {page, server} = getTestState(); + it('should work right after framenavigated', async () => { + const { page, server } = getTestState(); let frameEvaluation = null; - page.on('framenavigated', async frame => { + page.on('framenavigated', async (frame) => { frameEvaluation = frame.evaluate(() => 6 * 7); }); await page.goto(server.EMPTY_PAGE); expect(await frameEvaluation).toBe(42); }); - itFailsFirefox('should work from-inside an exposed function', async() => { - const {page} = getTestState(); + itFailsFirefox('should work from-inside an exposed function', async () => { + const { page } = getTestState(); // Setup inpage callback, which calls Page.evaluate - await page.exposeFunction('callController', async function(a, b) { + 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() { + const result = await page.evaluate(async function () { return await callController(9, 3); }); expect(result).toBe(27); }); - it('should reject promise with exception', async() => { - const {page} = getTestState(); + it('should reject promise with exception', async () => { + const { page } = getTestState(); let error = null; - await page.evaluate(() => not_existing_object.property).catch(error_ => error = error_); + await page + .evaluate(() => not_existing_object.property) + .catch((error_) => (error = error_)); expect(error).toBeTruthy(); expect(error.message).toContain('not_existing_object'); }); - it('should support thrown strings as error messages', async() => { - const {page} = getTestState(); + it('should support thrown strings as error messages', async () => { + const { page } = getTestState(); let error = null; - await page.evaluate(() => { throw 'qwerty'; }).catch(error_ => error = error_); + await page + .evaluate(() => { + throw 'qwerty'; + }) + .catch((error_) => (error = error_)); expect(error).toBeTruthy(); expect(error.message).toContain('qwerty'); }); - it('should support thrown numbers as error messages', async() => { - const {page} = getTestState(); + it('should support thrown numbers as error messages', async () => { + const { page } = getTestState(); let error = null; - await page.evaluate(() => { throw 100500; }).catch(error_ => error = error_); + await page + .evaluate(() => { + throw 100500; + }) + .catch((error_) => (error = error_)); expect(error).toBeTruthy(); expect(error.message).toContain('100500'); }); - it('should return complex objects', async() => { - const {page} = getTestState(); + it('should return complex objects', async () => { + const { page } = getTestState(); - const object = {foo: 'bar!'}; - const result = await page.evaluate(a => a, object); + const object = { foo: 'bar!' }; + const result = await page.evaluate((a) => a, object); expect(result).not.toBe(object); expect(result).toEqual(object); }); - (bigint ? itFailsFirefox : xit)('should return BigInt', async() => { - const {page} = getTestState(); + (bigint ? itFailsFirefox : xit)('should return BigInt', async () => { + const { page } = getTestState(); const result = await page.evaluate(() => BigInt(42)); expect(result).toBe(BigInt(42)); }); - itFailsFirefox('should return NaN', async() => { - const {page} = getTestState(); + itFailsFirefox('should return NaN', async () => { + const { page } = getTestState(); const result = await page.evaluate(() => NaN); expect(Object.is(result, NaN)).toBe(true); }); - itFailsFirefox('should return -0', async() => { - const {page} = getTestState(); + itFailsFirefox('should return -0', async () => { + const { page } = getTestState(); const result = await page.evaluate(() => -0); expect(Object.is(result, -0)).toBe(true); }); - itFailsFirefox('should return Infinity', async() => { - const {page} = getTestState(); + itFailsFirefox('should return Infinity', async () => { + const { page } = getTestState(); const result = await page.evaluate(() => Infinity); expect(Object.is(result, Infinity)).toBe(true); }); - itFailsFirefox('should return -Infinity', async() => { - const {page} = getTestState(); + itFailsFirefox('should return -Infinity', async () => { + const { page } = getTestState(); const result = await page.evaluate(() => -Infinity); expect(Object.is(result, -Infinity)).toBe(true); }); - it('should accept "undefined" as one of multiple parameters', async() => { - const {page} = getTestState(); + it('should accept "undefined" as one of multiple parameters', async () => { + const { page } = getTestState(); - const result = await page.evaluate((a, b) => Object.is(a, undefined) && Object.is(b, 'foo'), undefined, 'foo'); + const result = await page.evaluate( + (a, b) => Object.is(a, undefined) && Object.is(b, 'foo'), + undefined, + 'foo' + ); expect(result).toBe(true); }); - it('should properly serialize null fields', async() => { - const {page} = getTestState(); + it('should properly serialize null fields', async () => { + const { page } = getTestState(); - expect(await page.evaluate(() => ({a: undefined}))).toEqual({}); + expect(await page.evaluate(() => ({ a: undefined }))).toEqual({}); }); - itFailsFirefox('should return undefined for non-serializable objects', async() => { - const {page} = getTestState(); + itFailsFirefox( + 'should return undefined for non-serializable objects', + async () => { + const { page } = getTestState(); - expect(await page.evaluate(() => window)).toBe(undefined); - }); - itFailsFirefox('should fail for circular object', async() => { - const {page} = getTestState(); + expect(await page.evaluate(() => window)).toBe(undefined); + } + ); + itFailsFirefox('should fail for circular object', async () => { + const { page } = getTestState(); const result = await page.evaluate(() => { const a = {}; - const b = {a}; + const b = { a }; a.b = b; return a; }); expect(result).toBe(undefined); }); - itFailsFirefox('should be able to throw a tricky error', async() => { - const {page} = getTestState(); + itFailsFirefox('should be able to throw a tricky error', async () => { + const { page } = getTestState(); const windowHandle = await page.evaluateHandle(() => window); - const errorText = await windowHandle.jsonValue().catch(error_ => error_.message); - const error = await page.evaluate(errorText => { - throw new Error(errorText); - }, errorText).catch(error_ => error_); + const errorText = await windowHandle + .jsonValue() + .catch((error_) => error_.message); + const error = await page + .evaluate((errorText) => { + throw new Error(errorText); + }, errorText) + .catch((error_) => error_); expect(error.message).toContain(errorText); }); - it('should accept a string', async() => { - const {page} = getTestState(); + it('should accept a string', async () => { + const { page } = getTestState(); const result = await page.evaluate('1 + 2'); expect(result).toBe(3); }); - it('should accept a string with semi colons', async() => { - const {page} = getTestState(); + it('should accept a string with semi colons', async () => { + const { page } = getTestState(); const result = await page.evaluate('1 + 5;'); expect(result).toBe(6); }); - it('should accept a string with comments', async() => { - const {page} = getTestState(); + it('should accept a string with comments', async () => { + const { page } = getTestState(); const result = await page.evaluate('2 + 5;\n// do some math!'); expect(result).toBe(7); }); - itFailsFirefox('should accept element handle as an argument', async() => { - const {page} = getTestState(); + itFailsFirefox('should accept element handle as an argument', async () => { + const { page } = getTestState(); await page.setContent('
42
'); const element = await page.$('section'); - const text = await page.evaluate(e => e.textContent, element); + const text = await page.evaluate((e) => e.textContent, element); expect(text).toBe('42'); }); - itFailsFirefox('should throw if underlying element was disposed', async() => { - const {page} = getTestState(); - - 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(error_ => error = error_); - expect(error.message).toContain('JSHandle is disposed'); - }); - itFailsFirefox('should throw if elementHandles are from other frames', async() => { - const {page, server} = getTestState(); - - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - const bodyHandle = await page.frames()[1].$('body'); - let error = null; - await page.evaluate(body => body.innerHTML, bodyHandle).catch(error_ => error = error_); - expect(error).toBeTruthy(); - expect(error.message).toContain('JSHandles can be evaluated only in the context they were created'); - }); - itFailsFirefox('should simulate a user gesture', async() => { - const {page} = getTestState(); + itFailsFirefox( + 'should throw if underlying element was disposed', + async () => { + const { page } = getTestState(); + + 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((error_) => (error = error_)); + expect(error.message).toContain('JSHandle is disposed'); + } + ); + itFailsFirefox( + 'should throw if elementHandles are from other frames', + async () => { + const { page, server } = getTestState(); + + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + const bodyHandle = await page.frames()[1].$('body'); + let error = null; + await page + .evaluate((body) => body.innerHTML, bodyHandle) + .catch((error_) => (error = error_)); + expect(error).toBeTruthy(); + expect(error.message).toContain( + 'JSHandles can be evaluated only in the context they were created' + ); + } + ); + itFailsFirefox('should simulate a user gesture', async () => { + const { page } = getTestState(); const result = await page.evaluate(() => { document.body.appendChild(document.createTextNode('test')); @@ -302,99 +349,124 @@ describe('Evaluation specs', function() { }); expect(result).toBe(true); }); - itFailsFirefox('should throw a nice error after a navigation', async() => { - const {page} = getTestState(); + itFailsFirefox('should throw a nice error after a navigation', async () => { + const { page } = getTestState(); const executionContext = await page.mainFrame().executionContext(); await Promise.all([ page.waitForNavigation(), - executionContext.evaluate(() => window.location.reload()) + executionContext.evaluate(() => window.location.reload()), ]); - const error = await executionContext.evaluate(() => null).catch(error_ => error_); + const error = await executionContext + .evaluate(() => null) + .catch((error_) => error_); expect(error.message).toContain('navigation'); }); - itFailsFirefox('should not throw an error when evaluation does a navigation', async() => { - const {page, server} = getTestState(); - - await page.goto(server.PREFIX + '/one-style.html'); - const result = await page.evaluate(() => { - window.location = '/empty.html'; - return [42]; - }); - expect(result).toEqual([42]); - }); - itFailsFirefox('should transfer 100Mb of data from page to node.js', async function() { - const {page} = getTestState(); - - 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() => { - const {page} = getTestState(); + itFailsFirefox( + 'should not throw an error when evaluation does a navigation', + async () => { + const { page, server } = getTestState(); + + await page.goto(server.PREFIX + '/one-style.html'); + const result = await page.evaluate(() => { + window.location = '/empty.html'; + return [42]; + }); + expect(result).toEqual([42]); + } + ); + itFailsFirefox( + 'should transfer 100Mb of data from page to node.js', + async function () { + const { page } = getTestState(); + + 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 () => { + const { page } = getTestState(); let error = null; - await page.evaluate(() => new Promise(() => { - throw new Error('Error in promise'); - })).catch(error_ => error = error_); + await page + .evaluate( + () => + new Promise(() => { + throw new Error('Error in promise'); + }) + ) + .catch((error_) => (error = error_)); expect(error.message).toContain('Error in promise'); }); }); - describeFailsFirefox('Page.evaluateOnNewDocument', function() { - it('should evaluate before anything else on the page', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Page.evaluateOnNewDocument', function () { + it('should evaluate before anything else on the page', async () => { + const { page, server } = getTestState(); - await page.evaluateOnNewDocument(function(){ + await page.evaluateOnNewDocument(function () { window.injected = 123; }); await page.goto(server.PREFIX + '/tamperable.html'); expect(await page.evaluate(() => window.result)).toBe(123); }); - it('should work with CSP', async() => { - const {page, server} = getTestState(); + it('should work with CSP', async () => { + const { page, server } = getTestState(); server.setCSP('/empty.html', 'script-src ' + server.PREFIX); - await page.evaluateOnNewDocument(function(){ + await page.evaluateOnNewDocument(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(error => void error); + await page + .addScriptTag({ content: 'window.e = 10;' }) + .catch((error) => void error); expect(await page.evaluate(() => window.e)).toBe(undefined); }); }); - describe('Frame.evaluate', function() { - itFailsFirefox('should have different execution contexts', async() => { - const {page, server} = getTestState(); + describe('Frame.evaluate', function () { + itFailsFirefox('should have different execution contexts', async () => { + const { page, server } = getTestState(); 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'); + 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'); }); - itFailsFirefox('should have correct execution contexts', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should have correct execution contexts', async () => { + const { page, server } = getTestState(); 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`); + 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`); }); - it('should execute after cross-site navigation', async() => { - const {page, server} = getTestState(); + it('should execute after cross-site navigation', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); const mainFrame = page.mainFrame(); - expect(await mainFrame.evaluate(() => window.location.href)).toContain('localhost'); + 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'); + expect(await mainFrame.evaluate(() => window.location.href)).toContain( + '127' + ); }); }); }); diff --git a/test/fixtures.spec.js b/test/fixtures.spec.js index 04d38f22b784d..62af097db8f30 100644 --- a/test/fixtures.spec.js +++ b/test/fixtures.spec.js @@ -15,61 +15,75 @@ */ const expect = require('expect'); -const {getTestState} = require('./mocha-utils'); +const { getTestState } = require('./mocha-utils'); const path = require('path'); -describe('Fixtures', function() { - itFailsFirefox('dumpio option should work with pipe option ', async() => { - const {defaultBrowserOptions, puppeteerPath} = getTestState(); +describe('Fixtures', function () { + itFailsFirefox('dumpio option should work with pipe option ', async () => { + const { defaultBrowserOptions, puppeteerPath } = getTestState(); let dumpioData = ''; - const {spawn} = require('child_process'); - const options = Object.assign({}, defaultBrowserOptions, {pipe: true, dumpio: true}); - const res = spawn('node', - [path.join(__dirname, 'fixtures', 'dumpio.js'), puppeteerPath, JSON.stringify(options)]); - res.stderr.on('data', data => dumpioData += data.toString('utf8')); - await new Promise(resolve => res.on('close', resolve)); + const { spawn } = require('child_process'); + const options = Object.assign({}, defaultBrowserOptions, { + pipe: true, + dumpio: true, + }); + const res = spawn('node', [ + path.join(__dirname, 'fixtures', 'dumpio.js'), + puppeteerPath, + JSON.stringify(options), + ]); + res.stderr.on('data', (data) => (dumpioData += data.toString('utf8'))); + await new Promise((resolve) => res.on('close', resolve)); expect(dumpioData).toContain('message from dumpio'); }); - it('should dump browser process stderr', async() => { - const {defaultBrowserOptions, puppeteerPath} = getTestState(); + it('should dump browser process stderr', async () => { + const { defaultBrowserOptions, puppeteerPath } = getTestState(); let dumpioData = ''; - const {spawn} = require('child_process'); - const options = Object.assign({}, defaultBrowserOptions, {dumpio: true}); - const res = spawn('node', - [path.join(__dirname, 'fixtures', 'dumpio.js'), puppeteerPath, JSON.stringify(options)]); - res.stderr.on('data', data => dumpioData += data.toString('utf8')); - await new Promise(resolve => res.on('close', resolve)); + const { spawn } = require('child_process'); + const options = Object.assign({}, defaultBrowserOptions, { dumpio: true }); + const res = spawn('node', [ + path.join(__dirname, 'fixtures', 'dumpio.js'), + puppeteerPath, + JSON.stringify(options), + ]); + res.stderr.on('data', (data) => (dumpioData += data.toString('utf8'))); + await new Promise((resolve) => res.on('close', resolve)); expect(dumpioData).toContain('DevTools listening on ws://'); }); - it('should close the browser when the node process closes', async() => { - const {defaultBrowserOptions, puppeteerPath, puppeteer} = getTestState(); + it('should close the browser when the node process closes', async () => { + const { defaultBrowserOptions, puppeteerPath, puppeteer } = getTestState(); - const {spawn, execSync} = require('child_process'); + const { spawn, execSync } = require('child_process'); const options = Object.assign({}, defaultBrowserOptions, { // Disable DUMPIO to cleanly read stdout. dumpio: false, }); - const res = spawn('node', [path.join(__dirname, 'fixtures', 'closeme.js'), puppeteerPath, JSON.stringify(options)]); + const res = spawn('node', [ + path.join(__dirname, 'fixtures', 'closeme.js'), + puppeteerPath, + JSON.stringify(options), + ]); let wsEndPointCallback; - const wsEndPointPromise = new Promise(x => wsEndPointCallback = x); + const wsEndPointPromise = new Promise((x) => (wsEndPointCallback = x)); let output = ''; - res.stdout.on('data', data => { + res.stdout.on('data', (data) => { output += data; if (output.indexOf('\n')) wsEndPointCallback(output.substring(0, output.indexOf('\n'))); }); - const browser = await puppeteer.connect({browserWSEndpoint: await wsEndPointPromise}); + const browser = await puppeteer.connect({ + browserWSEndpoint: await wsEndPointPromise, + }); const promises = [ - new Promise(resolve => browser.once('disconnected', resolve)), - new Promise(resolve => res.on('close', resolve)) + new Promise((resolve) => browser.once('disconnected', resolve)), + new Promise((resolve) => res.on('close', resolve)), ]; if (process.platform === 'win32') execSync(`taskkill /pid ${res.pid} /T /F`); - else - process.kill(res.pid); + else process.kill(res.pid); await Promise.all(promises); }); }); diff --git a/test/fixtures/closeme.js b/test/fixtures/closeme.js index 66a1620f02298..dbe798f70dfa7 100644 --- a/test/fixtures/closeme.js +++ b/test/fixtures/closeme.js @@ -1,4 +1,4 @@ -(async() => { +(async () => { const [, , puppeteerRoot, options] = process.argv; const browser = await require(puppeteerRoot).launch(JSON.parse(options)); console.log(browser.wsEndpoint()); diff --git a/test/fixtures/dumpio.js b/test/fixtures/dumpio.js index d394998c7d1c9..40b9714f6caeb 100644 --- a/test/fixtures/dumpio.js +++ b/test/fixtures/dumpio.js @@ -1,4 +1,4 @@ -(async() => { +(async () => { const [, , puppeteerRoot, options] = process.argv; const browser = await require(puppeteerRoot).launch(JSON.parse(options)); const page = await browser.newPage(); diff --git a/test/frame.spec.js b/test/frame.spec.js index 00e857c025dbf..147c0629e330b 100644 --- a/test/frame.spec.js +++ b/test/frame.spec.js @@ -16,15 +16,19 @@ const utils = require('./utils'); const expect = require('expect'); -const {getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); -describe('Frame specs', function() { +describe('Frame specs', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - describe('Frame.executionContext', function() { - itFailsFirefox('should work', async() => { - const {page, server} = getTestState(); + describe('Frame.executionContext', function () { + itFailsFirefox('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); @@ -39,21 +43,21 @@ describe('Frame specs', function() { expect(context2.frame()).toBe(frame2); await Promise.all([ - context1.evaluate(() => window.a = 1), - context2.evaluate(() => window.a = 2) + context1.evaluate(() => (window.a = 1)), + context2.evaluate(() => (window.a = 2)), ]); const [a1, a2] = await Promise.all([ context1.evaluate(() => window.a), - context2.evaluate(() => window.a) + context2.evaluate(() => window.a), ]); expect(a1).toBe(1); expect(a2).toBe(2); }); }); - describe('Frame.evaluateHandle', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describe('Frame.evaluateHandle', function () { + it('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); const mainFrame = page.mainFrame(); @@ -62,21 +66,23 @@ describe('Frame specs', function() { }); }); - describe('Frame.evaluate', function() { - itFailsFirefox('should throw for detached frames', async() => { - const {page, server} = getTestState(); + describe('Frame.evaluate', function () { + itFailsFirefox('should throw for detached frames', async () => { + const { page, server } = getTestState(); const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.detachFrame(page, 'frame1'); let error = null; - await frame1.evaluate(() => 7 * 8).catch(error_ => error = error_); - expect(error.message).toContain('Execution Context is not available in detached frame'); + await frame1.evaluate(() => 7 * 8).catch((error_) => (error = error_)); + expect(error.message).toContain( + 'Execution Context is not available in detached frame' + ); }); }); - describe('Frame Management', function() { - itFailsFirefox('should handle nested frames', async() => { - const {page, server} = getTestState(); + describe('Frame Management', function () { + itFailsFirefox('should handle nested frames', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/frames/nested-frames.html'); expect(utils.dumpFrames(page.mainFrame())).toEqual([ @@ -84,70 +90,76 @@ describe('Frame specs', function() { ' http://localhost:/frames/two-frames.html (2frames)', ' http://localhost:/frames/frame.html (uno)', ' http://localhost:/frames/frame.html (dos)', - ' http://localhost:/frames/frame.html (aframe)' + ' http://localhost:/frames/frame.html (aframe)', ]); }); - itFailsFirefox('should send events when frames are manipulated dynamically', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should send events when frames are manipulated dynamically', + async () => { + const { page, server } = getTestState(); - 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 utils.navigateFrame(page, 'frame1', './empty.html'); - expect(navigatedFrames.length).toBe(1); - expect(navigatedFrames[0].url()).toBe(server.EMPTY_PAGE); + 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 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); - }); - itFailsFirefox('should send "framenavigated" when navigating on anchor URLs', async() => { - const {page, server} = getTestState(); + // validate framenavigated events + const navigatedFrames = []; + page.on('framenavigated', (frame) => navigatedFrames.push(frame)); + await utils.navigateFrame(page, 'frame1', './empty.html'); + expect(navigatedFrames.length).toBe(1); + expect(navigatedFrames[0].url()).toBe(server.EMPTY_PAGE); - await page.goto(server.EMPTY_PAGE); - await Promise.all([ - page.goto(server.EMPTY_PAGE + '#foo'), - utils.waitEvent(page, 'framenavigated') - ]); - expect(page.url()).toBe(server.EMPTY_PAGE + '#foo'); - }); - it('should persist mainFrame on cross-process navigation', async() => { - const {page, server} = getTestState(); + // 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); + } + ); + itFailsFirefox( + 'should send "framenavigated" when navigating on anchor URLs', + async () => { + const { page, server } = getTestState(); + + await page.goto(server.EMPTY_PAGE); + await Promise.all([ + page.goto(server.EMPTY_PAGE + '#foo'), + utils.waitEvent(page, 'framenavigated'), + ]); + expect(page.url()).toBe(server.EMPTY_PAGE + '#foo'); + } + ); + it('should persist mainFrame on cross-process navigation', async () => { + const { page, server } = getTestState(); 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() => { - const {page, server} = getTestState(); + it('should not send attach/detach events for main frame', async () => { + const { page, server } = getTestState(); let hasEvents = false; - page.on('frameattached', frame => hasEvents = true); - page.on('framedetached', frame => hasEvents = true); + page.on('frameattached', (frame) => (hasEvents = true)); + page.on('framedetached', (frame) => (hasEvents = true)); await page.goto(server.EMPTY_PAGE); expect(hasEvents).toBe(false); }); - itFailsFirefox('should detach child frames on navigation', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should detach child frames on navigation', async () => { + const { page, server } = getTestState(); 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)); + 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); @@ -161,15 +173,15 @@ describe('Frame specs', function() { expect(detachedFrames.length).toBe(4); expect(navigatedFrames.length).toBe(1); }); - itFailsFirefox('should support framesets', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should support framesets', async () => { + const { page, server } = getTestState(); 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)); + 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); @@ -183,36 +195,36 @@ describe('Frame specs', function() { expect(detachedFrames.length).toBe(4); expect(navigatedFrames.length).toBe(1); }); - itFailsFirefox('should report frame from-inside shadow DOM', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should report frame from-inside shadow DOM', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/shadow.html'); - await page.evaluate(async url => { + 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); + 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); }); - itFailsFirefox('should report frame.name()', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should report frame.name()', async () => { + const { page, server } = getTestState(); await utils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE); - await page.evaluate(url => { + 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); + 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'); }); - itFailsFirefox('should report frame.parent()', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should report frame.parent()', async () => { + const { page, server } = getTestState(); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); @@ -220,21 +232,28 @@ describe('Frame specs', function() { expect(page.frames()[1].parentFrame()).toBe(page.mainFrame()); expect(page.frames()[2].parentFrame()).toBe(page.mainFrame()); }); - itFailsFirefox('should report different frame instance when frame re-attaches', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should report different frame instance when frame re-attaches', + async () => { + const { page, server } = getTestState(); - 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([ - utils.waitEvent(page, 'frameattached'), - page.evaluate(() => document.body.appendChild(window.frame)), - ]); - expect(frame2.isDetached()).toBe(false); - expect(frame1).not.toBe(frame2); - }); + 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([ + utils.waitEvent(page, 'frameattached'), + page.evaluate(() => document.body.appendChild(window.frame)), + ]); + expect(frame2.isDetached()).toBe(false); + expect(frame1).not.toBe(frame2); + } + ); }); }); diff --git a/test/golden-utils.js b/test/golden-utils.js index 78f4cee11d7ce..11a5be71a304b 100644 --- a/test/golden-utils.js +++ b/test/golden-utils.js @@ -21,15 +21,14 @@ const PNG = require('pngjs').PNG; const jpeg = require('jpeg-js'); const pixelmatch = require('pixelmatch'); -module.exports = {compare}; +module.exports = { compare }; const GoldenComparators = { 'image/png': compareImages, 'image/jpeg': compareImages, - 'text/plain': compareText + 'text/plain': compareText, }; - /** * @param {?Object} actualBuffer * @param {!Buffer} expectedBuffer @@ -38,18 +37,31 @@ const GoldenComparators = { */ function compareImages(actualBuffer, expectedBuffer, mimeType) { if (!actualBuffer || !(actualBuffer instanceof Buffer)) - return {errorMessage: 'Actual result should be Buffer.'}; + return { errorMessage: 'Actual result should be Buffer.' }; - const actual = mimeType === 'image/png' ? PNG.sync.read(actualBuffer) : jpeg.decode(actualBuffer); - const expected = mimeType === 'image/png' ? PNG.sync.read(expectedBuffer) : jpeg.decode(expectedBuffer); + const actual = + mimeType === 'image/png' + ? PNG.sync.read(actualBuffer) + : jpeg.decode(actualBuffer); + const expected = + mimeType === 'image/png' + ? PNG.sync.read(expectedBuffer) + : jpeg.decode(expectedBuffer); if (expected.width !== actual.width || expected.height !== actual.height) { return { - errorMessage: `Sizes differ: expected image ${expected.width}px X ${expected.height}px, but got ${actual.width}px X ${actual.height}px. ` + errorMessage: `Sizes differ: expected image ${expected.width}px X ${expected.height}px, but got ${actual.width}px X ${actual.height}px. `, }; } - const diff = new PNG({width: expected.width, height: expected.height}); - const count = pixelmatch(expected.data, actual.data, diff.data, expected.width, expected.height, {threshold: 0.1}); - return count > 0 ? {diff: PNG.sync.write(diff)} : null; + const diff = new PNG({ width: expected.width, height: expected.height }); + const count = pixelmatch( + expected.data, + actual.data, + diff.data, + expected.width, + expected.height, + { threshold: 0.1 } + ); + return count > 0 ? { diff: PNG.sync.write(diff) } : null; } /** @@ -59,10 +71,9 @@ function compareImages(actualBuffer, expectedBuffer, mimeType) { */ function compareText(actual, expectedBuffer) { if (typeof actual !== 'string') - return {errorMessage: 'Actual result should be string'}; + return { errorMessage: 'Actual result should be string' }; const expected = expectedBuffer.toString('utf-8'); - if (expected === actual) - return null; + if (expected === actual) return null; const diff = new Diff(); const result = diff.main(expected, actual); diff.cleanupSemantic(result); @@ -71,7 +82,7 @@ function compareText(actual, expectedBuffer) { html = `` + html; return { diff: html, - diffExtension: '.html' + diffExtension: '.html', }; } @@ -86,14 +97,15 @@ function compare(goldenPath, outputPath, actual, goldenName) { const expectedPath = path.join(goldenPath, goldenName); const actualPath = path.join(outputPath, goldenName); - const messageSuffix = 'Output is saved in "' + path.basename(outputPath + '" directory'); + const messageSuffix = + 'Output is saved in "' + path.basename(outputPath + '" directory'); if (!fs.existsSync(expectedPath)) { ensureOutputDir(); fs.writeFileSync(actualPath, actual); return { pass: false, - message: goldenName + ' is missing in golden results. ' + messageSuffix + message: goldenName + ' is missing in golden results. ' + messageSuffix, }; } const expected = fs.readFileSync(expectedPath); @@ -102,12 +114,12 @@ function compare(goldenPath, outputPath, actual, goldenName) { if (!comparator) { return { pass: false, - message: 'Failed to find comparator with type ' + mimeType + ': ' + goldenName + message: + 'Failed to find comparator with type ' + mimeType + ': ' + goldenName, }; } const result = comparator(actual, expected, mimeType); - if (!result) - return {pass: true}; + if (!result) return { pass: true }; ensureOutputDir(); if (goldenPath === outputPath) { fs.writeFileSync(addSuffix(actualPath, '-actual'), actual); @@ -122,16 +134,14 @@ function compare(goldenPath, outputPath, actual, goldenName) { } let message = goldenName + ' mismatch!'; - if (result.errorMessage) - message += ' ' + result.errorMessage; + if (result.errorMessage) message += ' ' + result.errorMessage; return { pass: false, - message: message + ' ' + messageSuffix + message: message + ' ' + messageSuffix, }; function ensureOutputDir() { - if (!fs.existsSync(outputPath)) - fs.mkdirSync(outputPath); + if (!fs.existsSync(outputPath)) fs.mkdirSync(outputPath); } } diff --git a/test/headful.spec.js b/test/headful.spec.js index cd1192dc12f6a..680a6bfa9dfd3 100644 --- a/test/headful.spec.js +++ b/test/headful.spec.js @@ -17,10 +17,10 @@ const path = require('path'); const os = require('os'); const fs = require('fs'); -const {promisify} = require('util'); -const {waitEvent} = require('./utils'); +const { promisify } = require('util'); +const { waitEvent } = require('./utils'); const expect = require('expect'); -const {getTestState} = require('./mocha-utils'); +const { getTestState } = require('./mocha-utils'); const rmAsync = promisify(require('rimraf')); const mkdtempAsync = promisify(fs.mkdtemp); @@ -29,8 +29,7 @@ const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); const extensionPath = path.join(__dirname, 'assets', 'simple-extension'); -describeChromeOnly('headful tests', function() { - +describeChromeOnly('headful tests', function () { /* These tests fire up an actual browser so let's * allow a higher timeout */ @@ -41,12 +40,12 @@ describeChromeOnly('headful tests', function() { let extensionOptions; beforeEach(() => { - const {defaultBrowserOptions} = getTestState(); + const { defaultBrowserOptions } = getTestState(); headfulOptions = Object.assign({}, defaultBrowserOptions, { - headless: false + headless: false, }); headlessOptions = Object.assign({}, defaultBrowserOptions, { - headless: true + headless: true, }); extensionOptions = Object.assign({}, defaultBrowserOptions, { @@ -56,88 +55,101 @@ describeChromeOnly('headful tests', function() { `--load-extension=${extensionPath}`, ], }); - }); - describe('HEADFUL', function() { - it('background_page target type should be available', async() => { - const {puppeteer} = getTestState(); + describe('HEADFUL', function () { + it('background_page target type should be available', async () => { + const { puppeteer } = getTestState(); const browserWithExtension = await puppeteer.launch(extensionOptions); const page = await browserWithExtension.newPage(); - const backgroundPageTarget = await browserWithExtension.waitForTarget(target => target.type() === 'background_page'); + const backgroundPageTarget = await browserWithExtension.waitForTarget( + (target) => target.type() === 'background_page' + ); await page.close(); await browserWithExtension.close(); expect(backgroundPageTarget).toBeTruthy(); }); - it('target.page() should return a background_page', async function() { - const {puppeteer} = getTestState(); + it('target.page() should return a background_page', async function () { + const { puppeteer } = getTestState(); const browserWithExtension = await puppeteer.launch(extensionOptions); - const backgroundPageTarget = await browserWithExtension.waitForTarget(target => target.type() === 'background_page'); + const backgroundPageTarget = await browserWithExtension.waitForTarget( + (target) => target.type() === 'background_page' + ); const page = await backgroundPageTarget.page(); expect(await page.evaluate(() => 2 * 3)).toBe(6); expect(await page.evaluate(() => window.MAGIC)).toBe(42); await browserWithExtension.close(); }); - it('should have default url when launching browser', async function() { - const {puppeteer} = getTestState(); + it('should have default url when launching browser', async function () { + const { puppeteer } = getTestState(); const browser = await puppeteer.launch(extensionOptions); - const pages = (await browser.pages()).map(page => page.url()); + const pages = (await browser.pages()).map((page) => page.url()); expect(pages).toEqual(['about:blank']); await browser.close(); }); itFailsWindowsUntilDate( - /* We have deferred fixing this test on Windows in favour of - * getting all other Windows tests running on CI. Putting this - * date in to force us to come back and debug properly in the - * future. - */ - new Date('2020-06-01'), - 'headless should be able to read cookies written by headful', async() => { - const {server, puppeteer} = getTestState(); - - const userDataDir = await mkdtempAsync(TMP_FOLDER); - // Write a cookie in headful chrome - const headfulBrowser = await puppeteer.launch(Object.assign({userDataDir}, headfulOptions)); - const headfulPage = await headfulBrowser.newPage(); - await headfulPage.goto(server.EMPTY_PAGE); - await headfulPage.evaluate(() => document.cookie = 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'); - await headfulBrowser.close(); - // Read the cookie from headless chrome - const headlessBrowser = await puppeteer.launch(Object.assign({userDataDir}, headlessOptions)); - const headlessPage = await headlessBrowser.newPage(); - await headlessPage.goto(server.EMPTY_PAGE); - const cookie = await headlessPage.evaluate(() => document.cookie); - await headlessBrowser.close(); - // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 - await rmAsync(userDataDir).catch(error => {}); - expect(cookie).toBe('foo=true'); - }); + /* We have deferred fixing this test on Windows in favour of + * getting all other Windows tests running on CI. Putting this + * date in to force us to come back and debug properly in the + * future. + */ + new Date('2020-06-01'), + 'headless should be able to read cookies written by headful', + async () => { + const { server, puppeteer } = getTestState(); + + const userDataDir = await mkdtempAsync(TMP_FOLDER); + // Write a cookie in headful chrome + const headfulBrowser = await puppeteer.launch( + Object.assign({ userDataDir }, headfulOptions) + ); + const headfulPage = await headfulBrowser.newPage(); + await headfulPage.goto(server.EMPTY_PAGE); + await headfulPage.evaluate( + () => + (document.cookie = + 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT') + ); + await headfulBrowser.close(); + // Read the cookie from headless chrome + const headlessBrowser = await puppeteer.launch( + Object.assign({ userDataDir }, headlessOptions) + ); + const headlessPage = await headlessBrowser.newPage(); + await headlessPage.goto(server.EMPTY_PAGE); + const cookie = await headlessPage.evaluate(() => document.cookie); + await headlessBrowser.close(); + // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 + await rmAsync(userDataDir).catch((error) => {}); + expect(cookie).toBe('foo=true'); + } + ); // TODO: Support OOOPIF. @see https://github.com/puppeteer/puppeteer/issues/2548 - xit('OOPIF: should report google.com frame', async() => { - const {server} = getTestState(); + xit('OOPIF: should report google.com frame', async () => { + const { server } = getTestState(); // https://google.com is isolated by default in Chromium embedder. const browser = await puppeteer.launch(headfulOptions); const page = await browser.newPage(); await page.goto(server.EMPTY_PAGE); await page.setRequestInterception(true); - page.on('request', r => r.respond({body: 'YO, GOOGLE.COM'})); + page.on('request', (r) => r.respond({ 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); + return new Promise((x) => (frame.onload = x)); }); await page.waitForSelector('iframe[src="https://google.com/"]'); - const urls = page.frames().map(frame => frame.url()).sort(); - expect(urls).toEqual([ - server.EMPTY_PAGE, - 'https://google.com/' - ]); + const urls = page + .frames() + .map((frame) => frame.url()) + .sort(); + expect(urls).toEqual([server.EMPTY_PAGE, 'https://google.com/']); await browser.close(); }); - it('should close browser with beforeunload page', async() => { - const {server, puppeteer} = getTestState(); + it('should close browser with beforeunload page', async () => { + const { server, puppeteer } = getTestState(); const browser = await puppeteer.launch(headfulOptions); const page = await browser.newPage(); @@ -147,39 +159,47 @@ describeChromeOnly('headful tests', function() { await page.click('body'); await browser.close(); }); - it('should open devtools when "devtools: true" option is given', async() => { - const {puppeteer} = getTestState(); + it('should open devtools when "devtools: true" option is given', async () => { + const { puppeteer } = getTestState(); - const browser = await puppeteer.launch(Object.assign({devtools: true}, headfulOptions)); + const browser = await puppeteer.launch( + Object.assign({ devtools: true }, headfulOptions) + ); const context = await browser.createIncognitoBrowserContext(); await Promise.all([ context.newPage(), - context.waitForTarget(target => target.url().includes('devtools://')), + context.waitForTarget((target) => target.url().includes('devtools://')), ]); await browser.close(); }); }); - describe('Page.bringToFront', function() { - it('should work', async() => { - const {puppeteer} = getTestState(); + describe('Page.bringToFront', function () { + it('should work', async () => { + const { puppeteer } = getTestState(); const browser = await puppeteer.launch(headfulOptions); const page1 = await browser.newPage(); const page2 = await browser.newPage(); await page1.bringToFront(); - expect(await page1.evaluate(() => document.visibilityState)).toBe('visible'); - expect(await page2.evaluate(() => document.visibilityState)).toBe('hidden'); + expect(await page1.evaluate(() => document.visibilityState)).toBe( + 'visible' + ); + expect(await page2.evaluate(() => document.visibilityState)).toBe( + 'hidden' + ); await page2.bringToFront(); - expect(await page1.evaluate(() => document.visibilityState)).toBe('hidden'); - expect(await page2.evaluate(() => document.visibilityState)).toBe('visible'); + expect(await page1.evaluate(() => document.visibilityState)).toBe( + 'hidden' + ); + expect(await page2.evaluate(() => document.visibilityState)).toBe( + 'visible' + ); await page1.close(); await page2.close(); await browser.close(); }); }); - - }); diff --git a/test/ignorehttpserrors.spec.js b/test/ignorehttpserrors.spec.js index f549d24601289..648979be5bc82 100644 --- a/test/ignorehttpserrors.spec.js +++ b/test/ignorehttpserrors.spec.js @@ -15,9 +15,9 @@ */ const expect = require('expect'); -const {getTestState} = require('./mocha-utils'); +const { getTestState } = require('./mocha-utils'); -describeFailsFirefox('ignoreHTTPSErrors', function() { +describeFailsFirefox('ignoreHTTPSErrors', function () { /* Note that this test creates its own browser rather than use * the one provided by the test set-up as we need one * with ignoreHTTPSErrors set to true @@ -26,35 +26,38 @@ describeFailsFirefox('ignoreHTTPSErrors', function() { let context; let page; - before(async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); - const options = Object.assign({ignoreHTTPSErrors: true}, defaultBrowserOptions); + before(async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); + const options = Object.assign( + { ignoreHTTPSErrors: true }, + defaultBrowserOptions + ); browser = await puppeteer.launch(options); }); - after(async() => { + after(async () => { await browser.close(); browser = null; }); - beforeEach(async() => { + beforeEach(async () => { context = await browser.createIncognitoBrowserContext(); page = await context.newPage(); }); - afterEach(async() => { + afterEach(async () => { await context.close(); context = null; page = null; }); - describe('Response.securityDetails', function() { - it('should work', async() => { - const {httpsServer} = getTestState(); + describe('Response.securityDetails', function () { + it('should work', async () => { + const { httpsServer } = getTestState(); const [serverRequest, response] = await Promise.all([ httpsServer.waitForRequest('/empty.html'), - page.goto(httpsServer.EMPTY_PAGE) + page.goto(httpsServer.EMPTY_PAGE), ]); const securityDetails = response.securityDetails(); expect(securityDetails.issuer()).toBe('puppeteer-tests'); @@ -64,21 +67,21 @@ describeFailsFirefox('ignoreHTTPSErrors', function() { expect(securityDetails.validFrom()).toBe(1550084863); expect(securityDetails.validTo()).toBe(33086084863); }); - it('should be |null| for non-secure requests', async() => { - const {server} = getTestState(); + it('should be |null| for non-secure requests', async () => { + const { server } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); expect(response.securityDetails()).toBe(null); }); - it('Network redirects should report SecurityDetails', async() => { - const {httpsServer} = getTestState(); + it('Network redirects should report SecurityDetails', async () => { + const { httpsServer } = getTestState(); httpsServer.setRedirect('/plzredirect', '/empty.html'); - const responses = []; - page.on('response', response => responses.push(response)); - const [serverRequest, ] = await Promise.all([ + const responses = []; + page.on('response', (response) => responses.push(response)); + const [serverRequest] = await Promise.all([ httpsServer.waitForRequest('/plzredirect'), - page.goto(httpsServer.PREFIX + '/plzredirect') + page.goto(httpsServer.PREFIX + '/plzredirect'), ]); expect(responses.length).toBe(2); expect(responses[0].status()).toBe(302); @@ -88,29 +91,33 @@ describeFailsFirefox('ignoreHTTPSErrors', function() { }); }); - it('should work', async() => { - const {httpsServer} = getTestState(); + it('should work', async () => { + const { httpsServer } = getTestState(); let error = null; - const response = await page.goto(httpsServer.EMPTY_PAGE).catch(error_ => error = error_); + const response = await page + .goto(httpsServer.EMPTY_PAGE) + .catch((error_) => (error = error_)); expect(error).toBe(null); expect(response.ok()).toBe(true); }); - it('should work with request interception', async() => { - const {httpsServer} = getTestState(); + it('should work with request interception', async () => { + const { httpsServer } = getTestState(); await page.setRequestInterception(true); - page.on('request', request => request.continue()); + page.on('request', (request) => request.continue()); const response = await page.goto(httpsServer.EMPTY_PAGE); expect(response.status()).toBe(200); }); - it('should work with mixed content', async() => { - const {server, httpsServer} = getTestState(); + it('should work with mixed content', async () => { + const { server, httpsServer } = getTestState(); httpsServer.setRoute('/mixedcontent.html', (req, res) => { res.end(``); }); - await page.goto(httpsServer.PREFIX + '/mixedcontent.html', {waitUntil: 'load'}); + await page.goto(httpsServer.PREFIX + '/mixedcontent.html', { + waitUntil: 'load', + }); expect(page.frames().length).toBe(2); // Make sure blocked iframe has functional execution context // @see https://github.com/puppeteer/puppeteer/issues/2709 diff --git a/test/input.spec.js b/test/input.spec.js index a70a2002ee3d2..8094737da6cf8 100644 --- a/test/input.spec.js +++ b/test/input.spec.js @@ -16,42 +16,55 @@ const path = require('path'); const expect = require('expect'); -const {getTestState,setupTestBrowserHooks,setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); const FILE_TO_UPLOAD = path.join(__dirname, '/assets/file-to-upload.txt'); -describe('input tests', function() { +describe('input tests', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - describeFailsFirefox('input', function() { - it('should upload the file', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('input', function () { + it('should upload the file', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/fileupload.html'); const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD); const input = await page.$('input'); - await page.evaluate(e => { + await page.evaluate((e) => { window._inputEvents = []; - e.addEventListener('change', ev => window._inputEvents.push(ev.type)); - e.addEventListener('input', ev => window._inputEvents.push(ev.type)); + e.addEventListener('change', (ev) => window._inputEvents.push(ev.type)); + e.addEventListener('input', (ev) => window._inputEvents.push(ev.type)); }, input); await input.uploadFile(filePath); - expect(await page.evaluate(e => e.files[0].name, input)).toBe('file-to-upload.txt'); - expect(await page.evaluate(e => e.files[0].type, input)).toBe('text/plain'); - expect(await page.evaluate(() => window._inputEvents)).toEqual(['input', 'change']); - 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'); + expect(await page.evaluate((e) => e.files[0].name, input)).toBe( + 'file-to-upload.txt' + ); + expect(await page.evaluate((e) => e.files[0].type, input)).toBe( + 'text/plain' + ); + expect(await page.evaluate(() => window._inputEvents)).toEqual([ + 'input', + 'change', + ]); + 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'); }); }); - describeFailsFirefox('Page.waitForFileChooser', function() { - it('should work when file input is attached to DOM', async() => { - const {page} = getTestState(); + describeFailsFirefox('Page.waitForFileChooser', function () { + it('should work when file input is attached to DOM', async () => { + const { page } = getTestState(); await page.setContent(``); const [chooser] = await Promise.all([ @@ -60,8 +73,8 @@ describe('input tests', function() { ]); expect(chooser).toBeTruthy(); }); - it('should work when file input is not attached to DOM', async() => { - const {page} = getTestState(); + it('should work when file input is not attached to DOM', async () => { + const { page } = getTestState(); const [chooser] = await Promise.all([ page.waitForFileChooser(), @@ -73,104 +86,124 @@ describe('input tests', function() { ]); expect(chooser).toBeTruthy(); }); - it('should respect timeout', async() => { - const {page, puppeteer} = getTestState(); + it('should respect timeout', async () => { + const { page, puppeteer } = getTestState(); let error = null; - await page.waitForFileChooser({timeout: 1}).catch(error_ => error = error_); + await page + .waitForFileChooser({ timeout: 1 }) + .catch((error_) => (error = error_)); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - it('should respect default timeout when there is no custom timeout', async() => { - const {page, puppeteer} = getTestState(); + it('should respect default timeout when there is no custom timeout', async () => { + const { page, puppeteer } = getTestState(); page.setDefaultTimeout(1); let error = null; - await page.waitForFileChooser().catch(error_ => error = error_); + await page.waitForFileChooser().catch((error_) => (error = error_)); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - it('should prioritize exact timeout over default timeout', async() => { - const {page, puppeteer} = getTestState(); + it('should prioritize exact timeout over default timeout', async () => { + const { page, puppeteer } = getTestState(); page.setDefaultTimeout(0); let error = null; - await page.waitForFileChooser({timeout: 1}).catch(error_ => error = error_); + await page + .waitForFileChooser({ timeout: 1 }) + .catch((error_) => (error = error_)); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - it('should work with no timeout', async() => { - const {page} = getTestState(); + it('should work with no timeout', async () => { + const { page } = getTestState(); const [chooser] = await Promise.all([ - page.waitForFileChooser({timeout: 0}), - page.evaluate(() => setTimeout(() => { - const el = document.createElement('input'); - el.type = 'file'; - el.click(); - }, 50)) + page.waitForFileChooser({ 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() => { - const {page} = getTestState(); + it('should return the same file chooser when there are many watchdogs simultaneously', async () => { + const { page } = getTestState(); await page.setContent(``); const [fileChooser1, fileChooser2] = await Promise.all([ page.waitForFileChooser(), page.waitForFileChooser(), - page.$eval('input', input => input.click()), + page.$eval('input', (input) => input.click()), ]); expect(fileChooser1 === fileChooser2).toBe(true); }); }); - describeFailsFirefox('FileChooser.accept', function() { - it('should accept single file', async() => { - const {page} = getTestState(); + describeFailsFirefox('FileChooser.accept', function () { + it('should accept single file', async () => { + const { page } = getTestState(); - await page.setContent(``); + await page.setContent( + `` + ); const [chooser] = await Promise.all([ page.waitForFileChooser(), page.click('input'), ]); await Promise.all([ chooser.accept([FILE_TO_UPLOAD]), - new Promise(x => page.once('metrics', x)), + new Promise((x) => page.once('metrics', x)), ]); - 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'); + 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 be able to read selected file', async() => { - const {page} = getTestState(); + it('should be able to read selected file', async () => { + const { page } = getTestState(); await page.setContent(``); - page.waitForFileChooser().then(chooser => chooser.accept([FILE_TO_UPLOAD])); - expect(await 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); - })).toBe('contents of the file'); + page + .waitForFileChooser() + .then((chooser) => chooser.accept([FILE_TO_UPLOAD])); + expect( + await 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); + }) + ).toBe('contents of the file'); }); - it('should be able to reset selected files with empty file list', async() => { - const {page} = getTestState(); + it('should be able to reset selected files with empty file list', async () => { + const { page } = getTestState(); await page.setContent(``); - page.waitForFileChooser().then(chooser => chooser.accept([FILE_TO_UPLOAD])); - expect(await page.$eval('input', async picker => { - picker.click(); - await new Promise(x => picker.oninput = x); - return picker.files.length; - })).toBe(1); - page.waitForFileChooser().then(chooser => chooser.accept([])); - expect(await page.$eval('input', async picker => { - picker.click(); - await new Promise(x => picker.oninput = x); - return picker.files.length; - })).toBe(0); + page + .waitForFileChooser() + .then((chooser) => chooser.accept([FILE_TO_UPLOAD])); + expect( + await page.$eval('input', async (picker) => { + picker.click(); + await new Promise((x) => (picker.oninput = x)); + return picker.files.length; + }) + ).toBe(1); + page.waitForFileChooser().then((chooser) => chooser.accept([])); + expect( + await page.$eval('input', async (picker) => { + picker.click(); + await new Promise((x) => (picker.oninput = x)); + return picker.files.length; + }) + ).toBe(0); }); - it('should not accept multiple files for single-file input', async() => { - const {page} = getTestState(); + it('should not accept multiple files for single-file input', async () => { + const { page } = getTestState(); await page.setContent(``); const [chooser] = await Promise.all([ @@ -178,14 +211,19 @@ describe('input tests', function() { page.click('input'), ]); let error = null; - await chooser.accept([ - path.relative(process.cwd(), __dirname + '/assets/file-to-upload.txt'), - path.relative(process.cwd(), __dirname + '/assets/pptr.png'), - ]).catch(error_ => error = error_); + await chooser + .accept([ + path.relative( + process.cwd(), + __dirname + '/assets/file-to-upload.txt' + ), + path.relative(process.cwd(), __dirname + '/assets/pptr.png'), + ]) + .catch((error_) => (error = error_)); expect(error).not.toBe(null); }); - it('should fail for non-existent files', async() => { - const {page} = getTestState(); + it('should fail for non-existent files', async () => { + const { page } = getTestState(); await page.setContent(``); const [chooser] = await Promise.all([ @@ -193,27 +231,31 @@ describe('input tests', function() { page.click('input'), ]); let error = null; - await chooser.accept(['file-does-not-exist.txt']).catch(error_ => error = error_); + await chooser + .accept(['file-does-not-exist.txt']) + .catch((error_) => (error = error_)); expect(error).not.toBe(null); }); - it('should fail when accepting file chooser twice', async() => { - const {page} = getTestState(); + it('should fail when accepting file chooser twice', async () => { + const { page } = getTestState(); await page.setContent(``); const [fileChooser] = await Promise.all([ page.waitForFileChooser(), - page.$eval('input', input => input.click()), + page.$eval('input', (input) => input.click()), ]); await fileChooser.accept([]); let error = null; - await fileChooser.accept([]).catch(error_ => error = error_); - expect(error.message).toBe('Cannot accept FileChooser which is already handled!'); + await fileChooser.accept([]).catch((error_) => (error = error_)); + expect(error.message).toBe( + 'Cannot accept FileChooser which is already handled!' + ); }); }); - describeFailsFirefox('FileChooser.cancel', function() { - it('should cancel dialog', async() => { - const {page} = getTestState(); + describeFailsFirefox('FileChooser.cancel', function () { + it('should cancel dialog', async () => { + const { page } = getTestState(); // Consider file chooser canceled if we can summon another one. // There's no reliable way in WebPlatform to see that FileChooser was @@ -221,33 +263,35 @@ describe('input tests', function() { await page.setContent(``); const [fileChooser1] = await Promise.all([ page.waitForFileChooser(), - page.$eval('input', input => input.click()), + page.$eval('input', (input) => input.click()), ]); await fileChooser1.cancel(); // If this resolves, than we successfully canceled file chooser. await Promise.all([ page.waitForFileChooser(), - page.$eval('input', input => input.click()), + page.$eval('input', (input) => input.click()), ]); }); - it('should fail when canceling file chooser twice', async() => { - const {page} = getTestState(); + it('should fail when canceling file chooser twice', async () => { + const { page } = getTestState(); await page.setContent(``); const [fileChooser] = await Promise.all([ page.waitForFileChooser(), - page.$eval('input', input => input.click()), + page.$eval('input', (input) => input.click()), ]); await fileChooser.cancel(); let error = null; - await fileChooser.cancel().catch(error_ => error = error_); - expect(error.message).toBe('Cannot cancel FileChooser which is already handled!'); + await fileChooser.cancel().catch((error_) => (error = error_)); + expect(error.message).toBe( + 'Cannot cancel FileChooser which is already handled!' + ); }); }); describeFailsFirefox('FileChooser.isMultiple', () => { - it('should work for single file pick', async() => { - const {page} = getTestState(); + it('should work for single file pick', async () => { + const { page } = getTestState(); await page.setContent(``); const [chooser] = await Promise.all([ @@ -256,8 +300,8 @@ describe('input tests', function() { ]); expect(chooser.isMultiple()).toBe(false); }); - it('should work for "multiple"', async() => { - const {page} = getTestState(); + it('should work for "multiple"', async () => { + const { page } = getTestState(); await page.setContent(``); const [chooser] = await Promise.all([ @@ -266,8 +310,8 @@ describe('input tests', function() { ]); expect(chooser.isMultiple()).toBe(true); }); - it('should work for "webkitdirectory"', async() => { - const {page} = getTestState(); + it('should work for "webkitdirectory"', async () => { + const { page } = getTestState(); await page.setContent(``); const [chooser] = await Promise.all([ diff --git a/test/jshandle.spec.js b/test/jshandle.spec.js index c5860e24f734a..2a287fa504556 100644 --- a/test/jshandle.spec.js +++ b/test/jshandle.spec.js @@ -15,126 +15,134 @@ */ const expect = require('expect'); -const {getTestState,setupTestBrowserHooks,setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); -describe('JSHandle', function() { +describe('JSHandle', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - describe('Page.evaluateHandle', function() { - it('should work', async() => { - const {page} = getTestState(); + describe('Page.evaluateHandle', function () { + it('should work', async () => { + const { page } = getTestState(); const windowHandle = await page.evaluateHandle(() => window); expect(windowHandle).toBeTruthy(); }); - it('should accept object handle as an argument', async() => { - const {page} = getTestState(); + it('should accept object handle as an argument', async () => { + const { page } = getTestState(); const navigatorHandle = await page.evaluateHandle(() => navigator); - const text = await page.evaluate(e => e.userAgent, navigatorHandle); + const text = await page.evaluate((e) => e.userAgent, navigatorHandle); expect(text).toContain('Mozilla'); }); - it('should accept object handle to primitive types', async() => { - const {page} = getTestState(); + it('should accept object handle to primitive types', async () => { + const { page } = getTestState(); const aHandle = await page.evaluateHandle(() => 5); - const isFive = await page.evaluate(e => Object.is(e, 5), aHandle); + const isFive = await page.evaluate((e) => Object.is(e, 5), aHandle); expect(isFive).toBeTruthy(); }); - it('should warn on nested object handles', async() => { - const {page} = getTestState(); + it('should warn on nested object handles', async () => { + const { page } = getTestState(); const aHandle = await page.evaluateHandle(() => document.body); let error = null; - await page.evaluateHandle( - opts => opts.elem.querySelector('p'), - {elem: aHandle} - ).catch(error_ => error = error_); + await page + .evaluateHandle((opts) => opts.elem.querySelector('p'), { + elem: aHandle, + }) + .catch((error_) => (error = error_)); expect(error.message).toContain('Are you passing a nested JSHandle?'); }); - it('should accept object handle to unserializable value', async() => { - const {page} = getTestState(); + it('should accept object handle to unserializable value', async () => { + const { page } = getTestState(); const aHandle = await page.evaluateHandle(() => Infinity); - expect(await page.evaluate(e => Object.is(e, Infinity), aHandle)).toBe(true); + expect(await page.evaluate((e) => Object.is(e, Infinity), aHandle)).toBe( + true + ); }); - it('should use the same JS wrappers', async() => { - const {page} = getTestState(); + it('should use the same JS wrappers', async () => { + const { page } = getTestState(); const aHandle = await page.evaluateHandle(() => { window.FOO = 123; return window; }); - expect(await page.evaluate(e => e.FOO, aHandle)).toBe(123); + expect(await page.evaluate((e) => e.FOO, aHandle)).toBe(123); }); - it('should work with primitives', async() => { - const {page} = getTestState(); + it('should work with primitives', async () => { + const { page } = getTestState(); const aHandle = await page.evaluateHandle(() => { window.FOO = 123; return window; }); - expect(await page.evaluate(e => e.FOO, aHandle)).toBe(123); + expect(await page.evaluate((e) => e.FOO, aHandle)).toBe(123); }); }); - describe('JSHandle.getProperty', function() { - it('should work', async() => { - const {page} = getTestState(); + describe('JSHandle.getProperty', function () { + it('should work', async () => { + const { page } = getTestState(); const aHandle = await page.evaluateHandle(() => ({ one: 1, two: 2, - three: 3 + three: 3, })); const twoHandle = await aHandle.getProperty('two'); expect(await twoHandle.jsonValue()).toEqual(2); }); }); - describe('JSHandle.jsonValue', function() { - it('should work', async() => { - const {page} = getTestState(); + describe('JSHandle.jsonValue', function () { + it('should work', async () => { + const { page } = getTestState(); - const aHandle = await page.evaluateHandle(() => ({foo: 'bar'})); + const aHandle = await page.evaluateHandle(() => ({ foo: 'bar' })); const json = await aHandle.jsonValue(); - expect(json).toEqual({foo: 'bar'}); + expect(json).toEqual({ foo: 'bar' }); }); - itFailsFirefox('should not work with dates', async() => { - const {page} = getTestState(); + itFailsFirefox('should not work with dates', async () => { + const { page } = getTestState(); - const dateHandle = await page.evaluateHandle(() => new Date('2017-09-26T00:00:00.000Z')); + 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() => { - const {page, isChrome} = getTestState(); + it('should throw for circular objects', async () => { + const { page, isChrome } = getTestState(); const windowHandle = await page.evaluateHandle('window'); let error = null; - await windowHandle.jsonValue().catch(error_ => error = error_); + await windowHandle.jsonValue().catch((error_) => (error = error_)); if (isChrome) expect(error.message).toContain('Object reference chain is too long'); - else - expect(error.message).toContain('Object is not serializable'); + else expect(error.message).toContain('Object is not serializable'); }); }); - describe('JSHandle.getProperties', function() { - it('should work', async() => { - const {page} = getTestState(); + describe('JSHandle.getProperties', function () { + it('should work', async () => { + const { page } = getTestState(); const aHandle = await page.evaluateHandle(() => ({ - foo: 'bar' + foo: 'bar', })); const properties = await aHandle.getProperties(); const foo = properties.get('foo'); expect(foo).toBeTruthy(); expect(await foo.jsonValue()).toBe('bar'); }); - it('should return even non-own properties', async() => { - const {page} = getTestState(); + it('should return even non-own properties', async () => { + const { page } = getTestState(); const aHandle = await page.evaluateHandle(() => { class A { @@ -156,77 +164,120 @@ describe('JSHandle', function() { }); }); - describe('JSHandle.asElement', function() { - it('should work', async() => { - const {page} = getTestState(); + describe('JSHandle.asElement', function () { + it('should work', async () => { + const { page } = getTestState(); const aHandle = await page.evaluateHandle(() => document.body); const element = aHandle.asElement(); expect(element).toBeTruthy(); }); - it('should return null for non-elements', async() => { - const {page} = getTestState(); + it('should return null for non-elements', async () => { + const { page } = getTestState(); const aHandle = await page.evaluateHandle(() => 2); const element = aHandle.asElement(); expect(element).toBeFalsy(); }); - itFailsFirefox('should return ElementHandle for TextNodes', async() => { - const {page} = getTestState(); + itFailsFirefox('should return ElementHandle for TextNodes', async () => { + const { page } = getTestState(); await page.setContent('
ee!
'); - const aHandle = await page.evaluateHandle(() => document.querySelector('div').firstChild); + 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)); + expect( + await page.evaluate( + (e) => e.nodeType === HTMLElement.TEXT_NODE, + element + ) + ); }); - itFailsFirefox('should work with nullified Node', async() => { - const {page} = getTestState(); + itFailsFirefox('should work with nullified Node', async () => { + const { page } = getTestState(); await page.setContent('
test
'); await page.evaluate(() => delete Node); - const handle = await page.evaluateHandle(() => document.querySelector('section')); + 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() => { - const {page} = getTestState(); + describe('JSHandle.toString', function () { + it('should work for primitives', async () => { + const { page } = getTestState(); 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() => { - const {page} = getTestState(); + it('should work for complicated objects', async () => { + const { page } = getTestState(); const aHandle = await page.evaluateHandle(() => window); expect(aHandle.toString()).toBe('JSHandle@object'); }); - it('should work with different subtypes', async() => { - const {page} = getTestState(); + it('should work with different subtypes', async () => { + const { page } = getTestState(); - expect((await page.evaluateHandle('(function(){})')).toString()).toBe('JSHandle@function'); + 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'); - expect((await page.evaluateHandle('new Int32Array()')).toString()).toBe('JSHandle@typedarray'); - expect((await page.evaluateHandle('new Proxy({}, {})')).toString()).toBe('JSHandle@proxy'); + 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' + ); + expect((await page.evaluateHandle('new Int32Array()')).toString()).toBe( + '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 67df6385723ad..a1ca84ab19712 100644 --- a/test/keyboard.spec.js +++ b/test/keyboard.spec.js @@ -17,14 +17,18 @@ const utils = require('./utils'); const os = require('os'); const expect = require('expect'); -const {getTestState,setupTestBrowserHooks,setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); -describe('Keyboard', function() { +describe('Keyboard', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - it('should type into a textarea', async() => { - const {page} = getTestState(); + it('should type into a textarea', async () => { + const { page } = getTestState(); await page.evaluate(() => { const textarea = document.createElement('textarea'); @@ -33,159 +37,242 @@ describe('Keyboard', function() { }); 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); + expect( + await page.evaluate(() => document.querySelector('textarea').value) + ).toBe(text); }); - itFailsFirefox('should press the metaKey', async() => { - const {page, isFirefox} = getTestState(); + itFailsFirefox('should press the metaKey', async () => { + const { page, isFirefox } = getTestState(); await page.evaluate(() => { - window.keyPromise = new Promise(resolve => document.addEventListener('keydown', event => resolve(event.key))); + window.keyPromise = new Promise((resolve) => + document.addEventListener('keydown', (event) => resolve(event.key)) + ); }); await page.keyboard.press('Meta'); - expect(await page.evaluate('keyPromise')).toBe(isFirefox && os.platform() !== 'darwin' ? 'OS' : 'Meta'); + expect(await page.evaluate('keyPromise')).toBe( + isFirefox && os.platform() !== 'darwin' ? 'OS' : 'Meta' + ); }); - it('should move with the arrow keys', async() => { - const {page, server} = getTestState(); + it('should move with the arrow keys', async () => { + const { page, server } = getTestState(); 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'); + 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!'); + 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!'); + expect( + await page.evaluate(() => document.querySelector('textarea').value) + ).toBe('Hello World!'); }); - it('should send a character with ElementHandle.press', async() => { - const {page, server} = getTestState(); + it('should send a character with ElementHandle.press', async () => { + const { page, server } = getTestState(); 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'); + expect( + await page.evaluate(() => document.querySelector('textarea').value) + ).toBe('a'); - await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true)); + await page.evaluate(() => + window.addEventListener('keydown', (e) => e.preventDefault(), true) + ); await textarea.press('b'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('a'); + expect( + await page.evaluate(() => document.querySelector('textarea').value) + ).toBe('a'); }); - itFailsFirefox('ElementHandle.press should support |text| option', async() => { - const {page, server} = getTestState(); - - await page.goto(server.PREFIX + '/input/textarea.html'); - const textarea = await page.$('textarea'); - await textarea.press('a', {text: 'ё'}); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('ё'); - }); - itFailsFirefox('should send a character with sendCharacter', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'ElementHandle.press should support |text| option', + async () => { + const { page, server } = getTestState(); + + await page.goto(server.PREFIX + '/input/textarea.html'); + const textarea = await page.$('textarea'); + await textarea.press('a', { text: 'ё' }); + expect( + await page.evaluate(() => document.querySelector('textarea').value) + ).toBe('ё'); + } + ); + itFailsFirefox('should send a character with sendCharacter', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/textarea.html'); await page.focus('textarea'); await page.keyboard.sendCharacter('嗨'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨'); - await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true)); + expect( + await page.evaluate(() => document.querySelector('textarea').value) + ).toBe('嗨'); + await page.evaluate(() => + window.addEventListener('keydown', (e) => e.preventDefault(), true) + ); await page.keyboard.sendCharacter('a'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨a'); + expect( + await page.evaluate(() => document.querySelector('textarea').value) + ).toBe('嗨a'); }); - itFailsFirefox('should report shiftKey', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should report shiftKey', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/keyboard.html'); const keyboard = page.keyboard; - const codeForKey = {'Shift': 16, 'Alt': 18, 'Control': 17}; + 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 + ']'); + 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 + ']'); + 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 + ']'); + expect(await page.evaluate(() => getResult())).toBe( + 'Keydown: ! Digit1 49 [' + modifierKey + ']' + ); await keyboard.up('!'); - expect(await page.evaluate(() => getResult())).toBe('Keyup: ! Digit1 49 [' + modifierKey + ']'); + 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] + ' []'); + expect(await page.evaluate(() => getResult())).toBe( + 'Keyup: ' + + modifierKey + + ' ' + + modifierKey + + 'Left ' + + codeForKey[modifierKey] + + ' []' + ); } }); - it('should report multiple modifiers', async() => { - const {page, server} = getTestState(); + it('should report multiple modifiers', async () => { + const { page, server } = getTestState(); 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]'); + 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]'); + 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]'); + 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]'); + 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]'); + 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 []'); + expect(await page.evaluate(() => getResult())).toBe( + 'Keyup: Alt AltLeft 18 []' + ); }); - it('should send proper codes while typing', async() => { - const {page, server} = getTestState(); + it('should send proper codes while typing', async () => { + const { page, server } = getTestState(); 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')); + [ + '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')); + [ + 'Keydown: ^ Digit6 54 []', + 'Keypress: ^ Digit6 94 94 []', + 'Keyup: ^ Digit6 54 []', + ].join('\n') + ); }); - it('should send proper codes while typing with shift', async() => { - const {page, server} = getTestState(); + it('should send proper codes while typing with shift', async () => { + const { page, server } = getTestState(); 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')); + [ + '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() => { - const {page, server} = getTestState(); + it('should not type canceled events', async () => { + const { page, server } = getTestState(); 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); + 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!'); }); - itFailsFirefox('should specify repeat property', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should specify repeat property', async () => { + const { page, server } = getTestState(); 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.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'); @@ -200,8 +287,8 @@ describe('Keyboard', function() { await page.keyboard.down('a'); expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false); }); - itFailsFirefox('should type all kinds of characters', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should type all kinds of characters', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/textarea.html'); await page.focus('textarea'); @@ -209,12 +296,16 @@ describe('Keyboard', function() { await page.keyboard.type(text); expect(await page.evaluate('result')).toBe(text); }); - itFailsFirefox('should specify location', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should specify location', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/textarea.html'); await page.evaluate(() => { - window.addEventListener('keydown', event => window.keyLocation = event.location, true); + window.addEventListener( + 'keydown', + (event) => (window.keyLocation = event.location), + true + ); }); const textarea = await page.$('textarea'); @@ -230,60 +321,63 @@ describe('Keyboard', function() { await textarea.press('NumpadSubtract'); expect(await page.evaluate('keyLocation')).toBe(3); }); - it('should throw on unknown keys', async() => { - const {page} = getTestState(); + it('should throw on unknown keys', async () => { + const { page } = getTestState(); - let error = await page.keyboard.press('NotARealKey').catch(error_ => error_); + let error = await page.keyboard + .press('NotARealKey') + .catch((error_) => error_); expect(error.message).toBe('Unknown key: "NotARealKey"'); - error = await page.keyboard.press('ё').catch(error_ => error_); + error = await page.keyboard.press('ё').catch((error_) => error_); expect(error && error.message).toBe('Unknown key: "ё"'); - error = await page.keyboard.press('😊').catch(error_ => error_); + error = await page.keyboard.press('😊').catch((error_) => error_); expect(error && error.message).toBe('Unknown key: "😊"'); }); - itFailsFirefox('should type emoji', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should type emoji', async () => { + const { page, server } = getTestState(); 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 🇯🇵'); + expect(await page.$eval('textarea', (textarea) => textarea.value)).toBe( + '👹 Tokyo street Japan 🇯🇵' + ); }); - itFailsFirefox('should type emoji into an iframe', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should type emoji into an iframe', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'emoji-test', server.PREFIX + '/input/textarea.html'); + 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 🇯🇵'); + expect(await frame.$eval('textarea', (textarea) => textarea.value)).toBe( + '👹 Tokyo street Japan 🇯🇵' + ); }); - itFailsFirefox('should press the meta key', async() => { - const {page, isFirefox} = getTestState(); + itFailsFirefox('should press the meta key', async () => { + const { page, isFirefox } = getTestState(); await page.evaluate(() => { window.result = null; - document.addEventListener('keydown', event => { + 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 (isFirefox && os.platform() !== 'darwin') - expect(key).toBe('OS'); - else - expect(key).toBe('Meta'); - - if (isFirefox) - expect(code).toBe('OSLeft'); - else - expect(code).toBe('MetaLeft'); + if (isFirefox && os.platform() !== 'darwin') expect(key).toBe('OS'); + else expect(key).toBe('Meta'); - if (isFirefox && os.platform() !== 'darwin') - expect(metaKey).toBe(false); - else - expect(metaKey).toBe(true); + if (isFirefox) expect(code).toBe('OSLeft'); + else expect(code).toBe('MetaLeft'); + if (isFirefox && os.platform() !== 'darwin') expect(metaKey).toBe(false); + else expect(metaKey).toBe(true); }); }); diff --git a/test/launcher.spec.js b/test/launcher.spec.js index 71852aa095a00..afc645a238db2 100644 --- a/test/launcher.spec.js +++ b/test/launcher.spec.js @@ -16,7 +16,7 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); -const {helper} = require('../lib/helper'); +const { helper } = require('../lib/helper'); const rmAsync = helper.promisify(require('rimraf')); const mkdtempAsync = helper.promisify(fs.mkdtemp); const readFileAsync = helper.promisify(fs.readFile); @@ -24,25 +24,28 @@ const statAsync = helper.promisify(fs.stat); const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); const utils = require('./utils'); const expect = require('expect'); -const {getTestState} = require('./mocha-utils'); +const { getTestState } = require('./mocha-utils'); -describe('Launcher specs', function() { - describe('Puppeteer', function() { - describe('BrowserFetcher', function() { - it('should download and extract chrome linux binary', async() => { - const {server, puppeteer} = getTestState(); +describe('Launcher specs', function () { + describe('Puppeteer', function () { + describe('BrowserFetcher', function () { + it('should download and extract chrome linux binary', async () => { + const { server, puppeteer } = getTestState(); const downloadsFolder = await mkdtempAsync(TMP_FOLDER); const browserFetcher = puppeteer.createBrowserFetcher({ platform: 'linux', path: downloadsFolder, - host: server.PREFIX + host: server.PREFIX, }); const expectedRevision = '123456'; let revisionInfo = browserFetcher.revisionInfo(expectedRevision); - server.setRoute(revisionInfo.url.substring(server.PREFIX.length), (req, res) => { - server.serveFile(req, res, '/chromium-linux.zip'); - }); + server.setRoute( + revisionInfo.url.substring(server.PREFIX.length), + (req, res) => { + server.serveFile(req, res, '/chromium-linux.zip'); + } + ); expect(revisionInfo.local).toBe(false); expect(browserFetcher.platform()).toBe('linux'); @@ -53,16 +56,22 @@ describe('Launcher specs', function() { revisionInfo = await browserFetcher.download(expectedRevision); expect(revisionInfo.local).toBe(true); - expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe('LINUX BINARY\n'); + expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe( + 'LINUX BINARY\n' + ); const expectedPermissions = os.platform() === 'win32' ? 0o666 : 0o755; - expect((await statAsync(revisionInfo.executablePath)).mode & 0o777).toBe(expectedPermissions); - expect(await browserFetcher.localRevisions()).toEqual([expectedRevision]); + expect( + (await statAsync(revisionInfo.executablePath)).mode & 0o777 + ).toBe(expectedPermissions); + expect(await browserFetcher.localRevisions()).toEqual([ + expectedRevision, + ]); await browserFetcher.remove(expectedRevision); expect(await browserFetcher.localRevisions()).toEqual([]); await rmAsync(downloadsFolder); }); - it('should download and extract firefox linux binary', async() => { - const {server , puppeteer} = getTestState(); + it('should download and extract firefox linux binary', async () => { + const { server, puppeteer } = getTestState(); const downloadsFolder = await mkdtempAsync(TMP_FOLDER); const browserFetcher = puppeteer.createBrowserFetcher({ @@ -73,9 +82,16 @@ describe('Launcher specs', function() { }); const expectedVersion = '75'; let revisionInfo = browserFetcher.revisionInfo(expectedVersion); - server.setRoute(revisionInfo.url.substring(server.PREFIX.length), (req, res) => { - server.serveFile(req, res, `/firefox-${expectedVersion}.0a1.en-US.linux-x86_64.tar.bz2`); - }); + server.setRoute( + revisionInfo.url.substring(server.PREFIX.length), + (req, res) => { + server.serveFile( + req, + res, + `/firefox-${expectedVersion}.0a1.en-US.linux-x86_64.tar.bz2` + ); + } + ); expect(revisionInfo.local).toBe(false); expect(browserFetcher.platform()).toBe('linux'); @@ -85,55 +101,73 @@ describe('Launcher specs', function() { revisionInfo = await browserFetcher.download(expectedVersion); expect(revisionInfo.local).toBe(true); - expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe('FIREFOX LINUX BINARY\n'); + expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe( + 'FIREFOX LINUX BINARY\n' + ); const expectedPermissions = os.platform() === 'win32' ? 0o666 : 0o755; - expect((await statAsync(revisionInfo.executablePath)).mode & 0o777).toBe(expectedPermissions); - expect(await browserFetcher.localRevisions()).toEqual([expectedVersion]); + expect( + (await statAsync(revisionInfo.executablePath)).mode & 0o777 + ).toBe(expectedPermissions); + expect(await browserFetcher.localRevisions()).toEqual([ + expectedVersion, + ]); await browserFetcher.remove(expectedVersion); expect(await browserFetcher.localRevisions()).toEqual([]); await rmAsync(downloadsFolder); }); }); - describe('Browser.disconnect', function() { - it('should reject navigation when browser closes', async() => { - const {server, puppeteer, defaultBrowserOptions} = getTestState(); + describe('Browser.disconnect', function () { + it('should reject navigation when browser closes', async () => { + const { server, puppeteer, defaultBrowserOptions } = getTestState(); server.setRoute('/one-style.css', () => {}); const browser = await puppeteer.launch(defaultBrowserOptions); - const remote = await puppeteer.connect({browserWSEndpoint: browser.wsEndpoint()}); + const remote = await puppeteer.connect({ + browserWSEndpoint: browser.wsEndpoint(), + }); const page = await remote.newPage(); - const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(error_ => error_); + const navigationPromise = page + .goto(server.PREFIX + '/one-style.html', { timeout: 60000 }) + .catch((error_) => error_); await server.waitForRequest('/one-style.css'); remote.disconnect(); const error = await navigationPromise; - expect(error.message).toBe('Navigation failed because browser has disconnected!'); + expect(error.message).toBe( + 'Navigation failed because browser has disconnected!' + ); await browser.close(); }); - it('should reject waitForSelector when browser closes', async() => { - const {server, puppeteer, defaultBrowserOptions} = getTestState(); + it('should reject waitForSelector when browser closes', async () => { + const { server, puppeteer, defaultBrowserOptions } = getTestState(); server.setRoute('/empty.html', () => {}); const browser = await puppeteer.launch(defaultBrowserOptions); - const remote = await puppeteer.connect({browserWSEndpoint: browser.wsEndpoint()}); + const remote = await puppeteer.connect({ + browserWSEndpoint: browser.wsEndpoint(), + }); const page = await remote.newPage(); - const watchdog = page.waitForSelector('div', {timeout: 60000}).catch(error_ => error_); + const watchdog = page + .waitForSelector('div', { timeout: 60000 }) + .catch((error_) => error_); remote.disconnect(); const error = await watchdog; expect(error.message).toContain('Protocol error'); await browser.close(); }); }); - describe('Browser.close', function() { - it('should terminate network waiters', async() => { - const {server, puppeteer, defaultBrowserOptions} = getTestState(); + describe('Browser.close', function () { + it('should terminate network waiters', async () => { + const { server, puppeteer, defaultBrowserOptions } = getTestState(); const browser = await puppeteer.launch(defaultBrowserOptions); - const remote = await puppeteer.connect({browserWSEndpoint: browser.wsEndpoint()}); + const remote = await puppeteer.connect({ + browserWSEndpoint: browser.wsEndpoint(), + }); const newPage = await remote.newPage(); const results = await Promise.all([ - newPage.waitForRequest(server.EMPTY_PAGE).catch(error => error), - newPage.waitForResponse(server.EMPTY_PAGE).catch(error => error), - browser.close() + newPage.waitForRequest(server.EMPTY_PAGE).catch((error) => error), + newPage.waitForResponse(server.EMPTY_PAGE).catch((error) => error), + browser.close(), ]); for (let i = 0; i < 2; i++) { const message = results[i].message; @@ -143,30 +177,34 @@ describe('Launcher specs', function() { await browser.close(); }); }); - describe('Puppeteer.launch', function() { - it('should reject all promises when browser is closed', async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); + describe('Puppeteer.launch', function () { + it('should reject all promises when browser is closed', async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); const browser = await puppeteer.launch(defaultBrowserOptions); const page = await browser.newPage(); let error = null; - const neverResolves = page.evaluate(() => new Promise(r => {})).catch(error_ => error = error_); + const neverResolves = page + .evaluate(() => new Promise((r) => {})) + .catch((error_) => (error = error_)); await browser.close(); await neverResolves; expect(error.message).toContain('Protocol error'); }); - it('should reject if executable path is invalid', async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); + it('should reject if executable path is invalid', async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); let waitError = null; - const options = Object.assign({}, defaultBrowserOptions, {executablePath: 'random-invalid-path'}); - await puppeteer.launch(options).catch(error => waitError = error); + const options = Object.assign({}, defaultBrowserOptions, { + executablePath: 'random-invalid-path', + }); + await puppeteer.launch(options).catch((error) => (waitError = error)); expect(waitError.message).toContain('Failed to launch'); }); - it('userDataDir option', async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); + it('userDataDir option', async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); const userDataDir = await mkdtempAsync(TMP_FOLDER); - const options = Object.assign({userDataDir}, defaultBrowserOptions); + const options = Object.assign({ userDataDir }, defaultBrowserOptions); const browser = await puppeteer.launch(options); // Open a page to make sure its functional. await browser.newPage(); @@ -174,17 +212,17 @@ describe('Launcher specs', function() { await browser.close(); expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 - await rmAsync(userDataDir).catch(error => {}); + await rmAsync(userDataDir).catch((error) => {}); }); - it('userDataDir argument', async() => { - const {isChrome, puppeteer, defaultBrowserOptions} = getTestState(); + it('userDataDir argument', async () => { + const { isChrome, puppeteer, defaultBrowserOptions } = getTestState(); const userDataDir = await mkdtempAsync(TMP_FOLDER); const options = Object.assign({}, defaultBrowserOptions); if (isChrome) { options.args = [ ...(defaultBrowserOptions.args || []), - `--user-data-dir=${userDataDir}` + `--user-data-dir=${userDataDir}`, ]; } else { options.args = [ @@ -198,17 +236,17 @@ describe('Launcher specs', function() { await browser.close(); expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 - await rmAsync(userDataDir).catch(error => {}); + await rmAsync(userDataDir).catch((error) => {}); }); - it('userDataDir option should restore state', async() => { - const {server, puppeteer, defaultBrowserOptions} = getTestState(); + it('userDataDir option should restore state', async () => { + const { server, puppeteer, defaultBrowserOptions } = getTestState(); const userDataDir = await mkdtempAsync(TMP_FOLDER); - const options = Object.assign({userDataDir}, defaultBrowserOptions); + const options = Object.assign({ userDataDir }, defaultBrowserOptions); const browser = await puppeteer.launch(options); const page = await browser.newPage(); await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => localStorage.hey = 'hello'); + await page.evaluate(() => (localStorage.hey = 'hello')); await browser.close(); const browser2 = await puppeteer.launch(options); @@ -217,59 +255,79 @@ describe('Launcher specs', function() { expect(await page2.evaluate(() => localStorage.hey)).toBe('hello'); await browser2.close(); // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 - await rmAsync(userDataDir).catch(error => {}); + await rmAsync(userDataDir).catch((error) => {}); }); // This mysteriously fails on Windows on AppVeyor. See https://github.com/puppeteer/puppeteer/issues/4111 - xit('userDataDir option should restore cookies', async() => { - const {server , puppeteer} = getTestState(); + xit('userDataDir option should restore cookies', async () => { + const { server, puppeteer } = getTestState(); const userDataDir = await mkdtempAsync(TMP_FOLDER); - const options = Object.assign({userDataDir}, defaultBrowserOptions); + const options = Object.assign({ userDataDir }, defaultBrowserOptions); const browser = await puppeteer.launch(options); const page = await browser.newPage(); await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => document.cookie = 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'); + await page.evaluate( + () => + (document.cookie = + 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT') + ); await browser.close(); const browser2 = await puppeteer.launch(options); const page2 = await browser2.newPage(); await page2.goto(server.EMPTY_PAGE); - expect(await page2.evaluate(() => document.cookie)).toBe('doSomethingOnlyOnce=true'); + expect(await page2.evaluate(() => document.cookie)).toBe( + 'doSomethingOnlyOnce=true' + ); await browser2.close(); // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 - await rmAsync(userDataDir).catch(error => {}); + await rmAsync(userDataDir).catch((error) => {}); }); - it('should return the default arguments', async() => { - const {isChrome, isFirefox, puppeteer} = getTestState(); + it('should return the default arguments', async () => { + const { isChrome, isFirefox, puppeteer } = getTestState(); if (isChrome) { expect(puppeteer.defaultArgs()).toContain('--no-first-run'); expect(puppeteer.defaultArgs()).toContain('--headless'); - expect(puppeteer.defaultArgs({headless: false})).not.toContain('--headless'); - expect(puppeteer.defaultArgs({userDataDir: 'foo'})).toContain('--user-data-dir=foo'); + expect(puppeteer.defaultArgs({ headless: false })).not.toContain( + '--headless' + ); + expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain( + '--user-data-dir=foo' + ); } else if (isFirefox) { expect(puppeteer.defaultArgs()).toContain('--headless'); expect(puppeteer.defaultArgs()).toContain('--no-remote'); expect(puppeteer.defaultArgs()).toContain('--foreground'); - expect(puppeteer.defaultArgs({headless: false})).not.toContain('--headless'); - expect(puppeteer.defaultArgs({userDataDir: 'foo'})).toContain('--profile'); - expect(puppeteer.defaultArgs({userDataDir: 'foo'})).toContain('foo'); + expect(puppeteer.defaultArgs({ headless: false })).not.toContain( + '--headless' + ); + expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain( + '--profile' + ); + expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain( + 'foo' + ); } else { expect(puppeteer.defaultArgs()).toContain('-headless'); - expect(puppeteer.defaultArgs({headless: false})).not.toContain('-headless'); - expect(puppeteer.defaultArgs({userDataDir: 'foo'})).toContain('-profile'); - expect(puppeteer.defaultArgs({userDataDir: 'foo'})).toContain('foo'); + expect(puppeteer.defaultArgs({ headless: false })).not.toContain( + '-headless' + ); + expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain( + '-profile' + ); + expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain( + 'foo' + ); } }); - it('should report the correct product', async() => { - const {isChrome, isFirefox, puppeteer} = getTestState(); - if (isChrome) - expect(puppeteer.product).toBe('chrome'); - else if (isFirefox) - expect(puppeteer.product).toBe('firefox'); + it('should report the correct product', async () => { + const { isChrome, isFirefox, puppeteer } = getTestState(); + if (isChrome) expect(puppeteer.product).toBe('chrome'); + else if (isFirefox) expect(puppeteer.product).toBe('firefox'); }); - itFailsFirefox('should work with no default arguments', async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); + itFailsFirefox('should work with no default arguments', async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); const options = Object.assign({}, defaultBrowserOptions); options.ignoreDefaultArgs = true; const browser = await puppeteer.launch(options); @@ -278,48 +336,55 @@ describe('Launcher specs', function() { await page.close(); await browser.close(); }); - itFailsFirefox('should filter out ignored default arguments', async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); - // Make sure we launch with `--enable-automation` by default. - const defaultArgs = puppeteer.defaultArgs(); - const browser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { - // Ignore first and third default argument. - ignoreDefaultArgs: [ defaultArgs[0], defaultArgs[2] ], - })); - const spawnargs = browser.process().spawnargs; - expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1); - expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1); - expect(spawnargs.indexOf(defaultArgs[2])).toBe(-1); - await browser.close(); - }); - it('should have default URL when launching browser', async function() { - const {defaultBrowserOptions, puppeteer} = getTestState(); + itFailsFirefox( + 'should filter out ignored default arguments', + async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); + // Make sure we launch with `--enable-automation` by default. + const defaultArgs = puppeteer.defaultArgs(); + const browser = await puppeteer.launch( + Object.assign({}, defaultBrowserOptions, { + // Ignore first and third default argument. + ignoreDefaultArgs: [defaultArgs[0], defaultArgs[2]], + }) + ); + const spawnargs = browser.process().spawnargs; + expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1); + expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1); + expect(spawnargs.indexOf(defaultArgs[2])).toBe(-1); + await browser.close(); + } + ); + it('should have default URL when launching browser', async function () { + const { defaultBrowserOptions, puppeteer } = getTestState(); const browser = await puppeteer.launch(defaultBrowserOptions); - const pages = (await browser.pages()).map(page => page.url()); + const pages = (await browser.pages()).map((page) => page.url()); expect(pages).toEqual(['about:blank']); await browser.close(); }); - itFailsFirefox('should have custom URL when launching browser', async() => { - const {server, puppeteer, defaultBrowserOptions} = getTestState(); - - const options = Object.assign({}, defaultBrowserOptions); - options.args = [server.EMPTY_PAGE].concat(options.args || []); - const browser = await puppeteer.launch(options); - const pages = await browser.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 browser.close(); - }); - it('should set the default viewport', async() => { - const {puppeteer, defaultBrowserOptions} = getTestState(); + itFailsFirefox( + 'should have custom URL when launching browser', + async () => { + const { server, puppeteer, defaultBrowserOptions } = getTestState(); + + const options = Object.assign({}, defaultBrowserOptions); + options.args = [server.EMPTY_PAGE].concat(options.args || []); + const browser = await puppeteer.launch(options); + const pages = await browser.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 browser.close(); + } + ); + it('should set the default viewport', async () => { + const { puppeteer, defaultBrowserOptions } = getTestState(); const options = Object.assign({}, defaultBrowserOptions, { defaultViewport: { width: 456, - height: 789 - } + height: 789, + }, }); const browser = await puppeteer.launch(options); const page = await browser.newPage(); @@ -327,50 +392,53 @@ describe('Launcher specs', function() { expect(await page.evaluate('window.innerHeight')).toBe(789); await browser.close(); }); - it('should disable the default viewport', async() => { - const {puppeteer, defaultBrowserOptions} = getTestState(); + it('should disable the default viewport', async () => { + const { puppeteer, defaultBrowserOptions } = getTestState(); const options = Object.assign({}, defaultBrowserOptions, { - defaultViewport: null + defaultViewport: null, }); const browser = await puppeteer.launch(options); const page = await browser.newPage(); expect(page.viewport()).toBe(null); await browser.close(); }); - itFailsFirefox('should take fullPage screenshots when defaultViewport is null', async() => { - const {server, puppeteer, defaultBrowserOptions} = getTestState(); - - const options = Object.assign({}, defaultBrowserOptions, { - defaultViewport: null - }); - const browser = await puppeteer.launch(options); - const page = await browser.newPage(); - await page.goto(server.PREFIX + '/grid.html'); - const screenshot = await page.screenshot({ - fullPage: true - }); - expect(screenshot).toBeInstanceOf(Buffer); - await browser.close(); - }); + itFailsFirefox( + 'should take fullPage screenshots when defaultViewport is null', + async () => { + const { server, puppeteer, defaultBrowserOptions } = getTestState(); + + const options = Object.assign({}, defaultBrowserOptions, { + defaultViewport: null, + }); + const browser = await puppeteer.launch(options); + const page = await browser.newPage(); + await page.goto(server.PREFIX + '/grid.html'); + const screenshot = await page.screenshot({ + fullPage: true, + }); + expect(screenshot).toBeInstanceOf(Buffer); + await browser.close(); + } + ); }); - describe('Puppeteer.launch', function() { + describe('Puppeteer.launch', function () { let productName; - before(async() => { - const {puppeteer} = getTestState(); + before(async () => { + const { puppeteer } = getTestState(); productName = puppeteer._productName; }); - after(async() => { - const {puppeteer} = getTestState(); + after(async () => { + const { puppeteer } = getTestState(); puppeteer._lazyLauncher = undefined; puppeteer._productName = productName; }); - it('should be able to launch Chrome', async() => { - const {puppeteer} = getTestState(); - const browser = await puppeteer.launch({product: 'chrome'}); + it('should be able to launch Chrome', async () => { + const { puppeteer } = getTestState(); + const browser = await puppeteer.launch({ product: 'chrome' }); const userAgent = await browser.userAgent(); await browser.close(); expect(userAgent).toContain('Chrome'); @@ -381,55 +449,69 @@ describe('Launcher specs', function() { * this so we can get Windows CI stable and then dig into this * properly with help from the Mozilla folks. */ - itFailsWindowsUntilDate(new Date('2020-06-01'), 'should be able to launch Firefox', async() => { - const {puppeteer} = getTestState(); - const browser = await puppeteer.launch({product: 'firefox'}); - const userAgent = await browser.userAgent(); - await browser.close(); - expect(userAgent).toContain('Firefox'); - }); + itFailsWindowsUntilDate( + new Date('2020-06-01'), + 'should be able to launch Firefox', + async () => { + const { puppeteer } = getTestState(); + const browser = await puppeteer.launch({ product: 'firefox' }); + const userAgent = await browser.userAgent(); + await browser.close(); + expect(userAgent).toContain('Firefox'); + } + ); }); - describe('Puppeteer.connect', function() { - it('should be able to connect multiple times to the same browser', async() => { - const {puppeteer, defaultBrowserOptions} = getTestState(); + describe('Puppeteer.connect', function () { + it('should be able to connect multiple times to the same browser', async () => { + const { puppeteer, defaultBrowserOptions } = getTestState(); const originalBrowser = await puppeteer.launch(defaultBrowserOptions); const otherBrowser = await puppeteer.connect({ - browserWSEndpoint: originalBrowser.wsEndpoint() + browserWSEndpoint: originalBrowser.wsEndpoint(), }); const page = await otherBrowser.newPage(); expect(await page.evaluate(() => 7 * 8)).toBe(56); otherBrowser.disconnect(); const secondPage = await originalBrowser.newPage(); - expect(await secondPage.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work'); + expect(await secondPage.evaluate(() => 7 * 6)).toBe( + 42, + 'original browser should still work' + ); await originalBrowser.close(); }); - it('should be able to close remote browser', async() => { - const {defaultBrowserOptions, puppeteer} = getTestState(); + it('should be able to close remote browser', async () => { + const { defaultBrowserOptions, puppeteer } = getTestState(); const originalBrowser = await puppeteer.launch(defaultBrowserOptions); const remoteBrowser = await puppeteer.connect({ - browserWSEndpoint: originalBrowser.wsEndpoint() + browserWSEndpoint: originalBrowser.wsEndpoint(), }); await Promise.all([ utils.waitEvent(originalBrowser, 'disconnected'), remoteBrowser.close(), ]); }); - itFailsFirefox('should support ignoreHTTPSErrors option', async() => { - const {httpsServer, puppeteer, defaultBrowserOptions} = getTestState(); + itFailsFirefox('should support ignoreHTTPSErrors option', async () => { + const { + httpsServer, + puppeteer, + defaultBrowserOptions, + } = getTestState(); const originalBrowser = await puppeteer.launch(defaultBrowserOptions); const browserWSEndpoint = originalBrowser.wsEndpoint(); - const browser = await puppeteer.connect({browserWSEndpoint, ignoreHTTPSErrors: true}); + const browser = await puppeteer.connect({ + browserWSEndpoint, + ignoreHTTPSErrors: true, + }); const page = await browser.newPage(); let error = null; const [serverRequest, response] = await Promise.all([ httpsServer.waitForRequest('/empty.html'), - page.goto(httpsServer.EMPTY_PAGE).catch(error_ => error = error_) + page.goto(httpsServer.EMPTY_PAGE).catch((error_) => (error = error_)), ]); expect(error).toBe(null); expect(response.ok()).toBe(true); @@ -439,47 +521,59 @@ describe('Launcher specs', function() { await page.close(); await browser.close(); }); - itFailsFirefox('should be able to reconnect to a disconnected browser', async() => { - const {server , puppeteer, defaultBrowserOptions} = getTestState(); - - const originalBrowser = await puppeteer.launch(defaultBrowserOptions); - const browserWSEndpoint = originalBrowser.wsEndpoint(); - const page = await originalBrowser.newPage(); - await page.goto(server.PREFIX + '/frames/nested-frames.html'); - originalBrowser.disconnect(); - - const browser = await puppeteer.connect({browserWSEndpoint}); - const pages = await browser.pages(); - const restoredPage = pages.find(page => page.url() === server.PREFIX + '/frames/nested-frames.html'); - expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([ - 'http://localhost:/frames/nested-frames.html', - ' http://localhost:/frames/two-frames.html (2frames)', - ' http://localhost:/frames/frame.html (uno)', - ' http://localhost:/frames/frame.html (dos)', - ' http://localhost:/frames/frame.html (aframe)', - ]); - expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56); - await browser.close(); - }); + itFailsFirefox( + 'should be able to reconnect to a disconnected browser', + async () => { + const { server, puppeteer, defaultBrowserOptions } = getTestState(); + + const originalBrowser = await puppeteer.launch(defaultBrowserOptions); + const browserWSEndpoint = originalBrowser.wsEndpoint(); + const page = await originalBrowser.newPage(); + await page.goto(server.PREFIX + '/frames/nested-frames.html'); + originalBrowser.disconnect(); + + const browser = await puppeteer.connect({ browserWSEndpoint }); + const pages = await browser.pages(); + const restoredPage = pages.find( + (page) => + page.url() === server.PREFIX + '/frames/nested-frames.html' + ); + expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([ + 'http://localhost:/frames/nested-frames.html', + ' http://localhost:/frames/two-frames.html (2frames)', + ' http://localhost:/frames/frame.html (uno)', + ' http://localhost:/frames/frame.html (dos)', + ' http://localhost:/frames/frame.html (aframe)', + ]); + expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56); + await browser.close(); + } + ); // @see https://github.com/puppeteer/puppeteer/issues/4197#issuecomment-481793410 - itFailsFirefox('should be able to connect to the same page simultaneously', async() => { - const {puppeteer} = getTestState(); - - const browserOne = await puppeteer.launch(); - const browserTwo = await puppeteer.connect({browserWSEndpoint: browserOne.wsEndpoint()}); - const [page1, page2] = await Promise.all([ - new Promise(x => browserOne.once('targetcreated', target => x(target.page()))), - browserTwo.newPage(), - ]); - expect(await page1.evaluate(() => 7 * 8)).toBe(56); - expect(await page2.evaluate(() => 7 * 6)).toBe(42); - await browserOne.close(); - }); - + itFailsFirefox( + 'should be able to connect to the same page simultaneously', + async () => { + const { puppeteer } = getTestState(); + + const browserOne = await puppeteer.launch(); + const browserTwo = await puppeteer.connect({ + browserWSEndpoint: browserOne.wsEndpoint(), + }); + const [page1, page2] = await Promise.all([ + new Promise((x) => + browserOne.once('targetcreated', (target) => x(target.page())) + ), + browserTwo.newPage(), + ]); + expect(await page1.evaluate(() => 7 * 8)).toBe(56); + expect(await page2.evaluate(() => 7 * 6)).toBe(42); + await browserOne.close(); + } + ); }); - describe('Puppeteer.executablePath', function() { - itFailsFirefox('should work', async() => { - const {puppeteer} = getTestState(); + describe('Puppeteer.executablePath', function () { + itFailsFirefox('should work', async () => { + const { puppeteer } = getTestState(); const executablePath = puppeteer.executablePath(); expect(fs.existsSync(executablePath)).toBe(true); @@ -488,22 +582,25 @@ describe('Launcher specs', function() { }); }); - describe('Top-level requires', function() { - it('should require top-level Errors', async() => { - const {puppeteer, puppeteerPath} = getTestState(); + describe('Top-level requires', function () { + it('should require top-level Errors', async () => { + const { puppeteer, puppeteerPath } = getTestState(); const Errors = require(path.join(puppeteerPath, '/Errors')); expect(Errors.TimeoutError).toBe(puppeteer.errors.TimeoutError); }); - it('should require top-level DeviceDescriptors', async() => { - const {puppeteer, puppeteerPath} = getTestState(); - const {devicesMap} = require(path.join(puppeteerPath, '/DeviceDescriptors')); + it('should require top-level DeviceDescriptors', async () => { + const { puppeteer, puppeteerPath } = getTestState(); + const { devicesMap } = require(path.join( + puppeteerPath, + '/DeviceDescriptors' + )); expect(devicesMap['iPhone 6']).toBe(puppeteer.devices['iPhone 6']); }); }); - describe('Browser target events', function() { - itFailsFirefox('should work', async() => { - const {server , puppeteer, defaultBrowserOptions} = getTestState(); + describe('Browser target events', function () { + itFailsFirefox('should work', async () => { + const { server, puppeteer, defaultBrowserOptions } = getTestState(); const browser = await puppeteer.launch(defaultBrowserOptions); const events = []; @@ -518,13 +615,13 @@ describe('Launcher specs', function() { }); }); - describe('Browser.Events.disconnected', function() { - it('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async() => { - const {puppeteer, defaultBrowserOptions} = getTestState(); + describe('Browser.Events.disconnected', function () { + it('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async () => { + const { puppeteer, defaultBrowserOptions } = getTestState(); const originalBrowser = await puppeteer.launch(defaultBrowserOptions); const browserWSEndpoint = originalBrowser.wsEndpoint(); - const remoteBrowser1 = await puppeteer.connect({browserWSEndpoint}); - const remoteBrowser2 = await puppeteer.connect({browserWSEndpoint}); + const remoteBrowser1 = await puppeteer.connect({ browserWSEndpoint }); + const remoteBrowser2 = await puppeteer.connect({ browserWSEndpoint }); let disconnectedOriginal = 0; let disconnectedRemote1 = 0; diff --git a/test/mocha-utils.js b/test/mocha-utils.js index 496b7e21b4b97..4db2b7dc68ce9 100644 --- a/test/mocha-utils.js +++ b/test/mocha-utils.js @@ -14,15 +14,15 @@ * limitations under the License. */ -const {TestServer} = require('../utils/testserver/index'); +const { TestServer } = require('../utils/testserver/index'); const path = require('path'); const fs = require('fs'); const os = require('os'); const puppeteer = require('../'); const utils = require('./utils'); -const {trackCoverage} = require('./coverage-utils'); +const { trackCoverage } = require('./coverage-utils'); -const setupServer = async() => { +const setupServer = async () => { const assetsPath = path.join(__dirname, 'assets'); const cachedPath = path.join(__dirname, 'assets', 'cached'); @@ -42,14 +42,16 @@ const setupServer = async() => { httpsServer.CROSS_PROCESS_PREFIX = `https://127.0.0.1:${httpsPort}`; httpsServer.EMPTY_PAGE = `https://localhost:${httpsPort}/empty.html`; - return {server, httpsServer}; + return { server, httpsServer }; }; exports.getTestState = () => state; -const product = process.env.PRODUCT || process.env.PUPPETEER_PRODUCT || 'Chromium'; +const product = + process.env.PRODUCT || process.env.PUPPETEER_PRODUCT || 'Chromium'; -const isHeadless = (process.env.HEADLESS || 'true').trim().toLowerCase() === 'true'; +const isHeadless = + (process.env.HEADLESS || 'true').trim().toLowerCase() === 'true'; const isFirefox = product === 'firefox'; const isChrome = product === 'Chromium'; const defaultBrowserOptions = { @@ -60,25 +62,26 @@ const defaultBrowserOptions = { dumpio: !!process.env.DUMPIO, }; -(async() => { +(async () => { if (defaultBrowserOptions.executablePath) { - console.warn(`WARN: running ${product} tests with ${defaultBrowserOptions.executablePath}`); + console.warn( + `WARN: running ${product} tests with ${defaultBrowserOptions.executablePath}` + ); } else { - if (product === 'firefox') - await puppeteer._launcher._updateRevision(); + if (product === 'firefox') await puppeteer._launcher._updateRevision(); const executablePath = puppeteer.executablePath(); if (!fs.existsSync(executablePath)) - throw new Error(`Browser is not downloaded at ${executablePath}. Run 'npm install' and try to re-run tests`); + throw new Error( + `Browser is not downloaded at ${executablePath}. Run 'npm install' and try to re-run tests` + ); } })(); - const setupGoldenAssertions = () => { const suffix = product.toLowerCase(); const GOLDEN_DIR = path.join(__dirname, 'golden-' + suffix); const OUTPUT_DIR = path.join(__dirname, 'output-' + suffix); - if (fs.existsSync(OUTPUT_DIR)) - rm(OUTPUT_DIR); + if (fs.existsSync(OUTPUT_DIR)) rm(OUTPUT_DIR); utils.extendExpectWithToBeGolden(GOLDEN_DIR, OUTPUT_DIR); }; @@ -87,10 +90,8 @@ setupGoldenAssertions(); const state = {}; global.itFailsFirefox = (...args) => { - if (isFirefox) - return xit(...args); - else - return it(...args); + if (isFirefox) return xit(...args); + else return it(...args); }; global.itFailsWindowsUntilDate = (date, ...args) => { @@ -103,53 +104,49 @@ global.itFailsWindowsUntilDate = (date, ...args) => { }; global.describeFailsFirefox = (...args) => { - if (isFirefox) - return xdescribe(...args); - else - return describe(...args); + if (isFirefox) return xdescribe(...args); + else return describe(...args); }; global.describeChromeOnly = (...args) => { - if (isChrome) - return describe(...args); + if (isChrome) return describe(...args); }; -if (process.env.COVERAGE) - trackCoverage(); +if (process.env.COVERAGE) trackCoverage(); console.log( - `Running unit tests with: + `Running unit tests with: -> product: ${product} - -> binary: ${path.relative(process.cwd(), puppeteer.executablePath())}`); + -> binary: ${path.relative(process.cwd(), puppeteer.executablePath())}` +); exports.setupTestBrowserHooks = () => { - before(async() => { + before(async () => { const browser = await puppeteer.launch(defaultBrowserOptions); state.browser = browser; }); - after(async() => { + after(async () => { await state.browser.close(); state.browser = null; }); }; exports.setupTestPageAndContextHooks = () => { - beforeEach(async() => { + beforeEach(async () => { state.context = await state.browser.createIncognitoBrowserContext(); state.page = await state.context.newPage(); }); - afterEach(async() => { + afterEach(async () => { await state.context.close(); state.context = null; state.page = null; }); }; - -before(async() => { - const {server, httpsServer} = await setupServer(); +before(async () => { + const { server, httpsServer } = await setupServer(); state.puppeteer = puppeteer; state.defaultBrowserOptions = defaultBrowserOptions; @@ -161,12 +158,12 @@ before(async() => { state.puppeteerPath = path.resolve(path.join(__dirname, '..')); }); -beforeEach(async() => { +beforeEach(async () => { state.server.reset(); state.httpsServer.reset(); }); -after(async() => { +after(async () => { await state.server.stop(); state.server = null; await state.httpsServer.stop(); diff --git a/test/mouse.spec.js b/test/mouse.spec.js index aa069800aaf57..d46fb2f8caa9b 100644 --- a/test/mouse.spec.js +++ b/test/mouse.spec.js @@ -15,7 +15,11 @@ */ const os = require('os'); const expect = require('expect'); -const {getTestState,setupTestBrowserHooks,setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); function dimensions() { const rect = document.querySelector('textarea').getBoundingClientRect(); @@ -23,26 +27,26 @@ function dimensions() { x: rect.left, y: rect.top, width: rect.width, - height: rect.height + height: rect.height, }; } -describe('Mouse', function() { +describe('Mouse', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - it('should click the document', async() => { - const {page} = getTestState(); + it('should click the document', async () => { + const { page } = getTestState(); await page.evaluate(() => { - window.clickPromise = new Promise(resolve => { - document.addEventListener('click', event => { + 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 + button: event.button, }); }); }); @@ -56,11 +60,11 @@ describe('Mouse', function() { expect(event.isTrusted).toBe(true); expect(event.button).toBe(0); }); - itFailsFirefox('should resize the textarea', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should resize the textarea', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/textarea.html'); - const {x, y, width, height} = await page.evaluate(dimensions); + const { x, y, width, height } = await page.evaluate(dimensions); const mouse = page.mouse; await mouse.move(x + width - 4, y + height - 4); await mouse.down(); @@ -70,101 +74,138 @@ describe('Mouse', function() { expect(newDimensions.width).toBe(Math.round(width + 104)); expect(newDimensions.height).toBe(Math.round(height + 104)); }); - itFailsFirefox('should select the text with mouse', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should select the text with mouse', async () => { + const { page, server } = getTestState(); 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.'; + 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.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(100,100); + await page.mouse.move(100, 100); await page.mouse.up(); - expect(await page.evaluate(() => { - const textarea = document.querySelector('textarea'); - return textarea.value.substring(textarea.selectionStart, textarea.selectionEnd); - })).toBe(text); + expect( + await page.evaluate(() => { + const textarea = document.querySelector('textarea'); + return textarea.value.substring( + textarea.selectionStart, + textarea.selectionEnd + ); + }) + ).toBe(text); }); - itFailsFirefox('should trigger hover state', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should trigger hover state', async () => { + const { page, server } = getTestState(); 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'); + 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'); + 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'); + expect( + await page.evaluate(() => document.querySelector('button:hover').id) + ).toBe('button-91'); }); - itFailsFirefox('should trigger hover state with removed window.Node', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should trigger hover state with removed window.Node', + async () => { + const { page, server } = getTestState(); - 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() => { - const {page, server, isFirefox} = getTestState(); + 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 () => { + const { page, server, isFirefox } = getTestState(); 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'}; + 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 (isFirefox && os.platform() !== 'darwin') - delete modifiers['Meta']; + if (isFirefox && os.platform() !== 'darwin') 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]))) + 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]))) + if ( + await page.evaluate((mod) => window.lastEvent[mod], modifiers[modifier]) + ) throw new Error(modifiers[modifier] + ' should be false'); } }); - itFailsFirefox('should tween mouse movement', async() => { - const {page} = getTestState(); + itFailsFirefox('should tween mouse movement', async () => { + const { page } = getTestState(); await page.mouse.move(100, 100); await page.evaluate(() => { window.result = []; - document.addEventListener('mousemove', event => { + document.addEventListener('mousemove', (event) => { window.result.push([event.clientX, event.clientY]); }); }); - await page.mouse.move(200, 300, {steps: 5}); + await page.mouse.move(200, 300, { steps: 5 }); expect(await page.evaluate('result')).toEqual([ [120, 140], [140, 180], [160, 220], [180, 260], - [200, 300] + [200, 300], ]); }); // @see https://crbug.com/929806 - itFailsFirefox('should work with mobile viewports and cross process navigations', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should work with mobile viewports and cross process navigations', + async () => { + const { page, server } = getTestState(); - await page.goto(server.EMPTY_PAGE); - await page.setViewport({width: 360, height: 640, isMobile: true}); - 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.goto(server.EMPTY_PAGE); + await page.setViewport({ width: 360, height: 640, isMobile: true }); + 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); + await page.mouse.click(30, 40); - expect(await page.evaluate('result')).toEqual({x: 30, y: 40}); - }); + expect(await page.evaluate('result')).toEqual({ x: 30, y: 40 }); + } + ); }); diff --git a/test/navigation.spec.js b/test/navigation.spec.js index 0e0c1a1ec6be5..2d45d322b5f5e 100644 --- a/test/navigation.spec.js +++ b/test/navigation.spec.js @@ -16,20 +16,24 @@ const utils = require('./utils'); const expect = require('expect'); -const {getTestState,setupTestBrowserHooks,setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); -describe('navigation', function() { +describe('navigation', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - describe('Page.goto', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describe('Page.goto', function () { + it('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); expect(page.url()).toBe(server.EMPTY_PAGE); }); - itFailsFirefox('should work with anchor navigation', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work with anchor navigation', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); expect(page.url()).toBe(server.EMPTY_PAGE); @@ -38,28 +42,31 @@ describe('navigation', function() { await page.goto(server.EMPTY_PAGE + '#bar'); expect(page.url()).toBe(server.EMPTY_PAGE + '#bar'); }); - it('should work with redirects', async() => { - const {page, server} = getTestState(); + it('should work with redirects', async () => { + const { page, server } = getTestState(); server.setRedirect('/redirect/1.html', '/redirect/2.html'); server.setRedirect('/redirect/2.html', '/empty.html'); await page.goto(server.PREFIX + '/redirect/1.html'); expect(page.url()).toBe(server.EMPTY_PAGE); }); - it('should navigate to about:blank', async() => { - const {page} = getTestState(); + it('should navigate to about:blank', async () => { + const { page } = getTestState(); const response = await page.goto('about:blank'); expect(response).toBe(null); }); - itFailsFirefox('should return response when page changes its URL after load', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should return response when page changes its URL after load', + async () => { + const { page, server } = getTestState(); - const response = await page.goto(server.PREFIX + '/historyapi.html'); - expect(response.status()).toBe(200); - }); - it('should work with subframes return 204', async() => { - const {page, server} = getTestState(); + const response = await page.goto(server.PREFIX + '/historyapi.html'); + expect(response.status()).toBe(200); + } + ); + it('should work with subframes return 204', async () => { + const { page, server } = getTestState(); server.setRoute('/frames/frame.html', (req, res) => { res.statusCode = 204; @@ -67,324 +74,406 @@ describe('navigation', function() { }); await page.goto(server.PREFIX + '/frames/one-frame.html'); }); - itFailsFirefox('should fail when server returns 204', async() => { - const {page, server, isChrome} = getTestState(); + itFailsFirefox('should fail when server returns 204', async () => { + const { page, server, isChrome } = getTestState(); server.setRoute('/empty.html', (req, res) => { res.statusCode = 204; res.end(); }); let error = null; - await page.goto(server.EMPTY_PAGE).catch(error_ => error = error_); + await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_)); expect(error).not.toBe(null); - if (isChrome) - expect(error.message).toContain('net::ERR_ABORTED'); - else - expect(error.message).toContain('NS_BINDING_ABORTED'); - }); - itFailsFirefox('should navigate to empty page with domcontentloaded', async() => { - const {page, server} = getTestState(); - - const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'domcontentloaded'}); - expect(response.status()).toBe(200); - }); - itFailsFirefox('should work when page calls history API in beforeunload', async() => { - const {page, server} = getTestState(); - - 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); + if (isChrome) expect(error.message).toContain('net::ERR_ABORTED'); + else expect(error.message).toContain('NS_BINDING_ABORTED'); }); - itFailsFirefox('should navigate to empty page with networkidle0', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should navigate to empty page with domcontentloaded', + async () => { + const { page, server } = getTestState(); - const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle0'}); - expect(response.status()).toBe(200); - }); - itFailsFirefox('should navigate to empty page with networkidle2', async() => { - const {page, server} = getTestState(); + const response = await page.goto(server.EMPTY_PAGE, { + waitUntil: 'domcontentloaded', + }); + expect(response.status()).toBe(200); + } + ); + itFailsFirefox( + 'should work when page calls history API in beforeunload', + async () => { + const { page, server } = getTestState(); - const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle2'}); - expect(response.status()).toBe(200); - }); - itFailsFirefox('should fail when navigating to bad url', async() => { - const {page, isChrome} = getTestState(); + 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); + } + ); + itFailsFirefox( + 'should navigate to empty page with networkidle0', + async () => { + const { page, server } = getTestState(); + + const response = await page.goto(server.EMPTY_PAGE, { + waitUntil: 'networkidle0', + }); + expect(response.status()).toBe(200); + } + ); + itFailsFirefox( + 'should navigate to empty page with networkidle2', + async () => { + const { page, server } = getTestState(); + + const response = await page.goto(server.EMPTY_PAGE, { + waitUntil: 'networkidle2', + }); + expect(response.status()).toBe(200); + } + ); + itFailsFirefox('should fail when navigating to bad url', async () => { + const { page, isChrome } = getTestState(); let error = null; - await page.goto('asdfasdf').catch(error_ => error = error_); + await page.goto('asdfasdf').catch((error_) => (error = error_)); if (isChrome) expect(error.message).toContain('Cannot navigate to invalid URL'); - else - expect(error.message).toContain('Invalid url'); + else expect(error.message).toContain('Invalid url'); }); - itFailsFirefox('should fail when navigating to bad SSL', async() => { - const {page, httpsServer, isChrome} = getTestState(); + itFailsFirefox('should fail when navigating to bad SSL', async () => { + const { page, httpsServer, isChrome } = getTestState(); // 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(error_ => error = error_); - if (isChrome) - expect(error.message).toContain('net::ERR_CERT_AUTHORITY_INVALID'); - else - expect(error.message).toContain('SSL_ERROR_UNKNOWN'); - }); - itFailsFirefox('should fail when navigating to bad SSL after redirects', async() => { - const {page, server, httpsServer, isChrome} = getTestState(); - - server.setRedirect('/redirect/1.html', '/redirect/2.html'); - server.setRedirect('/redirect/2.html', '/empty.html'); + 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.PREFIX + '/redirect/1.html').catch(error_ => error = error_); + await page + .goto(httpsServer.EMPTY_PAGE) + .catch((error_) => (error = error_)); if (isChrome) expect(error.message).toContain('net::ERR_CERT_AUTHORITY_INVALID'); - else - expect(error.message).toContain('SSL_ERROR_UNKNOWN'); - }); - it('should throw if networkidle is passed as an option', async() => { - const {page, server} = getTestState(); - - let error = null; - await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle'}).catch(error_ => error = error_); - expect(error.message).toContain('"networkidle" option is no longer supported'); - }); - itFailsFirefox('should fail when main resources failed to load', async() => { - const {page, isChrome} = getTestState(); + else expect(error.message).toContain('SSL_ERROR_UNKNOWN'); + }); + itFailsFirefox( + 'should fail when navigating to bad SSL after redirects', + async () => { + const { page, server, httpsServer, isChrome } = getTestState(); + + 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((error_) => (error = error_)); + if (isChrome) + expect(error.message).toContain('net::ERR_CERT_AUTHORITY_INVALID'); + else expect(error.message).toContain('SSL_ERROR_UNKNOWN'); + } + ); + it('should throw if networkidle is passed as an option', async () => { + const { page, server } = getTestState(); let error = null; - await page.goto('http://localhost:44123/non-existing-url').catch(error_ => error = error_); - if (isChrome) - expect(error.message).toContain('net::ERR_CONNECTION_REFUSED'); - else - expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED'); - }); - it('should fail when exceeding maximum navigation timeout', async() => { - const {page, server, puppeteer} = getTestState(); + await page + .goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' }) + .catch((error_) => (error = error_)); + expect(error.message).toContain( + '"networkidle" option is no longer supported' + ); + }); + itFailsFirefox( + 'should fail when main resources failed to load', + async () => { + const { page, isChrome } = getTestState(); + + let error = null; + await page + .goto('http://localhost:44123/non-existing-url') + .catch((error_) => (error = error_)); + if (isChrome) + expect(error.message).toContain('net::ERR_CONNECTION_REFUSED'); + else expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED'); + } + ); + it('should fail when exceeding maximum navigation timeout', async () => { + const { page, server, puppeteer } = getTestState(); // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); + server.setRoute('/empty.html', (req, res) => {}); let error = null; - await page.goto(server.PREFIX + '/empty.html', {timeout: 1}).catch(error_ => error = error_); + await page + .goto(server.PREFIX + '/empty.html', { timeout: 1 }) + .catch((error_) => (error = error_)); expect(error.message).toContain('Navigation timeout of 1 ms exceeded'); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - it('should fail when exceeding default maximum navigation timeout', async() => { - const {page, server, puppeteer} = getTestState(); + it('should fail when exceeding default maximum navigation timeout', async () => { + const { page, server, puppeteer } = getTestState(); // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); + server.setRoute('/empty.html', (req, res) => {}); let error = null; page.setDefaultNavigationTimeout(1); - await page.goto(server.PREFIX + '/empty.html').catch(error_ => error = error_); + await page + .goto(server.PREFIX + '/empty.html') + .catch((error_) => (error = error_)); expect(error.message).toContain('Navigation timeout of 1 ms exceeded'); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - it('should fail when exceeding default maximum timeout', async() => { - const {page, server, puppeteer} = getTestState(); + it('should fail when exceeding default maximum timeout', async () => { + const { page, server, puppeteer } = getTestState(); // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); + server.setRoute('/empty.html', (req, res) => {}); let error = null; page.setDefaultTimeout(1); - await page.goto(server.PREFIX + '/empty.html').catch(error_ => error = error_); + await page + .goto(server.PREFIX + '/empty.html') + .catch((error_) => (error = error_)); expect(error.message).toContain('Navigation timeout of 1 ms exceeded'); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - it('should prioritize default navigation timeout over default timeout', async() => { - const {page, server, puppeteer} = getTestState(); + it('should prioritize default navigation timeout over default timeout', async () => { + const { page, server, puppeteer } = getTestState(); // Hang for request to the empty.html - server.setRoute('/empty.html', (req, res) => { }); + server.setRoute('/empty.html', (req, res) => {}); let error = null; page.setDefaultTimeout(0); page.setDefaultNavigationTimeout(1); - await page.goto(server.PREFIX + '/empty.html').catch(error_ => error = error_); + await page + .goto(server.PREFIX + '/empty.html') + .catch((error_) => (error = error_)); expect(error.message).toContain('Navigation timeout of 1 ms exceeded'); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - it('should disable timeout when its set to 0', async() => { - const {page, server} = getTestState(); + it('should disable timeout when its set to 0', async () => { + const { page, server } = getTestState(); let error = null; let loaded = false; - page.once('load', () => loaded = true); - await page.goto(server.PREFIX + '/grid.html', {timeout: 0, waitUntil: ['load']}).catch(error_ => error = error_); + page.once('load', () => (loaded = true)); + await page + .goto(server.PREFIX + '/grid.html', { timeout: 0, waitUntil: ['load'] }) + .catch((error_) => (error = error_)); expect(error).toBe(null); expect(loaded).toBe(true); }); - itFailsFirefox('should work when navigating to valid url', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work when navigating to valid url', async () => { + const { page, server } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); expect(response.ok()).toBe(true); }); - itFailsFirefox('should work when navigating to data url', async() => { - const {page} = getTestState(); + itFailsFirefox('should work when navigating to data url', async () => { + const { page } = getTestState(); const response = await page.goto('data:text/html,hello'); expect(response.ok()).toBe(true); }); - itFailsFirefox('should work when navigating to 404', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work when navigating to 404', async () => { + const { page, server } = getTestState(); const response = await page.goto(server.PREFIX + '/not-found'); expect(response.ok()).toBe(false); expect(response.status()).toBe(404); }); - itFailsFirefox('should return last response in redirect chain', async() => { - const {page, server} = getTestState(); - - 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); - }); - itFailsFirefox('should wait for network idle to succeed navigation', async() => { - const {page, server} = getTestState(); - - let responses = []; - // Hold on to a bunch of requests without answering. - server.setRoute('/fetch-request-a.js', (req, res) => responses.push(res)); - server.setRoute('/fetch-request-b.js', (req, res) => responses.push(res)); - server.setRoute('/fetch-request-c.js', (req, res) => responses.push(res)); - server.setRoute('/fetch-request-d.js', (req, res) => responses.push(res)); - const initialFetchResourcesRequested = Promise.all([ - server.waitForRequest('/fetch-request-a.js'), - server.waitForRequest('/fetch-request-b.js'), - server.waitForRequest('/fetch-request-c.js'), - ]); - const secondFetchResourceRequested = server.waitForRequest('/fetch-request-d.js'); - - // Navigate to a page which loads immediately and then does a bunch of - // requests via javascript's fetch method. - const navigationPromise = page.goto(server.PREFIX + '/networkidle.html', { - waitUntil: 'networkidle0', - }); - // Track when the navigation gets completed. - let navigationFinished = false; - navigationPromise.then(() => navigationFinished = true); - - // Wait for the page's 'load' event. - await new Promise(fulfill => page.once('load', fulfill)); - expect(navigationFinished).toBe(false); - - // Wait for the initial three resources to be requested. - await initialFetchResourcesRequested; - - // Expect navigation still to be not finished. - expect(navigationFinished).toBe(false); - - // Respond to initial requests. - for (const response of responses) { - response.statusCode = 404; - response.end(`File not found`); + itFailsFirefox( + 'should return last response in redirect chain', + async () => { + const { page, server } = getTestState(); + + 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); } - - // Reset responses array - responses = []; - - // Wait for the second round to be requested. - await secondFetchResourceRequested; - // Expect navigation still to be not finished. - expect(navigationFinished).toBe(false); - - // Respond to requests. - for (const response of responses) { - response.statusCode = 404; - response.end(`File not found`); + ); + itFailsFirefox( + 'should wait for network idle to succeed navigation', + async () => { + const { page, server } = getTestState(); + + let responses = []; + // Hold on to a bunch of requests without answering. + server.setRoute('/fetch-request-a.js', (req, res) => + responses.push(res) + ); + server.setRoute('/fetch-request-b.js', (req, res) => + responses.push(res) + ); + server.setRoute('/fetch-request-c.js', (req, res) => + responses.push(res) + ); + server.setRoute('/fetch-request-d.js', (req, res) => + responses.push(res) + ); + const initialFetchResourcesRequested = Promise.all([ + server.waitForRequest('/fetch-request-a.js'), + server.waitForRequest('/fetch-request-b.js'), + server.waitForRequest('/fetch-request-c.js'), + ]); + const secondFetchResourceRequested = server.waitForRequest( + '/fetch-request-d.js' + ); + + // Navigate to a page which loads immediately and then does a bunch of + // requests via javascript's fetch method. + const navigationPromise = page.goto( + server.PREFIX + '/networkidle.html', + { + waitUntil: 'networkidle0', + } + ); + // Track when the navigation gets completed. + let navigationFinished = false; + navigationPromise.then(() => (navigationFinished = true)); + + // Wait for the page's 'load' event. + await new Promise((fulfill) => page.once('load', fulfill)); + expect(navigationFinished).toBe(false); + + // Wait for the initial three resources to be requested. + await initialFetchResourcesRequested; + + // Expect navigation still to be not finished. + expect(navigationFinished).toBe(false); + + // Respond to initial requests. + for (const response of responses) { + response.statusCode = 404; + response.end(`File not found`); + } + + // Reset responses array + responses = []; + + // Wait for the second round to be requested. + await secondFetchResourceRequested; + // Expect navigation still to be not finished. + expect(navigationFinished).toBe(false); + + // Respond to requests. + for (const response of responses) { + response.statusCode = 404; + response.end(`File not found`); + } + + const response = await navigationPromise; + // Expect navigation to succeed. + expect(response.ok()).toBe(true); } - - const response = await navigationPromise; - // Expect navigation to succeed. - expect(response.ok()).toBe(true); - }); - it('should not leak listeners during navigation', async() => { - const {page, server} = getTestState(); - - 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); - }); - itFailsFirefox('should not leak listeners during bad navigation', async() => { - const {page} = getTestState(); + ); + it('should not leak listeners during navigation', async () => { + const { page, server } = getTestState(); let warning = null; - const warningHandler = w => warning = w; + const warningHandler = (w) => (warning = w); process.on('warning', warningHandler); - for (let i = 0; i < 20; ++i) - await page.goto('asdf').catch(error => {/* swallow navigation error */}); + 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 navigation of 11 pages', async() => { - const {context, server} = getTestState(); + itFailsFirefox( + 'should not leak listeners during bad navigation', + async () => { + const { page } = getTestState(); + + let warning = null; + const warningHandler = (w) => (warning = w); + process.on('warning', warningHandler); + for (let i = 0; i < 20; ++i) + await page.goto('asdf').catch((error) => { + /* swallow navigation error */ + }); + process.removeListener('warning', warningHandler); + expect(warning).toBe(null); + } + ); + it('should not leak listeners during navigation of 11 pages', async () => { + const { context, server } = getTestState(); let warning = null; - const warningHandler = w => warning = w; + 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(); - })); + 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); }); - itFailsFirefox('should navigate to dataURL and fire dataURL requests', async() => { - const {page} = getTestState(); - - const requests = []; - page.on('request', request => !utils.isFavicon(request) && requests.push(request)); - const dataURL = 'data:text/html,
yo
'; - const response = await page.goto(dataURL); - expect(response.status()).toBe(200); - expect(requests.length).toBe(1); - expect(requests[0].url()).toBe(dataURL); - }); - itFailsFirefox('should navigate to URL with hash and fire requests without hash', async() => { - const {page, server} = getTestState(); - - const requests = []; - page.on('request', request => !utils.isFavicon(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); - }); - itFailsFirefox('should work with self requesting page', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should navigate to dataURL and fire dataURL requests', + async () => { + const { page } = getTestState(); + + const requests = []; + page.on( + 'request', + (request) => !utils.isFavicon(request) && requests.push(request) + ); + const dataURL = 'data:text/html,
yo
'; + const response = await page.goto(dataURL); + expect(response.status()).toBe(200); + expect(requests.length).toBe(1); + expect(requests[0].url()).toBe(dataURL); + } + ); + itFailsFirefox( + 'should navigate to URL with hash and fire requests without hash', + async () => { + const { page, server } = getTestState(); + + const requests = []; + page.on( + 'request', + (request) => !utils.isFavicon(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); + } + ); + itFailsFirefox('should work with self requesting page', async () => { + const { page, server } = getTestState(); const response = await page.goto(server.PREFIX + '/self-request.html'); expect(response.status()).toBe(200); expect(response.url()).toContain('self-request.html'); }); - itFailsFirefox('should fail when navigating and show the url at the error message', async() => { - const {page, httpsServer} = getTestState(); - - const url = httpsServer.PREFIX + '/redirect/1.html'; - let error = null; - try { - await page.goto(url); - } catch (error_) { - error = error_; + itFailsFirefox( + 'should fail when navigating and show the url at the error message', + async () => { + const { page, httpsServer } = getTestState(); + + const url = httpsServer.PREFIX + '/redirect/1.html'; + let error = null; + try { + await page.goto(url); + } catch (error_) { + error = error_; + } + expect(error.message).toContain(url); } - expect(error.message).toContain(url); - }); - itFailsFirefox('should send referer', async() => { - const {page, server} = getTestState(); + ); + itFailsFirefox('should send referer', async () => { + const { page, server } = getTestState(); const [request1, request2] = await Promise.all([ server.waitForRequest('/grid.html'), @@ -399,32 +488,37 @@ describe('navigation', function() { }); }); - describe('Page.waitForNavigation', function() { - itFailsFirefox('should work', async() => { - const {page, server} = getTestState(); + describe('Page.waitForNavigation', function () { + itFailsFirefox('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); const [response] = await Promise.all([ page.waitForNavigation(), - page.evaluate(url => window.location.href = url, server.PREFIX + '/grid.html') + 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() => { - const {page, server} = getTestState(); + it('should work with both domcontentloaded and load', async () => { + const { page, server } = getTestState(); let response = null; - server.setRoute('/one-style.css', (req, res) => response = res); + server.setRoute('/one-style.css', (req, res) => (response = res)); const navigationPromise = page.goto(server.PREFIX + '/one-style.html'); const domContentLoadedPromise = page.waitForNavigation({ - waitUntil: 'domcontentloaded' + waitUntil: 'domcontentloaded', }); let bothFired = false; - const bothFiredPromise = page.waitForNavigation({ - waitUntil: ['load', 'domcontentloaded'] - }).then(() => bothFired = true); + const bothFiredPromise = page + .waitForNavigation({ + waitUntil: ['load', 'domcontentloaded'], + }) + .then(() => (bothFired = true)); await server.waitForRequest('/one-style.css'); await domContentLoadedPromise; @@ -433,8 +527,8 @@ describe('navigation', function() { await bothFiredPromise; await navigationPromise; }); - itFailsFirefox('should work with clicking on anchor links', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work with clicking on anchor links', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.setContent(`foobar`); @@ -445,8 +539,8 @@ describe('navigation', function() { expect(response).toBe(null); expect(page.url()).toBe(server.EMPTY_PAGE + '#foobar'); }); - itFailsFirefox('should work with history.pushState()', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work with history.pushState()', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.setContent(` @@ -462,8 +556,8 @@ describe('navigation', function() { expect(response).toBe(null); expect(page.url()).toBe(server.PREFIX + '/wow.html'); }); - itFailsFirefox('should work with history.replaceState()', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work with history.replaceState()', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.setContent(` @@ -479,11 +573,13 @@ describe('navigation', function() { expect(response).toBe(null); expect(page.url()).toBe(server.PREFIX + '/replaced.html'); }); - itFailsFirefox('should work with DOM history.back()/history.forward()', async() => { - const {page, server} = getTestState(); + itFailsFirefox( + 'should work with DOM history.back()/history.forward()', + async () => { + const { page, server } = getTestState(); - await page.goto(server.EMPTY_PAGE); - await page.setContent(` + 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'); - }); - itFailsFirefox('should work when subframe issues window.stop()', async() => { - const {page, server} = getTestState(); - - server.setRoute('/frames/style.css', (req, res) => {}); - const navigationPromise = page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = await utils.waitEvent(page, 'frameattached'); - await new Promise(fulfill => { - page.on('framenavigated', f => { - if (f === frame) - fulfill(); + 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'); + } + ); + itFailsFirefox( + 'should work when subframe issues window.stop()', + async () => { + const { page, server } = getTestState(); + + server.setRoute('/frames/style.css', (req, res) => {}); + const navigationPromise = page.goto( + server.PREFIX + '/frames/one-frame.html' + ); + const frame = await utils.waitEvent(page, 'frameattached'); + await new Promise((fulfill) => { + page.on('framenavigated', (f) => { + if (f === frame) fulfill(); + }); }); - }); - await Promise.all([ - frame.evaluate(() => window.stop()), - navigationPromise - ]); - }); + await Promise.all([ + frame.evaluate(() => window.stop()), + navigationPromise, + ]); + } + ); }); - describeFailsFirefox('Page.goBack', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Page.goBack', function () { + it('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.goto(server.PREFIX + '/grid.html'); @@ -544,8 +645,8 @@ describe('navigation', function() { response = await page.goForward(); expect(response).toBe(null); }); - it('should work with HistoryAPI', async() => { - const {page, server} = getTestState(); + it('should work with HistoryAPI', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.evaluate(() => { @@ -563,9 +664,9 @@ describe('navigation', function() { }); }); - describeFailsFirefox('Frame.goto', function() { - it('should navigate subframes', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Frame.goto', function () { + it('should navigate subframes', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/frames/one-frame.html'); expect(page.frames()[0].url()).toContain('/frames/one-frame.html'); @@ -575,21 +676,24 @@ describe('navigation', function() { expect(response.ok()).toBe(true); expect(response.frame()).toBe(page.frames()[1]); }); - it('should reject when frame detaches', async() => { - const {page, server} = getTestState(); + it('should reject when frame detaches', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/frames/one-frame.html'); server.setRoute('/empty.html', () => {}); - const navigationPromise = page.frames()[1].goto(server.EMPTY_PAGE).catch(error_ => error_); + const navigationPromise = page + .frames()[1] + .goto(server.EMPTY_PAGE) + .catch((error_) => error_); await server.waitForRequest('/empty.html'); - await page.$eval('iframe', frame => frame.remove()); + await page.$eval('iframe', (frame) => frame.remove()); const error = await navigationPromise; expect(error.message).toBe('Navigating frame was detached'); }); - it('should return matching responses', async() => { - const {page, server} = getTestState(); + it('should return matching responses', async () => { + const { page, server } = getTestState(); // Disable cache: otherwise, chromium will cache similar requests. await page.setCacheEnabled(false); @@ -602,7 +706,9 @@ describe('navigation', function() { ]); // Navigate all frames to the same URL. const serverResponses = []; - server.setRoute('/one-style.html', (req, res) => serverResponses.push(res)); + server.setRoute('/one-style.html', (req, res) => + serverResponses.push(res) + ); const navigations = []; for (let i = 0; i < 3; ++i) { navigations.push(frames[i].goto(server.PREFIX + '/one-style.html')); @@ -619,46 +725,51 @@ describe('navigation', function() { }); }); - describeFailsFirefox('Frame.waitForNavigation', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Frame.waitForNavigation', function () { + it('should work', async () => { + const { page, server } = getTestState(); 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') + 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() => { - const {page, server} = getTestState(); + it('should fail when frame detaches', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/frames/one-frame.html'); const frame = page.frames()[1]; server.setRoute('/empty.html', () => {}); let error = null; - const navigationPromise = frame.waitForNavigation().catch(error_ => error = error_); + const navigationPromise = frame + .waitForNavigation() + .catch((error_) => (error = error_)); await Promise.all([ server.waitForRequest('/empty.html'), - frame.evaluate(() => window.location = '/empty.html') + frame.evaluate(() => (window.location = '/empty.html')), ]); - await page.$eval('iframe', frame => frame.remove()); + await page.$eval('iframe', (frame) => frame.remove()); await navigationPromise; expect(error.message).toBe('Navigating frame was detached'); }); }); - describe('Page.reload', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describe('Page.reload', function () { + it('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => window._foo = 10); + await page.evaluate(() => (window._foo = 10)); await page.reload(); expect(await page.evaluate(() => window._foo)).toBe(undefined); }); diff --git a/test/network.spec.js b/test/network.spec.js index b75d4925d2423..4bacc4c5be5b2 100644 --- a/test/network.spec.js +++ b/test/network.spec.js @@ -18,77 +18,101 @@ const fs = require('fs'); const path = require('path'); const utils = require('./utils'); const expect = require('expect'); -const {getTestState,setupTestBrowserHooks,setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); -describe('network', function() { +describe('network', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - describe('Page.Events.Request', function() { - it('should fire for navigation requests', async() => { - const {page, server} = getTestState(); + describe('Page.Events.Request', function () { + it('should fire for navigation requests', async () => { + const { page, server } = getTestState(); const requests = []; - page.on('request', request => !utils.isFavicon(request) && requests.push(request)); + page.on( + 'request', + (request) => !utils.isFavicon(request) && requests.push(request) + ); await page.goto(server.EMPTY_PAGE); expect(requests.length).toBe(1); }); - itFailsFirefox('should fire for iframes', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should fire for iframes', async () => { + const { page, server } = getTestState(); const requests = []; - page.on('request', request => !utils.isFavicon(request) && requests.push(request)); + page.on( + 'request', + (request) => !utils.isFavicon(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() => { - const {page, server} = getTestState(); + it('should fire for fetches', async () => { + const { page, server } = getTestState(); const requests = []; - page.on('request', request => !utils.isFavicon(request) && requests.push(request)); + page.on( + 'request', + (request) => !utils.isFavicon(request) && requests.push(request) + ); await page.goto(server.EMPTY_PAGE); await page.evaluate(() => fetch('/empty.html')); expect(requests.length).toBe(2); }); }); - describe('Request.frame', function() { - it('should work for main frame navigation request', async() => { - const {page, server} = getTestState(); + describe('Request.frame', function () { + it('should work for main frame navigation request', async () => { + const { page, server } = getTestState(); const requests = []; - page.on('request', request => !utils.isFavicon(request) && requests.push(request)); + page.on( + 'request', + (request) => !utils.isFavicon(request) && requests.push(request) + ); await page.goto(server.EMPTY_PAGE); expect(requests.length).toBe(1); expect(requests[0].frame()).toBe(page.mainFrame()); }); - itFailsFirefox('should work for subframe navigation request', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work for subframe navigation request', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); const requests = []; - page.on('request', request => !utils.isFavicon(request) && requests.push(request)); + page.on( + 'request', + (request) => !utils.isFavicon(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() => { - const {page, server} = getTestState(); + it('should work for fetch requests', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); let requests = []; - page.on('request', request => !utils.isFavicon(request) && requests.push(request)); + page.on( + 'request', + (request) => !utils.isFavicon(request) && requests.push(request) + ); await page.evaluate(() => fetch('/digits/1.png')); - requests = requests.filter(request => !request.url().includes('favicon')); + requests = requests.filter( + (request) => !request.url().includes('favicon') + ); expect(requests.length).toBe(1); expect(requests[0].frame()).toBe(page.mainFrame()); }); }); - describeFailsFirefox('Request.headers', function() { - it('should work', async() => { - const {page, server, isChrome} = getTestState(); + describeFailsFirefox('Request.headers', function () { + it('should work', async () => { + const { page, server, isChrome } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); if (isChrome) @@ -98,9 +122,9 @@ describe('network', function() { }); }); - describeFailsFirefox('Response.headers', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Response.headers', function () { + it('should work', async () => { + const { page, server } = getTestState(); server.setRoute('/empty.html', (req, res) => { res.setHeader('foo', 'bar'); @@ -111,19 +135,24 @@ describe('network', function() { }); }); - describeFailsFirefox('Response.fromCache', function() { - it('should return |false| for non-cached content', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Response.fromCache', function () { + it('should return |false| for non-cached content', async () => { + const { page, server } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); expect(response.fromCache()).toBe(false); }); - it('should work', async() => { - const {page, server} = getTestState(); + it('should work', async () => { + const { page, server } = getTestState(); const responses = new Map(); - page.on('response', r => !utils.isFavicon(r.request()) && responses.set(r.url().split('/').pop(), r)); + page.on( + 'response', + (r) => + !utils.isFavicon(r.request()) && + responses.set(r.url().split('/').pop(), r) + ); // Load and re-load to make sure it's cached. await page.goto(server.PREFIX + '/cached/one-style.html'); @@ -137,23 +166,25 @@ describe('network', function() { }); }); - describeFailsFirefox('Response.fromServiceWorker', function() { - it('should return |false| for non-service-worker content', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Response.fromServiceWorker', function () { + it('should return |false| for non-service-worker content', async () => { + const { page, server } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); expect(response.fromServiceWorker()).toBe(false); }); - it('Response.fromServiceWorker', async() => { - const {page, server} = getTestState(); + it('Response.fromServiceWorker', async () => { + const { page, server } = getTestState(); const responses = new Map(); - page.on('response', r => responses.set(r.url().split('/').pop(), r)); + page.on('response', (r) => responses.set(r.url().split('/').pop(), r)); // Load and re-load to make sure serviceworker is installed and running. - await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html', {waitUntil: 'networkidle2'}); - await page.evaluate(async() => await window.activationPromise); + await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html', { + waitUntil: 'networkidle2', + }); + await page.evaluate(async () => await window.activationPromise); await page.reload(); expect(responses.size).toBe(2); @@ -164,36 +195,41 @@ describe('network', function() { }); }); - describeFailsFirefox('Request.postData', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Request.postData', function () { + it('should work', async () => { + const { page, server } = getTestState(); 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'})})); + 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() => { - const {page, server} = getTestState(); + it('should be |undefined| when there is no post data', async () => { + const { page, server } = getTestState(); const response = await page.goto(server.EMPTY_PAGE); expect(response.request().postData()).toBe(undefined); }); }); - describeFailsFirefox('Response.text', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Response.text', function () { + it('should work', async () => { + const { page, server } = getTestState(); const response = await page.goto(server.PREFIX + '/simple.json'); const responseText = (await response.text()).trimEnd(); expect(responseText).toBe('{"foo": "bar"}'); }); - it('should return uncompressed text', async() => { - const {page, server} = getTestState(); + it('should return uncompressed text', async () => { + const { page, server } = getTestState(); server.enableGzip('/simple.json'); const response = await page.goto(server.PREFIX + '/simple.json'); @@ -201,8 +237,8 @@ describe('network', function() { const responseText = (await response.text()).trimEnd(); expect(responseText).toBe('{"foo": "bar"}'); }); - it('should throw when requesting body of redirected response', async() => { - const {page, server} = getTestState(); + it('should throw when requesting body of redirected response', async () => { + const { page, server } = getTestState(); server.setRedirect('/foo.html', '/empty.html'); const response = await page.goto(server.PREFIX + '/foo.html'); @@ -211,11 +247,13 @@ describe('network', function() { const redirected = redirectChain[0].response(); expect(redirected.status()).toBe(302); let error = null; - await redirected.text().catch(error_ => error = error_); - expect(error.message).toContain('Response body is unavailable for redirect responses'); + await redirected.text().catch((error_) => (error = error_)); + expect(error.message).toContain( + 'Response body is unavailable for redirect responses' + ); }); - it('should wait until response completes', async() => { - const {page, server} = getTestState(); + it('should wait until response completes', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); // Setup server to trap request. @@ -229,11 +267,14 @@ describe('network', function() { }); // Setup page to trap response. let requestFinished = false; - page.on('requestfinished', r => requestFinished = requestFinished || r.url().includes('/get')); + page.on( + 'requestfinished', + (r) => (requestFinished = requestFinished || r.url().includes('/get')) + ); // send request and wait for server response const [pageResponse] = await Promise.all([ - page.waitForResponse(r => !utils.isFavicon(r.request())), - page.evaluate(() => fetch('./get', {method: 'GET'})), + page.waitForResponse((r) => !utils.isFavicon(r.request())), + page.evaluate(() => fetch('./get', { method: 'GET' })), server.waitForRequest('/get'), ]); @@ -244,45 +285,49 @@ describe('network', function() { const responseText = pageResponse.text(); // Write part of the response and wait for it to be flushed. - await new Promise(x => serverResponse.write('wor', x)); + await new Promise((x) => serverResponse.write('wor', x)); // Finish response. - await new Promise(x => serverResponse.end('ld!', x)); + await new Promise((x) => serverResponse.end('ld!', x)); expect(await responseText).toBe('hello world!'); }); }); - describeFailsFirefox('Response.json', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Response.json', function () { + it('should work', async () => { + const { page, server } = getTestState(); const response = await page.goto(server.PREFIX + '/simple.json'); - expect(await response.json()).toEqual({foo: 'bar'}); + expect(await response.json()).toEqual({ foo: 'bar' }); }); }); - describeFailsFirefox('Response.buffer', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Response.buffer', function () { + it('should work', async () => { + const { page, server } = getTestState(); const response = await page.goto(server.PREFIX + '/pptr.png'); - const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png')); + const imageBuffer = fs.readFileSync( + path.join(__dirname, 'assets', 'pptr.png') + ); const responseBuffer = await response.buffer(); expect(responseBuffer.equals(imageBuffer)).toBe(true); }); - it('should work with compression', async() => { - const {page, server} = getTestState(); + it('should work with compression', async () => { + const { page, server } = getTestState(); server.enableGzip('/pptr.png'); const response = await page.goto(server.PREFIX + '/pptr.png'); - const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png')); + const imageBuffer = fs.readFileSync( + path.join(__dirname, 'assets', 'pptr.png') + ); const responseBuffer = await response.buffer(); expect(responseBuffer.equals(imageBuffer)).toBe(true); }); }); - describeFailsFirefox('Response.statusText', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Response.statusText', function () { + it('should work', async () => { + const { page, server } = getTestState(); server.setRoute('/cool', (req, res) => { res.writeHead(200, 'cool!'); @@ -293,12 +338,12 @@ describe('network', function() { }); }); - describeFailsFirefox('Network Events', function() { - it('Page.Events.Request', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Network Events', function () { + it('Page.Events.Request', async () => { + const { page, server } = getTestState(); const requests = []; - page.on('request', request => requests.push(request)); + 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); @@ -308,11 +353,11 @@ describe('network', function() { expect(requests[0].frame() === page.mainFrame()).toBe(true); expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE); }); - it('Page.Events.Response', async() => { - const {page, server} = getTestState(); + it('Page.Events.Response', async () => { + const { page, server } = getTestState(); const responses = []; - page.on('response', response => responses.push(response)); + 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); @@ -321,22 +366,22 @@ describe('network', function() { expect(responses[0].request()).toBeTruthy(); const remoteAddress = responses[0].remoteAddress(); // Either IPv6 or IPv4, depending on environment. - expect(remoteAddress.ip.includes('::1') || remoteAddress.ip === '127.0.0.1').toBe(true); + expect( + remoteAddress.ip.includes('::1') || remoteAddress.ip === '127.0.0.1' + ).toBe(true); expect(remoteAddress.port).toBe(server.PORT); }); - it('Page.Events.RequestFailed', async() => { - const {page, server, isChrome} = getTestState(); + it('Page.Events.RequestFailed', async () => { + const { page, server, isChrome } = getTestState(); await page.setRequestInterception(true); - page.on('request', request => { - if (request.url().endsWith('css')) - request.abort(); - else - request.continue(); + page.on('request', (request) => { + if (request.url().endsWith('css')) request.abort(); + else request.continue(); }); const failedRequests = []; - page.on('requestfailed', request => failedRequests.push(request)); + 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'); @@ -348,11 +393,11 @@ describe('network', function() { expect(failedRequests[0].failure().errorText).toBe('NS_ERROR_FAILURE'); expect(failedRequests[0].frame()).toBeTruthy(); }); - it('Page.Events.RequestFinished', async() => { - const {page, server} = getTestState(); + it('Page.Events.RequestFinished', async () => { + const { page, server } = getTestState(); const requests = []; - page.on('requestfinished', request => requests.push(request)); + page.on('requestfinished', (request) => requests.push(request)); await page.goto(server.EMPTY_PAGE); expect(requests.length).toBe(1); expect(requests[0].url()).toBe(server.EMPTY_PAGE); @@ -360,24 +405,32 @@ describe('network', function() { expect(requests[0].frame() === page.mainFrame()).toBe(true); expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE); }); - it('should fire events in proper order', async() => { - const {page, server} = getTestState(); + it('should fire events in proper order', async () => { + const { page, server } = getTestState(); const events = []; - page.on('request', request => events.push('request')); - page.on('response', response => events.push('response')); - page.on('requestfinished', request => events.push('requestfinished')); + page.on('request', (request) => events.push('request')); + page.on('response', (response) => events.push('response')); + page.on('requestfinished', (request) => events.push('requestfinished')); await page.goto(server.EMPTY_PAGE); expect(events).toEqual(['request', 'response', 'requestfinished']); }); - it('should support redirects', async() => { - const {page, server} = getTestState(); + it('should support redirects', async () => { + const { page, server } = getTestState(); 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()}`)); + 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); @@ -387,23 +440,27 @@ describe('network', function() { `DONE ${FOO_URL}`, `GET ${server.EMPTY_PAGE}`, `200 ${server.EMPTY_PAGE}`, - `DONE ${server.EMPTY_PAGE}` + `DONE ${server.EMPTY_PAGE}`, ]); // Check redirect chain const redirectChain = response.request().redirectChain(); expect(redirectChain.length).toBe(1); expect(redirectChain[0].url()).toContain('/foo.html'); - expect(redirectChain[0].response().remoteAddress().port).toBe(server.PORT); + expect(redirectChain[0].response().remoteAddress().port).toBe( + server.PORT + ); }); }); describe('Request.isNavigationRequest', () => { - itFailsFirefox('should work', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work', async () => { + const { page, server } = getTestState(); const requests = new Map(); - page.on('request', request => requests.set(request.url().split('/').pop(), request)); + 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); @@ -412,11 +469,11 @@ describe('network', function() { expect(requests.get('script.js').isNavigationRequest()).toBe(false); expect(requests.get('style.css').isNavigationRequest()).toBe(false); }); - itFailsFirefox('should work with request interception', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work with request interception', async () => { + const { page, server } = getTestState(); const requests = new Map(); - page.on('request', request => { + page.on('request', (request) => { requests.set(request.url().split('/').pop(), request); request.continue(); }); @@ -429,22 +486,22 @@ describe('network', function() { expect(requests.get('script.js').isNavigationRequest()).toBe(false); expect(requests.get('style.css').isNavigationRequest()).toBe(false); }); - it('should work when navigating to image', async() => { - const {page, server} = getTestState(); + it('should work when navigating to image', async () => { + const { page, server } = getTestState(); const requests = []; - page.on('request', request => requests.push(request)); + page.on('request', (request) => requests.push(request)); await page.goto(server.PREFIX + '/pptr.png'); expect(requests[0].isNavigationRequest()).toBe(true); }); }); - describeFailsFirefox('Page.setExtraHTTPHeaders', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Page.setExtraHTTPHeaders', function () { + it('should work', async () => { + const { page, server } = getTestState(); await page.setExtraHTTPHeaders({ - foo: 'bar' + foo: 'bar', }); const [request] = await Promise.all([ server.waitForRequest('/empty.html'), @@ -452,53 +509,55 @@ describe('network', function() { ]); expect(request.headers['foo']).toBe('bar'); }); - it('should throw for non-string header values', async() => { - const {page} = getTestState(); + it('should throw for non-string header values', async () => { + const { page } = getTestState(); let error = null; try { - await page.setExtraHTTPHeaders({'foo': 1}); + await page.setExtraHTTPHeaders({ foo: 1 }); } catch (error_) { error = error_; } - expect(error.message).toBe('Expected value of header "foo" to be String, but "number" is found.'); + expect(error.message).toBe( + 'Expected value of header "foo" to be String, but "number" is found.' + ); }); }); - describeFailsFirefox('Page.authenticate', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Page.authenticate', function () { + it('should work', async () => { + const { page, server } = getTestState(); server.setAuth('/empty.html', 'user', 'pass'); let response = await page.goto(server.EMPTY_PAGE); expect(response.status()).toBe(401); await page.authenticate({ username: 'user', - password: 'pass' + password: 'pass', }); response = await page.reload(); expect(response.status()).toBe(200); }); - it('should fail if wrong credentials', async() => { - const {page, server} = getTestState(); + it('should fail if wrong credentials', async () => { + const { page, server } = getTestState(); // Use unique user/password since Chrome caches credentials per origin. server.setAuth('/empty.html', 'user2', 'pass2'); await page.authenticate({ username: 'foo', - password: 'bar' + password: 'bar', }); const response = await page.goto(server.EMPTY_PAGE); expect(response.status()).toBe(401); }); - it('should allow disable authentication', async() => { - const {page, server} = getTestState(); + it('should allow disable authentication', async () => { + const { page, server } = getTestState(); // Use unique user/password since Chrome caches credentials per origin. server.setAuth('/empty.html', 'user3', 'pass3'); await page.authenticate({ username: 'user3', - password: 'pass3' + password: 'pass3', }); let response = await page.goto(server.EMPTY_PAGE); expect(response.status()).toBe(200); @@ -508,5 +567,4 @@ describe('network', function() { expect(response.status()).toBe(401); }); }); - }); diff --git a/test/oopif.spec.js b/test/oopif.spec.js index 7ce0c3a12e1c9..97de9e5d8fb84 100644 --- a/test/oopif.spec.js +++ b/test/oopif.spec.js @@ -15,57 +15,60 @@ */ const expect = require('expect'); -const {getTestState} = require('./mocha-utils'); +const { getTestState } = require('./mocha-utils'); -describeChromeOnly('OOPIF', function() { +describeChromeOnly('OOPIF', function () { /* We use a special browser for this test as we need the --site-per-process flag */ let browser; let context; let page; - before(async() => { - const {puppeteer, defaultBrowserOptions} = getTestState(); - browser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { - args: (defaultBrowserOptions.args || []).concat(['--site-per-process']), - })); + before(async () => { + const { puppeteer, defaultBrowserOptions } = getTestState(); + browser = await puppeteer.launch( + Object.assign({}, defaultBrowserOptions, { + args: (defaultBrowserOptions.args || []).concat(['--site-per-process']), + }) + ); }); - beforeEach(async() => { + beforeEach(async () => { context = await browser.createIncognitoBrowserContext(); page = await context.newPage(); }); - afterEach(async() => { + afterEach(async () => { await context.close(); page = null; context = null; }); - after(async() => { + after(async () => { await browser.close(); browser = null; }); - xit('should report oopif frames', async() => { - const {server} = getTestState(); + xit('should report oopif frames', async () => { + const { server } = getTestState(); await page.goto(server.PREFIX + '/dynamic-oopif.html'); expect(oopifs(context).length).toBe(1); expect(page.frames().length).toBe(2); }); - it('should load oopif iframes with subresources and request interception', async() => { - const {server} = getTestState(); + it('should load oopif iframes with subresources and request interception', async () => { + const { server } = getTestState(); await page.setRequestInterception(true); - page.on('request', request => request.continue()); + page.on('request', (request) => request.continue()); await page.goto(server.PREFIX + '/dynamic-oopif.html'); expect(oopifs(context).length).toBe(1); }); }); - /** * @param {!BrowserContext} context */ function oopifs(context) { - return context.targets().filter(target => target._targetInfo.type === 'iframe'); + return context + .targets() + .filter((target) => target._targetInfo.type === 'iframe'); } diff --git a/test/page.spec.js b/test/page.spec.js index 3daa30cf99d54..597ecc752b5f9 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -16,54 +16,61 @@ const fs = require('fs'); const path = require('path'); const utils = require('./utils'); -const {waitEvent} = utils; +const { waitEvent } = utils; const expect = require('expect'); -const {getTestState,setupTestBrowserHooks,setupTestPageAndContextHooks} = require('./mocha-utils'); +const { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} = require('./mocha-utils'); -describe('Page', function() { +describe('Page', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); - describe('Page.close', function() { - it('should reject all promises when page is closed', async() => { - const {context} = getTestState(); + describe('Page.close', function () { + it('should reject all promises when page is closed', async () => { + const { context } = getTestState(); const newPage = await context.newPage(); let error = null; await Promise.all([ - newPage.evaluate(() => new Promise(r => {})).catch(error_ => error = error_), + newPage + .evaluate(() => new Promise((r) => {})) + .catch((error_) => (error = error_)), newPage.close(), ]); expect(error.message).toContain('Protocol error'); }); - it('should not be visible in browser.pages', async() => { - const {browser} = getTestState(); + it('should not be visible in browser.pages', async () => { + const { browser } = getTestState(); const newPage = await browser.newPage(); expect(await browser.pages()).toContain(newPage); await newPage.close(); expect(await browser.pages()).not.toContain(newPage); }); - itFailsFirefox('should run beforeunload if asked for', async() => { - const {context, server, isChrome} = getTestState(); + itFailsFirefox('should run beforeunload if asked for', async () => { + const { context, server, isChrome } = getTestState(); 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 pageClosingPromise = newPage.close({ runBeforeUnload: true }); const dialog = await waitEvent(newPage, 'dialog'); expect(dialog.type()).toBe('beforeunload'); expect(dialog.defaultValue()).toBe(''); - if (isChrome) - expect(dialog.message()).toBe(''); + if (isChrome) expect(dialog.message()).toBe(''); 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.'); + 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; }); - itFailsFirefox('should *not* run beforeunload by default', async() => { - const {context, server} = getTestState(); + itFailsFirefox('should *not* run beforeunload by default', async () => { + const { context, server } = getTestState(); const newPage = await context.newPage(); await newPage.goto(server.PREFIX + '/beforeunload.html'); @@ -72,22 +79,22 @@ describe('Page', function() { await newPage.click('body'); await newPage.close(); }); - it('should set the page close state', async() => { - const {context} = getTestState(); + it('should set the page close state', async () => { + const { context } = getTestState(); const newPage = await context.newPage(); expect(newPage.isClosed()).toBe(false); await newPage.close(); expect(newPage.isClosed()).toBe(true); }); - itFailsFirefox('should terminate network waiters', async() => { - const {context, server} = getTestState(); + itFailsFirefox('should terminate network waiters', async () => { + const { context, server } = getTestState(); const newPage = await context.newPage(); const results = await Promise.all([ - newPage.waitForRequest(server.EMPTY_PAGE).catch(error => error), - newPage.waitForResponse(server.EMPTY_PAGE).catch(error => error), - newPage.close() + newPage.waitForRequest(server.EMPTY_PAGE).catch((error) => error), + newPage.waitForResponse(server.EMPTY_PAGE).catch((error) => error), + newPage.close(), ]); for (let i = 0; i < 2; i++) { const message = results[i].message; @@ -97,9 +104,9 @@ describe('Page', function() { }); }); - describe('Page.Events.Load', function() { - it('should fire when expected', async() => { - const {page} = getTestState(); + describe('Page.Events.Load', function () { + it('should fire when expected', async () => { + const { page } = getTestState(); await Promise.all([ page.goto('about:blank'), @@ -109,84 +116,88 @@ describe('Page', function() { }); describeFailsFirefox('Async stacks', () => { - it('should work', async() => { - const {page, server} = getTestState(); + it('should work', async () => { + const { page, server } = getTestState(); server.setRoute('/empty.html', (req, res) => { res.statusCode = 204; res.end(); }); let error = null; - await page.goto(server.EMPTY_PAGE).catch(error_ => error = error_); + await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_)); expect(error).not.toBe(null); expect(error.stack).toContain(__filename); }); }); - describeFailsFirefox('Page.Events.error', function() { - it('should throw when page crashes', async() => { - const {page} = getTestState(); + describeFailsFirefox('Page.Events.error', function () { + it('should throw when page crashes', async () => { + const { page } = getTestState(); let error = null; - page.on('error', err => error = err); - page.goto('chrome://crash').catch(error_ => {}); + page.on('error', (err) => (error = err)); + page.goto('chrome://crash').catch((error_) => {}); await waitEvent(page, 'error'); expect(error.message).toBe('Page crashed!'); }); }); - describeFailsFirefox('Page.Events.Popup', function() { - it('should work', async() => { - const {page} = getTestState(); + describeFailsFirefox('Page.Events.Popup', function () { + it('should work', async () => { + const { page } = getTestState(); const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), + new Promise((x) => page.once('popup', x)), page.evaluate(() => window.open('about:blank')), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await popup.evaluate(() => !!window.opener)).toBe(true); }); - it('should work with noopener', async() => { - const {page} = getTestState(); + it('should work with noopener', async () => { + const { page } = getTestState(); const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), + new Promise((x) => page.once('popup', x)), page.evaluate(() => window.open('about:blank', null, 'noopener')), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await popup.evaluate(() => !!window.opener)).toBe(false); }); - it('should work with clicking target=_blank', async() => { - const {page, server} = getTestState(); + it('should work with clicking target=_blank', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.setContent('yo'); const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), + new Promise((x) => page.once('popup', x)), page.click('a'), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await popup.evaluate(() => !!window.opener)).toBe(true); }); - it('should work with fake-clicking target=_blank and rel=noopener', async() => { - const {page, server} = getTestState(); + it('should work with fake-clicking target=_blank and rel=noopener', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); + await page.setContent( + 'yo' + ); const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), - page.$eval('a', a => a.click()), + new Promise((x) => page.once('popup', x)), + page.$eval('a', (a) => a.click()), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await popup.evaluate(() => !!window.opener)).toBe(false); }); - it('should work with clicking target=_blank and rel=noopener', async() => { - const {page, server} = getTestState(); + it('should work with clicking target=_blank and rel=noopener', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); + await page.setContent( + 'yo' + ); const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), + new Promise((x) => page.once('popup', x)), page.click('a'), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); @@ -194,41 +205,47 @@ describe('Page', function() { }); }); - describe('BrowserContext.overridePermissions', function() { + describe('BrowserContext.overridePermissions', function () { function getPermission(page, name) { - return page.evaluate(name => navigator.permissions.query({name}).then(result => result.state), name); + return page.evaluate( + (name) => + navigator.permissions.query({ name }).then((result) => result.state), + name + ); } - it('should be prompt by default', async() => { - const {page, server} = getTestState(); + it('should be prompt by default', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); expect(await getPermission(page, 'geolocation')).toBe('prompt'); }); - itFailsFirefox('should deny permission when not listed', async() => { - const {page, server, context} = getTestState(); + itFailsFirefox('should deny permission when not listed', async () => { + const { page, server, context } = getTestState(); await page.goto(server.EMPTY_PAGE); await context.overridePermissions(server.EMPTY_PAGE, []); expect(await getPermission(page, 'geolocation')).toBe('denied'); }); - it('should fail when bad permission is given', async() => { - const {page, server, context} = getTestState(); + it('should fail when bad permission is given', async () => { + const { page, server, context } = getTestState(); await page.goto(server.EMPTY_PAGE); let error = null; - await context.overridePermissions(server.EMPTY_PAGE, ['foo']).catch(error_ => error = error_); + await context + .overridePermissions(server.EMPTY_PAGE, ['foo']) + .catch((error_) => (error = error_)); expect(error.message).toBe('Unknown permission: foo'); }); - itFailsFirefox('should grant permission when listed', async() => { - const {page, server, context} = getTestState(); + itFailsFirefox('should grant permission when listed', async () => { + const { page, server, context } = getTestState(); await page.goto(server.EMPTY_PAGE); await context.overridePermissions(server.EMPTY_PAGE, ['geolocation']); expect(await getPermission(page, 'geolocation')).toBe('granted'); }); - itFailsFirefox('should reset permissions', async() => { - const {page, server, context} = getTestState(); + itFailsFirefox('should reset permissions', async () => { + const { page, server, context } = getTestState(); await page.goto(server.EMPTY_PAGE); await context.overridePermissions(server.EMPTY_PAGE, ['geolocation']); @@ -236,71 +253,98 @@ describe('Page', function() { await context.clearPermissionOverrides(); expect(await getPermission(page, 'geolocation')).toBe('prompt'); }); - itFailsFirefox('should trigger permission onchange', async() => { - const {page, server, context} = getTestState(); + itFailsFirefox('should trigger permission onchange', async () => { + const { page, server, context } = getTestState(); await page.goto(server.EMPTY_PAGE); await page.evaluate(() => { window.events = []; - return navigator.permissions.query({name: 'geolocation'}).then(function(result) { - window.events.push(result.state); - result.onchange = function() { + return navigator.permissions + .query({ name: 'geolocation' }) + .then(function (result) { window.events.push(result.state); - }; - }); + result.onchange = function () { + window.events.push(result.state); + }; + }); }); expect(await page.evaluate(() => window.events)).toEqual(['prompt']); await context.overridePermissions(server.EMPTY_PAGE, []); - expect(await page.evaluate(() => window.events)).toEqual(['prompt', 'denied']); + expect(await page.evaluate(() => window.events)).toEqual([ + 'prompt', + 'denied', + ]); await context.overridePermissions(server.EMPTY_PAGE, ['geolocation']); - expect(await page.evaluate(() => window.events)).toEqual(['prompt', 'denied', 'granted']); - await context.clearPermissionOverrides(); - expect(await page.evaluate(() => window.events)).toEqual(['prompt', 'denied', 'granted', 'prompt']); - }); - itFailsFirefox('should isolate permissions between browser contexs', async() => { - const {page, server, context, browser} = getTestState(); - - await page.goto(server.EMPTY_PAGE); - const otherContext = await browser.createIncognitoBrowserContext(); - const otherPage = await otherContext.newPage(); - await otherPage.goto(server.EMPTY_PAGE); - expect(await getPermission(page, 'geolocation')).toBe('prompt'); - expect(await getPermission(otherPage, 'geolocation')).toBe('prompt'); - - await context.overridePermissions(server.EMPTY_PAGE, []); - await otherContext.overridePermissions(server.EMPTY_PAGE, ['geolocation']); - expect(await getPermission(page, 'geolocation')).toBe('denied'); - expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); - + expect(await page.evaluate(() => window.events)).toEqual([ + 'prompt', + 'denied', + 'granted', + ]); await context.clearPermissionOverrides(); - expect(await getPermission(page, 'geolocation')).toBe('prompt'); - expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); - - await otherContext.close(); + expect(await page.evaluate(() => window.events)).toEqual([ + 'prompt', + 'denied', + 'granted', + 'prompt', + ]); }); + itFailsFirefox( + 'should isolate permissions between browser contexs', + async () => { + const { page, server, context, browser } = getTestState(); + + await page.goto(server.EMPTY_PAGE); + const otherContext = await browser.createIncognitoBrowserContext(); + const otherPage = await otherContext.newPage(); + await otherPage.goto(server.EMPTY_PAGE); + expect(await getPermission(page, 'geolocation')).toBe('prompt'); + expect(await getPermission(otherPage, 'geolocation')).toBe('prompt'); + + await context.overridePermissions(server.EMPTY_PAGE, []); + await otherContext.overridePermissions(server.EMPTY_PAGE, [ + 'geolocation', + ]); + expect(await getPermission(page, 'geolocation')).toBe('denied'); + expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); + + await context.clearPermissionOverrides(); + expect(await getPermission(page, 'geolocation')).toBe('prompt'); + expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); + + await otherContext.close(); + } + ); }); - describe('Page.setGeolocation', function() { - itFailsFirefox('should work', async() => { - const {page, server, context} = getTestState(); + describe('Page.setGeolocation', function () { + itFailsFirefox('should work', async () => { + const { page, server, context } = getTestState(); await context.overridePermissions(server.PREFIX, ['geolocation']); await page.goto(server.EMPTY_PAGE); - await page.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}); - }))); + await page.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 + longitude: 10, }); }); - it('should throw when invalid longitude', async() => { - const {page} = getTestState(); + it('should throw when invalid longitude', async () => { + const { page } = getTestState(); let error = null; try { - await page.setGeolocation({longitude: 200, latitude: 10}); + await page.setGeolocation({ longitude: 200, latitude: 10 }); } catch (error_) { error = error_; } @@ -308,20 +352,20 @@ describe('Page', function() { }); }); - describeFailsFirefox('Page.setOfflineMode', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Page.setOfflineMode', function () { + it('should work', async () => { + const { page, server } = getTestState(); await page.setOfflineMode(true); let error = null; - await page.goto(server.EMPTY_PAGE).catch(error_ => error = error_); + await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_)); expect(error).toBeTruthy(); await page.setOfflineMode(false); const response = await page.reload(); expect(response.status()).toBe(200); }); - it('should emulate navigator.onLine', async() => { - const {page} = getTestState(); + it('should emulate navigator.onLine', async () => { + const { page } = getTestState(); expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); await page.setOfflineMode(true); @@ -331,70 +375,87 @@ describe('Page', function() { }); }); - describe('ExecutionContext.queryObjects', function() { - itFailsFirefox('should work', async() => { - const {page} = getTestState(); + describe('ExecutionContext.queryObjects', function () { + itFailsFirefox('should work', async () => { + const { page } = getTestState(); // Instantiate an object - await page.evaluate(() => window.set = new Set(['hello', 'world'])); + await page.evaluate(() => (window.set = new Set(['hello', 'world']))); const prototypeHandle = await page.evaluateHandle(() => Set.prototype); const objectsHandle = await page.queryObjects(prototypeHandle); - const count = await page.evaluate(objects => objects.length, objectsHandle); + const count = await page.evaluate( + (objects) => objects.length, + objectsHandle + ); expect(count).toBe(1); - const values = await page.evaluate(objects => Array.from(objects[0].values()), objectsHandle); + const values = await page.evaluate( + (objects) => Array.from(objects[0].values()), + objectsHandle + ); expect(values).toEqual(['hello', 'world']); }); - itFailsFirefox('should work for non-blank page', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work for non-blank page', async () => { + const { page, server } = getTestState(); // Instantiate an object await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => window.set = new Set(['hello', 'world'])); + await page.evaluate(() => (window.set = new Set(['hello', 'world']))); const prototypeHandle = await page.evaluateHandle(() => Set.prototype); const objectsHandle = await page.queryObjects(prototypeHandle); - const count = await page.evaluate(objects => objects.length, objectsHandle); + const count = await page.evaluate( + (objects) => objects.length, + objectsHandle + ); expect(count).toBe(1); }); - it('should fail for disposed handles', async() => { - const {page} = getTestState(); + it('should fail for disposed handles', async () => { + const { page } = getTestState(); - const prototypeHandle = await page.evaluateHandle(() => HTMLBodyElement.prototype); + const prototypeHandle = await page.evaluateHandle( + () => HTMLBodyElement.prototype + ); await prototypeHandle.dispose(); let error = null; - await page.queryObjects(prototypeHandle).catch(error_ => error = error_); + await page + .queryObjects(prototypeHandle) + .catch((error_) => (error = error_)); expect(error.message).toBe('Prototype JSHandle is disposed!'); }); - it('should fail primitive values as prototypes', async() => { - const {page} = getTestState(); + it('should fail primitive values as prototypes', async () => { + const { page } = getTestState(); const prototypeHandle = await page.evaluateHandle(() => 42); let error = null; - await page.queryObjects(prototypeHandle).catch(error_ => error = error_); - expect(error.message).toBe('Prototype JSHandle must not be referencing primitive value'); + await page + .queryObjects(prototypeHandle) + .catch((error_) => (error = error_)); + expect(error.message).toBe( + 'Prototype JSHandle must not be referencing primitive value' + ); }); }); - describeFailsFirefox('Page.Events.Console', function() { - it('should work', async() => { - const {page} = getTestState(); + describeFailsFirefox('Page.Events.Console', function () { + it('should work', async () => { + const { page } = getTestState(); let message = null; - page.once('console', m => message = m); + page.once('console', (m) => (message = m)); await Promise.all([ - page.evaluate(() => console.log('hello', 5, {foo: 'bar'})), - waitEvent(page, 'console') + page.evaluate(() => console.log('hello', 5, { foo: 'bar' })), + waitEvent(page, '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'}); + expect(await message.args()[2].jsonValue()).toEqual({ foo: 'bar' }); }); - it('should work for different console API calls', async() => { - const {page} = getTestState(); + it('should work for different console API calls', async () => { + const { page } = getTestState(); const messages = []; - page.on('console', msg => messages.push(msg)); + 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. @@ -406,11 +467,16 @@ describe('Page', function() { 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.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([ + expect(messages.slice(1).map((msg) => msg.text())).toEqual([ 'calling console.trace', 'calling console.dir', 'calling console.warn', @@ -418,33 +484,34 @@ describe('Page', function() { 'JSHandle@promise', ]); }); - it('should not fail for window object', async() => { - const {page} = getTestState(); + it('should not fail for window object', async () => { + const { page } = getTestState(); let message = null; - page.once('console', msg => message = msg); + page.once('console', (msg) => (message = msg)); await Promise.all([ page.evaluate(() => console.error(window)), - waitEvent(page, 'console') + waitEvent(page, 'console'), ]); expect(message.text()).toBe('JSHandle@object'); }); - it('should trigger correct Log', async() => { - const {page, server, isChrome} = getTestState(); + it('should trigger correct Log', async () => { + const { page, server, isChrome } = getTestState(); await page.goto('about:blank'); const [message] = await Promise.all([ waitEvent(page, 'console'), - page.evaluate(async url => fetch(url).catch(error => {}), server.EMPTY_PAGE) + page.evaluate( + async (url) => fetch(url).catch((error) => {}), + server.EMPTY_PAGE + ), ]); expect(message.text()).toContain('Access-Control-Allow-Origin'); - if (isChrome) - expect(message.type()).toEqual('error'); - else - expect(message.type()).toEqual('warn'); + if (isChrome) expect(message.type()).toEqual('error'); + else expect(message.type()).toEqual('warn'); }); - it('should have location when fetch fails', async() => { - const {page, server} = getTestState(); + it('should have location when fetch fails', async () => { + const { page, server } = getTestState(); // The point of this test is to make sure that we report console messages from // Log domain: https://vanilla.aslushnikov.com/?Log.entryAdded @@ -457,11 +524,11 @@ describe('Page', function() { expect(message.type()).toEqual('error'); expect(message.location()).toEqual({ url: 'http://wat/', - lineNumber: undefined + lineNumber: undefined, }); }); - it('should have location for console API calls', async() => { - const {page, server, isChrome} = getTestState(); + it('should have location for console API calls', async () => { + const { page, server, isChrome } = getTestState(); await page.goto(server.EMPTY_PAGE); const [message] = await Promise.all([ @@ -477,48 +544,57 @@ describe('Page', function() { }); }); // @see https://github.com/puppeteer/puppeteer/issues/3865 - it('should not throw when there are console messages in detached iframes', async() => { - const {page, server} = getTestState(); + it('should not throw when there are console messages in detached iframes', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.evaluate(async() => { + await page.evaluate(async () => { // 1. Create a popup that Puppeteer is not connected to. - const win = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=200,top=0,left=0'); - await new Promise(x => win.onload = x); + const win = window.open( + window.location.href, + 'Title', + 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=200,top=0,left=0' + ); + await new Promise((x) => (win.onload = x)); // 2. In this popup, create an iframe that console.logs a message. win.document.body.innerHTML = ``; const frame = win.document.querySelector('iframe'); - await new Promise(x => frame.onload = x); + await new Promise((x) => (frame.onload = x)); // 3. After that, remove the iframe. frame.remove(); }); - const popupTarget = page.browserContext().targets().find(target => target !== page.target()); + const popupTarget = page + .browserContext() + .targets() + .find((target) => target !== page.target()); // 4. Connect to the popup and make sure it doesn't throw. await popupTarget.page(); }); }); - describe('Page.Events.DOMContentLoaded', function() { - it('should fire when expected', async() => { - const {page} = getTestState(); + describe('Page.Events.DOMContentLoaded', function () { + it('should fire when expected', async () => { + const { page } = getTestState(); page.goto('about:blank'); await waitEvent(page, 'domcontentloaded'); }); }); - describeFailsFirefox('Page.metrics', function() { - it('should get metrics from a page', async() => { - const {page} = getTestState(); + describeFailsFirefox('Page.metrics', function () { + it('should get metrics from a page', async () => { + const { page } = getTestState(); await page.goto('about:blank'); const metrics = await page.metrics(); checkMetrics(metrics); }); - it('metrics event fired on console.timeStamp', async() => { - const {page} = getTestState(); + it('metrics event fired on console.timeStamp', async () => { + const { page } = getTestState(); - const metricsPromise = new Promise(fulfill => page.once('metrics', fulfill)); + const metricsPromise = new Promise((fulfill) => + page.once('metrics', fulfill) + ); await page.evaluate(() => console.timeStamp('test42')); const metrics = await metricsPromise; expect(metrics.title).toBe('test42'); @@ -549,9 +625,9 @@ describe('Page', function() { } }); - describe('Page.waitForRequest', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describe('Page.waitForRequest', function () { + it('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); const [request] = await Promise.all([ @@ -560,58 +636,66 @@ describe('Page', function() { 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() => { - const {page, server} = getTestState(); + it('should work with predicate', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); const [request] = await Promise.all([ - page.waitForRequest(request => request.url() === server.PREFIX + '/digits/2.png'), + page.waitForRequest( + (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() => { - const {page, puppeteer} = getTestState(); + it('should respect timeout', async () => { + const { page, puppeteer } = getTestState(); let error = null; - await page.waitForRequest(() => false, {timeout: 1}).catch(error_ => error = error_); + await page + .waitForRequest(() => false, { timeout: 1 }) + .catch((error_) => (error = error_)); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - it('should respect default timeout', async() => { - const {page, puppeteer} = getTestState(); + it('should respect default timeout', async () => { + const { page, puppeteer } = getTestState(); let error = null; page.setDefaultTimeout(1); - await page.waitForRequest(() => false).catch(error_ => error = error_); + await page + .waitForRequest(() => false) + .catch((error_) => (error = error_)); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - it('should work with no timeout', async() => { - const {page, server} = getTestState(); + it('should work with no timeout', async () => { + const { page, server } = getTestState(); 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)) + 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() { - itFailsFirefox('should work', async() => { - const {page, server} = getTestState(); + describe('Page.waitForResponse', function () { + itFailsFirefox('should work', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); const [response] = await Promise.all([ @@ -620,90 +704,98 @@ describe('Page', function() { 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() => { - const {page, puppeteer} = getTestState(); + it('should respect timeout', async () => { + const { page, puppeteer } = getTestState(); let error = null; - await page.waitForResponse(() => false, {timeout: 1}).catch(error_ => error = error_); + await page + .waitForResponse(() => false, { timeout: 1 }) + .catch((error_) => (error = error_)); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - it('should respect default timeout', async() => { - const {page, puppeteer} = getTestState(); + it('should respect default timeout', async () => { + const { page, puppeteer } = getTestState(); let error = null; page.setDefaultTimeout(1); - await page.waitForResponse(() => false).catch(error_ => error = error_); + await page + .waitForResponse(() => false) + .catch((error_) => (error = error_)); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - itFailsFirefox('should work with predicate', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work with predicate', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); const [response] = await Promise.all([ - page.waitForResponse(response => response.url() === server.PREFIX + '/digits/2.png'), + page.waitForResponse( + (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'); }); - itFailsFirefox('should work with no timeout', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work with no timeout', async () => { + const { page, server } = getTestState(); 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)) + 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'); }); }); - describeFailsFirefox('Page.exposeFunction', function() { - it('should work', async() => { - const {page} = getTestState(); + describeFailsFirefox('Page.exposeFunction', function () { + it('should work', async () => { + const { page } = getTestState(); - await page.exposeFunction('compute', function(a, b) { + await page.exposeFunction('compute', function (a, b) { return a * b; }); - const result = await page.evaluate(async function() { + const result = await page.evaluate(async function () { return await compute(9, 4); }); expect(result).toBe(36); }); - it('should throw exception in page context', async() => { - const {page} = getTestState(); + it('should throw exception in page context', async () => { + const { page } = getTestState(); - await page.exposeFunction('woof', function() { + await page.exposeFunction('woof', function () { throw new Error('WOOF WOOF'); }); - const {message, stack} = await page.evaluate(async() => { + const { message, stack } = await page.evaluate(async () => { try { await woof(); } catch (error) { - return {message: error.message, stack: error.stack}; + return { message: error.message, stack: error.stack }; } }); expect(message).toBe('WOOF WOOF'); expect(stack).toContain(__filename); }); - it('should support throwing "null"', async() => { - const {page} = getTestState(); + it('should support throwing "null"', async () => { + const { page } = getTestState(); - await page.exposeFunction('woof', function() { + await page.exposeFunction('woof', function () { throw null; }); - const thrown = await page.evaluate(async() => { + const thrown = await page.evaluate(async () => { try { await woof(); } catch (error) { @@ -712,100 +804,104 @@ describe('Page', function() { }); expect(thrown).toBe(null); }); - it('should be callable from-inside evaluateOnNewDocument', async() => { - const {page} = getTestState(); + it('should be callable from-inside evaluateOnNewDocument', async () => { + const { page } = getTestState(); let called = false; - await page.exposeFunction('woof', function() { + await page.exposeFunction('woof', function () { called = true; }); await page.evaluateOnNewDocument(() => woof()); await page.reload(); expect(called).toBe(true); }); - it('should survive navigation', async() => { - const {page, server} = getTestState(); + it('should survive navigation', async () => { + const { page, server } = getTestState(); - await page.exposeFunction('compute', function(a, b) { + await page.exposeFunction('compute', function (a, b) { return a * b; }); await page.goto(server.EMPTY_PAGE); - const result = await page.evaluate(async function() { + const result = await page.evaluate(async function () { return await compute(9, 4); }); expect(result).toBe(36); }); - it('should await returned promise', async() => { - const {page} = getTestState(); + it('should await returned promise', async () => { + const { page } = getTestState(); - await page.exposeFunction('compute', function(a, b) { + await page.exposeFunction('compute', function (a, b) { return Promise.resolve(a * b); }); - const result = await page.evaluate(async function() { + const result = await page.evaluate(async function () { return await compute(3, 5); }); expect(result).toBe(15); }); - it('should work on frames', async() => { - const {page, server} = getTestState(); + it('should work on frames', async () => { + const { page, server } = getTestState(); - await page.exposeFunction('compute', function(a, b) { + 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() { + const result = await frame.evaluate(async function () { return await compute(3, 5); }); expect(result).toBe(15); }); - it('should work on frames before navigation', async() => { - const {page, server} = getTestState(); + it('should work on frames before navigation', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/frames/nested-frames.html'); - await page.exposeFunction('compute', function(a, b) { + await page.exposeFunction('compute', function (a, b) { return Promise.resolve(a * b); }); const frame = page.frames()[1]; - const result = await frame.evaluate(async function() { + const result = await frame.evaluate(async function () { return await compute(3, 5); }); expect(result).toBe(15); }); - it('should work with complex objects', async() => { - const {page} = getTestState(); + it('should work with complex objects', async () => { + const { page } = getTestState(); - await page.exposeFunction('complexObject', function(a, b) { - return {x: a.x + b.x}; + await page.exposeFunction('complexObject', function (a, b) { + return { x: a.x + b.x }; }); - const result = await page.evaluate(async() => complexObject({x: 5}, {x: 2})); + const result = await page.evaluate(async () => + complexObject({ x: 5 }, { x: 2 }) + ); expect(result.x).toBe(7); }); }); - describeFailsFirefox('Page.Events.PageError', function() { - it('should fire', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Page.Events.PageError', function () { + it('should fire', async () => { + const { page, server } = getTestState(); let error = null; - page.once('pageerror', e => error = e); + page.once('pageerror', (e) => (error = e)); await Promise.all([ page.goto(server.PREFIX + '/error.html'), - waitEvent(page, 'pageerror') + waitEvent(page, 'pageerror'), ]); expect(error.message).toContain('Fancy'); }); }); - describe('Page.setUserAgent', function() { - itFailsFirefox('should work', async() => { - const {page, server} = getTestState(); + describe('Page.setUserAgent', function () { + itFailsFirefox('should work', async () => { + const { page, server } = getTestState(); - expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla'); + expect(await page.evaluate(() => navigator.userAgent)).toContain( + 'Mozilla' + ); await page.setUserAgent('foobar'); const [request] = await Promise.all([ server.waitForRequest('/empty.html'), @@ -813,10 +909,12 @@ describe('Page', function() { ]); expect(request.headers['user-agent']).toBe('foobar'); }); - itFailsFirefox('should work for subframes', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work for subframes', async () => { + const { page, server } = getTestState(); - expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla'); + expect(await page.evaluate(() => navigator.userAgent)).toContain( + 'Mozilla' + ); await page.setUserAgent('foobar'); const [request] = await Promise.all([ server.waitForRequest('/empty.html'), @@ -824,160 +922,187 @@ describe('Page', function() { ]); expect(request.headers['user-agent']).toBe('foobar'); }); - it('should emulate device user-agent', async() => { - const {page, server, puppeteer} = getTestState(); + it('should emulate device user-agent', async () => { + const { page, server, puppeteer } = getTestState(); await page.goto(server.PREFIX + '/mobile.html'); - expect(await page.evaluate(() => navigator.userAgent)).not.toContain('iPhone'); + expect(await page.evaluate(() => navigator.userAgent)).not.toContain( + 'iPhone' + ); await page.setUserAgent(puppeteer.devices['iPhone 6'].userAgent); - expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone'); + expect(await page.evaluate(() => navigator.userAgent)).toContain( + 'iPhone' + ); }); }); - describeFailsFirefox('Page.setContent', function() { - const expectedOutput = '
hello
'; - it('should work', async() => { - const {page} = getTestState(); + describeFailsFirefox('Page.setContent', function () { + const expectedOutput = + '
hello
'; + it('should work', async () => { + const { page } = getTestState(); await page.setContent('
hello
'); const result = await page.content(); expect(result).toBe(expectedOutput); }); - it('should work with doctype', async() => { - const {page} = getTestState(); + it('should work with doctype', async () => { + const { page } = getTestState(); 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() => { - const {page} = getTestState(); + it('should work with HTML 4 doctype', async () => { + const { page } = getTestState(); - const doctype = ''; await page.setContent(`${doctype}
hello
`); const result = await page.content(); expect(result).toBe(`${doctype}${expectedOutput}`); }); - it('should respect timeout', async() => { - const {page, server, puppeteer} = getTestState(); + it('should respect timeout', async () => { + const { page, server, puppeteer } = getTestState(); const imgPath = '/img.png'; // stall for image server.setRoute(imgPath, (req, res) => {}); let error = null; - await page.setContent(``, {timeout: 1}).catch(error_ => error = error_); + await page + .setContent(``, { + timeout: 1, + }) + .catch((error_) => (error = error_)); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - it('should respect default navigation timeout', async() => { - const {page, server, puppeteer} = getTestState(); + it('should respect default navigation timeout', async () => { + const { page, server, puppeteer } = getTestState(); page.setDefaultNavigationTimeout(1); const imgPath = '/img.png'; // stall for image server.setRoute(imgPath, (req, res) => {}); let error = null; - await page.setContent(``).catch(error_ => error = error_); + await page + .setContent(``) + .catch((error_) => (error = error_)); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); - it('should await resources to load', async() => { - const {page, server} = getTestState(); + it('should await resources to load', async () => { + const { page, server } = getTestState(); const imgPath = '/img.png'; let imgResponse = null; - server.setRoute(imgPath, (req, res) => imgResponse = res); + server.setRoute(imgPath, (req, res) => (imgResponse = res)); let loaded = false; - const contentPromise = page.setContent(``).then(() => loaded = true); + 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() => { - const {page} = getTestState(); + it('should work fast enough', async () => { + const { page } = getTestState(); - for (let i = 0; i < 20; ++i) - await page.setContent('
yo
'); + for (let i = 0; i < 20; ++i) await page.setContent('
yo
'); }); - it('should work with tricky content', async() => { - const {page} = getTestState(); + it('should work with tricky content', async () => { + const { page } = getTestState(); await page.setContent('
hello world
' + '\x7F'); - expect(await page.$eval('div', div => div.textContent)).toBe('hello world'); + expect(await page.$eval('div', (div) => div.textContent)).toBe( + 'hello world' + ); }); - it('should work with accents', async() => { - const {page} = getTestState(); + it('should work with accents', async () => { + const { page } = getTestState(); await page.setContent('
aberración
'); - expect(await page.$eval('div', div => div.textContent)).toBe('aberración'); + expect(await page.$eval('div', (div) => div.textContent)).toBe( + 'aberración' + ); }); - it('should work with emojis', async() => { - const {page} = getTestState(); + it('should work with emojis', async () => { + const { page } = getTestState(); await page.setContent('
🐥
'); - expect(await page.$eval('div', div => div.textContent)).toBe('🐥'); + expect(await page.$eval('div', (div) => div.textContent)).toBe('🐥'); }); - it('should work with newline', async() => { - const {page} = getTestState(); + it('should work with newline', async () => { + const { page } = getTestState(); await page.setContent('
\n
'); - expect(await page.$eval('div', div => div.textContent)).toBe('\n'); + expect(await page.$eval('div', (div) => div.textContent)).toBe('\n'); }); }); - describeFailsFirefox('Page.setBypassCSP', function() { - it('should bypass CSP meta tag', async() => { - const {page, server} = getTestState(); + describeFailsFirefox('Page.setBypassCSP', function () { + it('should bypass CSP meta tag', async () => { + const { page, server } = getTestState(); // Make sure CSP prohibits addScriptTag. await page.goto(server.PREFIX + '/csp.html'); - await page.addScriptTag({content: 'window.__injected = 42;'}).catch(error => void error); + await page + .addScriptTag({ content: 'window.__injected = 42;' }) + .catch((error) => void error); expect(await page.evaluate(() => window.__injected)).toBe(undefined); // By-pass CSP and try one more time. await page.setBypassCSP(true); await page.reload(); - await page.addScriptTag({content: 'window.__injected = 42;'}); + await page.addScriptTag({ content: 'window.__injected = 42;' }); expect(await page.evaluate(() => window.__injected)).toBe(42); }); - it('should bypass CSP header', async() => { - const {page, server} = getTestState(); + it('should bypass CSP header', async () => { + const { page, server } = getTestState(); // Make sure CSP prohibits addScriptTag. server.setCSP('/empty.html', 'default-src "self"'); await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({content: 'window.__injected = 42;'}).catch(error => void error); + await page + .addScriptTag({ content: 'window.__injected = 42;' }) + .catch((error) => void error); expect(await page.evaluate(() => window.__injected)).toBe(undefined); // By-pass CSP and try one more time. await page.setBypassCSP(true); await page.reload(); - await page.addScriptTag({content: 'window.__injected = 42;'}); + await page.addScriptTag({ content: 'window.__injected = 42;' }); expect(await page.evaluate(() => window.__injected)).toBe(42); }); - it('should bypass after cross-process navigation', async() => { - const {page, server} = getTestState(); + it('should bypass after cross-process navigation', async () => { + const { page, server } = getTestState(); await page.setBypassCSP(true); await page.goto(server.PREFIX + '/csp.html'); - await page.addScriptTag({content: 'window.__injected = 42;'}); + await page.addScriptTag({ content: 'window.__injected = 42;' }); expect(await page.evaluate(() => window.__injected)).toBe(42); await page.goto(server.CROSS_PROCESS_PREFIX + '/csp.html'); - await page.addScriptTag({content: 'window.__injected = 42;'}); + await page.addScriptTag({ content: 'window.__injected = 42;' }); expect(await page.evaluate(() => window.__injected)).toBe(42); }); - it('should bypass CSP in iframes as well', async() => { - const {page, server} = getTestState(); + it('should bypass CSP in iframes as well', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); { // Make sure CSP prohibits addScriptTag in an iframe. - const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); - await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(error => void error); + const frame = await utils.attachFrame( + page, + 'frame1', + server.PREFIX + '/csp.html' + ); + await frame + .addScriptTag({ content: 'window.__injected = 42;' }) + .catch((error) => void error); expect(await frame.evaluate(() => window.__injected)).toBe(undefined); } @@ -986,16 +1111,22 @@ describe('Page', function() { await page.reload(); { - const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); - await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(error => void error); + const frame = await utils.attachFrame( + page, + 'frame1', + server.PREFIX + '/csp.html' + ); + await frame + .addScriptTag({ content: 'window.__injected = 42;' }) + .catch((error) => void error); expect(await frame.evaluate(() => window.__injected)).toBe(42); } }); }); - describe('Page.addScriptTag', function() { - it('should throw an error if no options are provided', async() => { - const {page} = getTestState(); + describe('Page.addScriptTag', function () { + it('should throw an error if no options are provided', async () => { + const { page } = getTestState(); let error = null; try { @@ -1003,107 +1134,125 @@ describe('Page', function() { } catch (error_) { error = error_; } - expect(error.message).toBe('Provide an object with a `url`, `path` or `content` property'); + expect(error.message).toBe( + 'Provide an object with a `url`, `path` or `content` property' + ); }); - it('should work with a url', async() => { - const {page, server} = getTestState(); + it('should work with a url', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - const scriptHandle = await page.addScriptTag({url: '/injectedfile.js'}); + const scriptHandle = await page.addScriptTag({ url: '/injectedfile.js' }); expect(scriptHandle.asElement()).not.toBeNull(); expect(await page.evaluate(() => __injected)).toBe(42); }); - it('should work with a url and type=module', async() => { - const {page, server} = getTestState(); + it('should work with a url and type=module', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({url: '/es6/es6import.js', type: 'module'}); + 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() => { - const {page, server} = getTestState(); + it('should work with a path and type=module', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({path: path.join(__dirname, 'assets/es6/es6pathimport.js'), type: 'module'}); + 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() => { - const {page, server} = getTestState(); + it('should work with a content and type=module', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({content: `import num from '/es6/es6module.js';window.__es6injected = num;`, type: 'module'}); + 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() => { - const {page, server} = getTestState(); + it('should throw an error if loading from url fail', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); let error = null; try { - await page.addScriptTag({url: '/nonexistfile.js'}); + await page.addScriptTag({ url: '/nonexistfile.js' }); } catch (error_) { error = error_; } expect(error.message).toBe('Loading script from /nonexistfile.js failed'); }); - it('should work with a path', async() => { - const {page, server} = getTestState(); + it('should work with a path', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - const scriptHandle = await page.addScriptTag({path: path.join(__dirname, 'assets/injectedfile.js')}); + const scriptHandle = await page.addScriptTag({ + path: path.join(__dirname, 'assets/injectedfile.js'), + }); expect(scriptHandle.asElement()).not.toBeNull(); expect(await page.evaluate(() => __injected)).toBe(42); }); - it('should include sourcemap when path is provided', async() => { - const {page, server} = getTestState(); + it('should include sourcemap when path is provided', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.addScriptTag({path: path.join(__dirname, 'assets/injectedfile.js')}); + 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')); }); - it('should work with content', async() => { - const {page, server} = getTestState(); + it('should work with content', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - const scriptHandle = await page.addScriptTag({content: 'window.__injected = 35;'}); + const scriptHandle = await page.addScriptTag({ + content: 'window.__injected = 35;', + }); expect(scriptHandle.asElement()).not.toBeNull(); expect(await page.evaluate(() => __injected)).toBe(35); }); // @see https://github.com/puppeteer/puppeteer/issues/4840 - xit('should throw when added with content to the CSP page', async() => { - const {page, server} = getTestState(); + xit('should throw when added with content to the CSP page', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/csp.html'); let error = null; - await page.addScriptTag({content: 'window.__injected = 35;'}).catch(error_ => error = error_); + await page + .addScriptTag({ content: 'window.__injected = 35;' }) + .catch((error_) => (error = error_)); expect(error).toBeTruthy(); }); - it('should throw when added with URL to the CSP page', async() => { - const {page, server} = getTestState(); + it('should throw when added with URL to the CSP page', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/csp.html'); let error = null; - await page.addScriptTag({url: server.CROSS_PROCESS_PREFIX + '/injectedfile.js'}).catch(error_ => error = error_); + await page + .addScriptTag({ url: server.CROSS_PROCESS_PREFIX + '/injectedfile.js' }) + .catch((error_) => (error = error_)); expect(error).toBeTruthy(); }); }); - describe('Page.addStyleTag', function() { - it('should throw an error if no options are provided', async() => { - const {page} = getTestState(); + describe('Page.addStyleTag', function () { + it('should throw an error if no options are provided', async () => { + const { page } = getTestState(); let error = null; try { @@ -1111,81 +1260,113 @@ describe('Page', function() { } catch (error_) { error = error_; } - expect(error.message).toBe('Provide an object with a `url`, `path` or `content` property'); + expect(error.message).toBe( + 'Provide an object with a `url`, `path` or `content` property' + ); }); - it('should work with a url', async() => { - const {page, server} = getTestState(); + it('should work with a url', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - const styleHandle = await page.addStyleTag({url: '/injectedstyle.css'}); + 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)'); + 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() => { - const {page, server} = getTestState(); + it('should throw an error if loading from url fail', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); let error = null; try { - await page.addStyleTag({url: '/nonexistfile.js'}); + await page.addStyleTag({ url: '/nonexistfile.js' }); } catch (error_) { error = error_; } expect(error.message).toBe('Loading style from /nonexistfile.js failed'); }); - it('should work with a path', async() => { - const {page, server} = getTestState(); + it('should work with a path', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - const styleHandle = await page.addStyleTag({path: path.join(__dirname, 'assets/injectedstyle.css')}); + 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)'); + expect( + await page.evaluate( + `window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')` + ) + ).toBe('rgb(255, 0, 0)'); }); - it('should include sourcemap when path is provided', async() => { - const {page, server} = getTestState(); + it('should include sourcemap when path is provided', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - await page.addStyleTag({path: path.join(__dirname, 'assets/injectedstyle.css')}); + await page.addStyleTag({ + path: path.join(__dirname, 'assets/injectedstyle.css'), + }); const styleHandle = await page.$('style'); - const styleContent = await page.evaluate(style => style.innerHTML, styleHandle); + const styleContent = await page.evaluate( + (style) => style.innerHTML, + styleHandle + ); expect(styleContent).toContain(path.join('assets', 'injectedstyle.css')); }); - itFailsFirefox('should work with content', async() => { - const {page, server} = getTestState(); + itFailsFirefox('should work with content', async () => { + const { page, server } = getTestState(); await page.goto(server.EMPTY_PAGE); - const styleHandle = await page.addStyleTag({content: 'body { background-color: green; }'}); + 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)'); - }); - - itFailsFirefox('should throw when added with content to the CSP page', async() => { - const {page, server} = getTestState(); - - await page.goto(server.PREFIX + '/csp.html'); - let error = null; - await page.addStyleTag({content: 'body { background-color: green; }'}).catch(error_ => error = error_); - expect(error).toBeTruthy(); - }); + expect( + await page.evaluate( + `window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')` + ) + ).toBe('rgb(0, 128, 0)'); + }); + + itFailsFirefox( + 'should throw when added with content to the CSP page', + async () => { + const { page, server } = getTestState(); + + await page.goto(server.PREFIX + '/csp.html'); + let error = null; + await page + .addStyleTag({ content: 'body { background-color: green; }' }) + .catch((error_) => (error = error_)); + expect(error).toBeTruthy(); + } + ); - it('should throw when added with URL to the CSP page', async() => { - const {page, server} = getTestState(); + it('should throw when added with URL to the CSP page', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/csp.html'); let error = null; - await page.addStyleTag({url: server.CROSS_PROCESS_PREFIX + '/injectedstyle.css'}).catch(error_ => error = error_); + await page + .addStyleTag({ + url: server.CROSS_PROCESS_PREFIX + '/injectedstyle.css', + }) + .catch((error_) => (error = error_)); expect(error).toBeTruthy(); }); }); - describe('Page.url', function() { - it('should work', async() => { - const {page, server} = getTestState(); + describe('Page.url', function () { + it('should work', async () => { + const { page, server } = getTestState(); expect(page.url()).toBe('about:blank'); await page.goto(server.EMPTY_PAGE); @@ -1193,25 +1374,29 @@ describe('Page', function() { }); }); - describeFailsFirefox('Page.setJavaScriptEnabled', function() { - it('should work', async() => { - const {page} = getTestState(); + describeFailsFirefox('Page.setJavaScriptEnabled', function () { + it('should work', async () => { + const { page } = getTestState(); await page.setJavaScriptEnabled(false); - await page.goto('data:text/html, '); + await page.goto( + 'data:text/html, ' + ); let error = null; - await page.evaluate('something').catch(error_ => error = error_); + await page.evaluate('something').catch((error_) => (error = error_)); expect(error.message).toContain('something is not defined'); await page.setJavaScriptEnabled(true); - await page.goto('data:text/html, '); + await page.goto( + 'data:text/html, ' + ); expect(await page.evaluate('something')).toBe('forbidden'); }); }); - describe('Page.setCacheEnabled', function() { - it('should enable or disable the cache based on the state passed', async() => { - const {page, server} = getTestState(); + describe('Page.setCacheEnabled', function () { + it('should enable or disable the cache based on the state passed', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/cached/one-style.html'); const [cachedRequest] = await Promise.all([ @@ -1228,146 +1413,186 @@ describe('Page', function() { ]); expect(nonCachedRequest.headers['if-modified-since']).toBe(undefined); }); - itFailsFirefox('should stay disabled when toggling request interception on/off', async() => { - const {page, server} = getTestState(); - - await page.setCacheEnabled(false); - await page.setRequestInterception(true); - await page.setRequestInterception(false); - - await page.goto(server.PREFIX + '/cached/one-style.html'); - const [nonCachedRequest] = await Promise.all([ - server.waitForRequest('/cached/one-style.html'), - page.reload(), - ]); - expect(nonCachedRequest.headers['if-modified-since']).toBe(undefined); - }); + itFailsFirefox( + 'should stay disabled when toggling request interception on/off', + async () => { + const { page, server } = getTestState(); + + await page.setCacheEnabled(false); + await page.setRequestInterception(true); + await page.setRequestInterception(false); + + await page.goto(server.PREFIX + '/cached/one-style.html'); + const [nonCachedRequest] = await Promise.all([ + server.waitForRequest('/cached/one-style.html'), + page.reload(), + ]); + expect(nonCachedRequest.headers['if-modified-since']).toBe(undefined); + } + ); }); - describe('printing to PDF', function() { - itFailsFirefox('can print to PDF and save to file', async() => { - // Printing to pdf is currently only supported in headless - const {isHeadless, page} = getTestState(); + describe('printing to PDF', function () { + itFailsFirefox('can print to PDF and save to file', async () => { + // Printing to pdf is currently only supported in headless + const { isHeadless, page } = getTestState(); if (!isHeadless) return; const outputFile = __dirname + '/assets/output.pdf'; - await page.pdf({path: outputFile}); + await page.pdf({ path: outputFile }); expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0); fs.unlinkSync(outputFile); }); }); - describe('Page.title', function() { - it('should return the page title', async() => { - const {page, server} = getTestState(); + describe('Page.title', function () { + it('should return the page title', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/title.html'); expect(await page.title()).toBe('Woof-Woof'); }); }); - describe('Page.select', function() { - it('should select single option', async() => { - const {page, server} = getTestState(); + describe('Page.select', function () { + it('should select single option', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/select.html'); await page.select('select', 'blue'); expect(await page.evaluate(() => result.onInput)).toEqual(['blue']); expect(await page.evaluate(() => result.onChange)).toEqual(['blue']); }); - it('should select only first option', async() => { - const {page, server} = getTestState(); + it('should select only first option', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/select.html'); await page.select('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() => { - const {page, server} = getTestState(); + it('should not throw when select causes navigation', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/select.html'); - await page.$eval('select', select => select.addEventListener('input', () => window.location = '/empty.html')); + await page.$eval('select', (select) => + select.addEventListener( + 'input', + () => (window.location = '/empty.html') + ) + ); await Promise.all([ page.select('select', 'blue'), page.waitForNavigation(), ]); expect(page.url()).toContain('empty.html'); }); - it('should select multiple options', async() => { - const {page, server} = getTestState(); + it('should select multiple options', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/select.html'); await page.evaluate(() => makeMultiple()); await page.select('select', 'blue', 'green', 'red'); - expect(await page.evaluate(() => result.onInput)).toEqual(['blue', 'green', 'red']); - expect(await page.evaluate(() => result.onChange)).toEqual(['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 respect event bubbling', async() => { - const {page, server} = getTestState(); + it('should respect event bubbling', async () => { + const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/select.html'); await page.select('select', 'blue'); - expect(await page.evaluate(() => result.onBubblingInput)).toEqual(['blue']); - expect(await page.evaluate(() => result.onBubblingChange)).toEqual(['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 ', async () => { + const { page, server } = getTestState(); let error = null; await page.goto(server.PREFIX + '/input/select.html'); - await page.select('body', '').catch(error_ => error = error_); + await page.select('body', '').catch((error_) => (error = error_)); expect(error.message).toContain('Element is not a