Bug 1817934 - [puppeteer] Vendor Puppeteer v19.7.2. r=webdriver-reviewers,jdescottes

Differential Revision: https://phabricator.services.mozilla.com/D170799
This commit is contained in:
Henrik Skupin 2023-02-25 10:14:26 +00:00
parent 13840898bb
commit 33c84b1801
213 changed files with 7845 additions and 35529 deletions

View file

@ -117,10 +117,14 @@ _OPT\.OBJ/
# Ignore node_module directories and npm artifacts # Ignore node_module directories and npm artifacts
^remote/test/puppeteer/.*\.tsbuildinfo ^remote/test/puppeteer/.*\.tsbuildinfo
^remote/test/puppeteer/.*/lib/
^remote/test/puppeteer/.*/node_modules/
^remote/test/puppeteer/.*/\.wireit/
^remote/test/puppeteer/\.devcontainer/
^remote/test/puppeteer/\.github ^remote/test/puppeteer/\.github
^remote/test/puppeteer/\.husky ^remote/test/puppeteer/\.husky
^remote/test/puppeteer/\.wireit/
^remote/test/puppeteer/coverage/ ^remote/test/puppeteer/coverage/
^remote/test/puppeteer/.devcontainer/
^remote/test/puppeteer/docker/ ^remote/test/puppeteer/docker/
^remote/test/puppeteer/docs/puppeteer-core\.api\.json ^remote/test/puppeteer/docs/puppeteer-core\.api\.json
^remote/test/puppeteer/docs/puppeteer\.api\.json ^remote/test/puppeteer/docs/puppeteer\.api\.json

View file

@ -22,6 +22,8 @@ generated/
# IDE Artifacts # IDE Artifacts
.vscode .vscode
!.vscode/extensions.json
!.vscode/*.template.json
.devcontainer .devcontainer
# Misc # Misc

View file

@ -110,6 +110,14 @@ module.exports = {
], ],
'import/extensions': ['error', 'ignorePackages'], 'import/extensions': ['error', 'ignorePackages'],
'import/order': [
'error',
{
'newlines-between': 'always',
alphabetize: {order: 'asc', caseInsensitive: true},
},
],
'no-restricted-syntax': [ 'no-restricted-syntax': [
'error', 'error',
// Don't allow underscored declarations on camelCased variables/properties. // Don't allow underscored declarations on camelCased variables/properties.

View file

@ -23,6 +23,7 @@ generated/
# IDE Artifacts # IDE Artifacts
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json
!.vscode/*.template.json
.devcontainer .devcontainer
# Misc # Misc

View file

@ -1,6 +1,6 @@
{ {
"packages/puppeteer": "19.6.0", "packages/puppeteer": "19.7.2",
"packages/puppeteer-core": "19.6.0", "packages/puppeteer-core": "19.7.2",
"packages/testserver": "0.6.0", "packages/testserver": "0.6.0",
"packages/ng-schematics": "0.1.0" "packages/ng-schematics": "0.1.0"
} }

View file

@ -44,9 +44,17 @@ npm i puppeteer
``` ```
When you install Puppeteer, it automatically downloads a recent version of When you install Puppeteer, it automatically downloads a recent version of
Chromium (~170MB macOS, ~282MB Linux, ~280MB Windows) that is Chromium (~170MB macOS, ~282MB Linux, ~280MB Windows) that is [guaranteed to
[guaranteed to work](https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) work](https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy)
with Puppeteer. For a version of Puppeteer without installation, see with Puppeteer. The browser is downloaded to the `$HOME/.cache/puppeteer` folder
by default (starting with Puppeteer v19.0.0).
If you deploy a project using Puppeteer to a hosting provider, such as Render or
Heroku, you might need to reconfigure the location of the cache to be within
your project folder (see an example below) because not all hosting providers
include `$HOME/.cache` into the project's deployment.
For a version of Puppeteer without the browser installation, see
[`puppeteer-core`](#puppeteer-core). [`puppeteer-core`](#puppeteer-core).
#### Configuration #### Configuration
@ -149,7 +157,7 @@ import puppeteer from 'puppeteer';
await page.waitForSelector(searchResultSelector); await page.waitForSelector(searchResultSelector);
await page.click(searchResultSelector); await page.click(searchResultSelector);
// Localte the full title with a unique string // Locate the full title with a unique string
const textSelector = await page.waitForSelector( const textSelector = await page.waitForSelector(
'text/Customize and automate' 'text/Customize and automate'
); );

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,6 @@ origin:
description: Headless Chrome Node API description: Headless Chrome Node API
license: Apache-2.0 license: Apache-2.0
name: puppeteer name: puppeteer
release: e13e9647fc0d917da94af8851a09ed318fb0e07c release: puppeteer-v19.7.2
url: /Users/alexandraborovova/Projects/puppeteer url: https://github.com/puppeteer/puppeteer.git
schema: 1 schema: 1

View file

@ -19,7 +19,7 @@
"@microsoft/api-extractor": "7.33.7", "@microsoft/api-extractor": "7.33.7",
"@microsoft/api-extractor-model": "7.25.3", "@microsoft/api-extractor-model": "7.25.3",
"@pptr/testserver": "file:packages/testserver", "@pptr/testserver": "file:packages/testserver",
"@rollup/plugin-commonjs": "24.0.0", "@rollup/plugin-commonjs": "24.0.1",
"@rollup/plugin-node-resolve": "15.0.1", "@rollup/plugin-node-resolve": "15.0.1",
"@types/debug": "4.1.7", "@types/debug": "4.1.7",
"@types/diff": "5.0.2", "@types/diff": "5.0.2",
@ -40,7 +40,6 @@
"@typescript-eslint/eslint-plugin": "5.46.1", "@typescript-eslint/eslint-plugin": "5.46.1",
"@typescript-eslint/parser": "5.46.1", "@typescript-eslint/parser": "5.46.1",
"c8": "7.12.0", "c8": "7.12.0",
"chromium-bidi": "0.4.3",
"commonmark": "0.30.0", "commonmark": "0.30.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"diff": "5.1.0", "diff": "5.1.0",
@ -70,8 +69,7 @@
"pngjs": "6.0.0", "pngjs": "6.0.0",
"prettier": "2.8.1", "prettier": "2.8.1",
"puppeteer": "file:packages/puppeteer", "puppeteer": "file:packages/puppeteer",
"rollup": "2.79.1", "rollup": "3.12.1",
"rollup-plugin-dts": "4.2.2",
"semver": "7.3.8", "semver": "7.3.8",
"sinon": "15.0.1", "sinon": "15.0.1",
"source-map-support": "0.5.21", "source-map-support": "0.5.21",
@ -103,11 +101,11 @@
} }
}, },
"node_modules/@angular-devkit/architect": { "node_modules/@angular-devkit/architect": {
"version": "0.1501.2", "version": "0.1501.6",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1501.2.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1501.6.tgz",
"integrity": "sha512-AfORVGLN0FBIUXO3FkfGOKu+Gz6oJjF8Bu8cPn27duiI0wszxGNY3fATKwbSg7JcKx1oQS/G7RjyC5OiTA6a0Q==", "integrity": "sha512-u07zZFlfrg0Qn4mu5M9Nz0pH2Yd2028XF/73980PsZMxwkSm4diF08v4bHk3UyR7yPT7phwvt4znj6ryZhx1gw==",
"dependencies": { "dependencies": {
"@angular-devkit/core": "15.1.2", "@angular-devkit/core": "15.1.6",
"rxjs": "6.6.7" "rxjs": "6.6.7"
}, },
"engines": { "engines": {
@ -116,10 +114,10 @@
"yarn": ">= 1.13.0" "yarn": ">= 1.13.0"
} }
}, },
"node_modules/@angular-devkit/architect/node_modules/@angular-devkit/core": { "node_modules/@angular-devkit/core": {
"version": "15.1.2", "version": "15.1.6",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.1.2.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.1.6.tgz",
"integrity": "sha512-wkLZYvTZt30Ge6Z83Gxsr6mO1TIHCu3SImdE0zwW63EdU9o1NYkU74z1D9VUZ9Up7uHi1cHs/dssbxUuZ4eWOA==", "integrity": "sha512-jGgxyRjecVf6lEyqDxz7ltMEndNPxIg720pk6r40fgsu0dU8w9vjJSJe7k0XdJiXVRcN6wZa/J5nO/xcwWVIsA==",
"dependencies": { "dependencies": {
"ajv": "8.12.0", "ajv": "8.12.0",
"ajv-formats": "2.1.1", "ajv-formats": "2.1.1",
@ -141,7 +139,7 @@
} }
} }
}, },
"node_modules/@angular-devkit/architect/node_modules/ajv": { "node_modules/@angular-devkit/core/node_modules/ajv": {
"version": "8.12.0", "version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
@ -156,7 +154,7 @@
"url": "https://github.com/sponsors/epoberezkin" "url": "https://github.com/sponsors/epoberezkin"
} }
}, },
"node_modules/@angular-devkit/architect/node_modules/source-map": { "node_modules/@angular-devkit/core/node_modules/source-map": {
"version": "0.7.4", "version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
@ -1386,14 +1384,18 @@
"resolved": "test", "resolved": "test",
"link": true "link": true
}, },
"node_modules/@puppeteer/browsers": {
"resolved": "packages/browsers",
"link": true
},
"node_modules/@puppeteer/ng-schematics": { "node_modules/@puppeteer/ng-schematics": {
"resolved": "packages/ng-schematics", "resolved": "packages/ng-schematics",
"link": true "link": true
}, },
"node_modules/@rollup/plugin-commonjs": { "node_modules/@rollup/plugin-commonjs": {
"version": "24.0.0", "version": "24.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz",
"integrity": "sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g==", "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@rollup/pluginutils": "^5.0.1", "@rollup/pluginutils": "^5.0.1",
@ -1421,18 +1423,6 @@
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true "dev": true
}, },
"node_modules/@rollup/plugin-commonjs/node_modules/magic-string": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
"integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.13"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@rollup/plugin-node-resolve": { "node_modules/@rollup/plugin-node-resolve": {
"version": "15.0.1", "version": "15.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz",
@ -2673,13 +2663,14 @@
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
}, },
"node_modules/chromium-bidi": { "node_modules/chromium-bidi": {
"version": "0.4.3", "version": "0.4.4",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.3.tgz", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.4.tgz",
"integrity": "sha512-A40H1rdpJqkTdnGhnYDzMhtDdIbkXNFj2wgIfivMXL7LyHFDmBtv1hdyycDhnxtYunbPLDZtTs/n+ZT5j7Vnew==", "integrity": "sha512-4BX5cSaponuvVT1+SbLYTOAgDoVtX/Khoc9UsbFJ/AsPVUeFAM3RiIDFI6XFhLYMi9WmVJqh1ZH+dRpNKkKwiQ==",
"dev": true, "dependencies": {
"mitt": "3.0.0"
},
"peerDependencies": { "peerDependencies": {
"devtools-protocol": "*", "devtools-protocol": "*"
"mitt": "*"
} }
}, },
"node_modules/cli-cursor": { "node_modules/cli-cursor": {
@ -2717,7 +2708,6 @@
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"dependencies": { "dependencies": {
"string-width": "^4.2.0", "string-width": "^4.2.0",
"strip-ansi": "^6.0.1", "strip-ansi": "^6.0.1",
@ -3036,9 +3026,9 @@
} }
}, },
"node_modules/devtools-protocol": { "node_modules/devtools-protocol": {
"version": "0.0.1082910", "version": "0.0.1094867",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1082910.tgz", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz",
"integrity": "sha512-RqoZ2GmqaNxyx+99L/RemY5CkwG9D0WEfOKxekwCRXOGrDCep62ngezEJUVMq6rISYQ+085fJnWDQqGHlxVNww==" "integrity": "sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ=="
}, },
"node_modules/diff": { "node_modules/diff": {
"version": "5.1.0", "version": "5.1.0",
@ -5617,9 +5607,9 @@
"dev": true "dev": true
}, },
"node_modules/json5": { "node_modules/json5": {
"version": "2.2.1", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true, "dev": true,
"bin": { "bin": {
"json5": "lib/cli.js" "json5": "lib/cli.js"
@ -5851,12 +5841,12 @@
} }
}, },
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.26.7", "version": "0.27.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
"integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==", "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"sourcemap-codec": "^1.4.8" "@jridgewell/sourcemap-codec": "^1.4.13"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@ -6049,8 +6039,7 @@
"node_modules/mitt": { "node_modules/mitt": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz",
"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ=="
"dev": true
}, },
"node_modules/mkdirp-classic": { "node_modules/mkdirp-classic": {
"version": "0.5.3", "version": "0.5.3",
@ -7363,55 +7352,21 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "2.79.1", "version": "3.12.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", "integrity": "sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==",
"dev": true, "dev": true,
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
}, },
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=14.18.0",
"npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/rollup-plugin-dts": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-4.2.2.tgz",
"integrity": "sha512-A3g6Rogyko/PXeKoUlkjxkP++8UDVpgA7C+Tdl77Xj4fgEaIjPSnxRmR53EzvoYy97VMVwLAOcWJudaVAuxneQ==",
"dev": true,
"dependencies": {
"magic-string": "^0.26.1"
},
"engines": {
"node": ">=v12.22.11"
},
"funding": {
"url": "https://github.com/sponsors/Swatinem"
},
"optionalDependencies": {
"@babel/code-frame": "^7.16.7"
},
"peerDependencies": {
"rollup": "^2.55",
"typescript": "^4.1"
}
},
"node_modules/rollup-plugin-dts/node_modules/@babel/code-frame": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
"dev": true,
"optional": true,
"dependencies": {
"@babel/highlight": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/run-async": { "node_modules/run-async": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@ -8040,9 +7995,9 @@
} }
}, },
"node_modules/tsconfig-paths/node_modules/json5": { "node_modules/tsconfig-paths/node_modules/json5": {
"version": "1.0.1", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"minimist": "^1.2.0" "minimist": "^1.2.0"
@ -8192,7 +8147,7 @@
"version": "4.9.4", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
"dev": true, "devOptional": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@ -8469,10 +8424,9 @@
} }
}, },
"node_modules/yargs": { "node_modules/yargs": {
"version": "17.6.0", "version": "17.7.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.0.tgz",
"integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==", "integrity": "sha512-dwqOPg5trmrre9+v8SUo2q/hAwyKoVfu8OC1xPHKJGNdxAvPl4sKxL4vBnh3bQz/ZvvGAFeA5H3ou2kcOY8sQQ==",
"dev": true,
"dependencies": { "dependencies": {
"cliui": "^8.0.1", "cliui": "^8.0.1",
"escalade": "^3.1.1", "escalade": "^3.1.1",
@ -8480,7 +8434,7 @@
"require-directory": "^2.1.1", "require-directory": "^2.1.1",
"string-width": "^4.2.3", "string-width": "^4.2.3",
"y18n": "^5.0.5", "y18n": "^5.0.5",
"yargs-parser": "^21.0.0" "yargs-parser": "^21.1.1"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@ -8543,7 +8497,6 @@
"version": "21.1.1", "version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }
@ -8606,14 +8559,62 @@
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
}, },
"packages/browsers": {
"name": "@puppeteer/browsers",
"version": "0.0.1",
"license": "Apache-2.0",
"dependencies": {
"debug": "4.3.4",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"progress": "2.0.3",
"proxy-from-env": "1.1.0",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3",
"yargs": "17.7.0"
},
"bin": {
"browsers": "lib/cjs/browsers.js"
},
"devDependencies": {
"@types/node": "^14.15.0",
"@types/yargs": "17.0.22"
},
"engines": {
"node": ">=14.1.0"
},
"peerDependencies": {
"typescript": ">= 4.7.4"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"packages/browsers/node_modules/@types/node": {
"version": "14.18.36",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz",
"integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==",
"dev": true
},
"packages/browsers/node_modules/@types/yargs": {
"version": "17.0.22",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz",
"integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==",
"dev": true,
"dependencies": {
"@types/yargs-parser": "*"
}
},
"packages/ng-schematics": { "packages/ng-schematics": {
"name": "@puppeteer/ng-schematics", "name": "@puppeteer/ng-schematics",
"version": "0.1.0", "version": "0.1.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@angular-devkit/architect": "^0.1501.2", "@angular-devkit/architect": "^0.1501.6",
"@angular-devkit/core": "^15.1.2", "@angular-devkit/core": "^15.1.6",
"@angular-devkit/schematics": "^15.1.2" "@angular-devkit/schematics": "^15.1.6"
}, },
"devDependencies": { "devDependencies": {
"@schematics/angular": "^14.2.8", "@schematics/angular": "^14.2.8",
@ -8623,37 +8624,12 @@
"node": ">=14.1.0" "node": ">=14.1.0"
} }
}, },
"packages/ng-schematics/node_modules/@angular-devkit/core": {
"version": "15.1.2",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.1.2.tgz",
"integrity": "sha512-wkLZYvTZt30Ge6Z83Gxsr6mO1TIHCu3SImdE0zwW63EdU9o1NYkU74z1D9VUZ9Up7uHi1cHs/dssbxUuZ4eWOA==",
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"rxjs": "6.6.7",
"source-map": "0.7.4"
},
"engines": {
"node": "^14.20.0 || ^16.13.0 || >=18.10.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"chokidar": "^3.5.2"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"packages/ng-schematics/node_modules/@angular-devkit/schematics": { "packages/ng-schematics/node_modules/@angular-devkit/schematics": {
"version": "15.1.2", "version": "15.1.6",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.1.2.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.1.6.tgz",
"integrity": "sha512-HjJPm+4SS5TdAHHvdpXLv25wsvwVOn5RYs0A9MazTndlm80ct3PKeYUgakNDRFjRj8uORNlJMKmQIIhUSDjFsw==", "integrity": "sha512-cwmJFpS43zrdlmfwfHIxG/Nzg5rzFdtKrHx64ZXxNFm6JdyK2JTs/qrHUwv1FYWAcqhdiHn+00jYklMmvsvPOA==",
"dependencies": { "dependencies": {
"@angular-devkit/core": "15.1.2", "@angular-devkit/core": "15.1.6",
"jsonc-parser": "3.2.0", "jsonc-parser": "3.2.0",
"magic-string": "0.27.0", "magic-string": "0.27.0",
"ora": "5.4.1", "ora": "5.4.1",
@ -8671,21 +8647,6 @@
"integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==", "integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==",
"dev": true "dev": true
}, },
"packages/ng-schematics/node_modules/ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"packages/ng-schematics/node_modules/magic-string": { "packages/ng-schematics/node_modules/magic-string": {
"version": "0.27.0", "version": "0.27.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
@ -8697,16 +8658,8 @@
"node": ">=12" "node": ">=12"
} }
}, },
"packages/ng-schematics/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"engines": {
"node": ">= 8"
}
},
"packages/puppeteer": { "packages/puppeteer": {
"version": "19.6.0", "version": "19.7.2",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@ -8714,19 +8667,20 @@
"https-proxy-agent": "5.0.1", "https-proxy-agent": "5.0.1",
"progress": "2.0.3", "progress": "2.0.3",
"proxy-from-env": "1.1.0", "proxy-from-env": "1.1.0",
"puppeteer-core": "19.6.0" "puppeteer-core": "19.7.2"
}, },
"engines": { "engines": {
"node": ">=14.1.0" "node": ">=14.1.0"
} }
}, },
"packages/puppeteer-core": { "packages/puppeteer-core": {
"version": "19.6.0", "version": "19.7.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"chromium-bidi": "0.4.4",
"cross-fetch": "3.1.5", "cross-fetch": "3.1.5",
"debug": "4.3.4", "debug": "4.3.4",
"devtools-protocol": "0.0.1082910", "devtools-protocol": "0.0.1094867",
"extract-zip": "2.0.1", "extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1", "https-proxy-agent": "5.0.1",
"proxy-from-env": "1.1.0", "proxy-from-env": "1.1.0",
@ -8737,6 +8691,14 @@
}, },
"engines": { "engines": {
"node": ">=14.1.0" "node": ">=14.1.0"
},
"peerDependencies": {
"typescript": ">= 4.7.4"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
} }
}, },
"packages/puppeteer/node_modules/argparse": { "packages/puppeteer/node_modules/argparse": {
@ -8982,26 +8944,26 @@
} }
}, },
"@angular-devkit/architect": { "@angular-devkit/architect": {
"version": "0.1501.2", "version": "0.1501.6",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1501.2.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1501.6.tgz",
"integrity": "sha512-AfORVGLN0FBIUXO3FkfGOKu+Gz6oJjF8Bu8cPn27duiI0wszxGNY3fATKwbSg7JcKx1oQS/G7RjyC5OiTA6a0Q==", "integrity": "sha512-u07zZFlfrg0Qn4mu5M9Nz0pH2Yd2028XF/73980PsZMxwkSm4diF08v4bHk3UyR7yPT7phwvt4znj6ryZhx1gw==",
"requires": { "requires": {
"@angular-devkit/core": "15.1.2", "@angular-devkit/core": "15.1.6",
"rxjs": "6.6.7" "rxjs": "6.6.7"
}
},
"@angular-devkit/core": {
"version": "15.1.6",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.1.6.tgz",
"integrity": "sha512-jGgxyRjecVf6lEyqDxz7ltMEndNPxIg720pk6r40fgsu0dU8w9vjJSJe7k0XdJiXVRcN6wZa/J5nO/xcwWVIsA==",
"requires": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"rxjs": "6.6.7",
"source-map": "0.7.4"
}, },
"dependencies": { "dependencies": {
"@angular-devkit/core": {
"version": "15.1.2",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.1.2.tgz",
"integrity": "sha512-wkLZYvTZt30Ge6Z83Gxsr6mO1TIHCu3SImdE0zwW63EdU9o1NYkU74z1D9VUZ9Up7uHi1cHs/dssbxUuZ4eWOA==",
"requires": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"rxjs": "6.6.7",
"source-map": "0.7.4"
}
},
"ajv": { "ajv": {
"version": "8.12.0", "version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
@ -10007,34 +9969,54 @@
} }
} }
}, },
"@puppeteer/browsers": {
"version": "file:packages/browsers",
"requires": {
"@types/node": "^14.15.0",
"@types/yargs": "17.0.22",
"debug": "4.3.4",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"progress": "2.0.3",
"proxy-from-env": "1.1.0",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3",
"yargs": "17.7.0"
},
"dependencies": {
"@types/node": {
"version": "14.18.36",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz",
"integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==",
"dev": true
},
"@types/yargs": {
"version": "17.0.22",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz",
"integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
}
}
}
},
"@puppeteer/ng-schematics": { "@puppeteer/ng-schematics": {
"version": "file:packages/ng-schematics", "version": "file:packages/ng-schematics",
"requires": { "requires": {
"@angular-devkit/architect": "^0.1501.2", "@angular-devkit/architect": "^0.1501.6",
"@angular-devkit/core": "^15.1.2", "@angular-devkit/core": "^15.1.6",
"@angular-devkit/schematics": "^15.1.2", "@angular-devkit/schematics": "^15.1.6",
"@schematics/angular": "^14.2.8", "@schematics/angular": "^14.2.8",
"@types/node": "^14.15.0" "@types/node": "^14.15.0"
}, },
"dependencies": { "dependencies": {
"@angular-devkit/core": {
"version": "15.1.2",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.1.2.tgz",
"integrity": "sha512-wkLZYvTZt30Ge6Z83Gxsr6mO1TIHCu3SImdE0zwW63EdU9o1NYkU74z1D9VUZ9Up7uHi1cHs/dssbxUuZ4eWOA==",
"requires": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"jsonc-parser": "3.2.0",
"rxjs": "6.6.7",
"source-map": "0.7.4"
}
},
"@angular-devkit/schematics": { "@angular-devkit/schematics": {
"version": "15.1.2", "version": "15.1.6",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.1.2.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.1.6.tgz",
"integrity": "sha512-HjJPm+4SS5TdAHHvdpXLv25wsvwVOn5RYs0A9MazTndlm80ct3PKeYUgakNDRFjRj8uORNlJMKmQIIhUSDjFsw==", "integrity": "sha512-cwmJFpS43zrdlmfwfHIxG/Nzg5rzFdtKrHx64ZXxNFm6JdyK2JTs/qrHUwv1FYWAcqhdiHn+00jYklMmvsvPOA==",
"requires": { "requires": {
"@angular-devkit/core": "15.1.2", "@angular-devkit/core": "15.1.6",
"jsonc-parser": "3.2.0", "jsonc-parser": "3.2.0",
"magic-string": "0.27.0", "magic-string": "0.27.0",
"ora": "5.4.1", "ora": "5.4.1",
@ -10047,17 +10029,6 @@
"integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==", "integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==",
"dev": true "dev": true
}, },
"ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"magic-string": { "magic-string": {
"version": "0.27.0", "version": "0.27.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
@ -10065,18 +10036,13 @@
"requires": { "requires": {
"@jridgewell/sourcemap-codec": "^1.4.13" "@jridgewell/sourcemap-codec": "^1.4.13"
} }
},
"source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="
} }
} }
}, },
"@rollup/plugin-commonjs": { "@rollup/plugin-commonjs": {
"version": "24.0.0", "version": "24.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz",
"integrity": "sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g==", "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==",
"dev": true, "dev": true,
"requires": { "requires": {
"@rollup/pluginutils": "^5.0.1", "@rollup/pluginutils": "^5.0.1",
@ -10092,15 +10058,6 @@
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true "dev": true
},
"magic-string": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
"integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
"dev": true,
"requires": {
"@jridgewell/sourcemap-codec": "^1.4.13"
}
} }
} }
}, },
@ -11056,11 +11013,12 @@
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
}, },
"chromium-bidi": { "chromium-bidi": {
"version": "0.4.3", "version": "0.4.4",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.3.tgz", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.4.tgz",
"integrity": "sha512-A40H1rdpJqkTdnGhnYDzMhtDdIbkXNFj2wgIfivMXL7LyHFDmBtv1hdyycDhnxtYunbPLDZtTs/n+ZT5j7Vnew==", "integrity": "sha512-4BX5cSaponuvVT1+SbLYTOAgDoVtX/Khoc9UsbFJ/AsPVUeFAM3RiIDFI6XFhLYMi9WmVJqh1ZH+dRpNKkKwiQ==",
"dev": true, "requires": {
"requires": {} "mitt": "3.0.0"
}
}, },
"cli-cursor": { "cli-cursor": {
"version": "3.1.0", "version": "3.1.0",
@ -11085,7 +11043,6 @@
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"requires": { "requires": {
"string-width": "^4.2.0", "string-width": "^4.2.0",
"strip-ansi": "^6.0.1", "strip-ansi": "^6.0.1",
@ -11320,9 +11277,9 @@
} }
}, },
"devtools-protocol": { "devtools-protocol": {
"version": "0.0.1082910", "version": "0.0.1094867",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1082910.tgz", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz",
"integrity": "sha512-RqoZ2GmqaNxyx+99L/RemY5CkwG9D0WEfOKxekwCRXOGrDCep62ngezEJUVMq6rISYQ+085fJnWDQqGHlxVNww==" "integrity": "sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ=="
}, },
"diff": { "diff": {
"version": "5.1.0", "version": "5.1.0",
@ -13126,9 +13083,9 @@
"dev": true "dev": true
}, },
"json5": { "json5": {
"version": "2.2.1", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true "dev": true
}, },
"jsonc-parser": { "jsonc-parser": {
@ -13318,12 +13275,12 @@
} }
}, },
"magic-string": { "magic-string": {
"version": "0.26.7", "version": "0.27.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
"integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==", "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
"dev": true, "dev": true,
"requires": { "requires": {
"sourcemap-codec": "^1.4.8" "@jridgewell/sourcemap-codec": "^1.4.13"
} }
}, },
"make-dir": { "make-dir": {
@ -13460,8 +13417,7 @@
"mitt": { "mitt": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz",
"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ=="
"dev": true
}, },
"mkdirp-classic": { "mkdirp-classic": {
"version": "0.5.3", "version": "0.5.3",
@ -14130,7 +14086,7 @@
"https-proxy-agent": "5.0.1", "https-proxy-agent": "5.0.1",
"progress": "2.0.3", "progress": "2.0.3",
"proxy-from-env": "1.1.0", "proxy-from-env": "1.1.0",
"puppeteer-core": "19.6.0" "puppeteer-core": "19.7.2"
}, },
"dependencies": { "dependencies": {
"argparse": { "argparse": {
@ -14162,9 +14118,10 @@
"puppeteer-core": { "puppeteer-core": {
"version": "file:packages/puppeteer-core", "version": "file:packages/puppeteer-core",
"requires": { "requires": {
"chromium-bidi": "0.4.4",
"cross-fetch": "3.1.5", "cross-fetch": "3.1.5",
"debug": "4.3.4", "debug": "4.3.4",
"devtools-protocol": "0.0.1082910", "devtools-protocol": "0.0.1094867",
"extract-zip": "2.0.1", "extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1", "https-proxy-agent": "5.0.1",
"proxy-from-env": "1.1.0", "proxy-from-env": "1.1.0",
@ -14482,36 +14439,14 @@
} }
}, },
"rollup": { "rollup": {
"version": "2.79.1", "version": "3.12.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", "integrity": "sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==",
"dev": true, "dev": true,
"requires": { "requires": {
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"rollup-plugin-dts": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-4.2.2.tgz",
"integrity": "sha512-A3g6Rogyko/PXeKoUlkjxkP++8UDVpgA7C+Tdl77Xj4fgEaIjPSnxRmR53EzvoYy97VMVwLAOcWJudaVAuxneQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.16.7",
"magic-string": "^0.26.1"
},
"dependencies": {
"@babel/code-frame": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
"dev": true,
"optional": true,
"requires": {
"@babel/highlight": "^7.18.6"
}
}
}
},
"run-async": { "run-async": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@ -14981,9 +14916,9 @@
}, },
"dependencies": { "dependencies": {
"json5": { "json5": {
"version": "1.0.1", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true, "dev": true,
"requires": { "requires": {
"minimist": "^1.2.0" "minimist": "^1.2.0"
@ -15090,7 +15025,7 @@
"version": "4.9.4", "version": "4.9.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
"dev": true "devOptional": true
}, },
"unbox-primitive": { "unbox-primitive": {
"version": "1.0.2", "version": "1.0.2",
@ -15300,10 +15235,9 @@
"dev": true "dev": true
}, },
"yargs": { "yargs": {
"version": "17.6.0", "version": "17.7.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.0.tgz",
"integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==", "integrity": "sha512-dwqOPg5trmrre9+v8SUo2q/hAwyKoVfu8OC1xPHKJGNdxAvPl4sKxL4vBnh3bQz/ZvvGAFeA5H3ou2kcOY8sQQ==",
"dev": true,
"requires": { "requires": {
"cliui": "^8.0.1", "cliui": "^8.0.1",
"escalade": "^3.1.1", "escalade": "^3.1.1",
@ -15311,14 +15245,13 @@
"require-directory": "^2.1.1", "require-directory": "^2.1.1",
"string-width": "^4.2.3", "string-width": "^4.2.3",
"y18n": "^5.0.5", "y18n": "^5.0.5",
"yargs-parser": "^21.0.0" "yargs-parser": "^21.1.1"
}, },
"dependencies": { "dependencies": {
"yargs-parser": { "yargs-parser": {
"version": "21.1.1", "version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
"dev": true
} }
} }
}, },

View file

@ -7,13 +7,14 @@
}, },
"scripts": { "scripts": {
"bisect": "tsx tools/bisect.ts", "bisect": "tsx tools/bisect.ts",
"build": "npm run build --workspaces --if-present", "build": "wireit",
"build:docs": "wireit",
"check:pinned-deps": "tsx tools/ensure-pinned-deps", "check:pinned-deps": "tsx tools/ensure-pinned-deps",
"check": "npm run check --workspaces --if-present && run-p check:*", "check": "npm run check --workspaces --if-present && run-p check:*",
"clean": "npm run clean --workspaces --if-present && rimraf **/.wireit", "clean": "rimraf **/.wireit && npm run clean --workspaces --if-present",
"commitlint": "commitlint --from=HEAD~1", "commitlint": "commitlint --from=HEAD~1",
"debug": "mocha --inspect-brk", "debug": "mocha --inspect-brk",
"docs": "run-s build generate:markdown", "docs": "run-s build:docs generate:markdown",
"format:eslint": "eslint --ext js --ext ts --fix .", "format:eslint": "eslint --ext js --ext ts --fix .",
"format:prettier": "prettier --write .", "format:prettier": "prettier --write .",
"format": "run-s format:*", "format": "run-s format:*",
@ -25,17 +26,93 @@
"prepare": "husky install", "prepare": "husky install",
"test-install": "npm run test --workspace @puppeteer-test/installation", "test-install": "npm run test --workspace @puppeteer-test/installation",
"test-types": "tsd -t packages/puppeteer", "test-types": "tsd -t packages/puppeteer",
"test:chrome:headful": "npm test -- --test-suite chrome-headful", "test:chrome:headful": "wireit",
"test:chrome:headless-chrome": "npm test -- --test-suite chrome-new-headless", "test:chrome:new-headless": "wireit",
"test:chrome:headless": "npm test -- --test-suite chrome-headless", "test:chrome:headless": "wireit",
"test:chrome:bidi": "npm test -- --test-suite chrome-bidi", "test:chrome:bidi": "wireit",
"test:chrome": "run-s test:chrome:*", "test:chrome": "wireit",
"test:firefox:bidi": "npm test -- --test-suite firefox-bidi", "test:firefox:bidi": "wireit",
"test:firefox:headful": "npm test -- --test-suite firefox-headful", "test:firefox:headful": "wireit",
"test:firefox:headless": "npm test -- --test-suite firefox-headless", "test:firefox:headless": "wireit",
"test:firefox": "run-s test:firefox:*", "test:firefox": "wireit",
"test": "cross-env PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT=20000 node tools/mochaRunner/lib/main.js" "test": "cross-env PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT=20000 node tools/mochaRunner/lib/main.js"
}, },
"wireit": {
"build": {
"dependencies": [
"./packages/browsers:build",
"./packages/ng-schematics:build",
"./packages/puppeteer-core:build",
"./packages/puppeteer:build",
"./packages/testserver:build",
"./test:build",
"./test/installation:build"
]
},
"build:docs": {
"dependencies": [
"./packages/puppeteer:build:docs",
"./packages/puppeteer-core:build:docs"
]
},
"test:chrome:headful": {
"command": "npm test -- --test-suite chrome-headful",
"dependencies": [
"./test:build"
]
},
"test:chrome:headless": {
"command": "npm test -- --test-suite chrome-headless",
"dependencies": [
"./test:build"
]
},
"test:chrome:new-headless": {
"command": "npm test -- --test-suite chrome-new-headless",
"dependencies": [
"./test:build"
]
},
"test:chrome:bidi": {
"command": "npm test -- --test-suite chrome-bidi",
"dependencies": [
"./test:build"
]
},
"test:firefox:headful": {
"command": "npm test -- --test-suite firefox-headful",
"dependencies": [
"./test:build"
]
},
"test:firefox:headless": {
"command": "npm test -- --test-suite firefox-headless",
"dependencies": [
"./test:build"
]
},
"test:firefox:bidi": {
"command": "npm test -- --test-suite firefox-bidi",
"dependencies": [
"./test:build"
]
},
"test:chrome": {
"dependencies": [
"test:chrome:headful",
"test:chrome:headless",
"test:chrome:new-headless",
"test:chrome:bidi"
]
},
"test:firefox": {
"dependencies": [
"test:firefox:headful",
"test:firefox:headless",
"test:firefox:bidi"
]
}
},
"devDependencies": { "devDependencies": {
"@actions/core": "1.10.0", "@actions/core": "1.10.0",
"@commitlint/cli": "17.3.0", "@commitlint/cli": "17.3.0",
@ -44,7 +121,7 @@
"@microsoft/api-extractor": "7.33.7", "@microsoft/api-extractor": "7.33.7",
"@microsoft/api-extractor-model": "7.25.3", "@microsoft/api-extractor-model": "7.25.3",
"@pptr/testserver": "file:packages/testserver", "@pptr/testserver": "file:packages/testserver",
"@rollup/plugin-commonjs": "24.0.0", "@rollup/plugin-commonjs": "24.0.1",
"@rollup/plugin-node-resolve": "15.0.1", "@rollup/plugin-node-resolve": "15.0.1",
"@types/debug": "4.1.7", "@types/debug": "4.1.7",
"@types/diff": "5.0.2", "@types/diff": "5.0.2",
@ -65,7 +142,6 @@
"@typescript-eslint/eslint-plugin": "5.46.1", "@typescript-eslint/eslint-plugin": "5.46.1",
"@typescript-eslint/parser": "5.46.1", "@typescript-eslint/parser": "5.46.1",
"c8": "7.12.0", "c8": "7.12.0",
"chromium-bidi": "0.4.3",
"commonmark": "0.30.0", "commonmark": "0.30.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"diff": "5.1.0", "diff": "5.1.0",
@ -95,8 +171,7 @@
"pngjs": "6.0.0", "pngjs": "6.0.0",
"prettier": "2.8.1", "prettier": "2.8.1",
"puppeteer": "file:packages/puppeteer", "puppeteer": "file:packages/puppeteer",
"rollup": "2.79.1", "rollup": "3.12.1",
"rollup-plugin-dts": "4.2.2",
"semver": "7.3.8", "semver": "7.3.8",
"sinon": "15.0.1", "sinon": "15.0.1",
"source-map-support": "0.5.21", "source-map-support": "0.5.21",

View file

@ -0,0 +1,6 @@
module.exports = {
logLevel: 'debug',
spec: 'test/build/**/*.spec.js',
exit: !!process.env.CI,
reporter: 'spec',
};

View file

@ -0,0 +1,3 @@
# @puppeteer/browsers
TODO

View file

@ -0,0 +1,87 @@
{
"name": "@puppeteer/browsers",
"version": "0.0.1",
"description": "Download and launch browsers",
"scripts": {
"build": "wireit",
"build:test": "wireit",
"clean": "tsc --build --clean && rimraf lib",
"test": "wireit"
},
"bin": {
"@puppeteer/browsers": "lib/cjs/browsers.js"
},
"wireit": {
"build": {
"command": "tsc -b",
"files": [
"src/**/*.ts",
"tsconfig.json"
],
"output": [
"lib/**"
]
},
"build:test": {
"command": "tsc -b test/src/tsconfig.json",
"files": [
"test/**/*.ts",
"test/src/tsconfig.json"
],
"output": [
"test/build/**"
],
"dependencies": [
"build"
]
},
"test": {
"command": "mocha",
"files": [
".mocharc.cjs"
],
"dependencies": [
"build:test"
]
}
},
"keywords": [
"puppeteer",
"browsers"
],
"repository": {
"type": "git",
"url": "https://github.com/puppeteer/puppeteer/tree/main/packages/browsers"
},
"author": "The Chromium Authors",
"license": "Apache-2.0",
"engines": {
"node": ">=14.1.0"
},
"files": [
"lib",
"!*.tsbuildinfo"
],
"dependencies": {
"debug": "4.3.4",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"progress": "2.0.3",
"proxy-from-env": "1.1.0",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3",
"yargs": "17.7.0"
},
"devDependencies": {
"@types/node": "^14.15.0",
"@types/yargs": "17.0.22"
},
"peerDependencies": {
"typescript": ">= 4.7.4"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
}

View file

@ -0,0 +1,176 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ProgressBar from 'progress';
import yargs from 'yargs';
import {hideBin} from 'yargs/helpers';
import {Browser, BrowserPlatform} from './browsers/types.js';
import {fetch} from './fetch.js';
import {computeExecutablePath, launch} from './launcher.js';
type InstallArgs = {
browser: {
name: Browser;
revision: string;
};
path?: string;
platform?: BrowserPlatform;
};
type LaunchArgs = {
browser: {
name: Browser;
revision: string;
};
path?: string;
platform?: BrowserPlatform;
detached: boolean;
};
export class CLI {
#cachePath;
constructor(cachePath = process.cwd()) {
this.#cachePath = cachePath;
}
async run(argv: string[]): Promise<void> {
await yargs(hideBin(argv))
.command(
'install <browser>',
'Download and install the specified browser',
yargs => {
yargs.positional('browser', {
description: 'The browser version',
type: 'string',
coerce: (opt): InstallArgs['browser'] => {
return {
name: this.#parseBrowser(opt),
revision: this.#parseRevision(opt),
};
},
});
},
async argv => {
const args = argv as unknown as InstallArgs;
await fetch({
browser: args.browser.name,
revision: args.browser.revision,
platform: args.platform,
cacheDir: args.path ?? this.#cachePath,
downloadProgressCallback: this.#makeProgressCallback(
args.browser.name,
args.browser.revision
),
});
}
)
.option('path', {
type: 'string',
desc: 'Path where the browsers will be downloaded to and installed from',
default: process.cwd(),
})
.option('platform', {
type: 'string',
desc: 'Platform that the binary needs to be compatible with.',
choices: Object.values(BrowserPlatform),
defaultDescription: 'Auto-detected by default.',
})
.command(
'launch <browser>',
'Launch the specified browser',
yargs => {
yargs.positional('browser', {
description: 'The browser version',
type: 'string',
coerce: (opt): LaunchArgs['browser'] => {
return {
name: this.#parseBrowser(opt),
revision: this.#parseRevision(opt),
};
},
});
},
async argv => {
const args = argv as unknown as LaunchArgs;
const executablePath = computeExecutablePath({
browser: args.browser.name,
revision: args.browser.revision,
cacheDir: args.path ?? this.#cachePath,
platform: args.platform,
});
launch({
executablePath,
detached: args.detached,
});
}
)
.option('path', {
type: 'string',
desc: 'Path where the browsers will be downloaded to and installed from',
default: process.cwd(),
})
.option('detached', {
type: 'boolean',
desc: 'Whether to detach the child process.',
default: false,
})
.option('platform', {
type: 'string',
desc: 'Platform that the binary needs to be compatible with.',
choices: Object.values(BrowserPlatform),
defaultDescription: 'Auto-detected by default.',
})
.parse();
}
#parseBrowser(version: string): Browser {
return version.split('@').shift() as Browser;
}
#parseRevision(version: string): string {
return version.split('@').pop() ?? 'latest';
}
#toMegabytes(bytes: number) {
const mb = bytes / 1024 / 1024;
return `${Math.round(mb * 10) / 10} Mb`;
}
#makeProgressCallback(browser: Browser, revision: string) {
let progressBar: ProgressBar;
let lastDownloadedBytes = 0;
return (downloadedBytes: number, totalBytes: number) => {
if (!progressBar) {
progressBar = new ProgressBar(
`Downloading ${browser} r${revision} - ${this.#toMegabytes(
totalBytes
)} [:bar] :percent :etas `,
{
complete: '=',
incomplete: ' ',
width: 20,
total: totalBytes,
}
);
}
const delta = downloadedBytes - lastDownloadedBytes;
lastDownloadedBytes = downloadedBytes;
progressBar.tick(delta);
};
}
}

View file

@ -0,0 +1,53 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import path from 'path';
import {Browser, BrowserPlatform} from './browsers/types.js';
/**
* The cache used by Puppeteer relies on the following structure:
*
* - rootDir
* -- <browser1> | browserRoot(browser1)
* ---- <platform>-<revision> | installationDir()
* ------ the browser-platform-revision
* ------ specific structure.
* -- <browser2> | browserRoot(browser2)
* ---- <platform>-<revision> | installationDir()
* ------ the browser-platform-revision
* ------ specific structure.
* @internal
*/
export class CacheStructure {
#rootDir: string;
constructor(rootDir: string) {
this.#rootDir = rootDir;
}
browserRoot(browser: Browser): string {
return path.join(this.#rootDir, browser);
}
installationDir(
browser: Browser,
platform: BrowserPlatform,
revision: string
): string {
return path.join(this.browserRoot(browser), `${platform}-${revision}`);
}
}

View file

@ -1,5 +1,7 @@
#!/usr/bin/env node
/** /**
* Copyright 2022 Google Inc. All rights reserved. * Copyright 2023 Google Inc. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,5 +16,6 @@
* limitations under the License. * limitations under the License.
*/ */
export * as BidiMapper from 'chromium-bidi/lib/cjs/bidiMapper/bidiMapper.js'; import {CLI} from './CLI.js';
export * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
new CLI().run(process.argv);

View file

@ -0,0 +1,31 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as chrome from './chrome.js';
import * as firefox from './firefox.js';
import {Browser, BrowserPlatform} from './types.js';
export const downloadUrls = {
[Browser.CHROME]: chrome.resolveDownloadUrl,
[Browser.FIREFOX]: firefox.resolveDownloadUrl,
};
export const executablePathByBrowser = {
[Browser.CHROME]: chrome.relativeExecutablePath,
[Browser.FIREFOX]: firefox.relativeExecutablePath,
};
export {Browser, BrowserPlatform};

View file

@ -0,0 +1,81 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import path from 'path';
import {BrowserPlatform} from './types.js';
function archive(platform: BrowserPlatform, revision: string): string {
switch (platform) {
case BrowserPlatform.LINUX:
return 'chrome-linux';
case BrowserPlatform.MAC_ARM:
case BrowserPlatform.MAC:
return 'chrome-mac';
case BrowserPlatform.WIN32:
case BrowserPlatform.WIN64:
// Windows archive name changed at r591479.
return parseInt(revision, 10) > 591479 ? 'chrome-win' : 'chrome-win32';
}
}
function folder(platform: BrowserPlatform): string {
switch (platform) {
case BrowserPlatform.LINUX:
return 'Linux_x64';
case BrowserPlatform.MAC_ARM:
return 'Mac_Arm';
case BrowserPlatform.MAC:
return 'Mac';
case BrowserPlatform.WIN32:
return 'Win';
case BrowserPlatform.WIN64:
return 'Win_x64';
}
}
export function resolveDownloadUrl(
platform: BrowserPlatform,
revision: string,
baseUrl = 'https://storage.googleapis.com/chromium-browser-snapshots'
): string {
return `${baseUrl}/${folder(platform)}/${revision}/${archive(
platform,
revision
)}.zip`;
}
export function relativeExecutablePath(
platform: BrowserPlatform,
_revision: string
): string {
switch (platform) {
case BrowserPlatform.MAC:
case BrowserPlatform.MAC_ARM:
return path.join(
'chrome-mac',
'Chromium.app',
'Contents',
'MacOS',
'Chromium'
);
case BrowserPlatform.LINUX:
return path.join('chrome-linux', 'chrome');
case BrowserPlatform.WIN32:
case BrowserPlatform.WIN64:
return path.join('chrome-win', 'chrome.exe');
}
}

View file

@ -0,0 +1,56 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import path from 'path';
import {BrowserPlatform} from './types.js';
function archive(platform: BrowserPlatform, revision: string): string {
switch (platform) {
case BrowserPlatform.LINUX:
return `firefox-${revision}.en-US.${platform}-x86_64.tar.bz2`;
case BrowserPlatform.MAC_ARM:
case BrowserPlatform.MAC:
return `firefox-${revision}.en-US.mac.dmg`;
case BrowserPlatform.WIN32:
case BrowserPlatform.WIN64:
return `firefox-${revision}.en-US.${platform}.zip`;
}
}
export function resolveDownloadUrl(
platform: BrowserPlatform,
revision: string,
baseUrl = 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central'
): string {
return `${baseUrl}/${archive(platform, revision)}`;
}
export function relativeExecutablePath(
platform: BrowserPlatform,
_revision: string
): string {
switch (platform) {
case BrowserPlatform.MAC_ARM:
case BrowserPlatform.MAC:
return path.join('Firefox Nightly.app', 'Contents', 'MacOS', 'firefox');
case BrowserPlatform.LINUX:
return path.join('firefox', 'firefox');
case BrowserPlatform.WIN32:
case BrowserPlatform.WIN64:
return path.join('firefox', 'firefox.exe');
}
}

View file

@ -0,0 +1,43 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as chrome from './chrome.js';
import * as firefox from './firefox.js';
/**
* Supported browsers.
*/
export enum Browser {
CHROME = 'chrome',
FIREFOX = 'firefox',
}
/**
* Platform names used to identify a OS platfrom x architecture combination in the way
* that is relevant for the browser download.
*/
export enum BrowserPlatform {
LINUX = 'linux',
MAC = 'mac',
MAC_ARM = 'mac_arm',
WIN32 = 'win32',
WIN64 = 'win64',
}
export const downloadUrls = {
[Browser.CHROME]: chrome.resolveDownloadUrl,
[Browser.FIREFOX]: firefox.resolveDownloadUrl,
};

View file

@ -0,0 +1,19 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import debug from 'debug';
export {debug};

View file

@ -0,0 +1,58 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import os from 'os';
import {BrowserPlatform} from './browsers/browsers.js';
export function detectBrowserPlatform(): BrowserPlatform | undefined {
const platform = os.platform();
switch (platform) {
case 'darwin':
return os.arch() === 'arm64'
? BrowserPlatform.MAC_ARM
: BrowserPlatform.MAC;
case 'linux':
return BrowserPlatform.LINUX;
case 'win32':
return os.arch() === 'x64' ||
// Windows 11 for ARM supports x64 emulation
(os.arch() === 'arm64' && isWindows11(os.release()))
? BrowserPlatform.WIN64
: BrowserPlatform.WIN32;
default:
return undefined;
}
}
/**
* Windows 11 is identified by the version 10.0.22000 or greater
* @internal
*/
function isWindows11(version: string): boolean {
const parts = version.split('.');
if (parts.length > 2) {
const major = parseInt(parts[0] as string, 10);
const minor = parseInt(parts[1] as string, 10);
const patch = parseInt(parts[2] as string, 10);
return (
major > 10 ||
(major === 10 && minor > 0) ||
(major === 10 && minor === 0 && patch >= 22000)
);
}
return false;
}

View file

@ -0,0 +1,140 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import assert from 'assert';
import {existsSync} from 'fs';
import {mkdir, unlink} from 'fs/promises';
import os from 'os';
import path from 'path';
import {Browser, BrowserPlatform, downloadUrls} from './browsers/browsers.js';
import {CacheStructure} from './CacheStructure.js';
import {debug} from './debug.js';
import {detectBrowserPlatform} from './detectPlatform.js';
import {unpackArchive} from './fileUtil.js';
import {downloadFile, headHttpRequest} from './httpUtil.js';
const debugFetch = debug('puppeteer:browsers:fetcher');
/**
* @public
*/
export interface Options {
/**
* Determines the path to download browsers to.
*/
cacheDir: string;
/**
* Determines which platform the browser will be suited for.
*
* @defaultValue Auto-detected.
*/
platform?: BrowserPlatform;
/**
* Determines which browser to fetch.
*/
browser: Browser;
/**
* Determines which revision to dowloand. Revision should uniquely identify
* binaries and they are used for caching.
*/
revision: string;
/**
* Provides information about the progress of the download.
*/
downloadProgressCallback?: (
downloadedBytes: number,
totalBytes: number
) => void;
}
export type InstalledBrowser = {
path: string;
browser: Browser;
revision: string;
platform: BrowserPlatform;
};
export async function fetch(options: Options): Promise<InstalledBrowser> {
options.platform ??= detectBrowserPlatform();
if (!options.platform) {
throw new Error(
`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`
);
}
const url = getDownloadUrl(
options.browser,
options.platform,
options.revision
);
const fileName = url.toString().split('/').pop();
assert(fileName, `A malformed download URL was found: ${url}.`);
const structure = new CacheStructure(options.cacheDir);
const browserRoot = structure.browserRoot(options.browser);
const archivePath = path.join(browserRoot, fileName);
if (!existsSync(browserRoot)) {
await mkdir(browserRoot, {recursive: true});
}
const outputPath = structure.installationDir(
options.browser,
options.platform,
options.revision
);
if (existsSync(outputPath)) {
return {
path: outputPath,
browser: options.browser,
platform: options.platform,
revision: options.revision,
};
}
try {
debugFetch(`Downloading binary from ${url}`);
await downloadFile(url, archivePath, options.downloadProgressCallback);
debugFetch(`Installing ${archivePath} to ${outputPath}`);
await unpackArchive(archivePath, outputPath);
} finally {
if (existsSync(archivePath)) {
await unlink(archivePath);
}
}
return {
path: outputPath,
browser: options.browser,
platform: options.platform,
revision: options.revision,
};
}
export async function canFetch(options: Options): Promise<boolean> {
options.platform ??= detectBrowserPlatform();
if (!options.platform) {
throw new Error(
`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`
);
}
return await headHttpRequest(
getDownloadUrl(options.browser, options.platform, options.revision)
);
}
function getDownloadUrl(
browser: Browser,
platform: BrowserPlatform,
revision: string
): URL {
return new URL(downloadUrls[browser](platform, revision));
}

View file

@ -0,0 +1,89 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {exec as execChildProcess} from 'child_process';
import {createReadStream} from 'fs';
import {mkdir, readdir} from 'fs/promises';
import * as path from 'path';
import {promisify} from 'util';
import extractZip from 'extract-zip';
import tar from 'tar-fs';
import bzip from 'unbzip2-stream';
const exec = promisify(execChildProcess);
/**
* @internal
*/
export async function unpackArchive(
archivePath: string,
folderPath: string
): Promise<void> {
if (archivePath.endsWith('.zip')) {
await extractZip(archivePath, {dir: folderPath});
} else if (archivePath.endsWith('.tar.bz2')) {
await extractTar(archivePath, folderPath);
} else if (archivePath.endsWith('.dmg')) {
await mkdir(folderPath);
await installDMG(archivePath, folderPath);
} else {
throw new Error(`Unsupported archive format: ${archivePath}`);
}
}
/**
* @internal
*/
function extractTar(tarPath: string, folderPath: string): Promise<void> {
return new Promise((fulfill, reject) => {
const tarStream = tar.extract(folderPath);
tarStream.on('error', reject);
tarStream.on('finish', fulfill);
const readStream = createReadStream(tarPath);
readStream.pipe(bzip()).pipe(tarStream);
});
}
/**
* @internal
*/
async function installDMG(dmgPath: string, folderPath: string): Promise<void> {
const {stdout} = await exec(
`hdiutil attach -nobrowse -noautoopen "${dmgPath}"`
);
const volumes = stdout.match(/\/Volumes\/(.*)/m);
if (!volumes) {
throw new Error(`Could not find volume path in ${stdout}`);
}
const mountPath = volumes[0]!;
try {
const fileNames = await readdir(mountPath);
const appName = fileNames.find(item => {
return typeof item === 'string' && item.endsWith('.app');
});
if (!appName) {
throw new Error(`Cannot find app in ${mountPath}`);
}
const mountedPath = path.join(mountPath!, appName);
await exec(`cp -R "${mountedPath}" "${folderPath}"`);
} finally {
await exec(`hdiutil detach "${mountPath}" -quiet`);
}
}

View file

@ -0,0 +1,139 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {createWriteStream} from 'fs';
import * as http from 'http';
import * as https from 'https';
import {URL} from 'url';
import createHttpsProxyAgent from 'https-proxy-agent';
import {getProxyForUrl} from 'proxy-from-env';
export function headHttpRequest(url: URL): Promise<boolean> {
return new Promise(resolve => {
const request = httpRequest(
url,
'HEAD',
response => {
resolve(response.statusCode === 200);
},
false
);
request.on('error', () => {
resolve(false);
});
});
}
export function httpRequest(
url: URL,
method: string,
response: (x: http.IncomingMessage) => void,
keepAlive = true
): http.ClientRequest {
const options: http.RequestOptions = {
protocol: url.protocol,
hostname: url.hostname,
port: url.port,
path: url.pathname,
method,
headers: keepAlive ? {Connection: 'keep-alive'} : undefined,
};
const proxyURL = getProxyForUrl(url.toString());
if (proxyURL) {
const proxy = new URL(proxyURL);
if (proxy.protocol === 'http:') {
options.path = url.href;
options.hostname = proxy.hostname;
options.protocol = proxy.protocol;
options.port = proxy.port;
} else {
options.agent = createHttpsProxyAgent({
host: proxy.host,
path: proxy.pathname,
port: proxy.port,
secureProxy: proxy.protocol === 'https:',
headers: options.headers,
});
}
}
const requestCallback = (res: http.IncomingMessage): void => {
if (
res.statusCode &&
res.statusCode >= 300 &&
res.statusCode < 400 &&
res.headers.location
) {
httpRequest(new URL(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;
}
/**
* @internal
*/
export function downloadFile(
url: URL,
destinationPath: string,
progressCallback?: (downloadedBytes: number, totalBytes: number) => void
): Promise<void> {
return new Promise<void>((resolve, reject) => {
let downloadedBytes = 0;
let totalBytes = 0;
function onData(chunk: string): void {
downloadedBytes += chunk.length;
progressCallback!(downloadedBytes, totalBytes);
}
const request = httpRequest(url, 'GET', response => {
if (response.statusCode !== 200) {
const error = new Error(
`Download failed: server returned code ${response.statusCode}. URL: ${url}`
);
// consume response data to free up memory
response.resume();
reject(error);
return;
}
const file = createWriteStream(destinationPath);
file.on('finish', () => {
return resolve();
});
file.on('error', error => {
return reject(error);
});
response.pipe(file);
totalBytes = parseInt(response.headers['content-length']!, 10);
if (progressCallback) {
response.on('data', onData);
}
});
request.on('error', error => {
return reject(error);
});
});
}

View file

@ -0,0 +1,295 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import childProcess from 'child_process';
import os from 'os';
import path from 'path';
import {
Browser,
BrowserPlatform,
executablePathByBrowser,
} from './browsers/browsers.js';
import {CacheStructure} from './CacheStructure.js';
import {debug} from './debug.js';
import {detectBrowserPlatform} from './detectPlatform.js';
const debugLaunch = debug('puppeteer:browsers:launcher');
/**
* @public
*/
export interface Options {
/**
* Root path to the storage directory.
*/
cacheDir: string;
/**
* Determines which platform the browser will be suited for.
*
* @defaultValue Auto-detected.
*/
platform?: BrowserPlatform;
/**
* Determines which browser to fetch.
*/
browser: Browser;
/**
* Determines which revision to dowloand. Revision should uniquely identify
* binaries and they are used for caching.
*/
revision: string;
}
export function computeExecutablePath(options: Options): string {
options.platform ??= detectBrowserPlatform();
if (!options.platform) {
throw new Error(
`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`
);
}
const installationDir = new CacheStructure(options.cacheDir).installationDir(
options.browser,
options.platform,
options.revision
);
return path.join(
installationDir,
executablePathByBrowser[options.browser](options.platform, options.revision)
);
}
type LaunchOptions = {
executablePath: string;
pipe?: boolean;
dumpio?: boolean;
args?: string[];
env?: Record<string, string>;
handleSIGINT?: boolean;
handleSIGTERM?: boolean;
handleSIGHUP?: boolean;
detached?: boolean;
};
export function launch(opts: LaunchOptions): Process {
return new Process(opts);
}
class Process {
#executablePath;
#args: string[];
#browserProcess: childProcess.ChildProcess;
#exited = false;
#browserProcessExiting: Promise<void>;
constructor(opts: LaunchOptions) {
this.#executablePath = opts.executablePath;
this.#args = opts.args ?? [];
opts.pipe ??= false;
opts.dumpio ??= false;
opts.handleSIGINT ??= true;
opts.handleSIGTERM ??= true;
opts.handleSIGHUP ??= true;
opts.detached ??= true;
const stdio = this.#configureStdio({
pipe: opts.pipe,
dumpio: opts.dumpio,
});
debugLaunch(`Launching ${this.#executablePath} ${this.#args.join(' ')}`);
this.#browserProcess = childProcess.spawn(
this.#executablePath,
this.#args,
{
// 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: opts.detached,
env: opts.env,
stdio,
}
);
if (opts.dumpio) {
this.#browserProcess.stderr?.pipe(process.stderr);
this.#browserProcess.stdout?.pipe(process.stdout);
}
process.on('exit', this.#onDriverProcessExit);
if (opts.handleSIGINT) {
process.on('SIGINT', this.#onDriverProcessSignal);
}
if (opts.handleSIGTERM) {
process.on('SIGTERM', this.#onDriverProcessSignal);
}
if (opts.handleSIGHUP) {
process.on('SIGHUP', this.#onDriverProcessSignal);
}
this.#browserProcessExiting = new Promise(resolve => {
this.#browserProcess.once('exit', () => {
this.#exited = true;
this.#clearListeners();
resolve();
});
});
}
#configureStdio(opts: {
pipe: boolean;
dumpio: boolean;
}): Array<'ignore' | 'pipe'> {
if (opts.pipe) {
if (opts.dumpio) {
return ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'];
} else {
return ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'];
}
} else {
if (opts.dumpio) {
return ['pipe', 'pipe', 'pipe'];
} else {
return ['pipe', 'ignore', 'pipe'];
}
}
}
#clearListeners(): void {
process.off('exit', this.#onDriverProcessExit);
process.off('SIGINT', this.#onDriverProcessSignal);
process.off('SIGTERM', this.#onDriverProcessSignal);
process.off('SIGHUP', this.#onDriverProcessSignal);
}
#onDriverProcessExit = (_code: number) => {
this.kill();
};
#onDriverProcessSignal = (signal: string): void => {
switch (signal) {
case 'SIGINT':
this.kill();
process.exit(130);
case 'SIGTERM':
case 'SIGUP':
this.kill();
break;
}
};
close(): Promise<void> {
if (this.#exited) {
return Promise.resolve();
}
this.kill();
return this.#browserProcessExiting;
}
kill(): void {
// If the process failed to launch (for example if the browser executable path
// is invalid), then the process does not get a pid assigned. A call to
// `proc.kill` would error, as the `pid` to-be-killed can not be found.
if (
this.#browserProcess &&
this.#browserProcess.pid &&
pidExists(this.#browserProcess.pid)
) {
try {
if (process.platform === 'win32') {
childProcess.exec(
`taskkill /pid ${this.#browserProcess.pid} /T /F`,
error => {
if (error) {
// taskkill can fail to kill the process e.g. due to missing permissions.
// Let's kill the process via Node API. This delays killing of all child
// processes of `this.proc` until the main Node.js process dies.
this.#browserProcess.kill();
}
}
);
} else {
// on linux the process group can be killed with the group id prefixed with
// a minus sign. The process group id is the group leader's pid.
const processGroupId = -this.#browserProcess.pid;
try {
process.kill(processGroupId, 'SIGKILL');
} catch (error) {
// Killing the process group can fail due e.g. to missing permissions.
// Let's kill the process via Node API. This delays killing of all child
// processes of `this.proc` until the main Node.js process dies.
this.#browserProcess.kill('SIGKILL');
}
}
} catch (error) {
throw new Error(
`${PROCESS_ERROR_EXPLANATION}\nError cause: ${
isErrorLike(error) ? error.stack : error
}`
);
}
}
this.#clearListeners();
}
}
const PROCESS_ERROR_EXPLANATION = `Puppeteer was unable to kill the process which ran the browser binary.
This means that, on future Puppeteer launches, Puppeteer might not be able to launch the browser.
Please check your open processes and ensure that the browser processes that Puppeteer launched have been killed.
If you think this is a bug, please report it on the Puppeteer issue tracker.`;
/**
* @internal
*/
function pidExists(pid: number): boolean {
try {
return process.kill(pid, 0);
} catch (error) {
if (isErrnoException(error)) {
if (error.code && error.code === 'ESRCH') {
return false;
}
}
throw error;
}
}
/**
* @internal
*/
export interface ErrorLike extends Error {
name: string;
message: string;
}
/**
* @internal
*/
export function isErrorLike(obj: unknown): obj is ErrorLike {
return (
typeof obj === 'object' && obj !== null && 'name' in obj && 'message' in obj
);
}
/**
* @internal
*/
export function isErrnoException(obj: unknown): obj is NodeJS.ErrnoException {
return (
isErrorLike(obj) &&
('errno' in obj || 'code' in obj || 'path' in obj || 'syscall' in obj)
);
}

View file

@ -0,0 +1,7 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"module": "CommonJS",
"outDir": "../lib/cjs"
}
}

View file

@ -0,0 +1,6 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../lib/esm"
}
}

View file

@ -0,0 +1,72 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import assert from 'assert';
import path from 'path';
import {BrowserPlatform} from '../../lib/cjs/browsers/browsers.js';
import {
resolveDownloadUrl,
relativeExecutablePath,
} from '../../lib/cjs/browsers/chrome.js';
describe('Chrome', () => {
it('should resolve download URLs', () => {
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.LINUX, '1083080'),
'https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/1083080/chrome-linux.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.MAC, '1083080'),
'https://storage.googleapis.com/chromium-browser-snapshots/Mac/1083080/chrome-mac.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.MAC_ARM, '1083080'),
'https://storage.googleapis.com/chromium-browser-snapshots/Mac_Arm/1083080/chrome-mac.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.WIN32, '1083080'),
'https://storage.googleapis.com/chromium-browser-snapshots/Win/1083080/chrome-win.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.WIN64, '1083080'),
'https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/1083080/chrome-win.zip'
);
});
it('should resolve executable paths', () => {
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.LINUX, '12372323'),
path.join('chrome-linux', 'chrome')
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.MAC, '12372323'),
path.join('chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium')
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.MAC_ARM, '12372323'),
path.join('chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium')
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.WIN32, '12372323'),
path.join('chrome-win', 'chrome.exe')
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.WIN64, '12372323'),
path.join('chrome-win', 'chrome.exe')
);
});
});

View file

@ -0,0 +1,75 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import assert from 'assert';
import fs from 'fs';
import os from 'os';
import path from 'path';
import {CLI} from '../../lib/cjs/CLI.js';
describe('CLI', function () {
this.timeout(60000);
let tmpDir = '/tmp/puppeteer-browsers-test';
const testChromeRevision = '1083080';
const testFirefoxRevision = '111.0a1';
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'puppeteer-browsers-test'));
});
afterEach(() => {
fs.rmSync(tmpDir, {recursive: true});
});
it('should download Chromium binaries', async () => {
await new CLI(tmpDir).run([
'npx',
'@puppeteer/browsers',
'install',
`chrome@${testChromeRevision}`,
`--path=${tmpDir}`,
'--platform=linux',
]);
assert.ok(
fs.existsSync(
path.join(
tmpDir,
'chrome',
`linux-${testChromeRevision}`,
'chrome-linux'
)
)
);
});
it('should download Firefox binaries', async () => {
await new CLI(tmpDir).run([
'npx',
'@puppeteer/browsers',
'install',
`firefox@${testFirefoxRevision}`,
`--path=${tmpDir}`,
'--platform=linux',
]);
assert.ok(
fs.existsSync(
path.join(tmpDir, 'firefox', `linux-${testFirefoxRevision}`, 'firefox')
)
);
});
});

View file

@ -0,0 +1,228 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import assert from 'assert';
import fs from 'fs';
import http from 'http';
import https from 'https';
import os from 'os';
import path from 'path';
import {Browser, BrowserPlatform} from '../../lib/cjs/browsers/browsers.js';
import {fetch, canFetch} from '../../lib/cjs/fetch.js';
/**
* Tests in this spec use real download URLs and unpack live browser archives
* so it requires the network access.
*/
describe('fetch', () => {
let tmpDir = '/tmp/puppeteer-browsers-test';
const testChromeRevision = '1083080';
const testFirefoxRevision = '111.0a1';
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'puppeteer-browsers-test'));
});
afterEach(() => {
fs.rmSync(tmpDir, {recursive: true});
});
it('should check if a revision can be downloaded', async () => {
assert.ok(
await canFetch({
cacheDir: tmpDir,
browser: Browser.CHROME,
platform: BrowserPlatform.LINUX,
revision: testChromeRevision,
})
);
});
it('should report if a revision is not downloadable', async () => {
assert.strictEqual(
await canFetch({
cacheDir: tmpDir,
browser: Browser.CHROME,
platform: BrowserPlatform.LINUX,
revision: 'unknown',
}),
false
);
});
it('should download a revision that is a zip archive', async function () {
this.timeout(60000);
const expectedOutputPath = path.join(
tmpDir,
'chrome',
`${BrowserPlatform.LINUX}-${testChromeRevision}`
);
assert.strictEqual(fs.existsSync(expectedOutputPath), false);
let browser = await fetch({
cacheDir: tmpDir,
browser: Browser.CHROME,
platform: BrowserPlatform.LINUX,
revision: testChromeRevision,
});
assert.strictEqual(browser.path, expectedOutputPath);
assert.ok(fs.existsSync(expectedOutputPath));
// Second iteration should be no-op.
browser = await fetch({
cacheDir: tmpDir,
browser: Browser.CHROME,
platform: BrowserPlatform.LINUX,
revision: testChromeRevision,
});
assert.strictEqual(browser.path, expectedOutputPath);
assert.ok(fs.existsSync(expectedOutputPath));
});
it('should download a revision that is a bzip2 archive', async function () {
this.timeout(60000);
const expectedOutputPath = path.join(
tmpDir,
'firefox',
`${BrowserPlatform.LINUX}-${testFirefoxRevision}`
);
assert.strictEqual(fs.existsSync(expectedOutputPath), false);
const browser = await fetch({
cacheDir: tmpDir,
browser: Browser.FIREFOX,
platform: BrowserPlatform.LINUX,
revision: testFirefoxRevision,
});
assert.strictEqual(browser.path, expectedOutputPath);
assert.ok(fs.existsSync(expectedOutputPath));
});
// Fetch relies on the `hdiutil` utility on MacOS.
// The utility is not available on other platforms.
(os.platform() === 'darwin' ? it : it.skip)(
'should download a revision that is a dmg archive',
async function () {
this.timeout(120000);
const expectedOutputPath = path.join(
tmpDir,
'firefox',
`${BrowserPlatform.MAC}-${testFirefoxRevision}`
);
assert.strictEqual(fs.existsSync(expectedOutputPath), false);
const browser = await fetch({
cacheDir: tmpDir,
browser: Browser.FIREFOX,
platform: BrowserPlatform.MAC,
revision: testFirefoxRevision,
});
assert.strictEqual(browser.path, expectedOutputPath);
assert.ok(fs.existsSync(expectedOutputPath));
}
);
describe('with proxy', () => {
const proxyUrl = new URL(`http://localhost:54321`);
let proxyServer: http.Server;
let proxiedRequestUrls: string[] = [];
beforeEach(() => {
proxiedRequestUrls = [];
proxyServer = http
.createServer(
(
originalRequest: http.IncomingMessage,
originalResponse: http.ServerResponse
) => {
const url = originalRequest.url as string;
const proxyRequest = (
url.startsWith('http:') ? http : https
).request(
url,
{
method: originalRequest.method,
rejectUnauthorized: false,
},
proxyResponse => {
originalResponse.writeHead(
proxyResponse.statusCode as number,
proxyResponse.headers
);
proxyResponse.pipe(originalResponse, {end: true});
}
);
originalRequest.pipe(proxyRequest, {end: true});
proxiedRequestUrls.push(url);
}
)
.listen({
port: proxyUrl.port,
hostname: proxyUrl.hostname,
});
process.env['HTTPS_PROXY'] = proxyUrl.toString();
process.env['HTTP_PROXY'] = proxyUrl.toString();
});
afterEach(async () => {
await new Promise((resolve, reject) => {
proxyServer.close(error => {
if (error) {
reject(error);
} else {
resolve(undefined);
}
});
});
delete process.env['HTTP_PROXY'];
delete process.env['HTTPS_PROXY'];
});
it('can send canFetch requests via a proxy', async () => {
assert.strictEqual(
await canFetch({
cacheDir: tmpDir,
browser: Browser.CHROME,
platform: BrowserPlatform.LINUX,
revision: testChromeRevision,
}),
true
);
assert.deepStrictEqual(proxiedRequestUrls, [
'https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/1083080/chrome-linux.zip',
]);
});
it('can fetch via a proxy', async function () {
this.timeout(60000);
const expectedOutputPath = path.join(
tmpDir,
'chrome',
`${BrowserPlatform.LINUX}-${testChromeRevision}`
);
assert.strictEqual(fs.existsSync(expectedOutputPath), false);
const browser = await fetch({
cacheDir: tmpDir,
browser: Browser.CHROME,
platform: BrowserPlatform.LINUX,
revision: testChromeRevision,
});
assert.strictEqual(browser.path, expectedOutputPath);
assert.ok(fs.existsSync(expectedOutputPath));
assert.deepStrictEqual(proxiedRequestUrls, [
'https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/1083080/chrome-linux.zip',
]);
});
});
});

View file

@ -0,0 +1,72 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import assert from 'assert';
import path from 'path';
import {BrowserPlatform} from '../../lib/cjs/browsers/browsers.js';
import {
relativeExecutablePath,
resolveDownloadUrl,
} from '../../lib/cjs/browsers/firefox.js';
describe('Firefox', () => {
it('should resolve download URLs', () => {
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.LINUX, '111.0a1'),
'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.linux-x86_64.tar.bz2'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.MAC, '111.0a1'),
'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.mac.dmg'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.MAC_ARM, '111.0a1'),
'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.mac.dmg'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.WIN32, '111.0a1'),
'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.win32.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.WIN64, '111.0a1'),
'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.win64.zip'
);
});
it('should resolve executable paths', () => {
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.LINUX, '111.0a1'),
path.join('firefox', 'firefox')
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.MAC, '111.0a1'),
path.join('Firefox Nightly.app', 'Contents', 'MacOS', 'firefox')
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.MAC_ARM, '111.0a1'),
path.join('Firefox Nightly.app', 'Contents', 'MacOS', 'firefox')
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.WIN32, '111.0a1'),
path.join('firefox', 'firefox.exe')
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.WIN64, '111.0a1'),
path.join('firefox', 'firefox.exe')
);
});
});

View file

@ -0,0 +1,124 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import assert from 'assert';
import fs from 'fs';
import os from 'os';
import path from 'path';
import {Browser, BrowserPlatform} from '../../lib/cjs/browsers/browsers.js';
import {fetch} from '../../lib/cjs/fetch.js';
import {computeExecutablePath, launch} from '../../lib/cjs/launcher.js';
describe('launcher', () => {
it('should compute executable path for Chrome', () => {
assert.strictEqual(
computeExecutablePath({
browser: Browser.CHROME,
platform: BrowserPlatform.LINUX,
revision: '123',
cacheDir: 'cache',
}),
path.join('cache', 'chrome', 'linux-123', 'chrome-linux', 'chrome')
);
});
it('should compute executable path for Firefox', () => {
assert.strictEqual(
computeExecutablePath({
browser: Browser.FIREFOX,
platform: BrowserPlatform.LINUX,
revision: '123',
cacheDir: 'cache',
}),
path.join('cache', 'firefox', 'linux-123', 'firefox', 'firefox')
);
});
describe('Chrome', function () {
this.timeout(60000);
let tmpDir = '/tmp/puppeteer-browsers-test';
const testChromeRevision = '1083080';
beforeEach(async () => {
tmpDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'puppeteer-browsers-test')
);
await fetch({
cacheDir: tmpDir,
browser: Browser.CHROME,
revision: testChromeRevision,
});
});
afterEach(() => {
fs.rmSync(tmpDir, {recursive: true});
});
it('should launch a Chrome browser', async () => {
const executablePath = computeExecutablePath({
cacheDir: tmpDir,
browser: Browser.CHROME,
revision: testChromeRevision,
});
const process = launch({
executablePath,
args: [
'--use-mock-keychain',
'--disable-features=DialMediaRouteProvider',
`--user-data-dir=${path.join(tmpDir, 'profile')}`,
],
});
await process.close();
});
});
describe('Firefox', function () {
this.timeout(60000);
let tmpDir = '/tmp/puppeteer-browsers-test';
const testFirefoxRevision = '111.0a1';
beforeEach(async () => {
tmpDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'puppeteer-browsers-test')
);
await fetch({
cacheDir: tmpDir,
browser: Browser.FIREFOX,
revision: testFirefoxRevision,
});
});
afterEach(() => {
fs.rmSync(tmpDir, {recursive: true});
});
it('should launch a Firefox browser', async () => {
const executablePath = computeExecutablePath({
cacheDir: tmpDir,
browser: Browser.FIREFOX,
revision: testFirefoxRevision,
});
const process = launch({
executablePath,
args: [`--user-data-dir=${path.join(tmpDir, 'profile')}`],
});
await process.close();
});
});
});

View file

@ -0,0 +1,8 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "CommonJS",
"outDir": "../build"
},
"references": [{"path": "../../tsconfig.json"}]
}

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"references": [
{"path": "src/tsconfig.esm.json"},
{"path": "src/tsconfig.cjs.json"}
]
}

View file

@ -3,18 +3,16 @@
"version": "0.1.0", "version": "0.1.0",
"description": "Puppeteer Angular schematics", "description": "Puppeteer Angular schematics",
"scripts": { "scripts": {
"dev": "npm run build --watch", "build:tsc": "wireit",
"dev:test": "npm run test --watch",
"copy": "wireit",
"build": "wireit", "build": "wireit",
"clean": "tsc --build --clean && rimraf lib", "clean": "tsc -b --clean && rimraf lib && rimraf test/build",
"clean:test": "rimraf test/build", "dev:test": "npm run test --watch",
"dev": "npm run build --watch",
"test": "wireit" "test": "wireit"
}, },
"wireit": { "wireit": {
"copy": { "build": {
"clean": "if-file-deleted", "command": "node tools/copySchemaFiles.js",
"command": "node copySchemaFiles.js",
"files": [ "files": [
"src/**/files/**", "src/**/files/**",
"src/**/*.json" "src/**/*.json"
@ -24,29 +22,25 @@
"lib/**/*.json" "lib/**/*.json"
], ],
"dependencies": [ "dependencies": [
"clean" "build:tsc"
] ]
}, },
"build": { "build:tsc": {
"command": "tsc -b", "command": "tsc -b",
"clean": "if-file-deleted",
"files": [ "files": [
"src/**/*.ts", "**/tsconfig.*.json",
"!src/**/files", "**/tsconfig.json",
"!src/**/*.json" "src/**/*.ts"
], ],
"output": [ "output": [
"lib/**", "lib/**/*.{ts,js}",
"!lib/**/files", "lib/**/*.{ts,js}.map"
"!lib/**/*.json"
],
"dependencies": [
"copy"
] ]
}, },
"test": { "test": {
"command": "mocha", "command": "mocha",
"dependencies": [ "dependencies": [
"clean:test",
"build" "build"
] ]
} }
@ -66,9 +60,9 @@
"node": ">=14.1.0" "node": ">=14.1.0"
}, },
"dependencies": { "dependencies": {
"@angular-devkit/architect": "^0.1501.2", "@angular-devkit/architect": "^0.1501.6",
"@angular-devkit/core": "^15.1.2", "@angular-devkit/core": "^15.1.6",
"@angular-devkit/schematics": "^15.1.2" "@angular-devkit/schematics": "^15.1.6"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^14.15.0", "@types/node": "^14.15.0",

View file

@ -1,3 +1,5 @@
import {spawn} from 'child_process';
import { import {
createBuilder, createBuilder,
BuilderContext, BuilderContext,
@ -6,7 +8,6 @@ import {
BuilderRun, BuilderRun,
} from '@angular-devkit/architect'; } from '@angular-devkit/architect';
import {JsonObject} from '@angular-devkit/core'; import {JsonObject} from '@angular-devkit/core';
import {spawn} from 'child_process';
import {PuppeteerBuilderOptions} from './types.js'; import {PuppeteerBuilderOptions} from './types.js';

View file

@ -16,14 +16,15 @@
import {chain, Rule, SchematicContext, Tree} from '@angular-devkit/schematics'; import {chain, Rule, SchematicContext, Tree} from '@angular-devkit/schematics';
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks'; import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
import {concatMap, map, scan} from 'rxjs/operators';
import {of} from 'rxjs'; import {of} from 'rxjs';
import {concatMap, map, scan} from 'rxjs/operators';
import { import {
addBaseFiles, addBaseFiles,
addFrameworkFiles, addFrameworkFiles,
getNgCommandName, getNgCommandName,
} from '../utils/files.js'; } from '../utils/files.js';
import {getAngularConfig} from '../utils/json.js';
import { import {
addPackageJsonDependencies, addPackageJsonDependencies,
addPackageJsonScripts, addPackageJsonScripts,
@ -33,9 +34,7 @@ import {
type NodePackage, type NodePackage,
updateAngularJsonScripts, updateAngularJsonScripts,
} from '../utils/packages.js'; } from '../utils/packages.js';
import {type SchematicsOptions} from '../utils/types.js'; import {type SchematicsOptions} from '../utils/types.js';
import {getAngularConfig} from '../utils/json.js';
// You don't have to export the function as default. You can also have more than one rule // You don't have to export the function as default. You can also have more than one rule
// factory per file. // factory per file.

View file

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import {relative, resolve} from 'path';
import {getSystemPath, normalize, strings} from '@angular-devkit/core'; import {getSystemPath, normalize, strings} from '@angular-devkit/core';
import { import {
SchematicContext, SchematicContext,
@ -26,7 +28,7 @@ import {
move, move,
url, url,
} from '@angular-devkit/schematics'; } from '@angular-devkit/schematics';
import {relative, resolve} from 'path';
import {SchematicsOptions, TestingFramework} from './types.js'; import {SchematicsOptions, TestingFramework} from './types.js';
export interface FilesOptions { export interface FilesOptions {

View file

@ -14,15 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
import {Tree} from '@angular-devkit/schematics';
import {get} from 'https'; import {get} from 'https';
import {SchematicsOptions, TestingFramework} from './types.js';
import {Tree} from '@angular-devkit/schematics';
import {getNgCommandName, getScriptFromOptions} from './files.js';
import { import {
getAngularConfig, getAngularConfig,
getJsonFileAsObject, getJsonFileAsObject,
getObjectAsJson, getObjectAsJson,
} from './json.js'; } from './json.js';
import {getNgCommandName, getScriptFromOptions} from './files.js'; import {SchematicsOptions, TestingFramework} from './types.js';
export interface NodePackage { export interface NodePackage {
name: string; name: string;
version: string; version: string;

View file

@ -1,12 +1,13 @@
import expect from 'expect';
import sinon from 'sinon';
import https from 'https'; import https from 'https';
import {join} from 'path'; import {join} from 'path';
import {JsonObject} from '@angular-devkit/core';
import { import {
SchematicTestRunner, SchematicTestRunner,
UnitTestTree, UnitTestTree,
} from '@angular-devkit/schematics/testing/schematic-test-runner'; } from '@angular-devkit/schematics/testing/schematic-test-runner';
import {JsonObject} from '@angular-devkit/core'; import expect from 'expect';
import sinon from 'sinon';
const WORKSPACE_OPTIONS = { const WORKSPACE_OPTIONS = {
name: 'workspace', name: 'workspace',

View file

@ -15,7 +15,9 @@
*/ */
const fs = require('fs/promises'); const fs = require('fs/promises');
const {join} = require('path');
const path = require('path'); const path = require('path');
/** /**
* *
* @param {String} directory * @param {String} directory
@ -42,8 +44,8 @@ async function findSchemaFiles(directory, files = []) {
} }
async function copySchemaFiles() { async function copySchemaFiles() {
const srcDir = './src'; const srcDir = join(__dirname, '..', 'src');
const outputDir = './lib'; const outputDir = join(__dirname, '..', 'lib');
const files = await findSchemaFiles(srcDir); const files = await findSchemaFiles(srcDir);
const moves = files.map(file => { const moves = files.map(file => {

View file

@ -2,6 +2,59 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [19.7.2](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v19.7.1...puppeteer-core-v19.7.2) (2023-02-20)
### Bug Fixes
* bump chromium-bidi to a version that does not declare mitt as a peer dependency ([#9701](https://github.com/puppeteer/puppeteer/issues/9701)) ([82916c1](https://github.com/puppeteer/puppeteer/commit/82916c102b2c399093ba9019e272207b5ce81849))
## [19.7.1](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v19.7.0...puppeteer-core-v19.7.1) (2023-02-15)
### Bug Fixes
* fix circularity on JSHandle interface ([#9661](https://github.com/puppeteer/puppeteer/issues/9661)) ([eb13863](https://github.com/puppeteer/puppeteer/commit/eb138635d661d3cdaf2940959fece5aca482178a))
* make chromium-bidi an opt peer dep ([#9667](https://github.com/puppeteer/puppeteer/issues/9667)) ([c6054ac](https://github.com/puppeteer/puppeteer/commit/c6054ac1a56c08ee7bf01321878699b7b4ab4e0b))
## [19.7.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v19.6.3...puppeteer-core-v19.7.0) (2023-02-13)
### Features
* add touchstart, touchmove and touchend methods ([#9622](https://github.com/puppeteer/puppeteer/issues/9622)) ([c8bb11a](https://github.com/puppeteer/puppeteer/commit/c8bb11adfcf1537032730a91baa3c36a6e324926))
* **chromium:** roll to Chromium 111.0.5556.0 (r1095492) ([#9656](https://github.com/puppeteer/puppeteer/issues/9656)) ([df59d01](https://github.com/puppeteer/puppeteer/commit/df59d010c20644da06eb4c4e28a11c4eea164aba))
### Bug Fixes
* `page.goto` error throwing on 40x/50x responses with an empty body ([#9523](https://github.com/puppeteer/puppeteer/issues/9523)) ([#9577](https://github.com/puppeteer/puppeteer/issues/9577)) ([ddb0cc1](https://github.com/puppeteer/puppeteer/commit/ddb0cc174d2a14c0948dcdaf9bae78620937c667))
## [19.6.3](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v19.6.2...puppeteer-core-v19.6.3) (2023-02-01)
### Bug Fixes
* ignore not found contexts for console messages ([#9595](https://github.com/puppeteer/puppeteer/issues/9595)) ([390685b](https://github.com/puppeteer/puppeteer/commit/390685bbe52c22b686fc0e3119b4ac7b1073c581))
* restore WaitTask terminate condition ([#9612](https://github.com/puppeteer/puppeteer/issues/9612)) ([e16cbc6](https://github.com/puppeteer/puppeteer/commit/e16cbc6626cffd40d0caa30801620e7293455006))
## [19.6.2](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v19.6.1...puppeteer-core-v19.6.2) (2023-01-27)
### Bug Fixes
* atomically get Puppeteer utilities ([#9597](https://github.com/puppeteer/puppeteer/issues/9597)) ([050a7b0](https://github.com/puppeteer/puppeteer/commit/050a7b062415ebaf10bcb71c405143eacc4e5d4b))
## [19.6.1](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v19.6.0...puppeteer-core-v19.6.1) (2023-01-26)
### Bug Fixes
* don't clean up previous browser versions ([#9568](https://github.com/puppeteer/puppeteer/issues/9568)) ([344bc2a](https://github.com/puppeteer/puppeteer/commit/344bc2af62e4068fe2cb8162d4b6c8242aac843b)), closes [#9533](https://github.com/puppeteer/puppeteer/issues/9533)
* mimic rejection for PuppeteerUtil on early call ([#9589](https://github.com/puppeteer/puppeteer/issues/9589)) ([1980de9](https://github.com/puppeteer/puppeteer/commit/1980de91a161523c7098a79919b20e6d8d2e5d81))
* **revert:** use LazyArg for puppeteer utilities ([#9590](https://github.com/puppeteer/puppeteer/issues/9590)) ([6edd996](https://github.com/puppeteer/puppeteer/commit/6edd99676827de2c83f7a858e4f903b1c34e7d35))
* use LazyArg for puppeteer utilities ([#9575](https://github.com/puppeteer/puppeteer/issues/9575)) ([496658f](https://github.com/puppeteer/puppeteer/commit/496658f02945b53096483f36cb3d64556cff045e))
## [19.6.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v19.5.2...puppeteer-core-v19.6.0) (2023-01-23) ## [19.6.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v19.5.2...puppeteer-core-v19.6.0) (2023-01-23)

View file

@ -0,0 +1,15 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"mainEntryPointFilePath": "<projectFolder>/lib/esm/puppeteer/puppeteer-core.d.ts",
"extends": "./api-extractor.json",
"dtsRollup": {
"enabled": false
},
"docModel": {
"enabled": true,
"apiJsonFilePath": "<projectFolder>/../../docs/<unscopedPackageName>.api.json"
}
}

View file

@ -8,8 +8,7 @@
}, },
"docModel": { "docModel": {
"enabled": true, "enabled": false
"apiJsonFilePath": "<projectFolder>/../../docs/<unscopedPackageName>.api.json"
}, },
"dtsRollup": { "dtsRollup": {

View file

@ -1,6 +1,6 @@
{ {
"name": "puppeteer-core", "name": "puppeteer-core",
"version": "19.6.0", "version": "19.7.2",
"description": "A high-level API to control headless Chrome over the DevTools Protocol", "description": "A high-level API to control headless Chrome over the DevTools Protocol",
"keywords": [ "keywords": [
"puppeteer", "puppeteer",
@ -34,17 +34,15 @@
"node": ">=14.1.0" "node": ">=14.1.0"
}, },
"scripts": { "scripts": {
"build:third_party": "wireit", "build:docs": "wireit",
"build:tsc": "wireit", "build:tsc": "wireit",
"build:types": "wireit", "build:types": "wireit",
"build": "wireit", "build": "wireit",
"check": "tsx tools/ensure-correct-devtools-protocol-package", "check": "tsx tools/ensure-correct-devtools-protocol-package",
"format:types": "wireit", "clean": "tsc -b --clean && rimraf lib src/generated",
"generate:package-json": "wireit", "generate:package-json": "wireit",
"generate:sources": "wireit", "generate:sources": "wireit",
"prepack": "wireit", "prepack": "wireit"
"clean": "tsc -b --clean && rimraf lib src/generated",
"clean:third_party": "wireit"
}, },
"wireit": { "wireit": {
"prepack": { "prepack": {
@ -58,93 +56,71 @@
}, },
"build": { "build": {
"dependencies": [ "dependencies": [
"build:third_party", "build:tsc",
"format:types", "build:types"
"generate:package-json"
] ]
}, },
"generate:sources": { "generate:sources": {
"command": "tsx tools/generate_sources.ts", "command": "tsx tools/generate_sources.ts",
"clean": "if-file-deleted",
"files": [ "files": [
"tools/generate_sources.ts", "../../versions.js",
"src/templates/**" "src/{injected,templates}/**",
"tools/generate_sources.ts"
], ],
"output": [ "output": [
"src/generated/**" "src/generated/*.ts"
]
},
"clean:third_party": {
"command": "rimraf lib/esm/third_party lib/cjs/third_party"
},
"build:third_party": {
"command": "rollup --config rollup.third_party.config.js",
"dependencies": [
"build:tsc"
],
"clean": false,
"files": [
"lib/esm/third_party/**",
"lib/cjs/third_party/**"
],
"output": [
"lib/esm/third_party/**",
"lib/cjs/third_party/**"
] ]
}, },
"generate:package-json": { "generate:package-json": {
"command": "tsx ../../tools/generate_module_package_json.ts lib/esm/package.json", "command": "tsx ../../tools/generate_module_package_json.ts lib/esm/package.json",
"clean": "if-file-deleted", "files": [
"dependencies": [ "../../tools/generate_module_package_json.ts"
"build:tsc"
], ],
"output": [ "output": [
"lib/esm/package.json" "lib/esm/package.json"
] ]
}, },
"build:types": { "build:docs": {
"command": "api-extractor run --local", "command": "api-extractor run --local --config \"./api-extractor.docs.json\"",
"files": [
"api-extractor.docs.json",
"lib/esm/puppeteer/puppeteer-core.d.ts",
"tsconfig.json"
],
"dependencies": [ "dependencies": [
"build:tsc" "build:tsc"
],
"files": [
"tsconfig.json",
"api-extractor.json",
"lib/esm/puppeteer/types.d.ts"
],
"output": [
"lib/types.d.ts"
]
},
"format:types": {
"command": "eslint --cache-location .eslintcache --cache --ext=ts --no-ignore --no-eslintrc -c=../../.eslintrc.types.cjs --fix lib/types.d.ts",
"dependencies": [
"build:types"
],
"clean": false,
"files": [
"lib/types.d.ts",
"../../.eslintrc.types.cjs"
],
"output": [
"lib/types.d.ts"
] ]
}, },
"build:tsc": { "build:tsc": {
"command": "tsc -b", "command": "tsc -b && rollup --config rollup.third_party.config.mjs",
"clean": "if-file-deleted", "clean": "if-file-deleted",
"dependencies": [ "dependencies": [
"clean:third_party", "generate:package-json",
"generate:sources" "generate:sources"
], ],
"files": [ "files": [
"src/**", "{compat,src,third_party}/**",
"compat/**", "rollup.third_party.config.mjs"
"third_party/**",
"**/tsconfig.*.json"
], ],
"output": [ "output": [
"lib/esm/**", "lib/{cjs,esm}/**",
"lib/cjs/**" "!lib/esm/package.json"
]
},
"build:types": {
"command": "api-extractor run --local && eslint --cache-location .eslintcache --cache --ext=ts --no-ignore --no-eslintrc -c=../../.eslintrc.types.cjs --fix lib/types.d.ts",
"files": [
"../../.eslintrc.types.cjs",
"api-extractor.json",
"lib/esm/puppeteer/types.d.ts",
"tsconfig.json"
],
"output": [
"lib/types.d.ts"
],
"dependencies": [
"build:tsc"
] ]
} }
}, },
@ -155,9 +131,10 @@
"author": "The Chromium Authors", "author": "The Chromium Authors",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"chromium-bidi": "0.4.4",
"cross-fetch": "3.1.5", "cross-fetch": "3.1.5",
"debug": "4.3.4", "debug": "4.3.4",
"devtools-protocol": "0.0.1082910", "devtools-protocol": "0.0.1094867",
"extract-zip": "2.0.1", "extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1", "https-proxy-agent": "5.0.1",
"proxy-from-env": "1.1.0", "proxy-from-env": "1.1.0",
@ -165,5 +142,13 @@
"tar-fs": "2.1.1", "tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3", "unbzip2-stream": "1.4.3",
"ws": "8.11.0" "ws": "8.11.0"
},
"peerDependencies": {
"typescript": ">= 4.7.4"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
} }
} }

View file

@ -13,29 +13,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import glob from 'glob';
import dts from 'rollup-plugin-dts';
import {nodeResolve} from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs'; import commonjs from '@rollup/plugin-commonjs';
import {nodeResolve} from '@rollup/plugin-node-resolve';
import glob from 'glob';
export default ['cjs', 'esm'].flatMap(outputType => { export default ['cjs', 'esm'].flatMap(outputType => {
const configs = []; const configs = [];
// Note we don't use path.join here. We cannot since `glob` does not support // Note we don't use path.join here. We cannot since `glob` does not support
// the backslash path separator. // the backslash path separator.
const thirdPartyPath = `lib/${outputType}/third_party`; for (const file of glob.sync(`lib/${outputType}/third_party/**/*.js`)) {
for (const jsFile of glob.sync(`${thirdPartyPath}/**/*.js`)) {
configs.push({ configs.push({
input: jsFile, input: file,
output: {file: jsFile, format: outputType}, output: {
file,
format: outputType,
},
plugins: [commonjs(), nodeResolve()], plugins: [commonjs(), nodeResolve()],
}); });
} }
for (const typesFile of glob.sync(`${thirdPartyPath}/**/*.d.ts`)) {
configs.push({
input: typesFile,
output: {file: typesFile, format: outputType},
plugins: [dts({respectExternal: true})],
});
}
return configs; return configs;
}); });

View file

@ -17,11 +17,14 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import {ChildProcess} from 'child_process'; import {ChildProcess} from 'child_process';
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {EventEmitter} from '../common/EventEmitter.js'; import {EventEmitter} from '../common/EventEmitter.js';
import type {Page} from './Page.js'; // TODO: move to ./api
import type {Target} from '../common/Target.js'; // TODO: move to ./api import type {Target} from '../common/Target.js'; // TODO: move to ./api
import type {BrowserContext} from './BrowserContext.js'; import type {BrowserContext} from './BrowserContext.js';
import type {Page} from './Page.js'; // TODO: move to ./api
/** /**
* BrowserContext options. * BrowserContext options.

View file

@ -15,9 +15,10 @@
*/ */
import {EventEmitter} from '../common/EventEmitter.js'; import {EventEmitter} from '../common/EventEmitter.js';
import {Page} from './Page.js';
import {Target} from '../common/Target.js'; import {Target} from '../common/Target.js';
import type {Permission, Browser} from './Browser.js'; import type {Permission, Browser} from './Browser.js';
import {Page} from './Page.js';
/** /**
* BrowserContexts provide a way to operate multiple independent browser * BrowserContexts provide a way to operate multiple independent browser

View file

@ -0,0 +1,728 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../common/Connection.js';
import {ExecutionContext} from '../common/ExecutionContext.js';
import {Frame} from '../common/Frame.js';
import {MouseButton} from '../common/Input.js';
import {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
import {
ElementFor,
EvaluateFuncWith,
HandleFor,
NodeFor,
} from '../common/types.js';
import {KeyInput} from '../common/USKeyboardLayout.js';
import {JSHandle} from './JSHandle.js';
import {ScreenshotOptions} from './Page.js';
/**
* @public
*/
export interface BoxModel {
content: Point[];
padding: Point[];
border: Point[];
margin: Point[];
width: number;
height: number;
}
/**
* @public
*/
export interface BoundingBox extends Point {
/**
* the width of the element in pixels.
*/
width: number;
/**
* the height of the element in pixels.
*/
height: number;
}
/**
* @public
*/
export interface Offset {
/**
* x-offset for the clickable point relative to the top-left corner of the border box.
*/
x: number;
/**
* y-offset for the clickable point relative to the top-left corner of the border box.
*/
y: number;
}
/**
* @public
*/
export interface ClickOptions {
/**
* Time to wait between `mousedown` and `mouseup` in milliseconds.
*
* @defaultValue 0
*/
delay?: number;
/**
* @defaultValue 'left'
*/
button?: MouseButton;
/**
* @defaultValue 1
*/
clickCount?: number;
/**
* Offset for the clickable point relative to the top-left corner of the border box.
*/
offset?: Offset;
}
/**
* @public
*/
export interface PressOptions {
/**
* Time to wait between `keydown` and `keyup` in milliseconds. Defaults to 0.
*/
delay?: number;
/**
* If specified, generates an input event with this text.
*/
text?: string;
}
/**
* @public
*/
export interface Point {
x: number;
y: number;
}
/**
* ElementHandle represents an in-page DOM element.
*
* @remarks
* ElementHandles can be created with the {@link Page.$} method.
*
* ```ts
* import puppeteer from 'puppeteer';
*
* (async () => {
* const browser = await puppeteer.launch();
* const page = await browser.newPage();
* await page.goto('https://example.com');
* const hrefElement = await page.$('a');
* await hrefElement.click();
* // ...
* })();
* ```
*
* ElementHandle prevents the DOM element from being garbage-collected unless the
* handle is {@link JSHandle.dispose | disposed}. ElementHandles are auto-disposed
* when their origin frame gets navigated.
*
* ElementHandle instances can be used as arguments in {@link Page.$eval} and
* {@link Page.evaluate} methods.
*
* If you're using TypeScript, ElementHandle takes a generic argument that
* denotes the type of element the handle is holding within. For example, if you
* have a handle to a `<select>` element, you can type it as
* `ElementHandle<HTMLSelectElement>` and you get some nicer type checks.
*
* @public
*/
export class ElementHandle<
ElementType extends Node = Element
> extends JSHandle<ElementType> {
/**
* @internal
*/
constructor() {
super();
}
/**
* @internal
*/
override executionContext(): ExecutionContext {
throw new Error('Not implemented');
}
/**
* @internal
*/
override get client(): CDPSession {
throw new Error('Not implemented');
}
get frame(): Frame {
throw new Error('Not implemented');
}
/**
* Queries the current element for an element matching the given selector.
*
* @param selector - The selector to query for.
* @returns A {@link ElementHandle | element handle} to the first element
* matching the given selector. Otherwise, `null`.
*/
async $<Selector extends string>(
selector: Selector
): Promise<ElementHandle<NodeFor<Selector>> | null>;
async $<Selector extends string>(): Promise<ElementHandle<
NodeFor<Selector>
> | null> {
throw new Error('Not implemented');
}
/**
* Queries the current element for all elements matching the given selector.
*
* @param selector - The selector to query for.
* @returns An array of {@link ElementHandle | element handles} that point to
* elements matching the given selector.
*/
async $$<Selector extends string>(
selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>>;
async $$<Selector extends string>(): Promise<
Array<ElementHandle<NodeFor<Selector>>>
> {
throw new Error('Not implemented');
}
/**
* Runs the given function on the first element matching the given selector in
* the current element.
*
* If the given function returns a promise, then this method will wait till
* the promise resolves.
*
* @example
*
* ```ts
* const tweetHandle = await page.$('.tweet');
* expect(await tweetHandle.$eval('.like', node => node.innerText)).toBe(
* '100'
* );
* expect(await tweetHandle.$eval('.retweets', node => node.innerText)).toBe(
* '10'
* );
* ```
*
* @param selector - The selector to query for.
* @param pageFunction - The function to be evaluated in this element's page's
* context. The first element matching the selector will be passed in as the
* first argument.
* @param args - Additional arguments to pass to `pageFunction`.
* @returns A promise to the result of the function.
*/
async $eval<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
NodeFor<Selector>,
Params
>
>(
selector: Selector,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>>;
async $eval(): Promise<unknown> {
throw new Error('Not implemented');
}
/**
* Runs the given function on an array of elements matching the given selector
* in the current element.
*
* If the given function returns a promise, then this method will wait till
* the promise resolves.
*
* @example
* HTML:
*
* ```html
* <div class="feed">
* <div class="tweet">Hello!</div>
* <div class="tweet">Hi!</div>
* </div>
* ```
*
* JavaScript:
*
* ```js
* const feedHandle = await page.$('.feed');
* expect(
* await feedHandle.$$eval('.tweet', nodes => nodes.map(n => n.innerText))
* ).toEqual(['Hello!', 'Hi!']);
* ```
*
* @param selector - The selector to query for.
* @param pageFunction - The function to be evaluated in the element's page's
* context. An array of elements matching the given selector will be passed to
* the function as its first argument.
* @param args - Additional arguments to pass to `pageFunction`.
* @returns A promise to the result of the function.
*/
async $$eval<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFuncWith<
Array<NodeFor<Selector>>,
Params
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
>(
selector: Selector,
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>>;
async $$eval(): Promise<unknown> {
throw new Error('Not implemented');
}
/**
* @deprecated Use {@link ElementHandle.$$} with the `xpath` prefix.
*
* Example: `await elementHandle.$$('xpath/' + xpathExpression)`
*
* The method evaluates the XPath expression relative to the elementHandle.
* If `xpath` starts with `//` instead of `.//`, the dot will be appended
* automatically.
*
* If there are no such elements, the method will resolve to an empty array.
* @param expression - Expression to {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate | evaluate}
*/
async $x(expression: string): Promise<Array<ElementHandle<Node>>>;
async $x(): Promise<Array<ElementHandle<Node>>> {
throw new Error('Not implemented');
}
/**
* Wait for an element matching the given selector to appear in the current
* element.
*
* Unlike {@link Frame.waitForSelector}, this method does not work across
* navigations or if the element is detached from DOM.
*
* @example
*
* ```ts
* import puppeteer from 'puppeteer';
*
* (async () => {
* const browser = await puppeteer.launch();
* const page = await browser.newPage();
* let currentURL;
* page
* .mainFrame()
* .waitForSelector('img')
* .then(() => console.log('First URL with image: ' + currentURL));
*
* for (currentURL of [
* 'https://example.com',
* 'https://google.com',
* 'https://bbc.com',
* ]) {
* await page.goto(currentURL);
* }
* await browser.close();
* })();
* ```
*
* @param selector - The selector to query and wait for.
* @param options - Options for customizing waiting behavior.
* @returns An element matching the given selector.
* @throws Throws if an element matching the given selector doesn't appear.
*/
async waitForSelector<Selector extends string>(
selector: Selector,
options?: WaitForSelectorOptions
): Promise<ElementHandle<NodeFor<Selector>> | null>;
async waitForSelector<Selector extends string>(): Promise<ElementHandle<
NodeFor<Selector>
> | null> {
throw new Error('Not implemented');
}
/**
* @deprecated Use {@link ElementHandle.waitForSelector} with the `xpath`
* prefix.
*
* Example: `await elementHandle.waitForSelector('xpath/' + xpathExpression)`
*
* The method evaluates the XPath expression relative to the elementHandle.
*
* Wait for the `xpath` within the element. If at the moment of calling the
* method the `xpath` already exists, the method will return immediately. If
* the `xpath` doesn't appear after the `timeout` milliseconds of waiting, the
* function will throw.
*
* If `xpath` starts with `//` instead of `.//`, the dot will be appended
* automatically.
*
* This method works across navigation.
*
* ```ts
* import puppeteer from 'puppeteer';
* (async () => {
* const browser = await puppeteer.launch();
* const page = await browser.newPage();
* let currentURL;
* page
* .waitForXPath('//img')
* .then(() => console.log('First URL with image: ' + currentURL));
* for (currentURL of [
* 'https://example.com',
* 'https://google.com',
* 'https://bbc.com',
* ]) {
* await page.goto(currentURL);
* }
* await browser.close();
* })();
* ```
*
* @param xpath - A
* {@link https://developer.mozilla.org/en-US/docs/Web/XPath | xpath} of an
* element to wait for
* @param options - Optional waiting parameters
* @returns Promise which resolves when element specified by xpath string is
* added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is
* not found in DOM, otherwise resolves to `ElementHandle`.
* @remarks
* The optional Argument `options` have properties:
*
* - `visible`: A boolean to wait for element to be present in DOM and to be
* visible, i.e. to not have `display: none` or `visibility: hidden` CSS
* properties. Defaults to `false`.
*
* - `hidden`: A boolean wait for element to not be found in the DOM or to be
* hidden, i.e. have `display: none` or `visibility: hidden` CSS properties.
* Defaults to `false`.
*
* - `timeout`: A number which is maximum time to wait for in milliseconds.
* Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The
* default value can be changed by using the {@link Page.setDefaultTimeout}
* method.
*/
async waitForXPath(
xpath: string,
options?: {
visible?: boolean;
hidden?: boolean;
timeout?: number;
}
): Promise<ElementHandle<Node> | null>;
async waitForXPath(): Promise<ElementHandle<Node> | null> {
throw new Error('Not implemented');
}
/**
* Converts the current handle to the given element type.
*
* @example
*
* ```ts
* const element: ElementHandle<Element> = await page.$(
* '.class-name-of-anchor'
* );
* // DO NOT DISPOSE `element`, this will be always be the same handle.
* const anchor: ElementHandle<HTMLAnchorElement> = await element.toElement(
* 'a'
* );
* ```
*
* @param tagName - The tag name of the desired element type.
* @throws An error if the handle does not match. **The handle will not be
* automatically disposed.**
*/
async toElement<
K extends keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap
>(tagName: K): Promise<HandleFor<ElementFor<K>>>;
async toElement<
K extends keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap
>(): Promise<HandleFor<ElementFor<K>>> {
throw new Error('Not implemented');
}
override asElement(): ElementHandle<ElementType> | null {
return this;
}
/**
* Resolves to the content frame for element handles referencing
* iframe nodes, or null otherwise
*/
async contentFrame(): Promise<Frame | null> {
throw new Error('Not implemented');
}
/**
* Returns the middle point within an element unless a specific offset is provided.
*/
async clickablePoint(offset?: Offset): Promise<Point>;
async clickablePoint(): Promise<Point> {
throw new Error('Not implemented');
}
/**
* This method scrolls element into view if needed, and then
* uses {@link Page.mouse} to hover over the center of the element.
* If the element is detached from DOM, the method throws an error.
*/
async hover(this: ElementHandle<Element>): Promise<void> {
throw new Error('Not implemented');
}
/**
* This method scrolls element into view if needed, and then
* uses {@link Page.mouse} to click in the center of the element.
* If the element is detached from DOM, the method throws an error.
*/
async click(
this: ElementHandle<Element>,
options?: ClickOptions
): Promise<void>;
async click(this: ElementHandle<Element>): Promise<void> {
throw new Error('Not implemented');
}
/**
* This method creates and captures a dragevent from the element.
*/
async drag(
this: ElementHandle<Element>,
target: Point
): Promise<Protocol.Input.DragData>;
async drag(this: ElementHandle<Element>): Promise<Protocol.Input.DragData> {
throw new Error('Not implemented');
}
/**
* This method creates a `dragenter` event on the element.
*/
async dragEnter(
this: ElementHandle<Element>,
data?: Protocol.Input.DragData
): Promise<void>;
async dragEnter(this: ElementHandle<Element>): Promise<void> {
throw new Error('Not implemented');
}
/**
* This method creates a `dragover` event on the element.
*/
async dragOver(
this: ElementHandle<Element>,
data?: Protocol.Input.DragData
): Promise<void>;
async dragOver(this: ElementHandle<Element>): Promise<void> {
throw new Error('Not implemented');
}
/**
* This method triggers a drop on the element.
*/
async drop(
this: ElementHandle<Element>,
data?: Protocol.Input.DragData
): Promise<void>;
async drop(this: ElementHandle<Element>): Promise<void> {
throw new Error('Not implemented');
}
/**
* This method triggers a dragenter, dragover, and drop on the element.
*/
async dragAndDrop(
this: ElementHandle<Element>,
target: ElementHandle<Node>,
options?: {delay: number}
): Promise<void>;
async dragAndDrop(this: ElementHandle<Element>): Promise<void> {
throw new Error('Not implemented');
}
/**
* Triggers a `change` and `input` event once all the provided options have been
* selected. If there's no `<select>` element matching `selector`, the method
* throws an error.
*
* @example
*
* ```ts
* handle.select('blue'); // single selection
* handle.select('red', 'green', 'blue'); // multiple selections
* ```
*
* @param values - Values of options to select. If the `<select>` has the
* `multiple` attribute, all values are considered, otherwise only the first
* one is taken into account.
*/
async select(...values: string[]): Promise<string[]>;
async select(): Promise<string[]> {
throw new Error('Not implemented');
}
/**
* This method expects `elementHandle` to point to an
* {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input | input element}.
*
* @param filePaths - Sets the value of the file input to these paths.
* If a path is relative, then it is resolved against the
* {@link https://nodejs.org/api/process.html#process_process_cwd | current working directory}.
* Note for locals script connecting to remote chrome environments,
* paths must be absolute.
*/
async uploadFile(
this: ElementHandle<HTMLInputElement>,
...filePaths: string[]
): Promise<void>;
async uploadFile(this: ElementHandle<HTMLInputElement>): Promise<void> {
throw new Error('Not implemented');
}
/**
* This method scrolls element into view if needed, and then uses
* {@link Touchscreen.tap} to tap in the center of the element.
* If the element is detached from DOM, the method throws an error.
*/
async tap(this: ElementHandle<Element>): Promise<void> {
throw new Error('Not implemented');
}
async touchStart(this: ElementHandle<Element>): Promise<void> {
throw new Error('Not implemented');
}
async touchMove(this: ElementHandle<Element>): Promise<void> {
throw new Error('Not implemented');
}
async touchEnd(this: ElementHandle<Element>): Promise<void> {
throw new Error('Not implemented');
}
/**
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus | focus} on the element.
*/
async focus(): Promise<void> {
throw new Error('Not implemented');
}
/**
* Focuses the element, and then sends a `keydown`, `keypress`/`input`, and
* `keyup` event for each character in the text.
*
* To press a special key, like `Control` or `ArrowDown`,
* use {@link ElementHandle.press}.
*
* @example
*
* ```ts
* await elementHandle.type('Hello'); // Types instantly
* await elementHandle.type('World', {delay: 100}); // Types slower, like a user
* ```
*
* @example
* An example of typing into a text field and then submitting the form:
*
* ```ts
* const elementHandle = await page.$('input');
* await elementHandle.type('some text');
* await elementHandle.press('Enter');
* ```
*/
async type(text: string, options?: {delay: number}): Promise<void>;
async type(): Promise<void> {
throw new Error('Not implemented');
}
/**
* Focuses the element, and then uses {@link Keyboard.down} and {@link Keyboard.up}.
*
* @remarks
* If `key` is a single character and no modifier keys besides `Shift`
* are being held down, a `keypress`/`input` event will also be generated.
* The `text` option can be specified to force an input event to be generated.
*
* **NOTE** Modifier keys DO affect `elementHandle.press`. Holding down `Shift`
* will type the text in upper case.
*
* @param key - Name of key to press, such as `ArrowLeft`.
* See {@link KeyInput} for a list of all key names.
*/
async press(key: KeyInput, options?: PressOptions): Promise<void>;
async press(): Promise<void> {
throw new Error('Not implemented');
}
/**
* This method returns the bounding box of the element (relative to the main frame),
* or `null` if the element is not visible.
*/
async boundingBox(): Promise<BoundingBox | null> {
throw new Error('Not implemented');
}
/**
* This method returns boxes of the element, or `null` if the element is not visible.
*
* @remarks
*
* Boxes are represented as an array of points;
* Each Point is an object `{x, y}`. Box points are sorted clock-wise.
*/
async boxModel(): Promise<BoxModel | null> {
throw new Error('Not implemented');
}
/**
* This method scrolls element into view if needed, and then uses
* {@link Page.screenshot} to take a screenshot of the element.
* If the element is detached from DOM, the method throws an error.
*/
async screenshot(
this: ElementHandle<Element>,
options?: ScreenshotOptions
): Promise<string | Buffer>;
async screenshot(this: ElementHandle<Element>): Promise<string | Buffer> {
throw new Error('Not implemented');
}
/**
* Resolves to true if the element is visible in the current viewport.
*/
async isIntersectingViewport(
this: ElementHandle<Element>,
options?: {
threshold?: number;
}
): Promise<boolean>;
async isIntersectingViewport(): Promise<boolean> {
throw new Error('Not implemented');
}
}

View file

@ -0,0 +1,197 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Protocol from 'devtools-protocol';
import {CDPSession} from '../common/Connection.js';
import {ExecutionContext} from '../common/ExecutionContext.js';
import {EvaluateFuncWith, HandleFor, HandleOr} from '../common/types.js';
import {ElementHandle} from './ElementHandle.js';
declare const __JSHandleSymbol: unique symbol;
/**
* Represents a reference to a JavaScript object. Instances can be created using
* {@link Page.evaluateHandle}.
*
* Handles prevent the referenced JavaScript object from being garbage-collected
* unless the handle is purposely {@link JSHandle.dispose | disposed}. JSHandles
* are auto-disposed when their associated frame is navigated away or the parent
* context gets destroyed.
*
* Handles can be used as arguments for any evaluation function such as
* {@link Page.$eval}, {@link Page.evaluate}, and {@link Page.evaluateHandle}.
* They are resolved to their referenced object.
*
* @example
*
* ```ts
* const windowHandle = await page.evaluateHandle(() => window);
* ```
*
* @public
*/
export class JSHandle<T = unknown> {
/**
* Used for nominally typing {@link JSHandle}.
*/
[__JSHandleSymbol]?: T;
/**
* @internal
*/
constructor() {}
/**
* @internal
*/
get disposed(): boolean {
throw new Error('Not implemented');
}
/**
* @internal
*/
executionContext(): ExecutionContext {
throw new Error('Not implemented');
}
/**
* @internal
*/
get client(): CDPSession {
throw new Error('Not implemented');
}
/**
* Evaluates the given function with the current handle as its first argument.
*/
async evaluate<
Params extends unknown[],
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>
>(
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>>;
async evaluate(): Promise<unknown> {
throw new Error('Not implemented');
}
/**
* Evaluates the given function with the current handle as its first argument.
*
*/
async evaluateHandle<
Params extends unknown[],
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>
>(
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
async evaluateHandle(): Promise<HandleFor<unknown>> {
throw new Error('Not implemented');
}
/**
* Fetches a single property from the referenced object.
*/
async getProperty<K extends keyof T>(
propertyName: HandleOr<K>
): Promise<HandleFor<T[K]>>;
async getProperty(propertyName: string): Promise<JSHandle<unknown>>;
async getProperty<K extends keyof T>(
propertyName: HandleOr<K>
): Promise<HandleFor<T[K]>>;
async getProperty<K extends keyof T>(): Promise<HandleFor<T[K]>> {
throw new Error('Not implemented');
}
/**
* Gets a map of handles representing the properties of the current handle.
*
* @example
*
* ```ts
* const listHandle = await page.evaluateHandle(() => document.body.children);
* const properties = await listHandle.getProperties();
* const children = [];
* for (const property of properties.values()) {
* const element = property.asElement();
* if (element) {
* children.push(element);
* }
* }
* children; // holds elementHandles to all children of document.body
* ```
*/
async getProperties(): Promise<Map<string, JSHandle>> {
throw new Error('Not implemented');
}
/**
* @returns A vanilla object representing the serializable portions of the
* referenced object.
* @throws Throws if the object cannot be serialized due to circularity.
*
* @remarks
* If the object has a `toJSON` function, it **will not** be called.
*/
async jsonValue(): Promise<T> {
throw new Error('Not implemented');
}
/**
* @returns Either `null` or the handle itself if the handle is an
* instance of {@link ElementHandle}.
*/
asElement(): ElementHandle<Node> | null {
throw new Error('Not implemented');
}
/**
* Releases the object referenced by the handle for garbage collection.
*/
async dispose(): Promise<void> {
throw new Error('Not implemented');
}
/**
* Returns a string representation of the JSHandle.
*
* @remarks
* Useful during debugging.
*/
toString(): string {
throw new Error('Not implemented');
}
/**
* @internal
*/
get id(): string | undefined {
throw new Error('Not implemented');
}
/**
* Provides access to the
* [Protocol.Runtime.RemoteObject](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObject)
* backing this handle.
*/
remoteObject(): Protocol.Runtime.RemoteObject {
throw new Error('Not implemented');
}
}

View file

@ -14,14 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
import {Protocol} from 'devtools-protocol';
import type {Readable} from 'stream'; import type {Readable} from 'stream';
import {Protocol} from 'devtools-protocol';
import type {Accessibility} from '../common/Accessibility.js'; import type {Accessibility} from '../common/Accessibility.js';
import type {ConsoleMessage} from '../common/ConsoleMessage.js'; import type {ConsoleMessage} from '../common/ConsoleMessage.js';
import type {Coverage} from '../common/Coverage.js'; import type {Coverage} from '../common/Coverage.js';
import {Device} from '../common/Device.js'; import {Device} from '../common/Device.js';
import type {Dialog} from '../common/Dialog.js'; import type {Dialog} from '../common/Dialog.js';
import type {ElementHandle} from '../common/ElementHandle.js';
import {EventEmitter, Handler} from '../common/EventEmitter.js'; import {EventEmitter, Handler} from '../common/EventEmitter.js';
import type {FileChooser} from '../common/FileChooser.js'; import type {FileChooser} from '../common/FileChooser.js';
import type { import type {
@ -39,17 +40,24 @@ import type {
Touchscreen, Touchscreen,
} from '../common/Input.js'; } from '../common/Input.js';
import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js'; import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
import type {JSHandle} from '../common/JSHandle.js';
import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js'; import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js';
import type {Credentials, NetworkConditions} from '../common/NetworkManager.js'; import type {Credentials, NetworkConditions} from '../common/NetworkManager.js';
import type {PDFOptions} from '../common/PDFOptions.js'; import type {PDFOptions} from '../common/PDFOptions.js';
import type {Viewport} from '../common/PuppeteerViewport.js'; import type {Viewport} from '../common/PuppeteerViewport.js';
import type {Target} from '../common/Target.js'; import type {Target} from '../common/Target.js';
import type {Tracing} from '../common/Tracing.js'; import type {Tracing} from '../common/Tracing.js';
import type {EvaluateFunc, HandleFor, NodeFor} from '../common/types.js'; import type {
EvaluateFunc,
EvaluateFuncWith,
HandleFor,
NodeFor,
} from '../common/types.js';
import type {WebWorker} from '../common/WebWorker.js'; import type {WebWorker} from '../common/WebWorker.js';
import type {Browser} from './Browser.js'; import type {Browser} from './Browser.js';
import type {BrowserContext} from './BrowserContext.js'; import type {BrowserContext} from './BrowserContext.js';
import type {ElementHandle} from './ElementHandle.js';
import type {JSHandle} from './JSHandle.js';
/** /**
* @public * @public
@ -956,21 +964,16 @@ export class Page extends EventEmitter {
async $eval< async $eval<
Selector extends string, Selector extends string,
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc< Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
[ElementHandle<NodeFor<Selector>>, ...Params] NodeFor<Selector>,
> = EvaluateFunc<[ElementHandle<NodeFor<Selector>>, ...Params]> Params
>
>( >(
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
...args: Params ...args: Params
): Promise<Awaited<ReturnType<Func>>>; ): Promise<Awaited<ReturnType<Func>>>;
async $eval< async $eval(): Promise<unknown> {
Selector extends string,
Params extends unknown[],
Func extends EvaluateFunc<
[ElementHandle<NodeFor<Selector>>, ...Params]
> = EvaluateFunc<[ElementHandle<NodeFor<Selector>>, ...Params]>
>(): Promise<Awaited<ReturnType<Func>>> {
throw new Error('Not implemented'); throw new Error('Not implemented');
} }
@ -1039,21 +1042,16 @@ export class Page extends EventEmitter {
async $$eval< async $$eval<
Selector extends string, Selector extends string,
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc< Func extends EvaluateFuncWith<
[Array<NodeFor<Selector>>, ...Params] Array<NodeFor<Selector>>,
> = EvaluateFunc<[Array<NodeFor<Selector>>, ...Params]> Params
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
>( >(
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
...args: Params ...args: Params
): Promise<Awaited<ReturnType<Func>>>; ): Promise<Awaited<ReturnType<Func>>>;
async $$eval< async $$eval(): Promise<unknown> {
Selector extends string,
Params extends unknown[],
Func extends EvaluateFunc<
[Array<NodeFor<Selector>>, ...Params]
> = EvaluateFunc<[Array<NodeFor<Selector>>, ...Params]>
>(): Promise<Awaited<ReturnType<Func>>> {
throw new Error('Not implemented'); throw new Error('Not implemented');
} }
@ -2465,7 +2463,7 @@ export class Page extends EventEmitter {
* @param options - Optional waiting parameters * @param options - Optional waiting parameters
* @returns Promise which resolves when element specified by xpath string is * @returns Promise which resolves when element specified by xpath string is
* added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is * added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is
* not found in DOM. * not found in DOM, otherwise resolves to `ElementHandle`.
* @remarks * @remarks
* The optional Argument `options` have properties: * The optional Argument `options` have properties:
* *
@ -2483,11 +2481,7 @@ export class Page extends EventEmitter {
*/ */
waitForXPath( waitForXPath(
xpath: string, xpath: string,
options?: { options?: WaitForSelectorOptions
visible?: boolean;
hidden?: boolean;
timeout?: number;
}
): Promise<ElementHandle<Node> | null>; ): Promise<ElementHandle<Node> | null>;
waitForXPath(): Promise<ElementHandle<Node> | null> { waitForXPath(): Promise<ElementHandle<Node> | null> {
throw new Error('Not implemented'); throw new Error('Not implemented');

View file

@ -17,3 +17,5 @@
export * from './Browser.js'; export * from './Browser.js';
export * from './BrowserContext.js'; export * from './BrowserContext.js';
export * from './Page.js'; export * from './Page.js';
export * from './JSHandle.js';
export * from './ElementHandle.js';

View file

@ -15,8 +15,10 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {ElementHandle} from '../api/ElementHandle.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {ElementHandle} from './ElementHandle.js';
/** /**
* Represents a Node and the properties of it that are relevant to Accessibility. * Represents a Node and the properties of it that are relevant to Accessibility.
@ -186,7 +188,7 @@ export class Accessibility {
let backendNodeId: number | undefined; let backendNodeId: number | undefined;
if (root) { if (root) {
const {node} = await this.#client.send('DOM.describeNode', { const {node} = await this.#client.send('DOM.describeNode', {
objectId: root.remoteObject().objectId, objectId: root.id,
}); });
backendNodeId = node.backendNodeId; backendNodeId = node.backendNodeId;
} }

View file

@ -16,46 +16,42 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {ElementHandle} from '../api/ElementHandle.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; import {QueryHandler, QuerySelector} from './QueryHandler.js';
import {AwaitableIterable} from './types.js';
import type {ElementHandle} from './ElementHandle.js'; const queryAXTree = async (
import type {PuppeteerQueryHandler} from './QueryHandler.js';
import type {Frame} from './Frame.js';
async function queryAXTree(
client: CDPSession, client: CDPSession,
element: ElementHandle<Node>, element: ElementHandle<Node>,
accessibleName?: string, accessibleName?: string,
role?: string role?: string
): Promise<Protocol.Accessibility.AXNode[]> { ): Promise<Protocol.Accessibility.AXNode[]> => {
const {nodes} = await client.send('Accessibility.queryAXTree', { const {nodes} = await client.send('Accessibility.queryAXTree', {
objectId: element.remoteObject().objectId, objectId: element.id,
accessibleName, accessibleName,
role, role,
}); });
const filteredNodes: Protocol.Accessibility.AXNode[] = nodes.filter( return nodes.filter((node: Protocol.Accessibility.AXNode) => {
(node: Protocol.Accessibility.AXNode) => { return !node.role || node.role.value !== 'StaticText';
return !node.role || node.role.value !== 'StaticText'; });
} };
);
return filteredNodes; type ARIASelector = {name?: string; role?: string};
}
const KNOWN_ATTRIBUTES = Object.freeze(['name', 'role']);
const isKnownAttribute = (
attribute: string
): attribute is keyof ARIASelector => {
return KNOWN_ATTRIBUTES.includes(attribute);
};
const normalizeValue = (value: string): string => { const normalizeValue = (value: string): string => {
return value.replace(/ +/g, ' ').trim(); return value.replace(/ +/g, ' ').trim();
}; };
const knownAttributes = new Set(['name', 'role']);
const attributeRegexp =
/\[\s*(?<attribute>\w+)\s*=\s*(?<quote>"|')(?<value>\\.|.*?(?=\k<quote>))\k<quote>\s*\]/g;
type ARIAQueryOption = {name?: string; role?: string};
function isKnownAttribute(
attribute: string
): attribute is keyof ARIAQueryOption {
return knownAttributes.has(attribute);
}
/** /**
* The selectors consist of an accessible name to query for and optionally * The selectors consist of an accessible name to query for and optionally
@ -68,11 +64,13 @@ function isKnownAttribute(
* - 'label' queries for elements with name 'label' and any role. * - 'label' queries for elements with name 'label' and any role.
* - '[name=""][role="button"]' queries for elements with no name and role 'button'. * - '[name=""][role="button"]' queries for elements with no name and role 'button'.
*/ */
function parseAriaSelector(selector: string): ARIAQueryOption { const ATTRIBUTE_REGEXP =
const queryOptions: ARIAQueryOption = {}; /\[\s*(?<attribute>\w+)\s*=\s*(?<quote>"|')(?<value>\\.|.*?(?=\k<quote>))\k<quote>\s*\]/g;
const parseARIASelector = (selector: string): ARIASelector => {
const queryOptions: ARIASelector = {};
const defaultName = selector.replace( const defaultName = selector.replace(
attributeRegexp, ATTRIBUTE_REGEXP,
(_, attribute: string, _quote: string, value: string) => { (_, attribute, __, value) => {
attribute = attribute.trim(); attribute = attribute.trim();
assert( assert(
isKnownAttribute(attribute), isKnownAttribute(attribute),
@ -86,104 +84,41 @@ function parseAriaSelector(selector: string): ARIAQueryOption {
queryOptions.name = normalizeValue(defaultName); queryOptions.name = normalizeValue(defaultName);
} }
return queryOptions; return queryOptions;
}
const queryOneId = async (element: ElementHandle<Node>, selector: string) => {
const {name, role} = parseAriaSelector(selector);
const res = await queryAXTree(element.client, element, name, role);
if (!res[0] || !res[0].backendDOMNodeId) {
return null;
}
return res[0].backendDOMNodeId;
};
const queryOne: PuppeteerQueryHandler['queryOne'] = async (
element,
selector
) => {
const id = await queryOneId(element, selector);
if (!id) {
return null;
}
return (await element.frame.worlds[MAIN_WORLD].adoptBackendNode(
id
)) as ElementHandle<Node>;
};
const waitFor: PuppeteerQueryHandler['waitFor'] = async (
elementOrFrame,
selector,
options
) => {
let frame: Frame;
let element: ElementHandle<Node> | undefined;
if ('isOOPFrame' in elementOrFrame) {
frame = elementOrFrame;
} else {
frame = elementOrFrame.frame;
element = await frame.worlds[PUPPETEER_WORLD].adoptHandle(elementOrFrame);
}
const ariaQuerySelector = async (selector: string) => {
const id = await queryOneId(
element || (await frame.worlds[PUPPETEER_WORLD].document()),
selector
);
if (!id) {
return null;
}
return (await frame.worlds[PUPPETEER_WORLD].adoptBackendNode(
id
)) as ElementHandle<Node>;
};
const result = await frame.worlds[PUPPETEER_WORLD]._waitForSelectorInPage(
(_: Element, selector: string) => {
return (
globalThis as unknown as {
ariaQuerySelector(selector: string): Node | null;
}
).ariaQuerySelector(selector);
},
element,
selector,
options,
new Map([['ariaQuerySelector', ariaQuerySelector]])
);
if (element) {
await element.dispose();
}
const handle = result?.asElement();
if (!handle) {
await result?.dispose();
return null;
}
return handle.frame.worlds[MAIN_WORLD].transferHandle(handle);
};
const queryAll: PuppeteerQueryHandler['queryAll'] = async (
element,
selector
) => {
const exeCtx = element.executionContext();
const {name, role} = parseAriaSelector(selector);
const res = await queryAXTree(exeCtx._client, element, name, role);
const world = exeCtx._world!;
return Promise.all(
res.map(axNode => {
return world.adoptBackendNode(axNode.backendDOMNodeId) as Promise<
ElementHandle<Node>
>;
})
);
}; };
/** /**
* @internal * @internal
*/ */
export const ariaHandler: PuppeteerQueryHandler = { export class ARIAQueryHandler extends QueryHandler {
queryOne, static override querySelector: QuerySelector = async (
waitFor, node,
queryAll, selector,
}; {ariaQuerySelector}
) => {
return ariaQuerySelector(node, selector);
};
static override async *queryAll(
element: ElementHandle<Node>,
selector: string
): AwaitableIterable<ElementHandle<Node>> {
const context = element.executionContext();
const {name, role} = parseARIASelector(selector);
const results = await queryAXTree(context._client, element, name, role);
const world = context._world!;
yield* AsyncIterableUtil.map(results, node => {
return world.adoptBackendNode(node.backendDOMNodeId) as Promise<
ElementHandle<Node>
>;
});
}
static override queryOne = async (
element: ElementHandle<Node>,
selector: string
): Promise<ElementHandle<Node> | null> => {
return (
(await AsyncIterableUtil.first(this.queryAll(element, selector))) ?? null
);
};
}

View file

@ -0,0 +1,123 @@
import {JSHandle} from '../api/JSHandle.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {ExecutionContext} from './ExecutionContext.js';
import {debugError} from './util.js';
/**
* @internal
*/
export class Binding {
#name: string;
#fn: (...args: unknown[]) => unknown;
constructor(name: string, fn: (...args: unknown[]) => unknown) {
this.#name = name;
this.#fn = fn;
}
get name(): string {
return this.#name;
}
/**
* @param context - Context to run the binding in; the context should have
* the binding added to it beforehand.
* @param id - ID of the call. This should come from the CDP
* `onBindingCalled` response.
* @param args - Plain arguments from CDP.
*/
async run(
context: ExecutionContext,
id: number,
args: unknown[],
isTrivial: boolean
): Promise<void> {
const garbage = [];
try {
if (!isTrivial) {
// Getting non-trivial arguments.
const handles = await context.evaluateHandle(
(name, seq) => {
// @ts-expect-error Code is evaluated in a different context.
return globalThis[name].args.get(seq);
},
this.#name,
id
);
try {
const properties = await handles.getProperties();
for (const [index, handle] of properties) {
// This is not straight-forward since some arguments can stringify, but
// aren't plain objects so add subtypes when the use-case arises.
if (index in args) {
switch (handle.remoteObject().subtype) {
case 'node':
args[+index] = handle;
break;
default:
garbage.push(handle.dispose());
}
} else {
garbage.push(handle.dispose());
}
}
} finally {
await handles.dispose();
}
}
await context.evaluate(
(name, seq, result) => {
// @ts-expect-error Code is evaluated in a different context.
const callbacks = globalThis[name].callbacks;
callbacks.get(seq).resolve(result);
callbacks.delete(seq);
},
this.#name,
id,
await this.#fn(...args)
);
for (const arg of args) {
if (arg instanceof JSHandle) {
garbage.push(arg.dispose());
}
}
} catch (error) {
if (isErrorLike(error)) {
await context
.evaluate(
(name, seq, message, stack) => {
const error = new Error(message);
error.stack = stack;
// @ts-expect-error Code is evaluated in a different context.
const callbacks = globalThis[name].callbacks;
callbacks.get(seq).reject(error);
callbacks.delete(seq);
},
this.#name,
id,
error.message,
error.stack
)
.catch(debugError);
} else {
await context
.evaluate(
(name, seq, error) => {
// @ts-expect-error Code is evaluated in a different context.
const callbacks = globalThis[name].callbacks;
callbacks.get(seq).reject(error);
callbacks.delete(seq);
},
this.#name,
id,
error
)
.catch(debugError);
}
} finally {
await Promise.all(garbage);
}
}
}

View file

@ -15,17 +15,9 @@
*/ */
import {ChildProcess} from 'child_process'; import {ChildProcess} from 'child_process';
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js';
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js';
import {waitWithTimeout} from './util.js';
import {Page} from '../api/Page.js';
import {Viewport} from './PuppeteerViewport.js';
import {Target} from './Target.js';
import {TaskQueue} from './TaskQueue.js';
import {TargetManager, TargetManagerEmittedEvents} from './TargetManager.js';
import {ChromeTargetManager} from './ChromeTargetManager.js';
import {FirefoxTargetManager} from './FirefoxTargetManager.js';
import { import {
Browser as BrowserBase, Browser as BrowserBase,
BrowserCloseCallback, BrowserCloseCallback,
@ -39,6 +31,17 @@ import {
Permission, Permission,
} from '../api/Browser.js'; } from '../api/Browser.js';
import {BrowserContext} from '../api/BrowserContext.js'; import {BrowserContext} from '../api/BrowserContext.js';
import {Page} from '../api/Page.js';
import {assert} from '../util/assert.js';
import {ChromeTargetManager} from './ChromeTargetManager.js';
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js';
import {FirefoxTargetManager} from './FirefoxTargetManager.js';
import {Viewport} from './PuppeteerViewport.js';
import {Target} from './Target.js';
import {TargetManager, TargetManagerEmittedEvents} from './TargetManager.js';
import {TaskQueue} from './TaskQueue.js';
import {waitWithTimeout} from './util.js';
/** /**
* @internal * @internal

View file

@ -14,18 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
import {debugError} from './util.js'; import {IsPageTargetCallback, TargetFilterCallback} from '../api/Browser.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {isNode} from '../environment.js'; import {isNode} from '../environment.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {IsPageTargetCallback, TargetFilterCallback} from '../api/Browser.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {CDPBrowser} from './Browser.js'; import {CDPBrowser} from './Browser.js';
import {Connection} from './Connection.js'; import {Connection} from './Connection.js';
import {ConnectionTransport} from './ConnectionTransport.js'; import {ConnectionTransport} from './ConnectionTransport.js';
import {getFetch} from './fetch.js'; import {getFetch} from './fetch.js';
import {Viewport} from './PuppeteerViewport.js';
import type {ConnectOptions} from './Puppeteer.js'; import type {ConnectOptions} from './Puppeteer.js';
import {Viewport} from './PuppeteerViewport.js';
import {debugError} from './util.js';
/** /**
* Generic browser options that can be passed when launching any browser or when * Generic browser options that can be passed when launching any browser or when
* connecting to an existing browser instance. * connecting to an existing browser instance.

View file

@ -15,18 +15,20 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {TargetFilterCallback} from '../api/Browser.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {CDPSession, Connection} from './Connection.js'; import {CDPSession, Connection} from './Connection.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
import {Target} from './Target.js'; import {Target} from './Target.js';
import {debugError} from './util.js';
import {TargetFilterCallback} from '../api/Browser.js';
import { import {
TargetInterceptor, TargetInterceptor,
TargetFactory, TargetFactory,
TargetManager, TargetManager,
TargetManagerEmittedEvents, TargetManagerEmittedEvents,
} from './TargetManager.js'; } from './TargetManager.js';
import {debugError} from './util.js';
/** /**
* ChromeTargetManager uses the CDP's auto-attach mechanism to intercept * ChromeTargetManager uses the CDP's auto-attach mechanism to intercept

View file

@ -13,16 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/* eslint-disable import/order */
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {debug} from './Debug.js'; import {debug} from './Debug.js';
const debugProtocolSend = debug('puppeteer:protocol:SEND ►'); const debugProtocolSend = debug('puppeteer:protocol:SEND ►');
const debugProtocolReceive = debug('puppeteer:protocol:RECV ◀'); const debugProtocolReceive = debug('puppeteer:protocol:RECV ◀');
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js'; import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
import {ConnectionTransport} from './ConnectionTransport.js'; import {ConnectionTransport} from './ConnectionTransport.js';
import {EventEmitter} from './EventEmitter.js';
import {ProtocolError} from './Errors.js'; import {ProtocolError} from './Errors.js';
import {EventEmitter} from './EventEmitter.js';
/** /**
* @public * @public

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import {JSHandle} from './JSHandle.js'; import {JSHandle} from '../api/JSHandle.js';
/** /**
* @public * @public

View file

@ -14,12 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
import {assert} from '../util/assert.js';
import {addEventListener, debugError, PuppeteerEventListener} from './util.js';
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from './Connection.js';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {EVALUATION_SCRIPT_URL} from './ExecutionContext.js'; import {EVALUATION_SCRIPT_URL} from './ExecutionContext.js';
import {addEventListener, debugError, PuppeteerEventListener} from './util.js';
import {removeEventListeners} from './util.js'; import {removeEventListeners} from './util.js';
/** /**

View file

@ -0,0 +1,227 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type PuppeteerUtil from '../injected/injected.js';
import {assert} from '../util/assert.js';
import {interpolateFunction, stringifyFunction} from '../util/Function.js';
import {QueryHandler, QuerySelector, QuerySelectorAll} from './QueryHandler.js';
import {scriptInjector} from './ScriptInjector.js';
/**
* @public
*/
export interface CustomQueryHandler {
/**
* @returns A {@link Node} matching the given `selector` from {@link node}.
*/
queryOne?: (node: Node, selector: string) => Node | null;
/**
* @returns Some {@link Node}s matching the given `selector` from {@link node}.
*/
queryAll?: (node: Node, selector: string) => Iterable<Node>;
}
/**
* The registry of {@link CustomQueryHandler | custom query handlers}.
*
* @example
*
* ```ts
* Puppeteer.customQueryHandlers.register('lit', { });
* const aHandle = await page.$('lit/…');
* ```
*
* @internal
*/
export class CustomQueryHandlerRegistry {
#handlers = new Map<
string,
[registerScript: string, Handler: typeof QueryHandler]
>();
/**
* @internal
*/
get(name: string): typeof QueryHandler | undefined {
const handler = this.#handlers.get(name);
return handler ? handler[1] : undefined;
}
/**
* Registers a {@link CustomQueryHandler | custom query handler}.
*
* @remarks
* After registration, the handler can be used everywhere where a selector is
* expected by prepending the selection string with `<name>/`. The name is
* only allowed to consist of lower- and upper case latin letters.
*
* @example
*
* ```ts
* Puppeteer.customQueryHandlers.register('lit', { });
* const aHandle = await page.$('lit/…');
* ```
*
* @param name - Name to register under.
* @param queryHandler - {@link CustomQueryHandler | Custom query handler} to
* register.
*
* @internal
*/
register(name: string, handler: CustomQueryHandler): void {
if (this.#handlers.has(name)) {
throw new Error(`Cannot register over existing handler: ${name}`);
}
assert(
!this.#handlers.has(name),
`Cannot register over existing handler: ${name}`
);
assert(
/^[a-zA-Z]+$/.test(name),
`Custom query handler names may only contain [a-zA-Z]`
);
assert(
handler.queryAll || handler.queryOne,
`At least one query method must be implemented.`
);
const Handler = class extends QueryHandler {
static override querySelectorAll: QuerySelectorAll = interpolateFunction(
(node, selector, PuppeteerUtil) => {
return PuppeteerUtil.customQuerySelectors
.get(PLACEHOLDER('name'))!
.querySelectorAll(node, selector);
},
{name: JSON.stringify(name)}
);
static override querySelector: QuerySelector = interpolateFunction(
(node, selector, PuppeteerUtil) => {
return PuppeteerUtil.customQuerySelectors
.get(PLACEHOLDER('name'))!
.querySelector(node, selector);
},
{name: JSON.stringify(name)}
);
};
const registerScript = interpolateFunction(
(PuppeteerUtil: PuppeteerUtil) => {
PuppeteerUtil.customQuerySelectors.register(PLACEHOLDER('name'), {
queryAll: PLACEHOLDER('queryAll'),
queryOne: PLACEHOLDER('queryOne'),
});
},
{
name: JSON.stringify(name),
queryAll: handler.queryAll
? stringifyFunction(handler.queryAll)
: String(undefined),
queryOne: handler.queryOne
? stringifyFunction(handler.queryOne)
: String(undefined),
}
).toString();
this.#handlers.set(name, [registerScript, Handler]);
scriptInjector.append(registerScript);
}
/**
* Unregisters the {@link CustomQueryHandler | custom query handler} for the
* given name.
*
* @throws `Error` if there is no handler under the given name.
*
* @internal
*/
unregister(name: string): void {
const handler = this.#handlers.get(name);
if (!handler) {
throw new Error(`Cannot unregister unknown handler: ${name}`);
}
scriptInjector.pop(handler[0]);
this.#handlers.delete(name);
}
/**
* Gets the names of all {@link CustomQueryHandler | custom query handlers}.
*
* @internal
*/
names(): string[] {
return [...this.#handlers.keys()];
}
/**
* Unregisters all custom query handlers.
*
* @internal
*/
clear(): void {
for (const [registerScript] of this.#handlers) {
scriptInjector.pop(registerScript);
}
this.#handlers.clear();
}
}
/**
* @internal
*/
export const customQueryHandlers = new CustomQueryHandlerRegistry();
/**
* @deprecated Import {@link Puppeteer} and use the static method
* {@link Puppeteer.registerCustomQueryHandler}
*
* @public
*/
export function registerCustomQueryHandler(
name: string,
handler: CustomQueryHandler
): void {
customQueryHandlers.register(name, handler);
}
/**
* @deprecated Import {@link Puppeteer} and use the static method
* {@link Puppeteer.unregisterCustomQueryHandler}
*
* @public
*/
export function unregisterCustomQueryHandler(name: string): void {
customQueryHandlers.unregister(name);
}
/**
* @deprecated Import {@link Puppeteer} and use the static method
* {@link Puppeteer.customQueryHandlerNames}
*
* @public
*/
export function customQueryHandlerNames(): string[] {
return customQueryHandlers.names();
}
/**
* @deprecated Import {@link Puppeteer} and use the static method
* {@link Puppeteer.clearCustomQueryHandlers}
*
* @public
*/
export function clearCustomQueryHandlers(): void {
customQueryHandlers.clear();
}

View file

@ -14,10 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
/** /**
* Dialog instances are dispatched by the {@link Page} via the `dialog` event. * Dialog instances are dispatched by the {@link Page} via the `dialog` event.
* *

View file

@ -15,26 +15,38 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js';
import {ExecutionContext} from './ExecutionContext.js';
import {Frame} from './Frame.js';
import {FrameManager} from './FrameManager.js';
import {WaitForSelectorOptions} from './IsolatedWorld.js';
import { import {
BoundingBox, BoundingBox,
BoxModel, BoxModel,
ClickOptions, ClickOptions,
JSHandle, ElementHandle,
Offset, Offset,
Point, Point,
PressOptions, PressOptions,
} from './JSHandle.js'; } from '../api/ElementHandle.js';
import {JSHandle} from '../api/JSHandle.js';
import {Page, ScreenshotOptions} from '../api/Page.js'; import {Page, ScreenshotOptions} from '../api/Page.js';
import {getQueryHandlerAndSelector} from './QueryHandler.js'; import {assert} from '../util/assert.js';
import {ElementFor, EvaluateFunc, HandleFor, NodeFor} from './types.js'; import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
import {CDPSession} from './Connection.js';
import {ExecutionContext} from './ExecutionContext.js';
import {Frame} from './Frame.js';
import {FrameManager} from './FrameManager.js';
import {getQueryHandlerAndSelector} from './GetQueryHandler.js';
import {WaitForSelectorOptions} from './IsolatedWorld.js';
import {CDPJSHandle} from './JSHandle.js';
import {CDPPage} from './Page.js';
import {
ElementFor,
EvaluateFuncWith,
HandleFor,
HandleOr,
NodeFor,
} from './types.js';
import {KeyInput} from './USKeyboardLayout.js'; import {KeyInput} from './USKeyboardLayout.js';
import {debugError, isString} from './util.js'; import {debugError, isString} from './util.js';
import {CDPPage} from './Page.js';
const applyOffsetsToQuad = ( const applyOffsetsToQuad = (
quad: Point[], quad: Point[],
@ -47,56 +59,76 @@ const applyOffsetsToQuad = (
}; };
/** /**
* ElementHandle represents an in-page DOM element. * The CDPElementHandle extends ElementHandle now to keep compatibility
* with `instanceof` because of that we need to have methods for
* CDPJSHandle to in this implementation as well.
* *
* @remarks * @internal
* ElementHandles can be created with the {@link Page.$} method.
*
* ```ts
* import puppeteer from 'puppeteer';
*
* (async () => {
* const browser = await puppeteer.launch();
* const page = await browser.newPage();
* await page.goto('https://example.com');
* const hrefElement = await page.$('a');
* await hrefElement.click();
* // ...
* })();
* ```
*
* ElementHandle prevents the DOM element from being garbage-collected unless the
* handle is {@link JSHandle.dispose | disposed}. ElementHandles are auto-disposed
* when their origin frame gets navigated.
*
* ElementHandle instances can be used as arguments in {@link Page.$eval} and
* {@link Page.evaluate} methods.
*
* If you're using TypeScript, ElementHandle takes a generic argument that
* denotes the type of element the handle is holding within. For example, if you
* have a handle to a `<select>` element, you can type it as
* `ElementHandle<HTMLSelectElement>` and you get some nicer type checks.
*
* @public
*/ */
export class CDPElementHandle<
export class ElementHandle<
ElementType extends Node = Element ElementType extends Node = Element
> extends JSHandle<ElementType> { > extends ElementHandle<ElementType> {
#frame: Frame; #frame: Frame;
#jsHandle: CDPJSHandle<ElementType>;
/**
* @internal
*/
constructor( constructor(
context: ExecutionContext, context: ExecutionContext,
remoteObject: Protocol.Runtime.RemoteObject, remoteObject: Protocol.Runtime.RemoteObject,
frame: Frame frame: Frame
) { ) {
super(context, remoteObject); super();
this.#jsHandle = new CDPJSHandle(context, remoteObject);
this.#frame = frame; this.#frame = frame;
} }
/**
* @internal
*/
override executionContext(): ExecutionContext {
return this.#jsHandle.executionContext();
}
/**
* @internal
*/
override get client(): CDPSession {
return this.#jsHandle.client;
}
override get id(): string | undefined {
return this.#jsHandle.id;
}
override remoteObject(): Protocol.Runtime.RemoteObject {
return this.#jsHandle.remoteObject();
}
override async evaluate<
Params extends unknown[],
Func extends EvaluateFuncWith<ElementType, Params> = EvaluateFuncWith<
ElementType,
Params
>
>(
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
return this.executionContext().evaluate(pageFunction, this, ...args);
}
override evaluateHandle<
Params extends unknown[],
Func extends EvaluateFuncWith<ElementType, Params> = EvaluateFuncWith<
ElementType,
Params
>
>(
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
return this.executionContext().evaluateHandle(pageFunction, this, ...args);
}
get #frameManager(): FrameManager { get #frameManager(): FrameManager {
return this.#frame._frameManager; return this.#frame._frameManager;
} }
@ -105,85 +137,72 @@ export class ElementHandle<
return this.#frame.page(); return this.#frame.page();
} }
get frame(): Frame { override get frame(): Frame {
return this.#frame; return this.#frame;
} }
/** override get disposed(): boolean {
* Queries the current element for an element matching the given selector. return this.#jsHandle.disposed;
* }
* @param selector - The selector to query for.
* @returns A {@link ElementHandle | element handle} to the first element override async getProperty<K extends keyof ElementType>(
* matching the given selector. Otherwise, `null`. propertyName: HandleOr<K>
*/ ): Promise<HandleFor<ElementType[K]>>;
async $<Selector extends string>( override async getProperty(propertyName: string): Promise<JSHandle<unknown>>;
override async getProperty<K extends keyof ElementType>(
propertyName: HandleOr<K>
): Promise<HandleFor<ElementType[K]>> {
return this.#jsHandle.getProperty(propertyName);
}
override async getProperties(): Promise<Map<string, JSHandle>> {
return this.#jsHandle.getProperties();
}
override asElement(): CDPElementHandle<ElementType> | null {
return this;
}
override async jsonValue(): Promise<ElementType> {
return this.#jsHandle.jsonValue();
}
override toString(): string {
return this.#jsHandle.toString();
}
override async dispose(): Promise<void> {
return await this.#jsHandle.dispose();
}
override async $<Selector extends string>(
selector: Selector selector: Selector
): Promise<ElementHandle<NodeFor<Selector>> | null> { ): Promise<CDPElementHandle<NodeFor<Selector>> | null> {
const {updatedSelector, queryHandler} = const {updatedSelector, QueryHandler} =
getQueryHandlerAndSelector(selector); getQueryHandlerAndSelector(selector);
assert( return (await QueryHandler.queryOne(
queryHandler.queryOne,
'Cannot handle queries for a single element with the given selector'
);
return (await queryHandler.queryOne(
this, this,
updatedSelector updatedSelector
)) as ElementHandle<NodeFor<Selector>> | null; )) as CDPElementHandle<NodeFor<Selector>> | null;
} }
/** override async $$<Selector extends string>(
* Queries the current element for all elements matching the given selector.
*
* @param selector - The selector to query for.
* @returns An array of {@link ElementHandle | element handles} that point to
* elements matching the given selector.
*/
async $$<Selector extends string>(
selector: Selector selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>> { ): Promise<Array<CDPElementHandle<NodeFor<Selector>>>> {
const {updatedSelector, queryHandler} = const {updatedSelector, QueryHandler} =
getQueryHandlerAndSelector(selector); getQueryHandlerAndSelector(selector);
assert( return AsyncIterableUtil.collect(
queryHandler.queryAll, QueryHandler.queryAll(this, updatedSelector)
'Cannot handle queries for a multiple element with the given selector' ) as Promise<Array<CDPElementHandle<NodeFor<Selector>>>>;
);
return (await queryHandler.queryAll(this, updatedSelector)) as Array<
ElementHandle<NodeFor<Selector>>
>;
} }
/** override async $eval<
* Runs the given function on the first element matching the given selector in
* the current element.
*
* If the given function returns a promise, then this method will wait till
* the promise resolves.
*
* @example
*
* ```ts
* const tweetHandle = await page.$('.tweet');
* expect(await tweetHandle.$eval('.like', node => node.innerText)).toBe(
* '100'
* );
* expect(await tweetHandle.$eval('.retweets', node => node.innerText)).toBe(
* '10'
* );
* ```
*
* @param selector - The selector to query for.
* @param pageFunction - The function to be evaluated in this element's page's
* context. The first element matching the selector will be passed in as the
* first argument.
* @param args - Additional arguments to pass to `pageFunction`.
* @returns A promise to the result of the function.
*/
async $eval<
Selector extends string, Selector extends string,
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc< Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
[ElementHandle<NodeFor<Selector>>, ...Params] NodeFor<Selector>,
> = EvaluateFunc<[ElementHandle<NodeFor<Selector>>, ...Params]> Params
>
>( >(
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
@ -200,238 +219,69 @@ export class ElementHandle<
return result; return result;
} }
/** override async $$eval<
* Runs the given function on an array of elements matching the given selector
* in the current element.
*
* If the given function returns a promise, then this method will wait till
* the promise resolves.
*
* @example
* HTML:
*
* ```html
* <div class="feed">
* <div class="tweet">Hello!</div>
* <div class="tweet">Hi!</div>
* </div>
* ```
*
* JavaScript:
*
* ```js
* const feedHandle = await page.$('.feed');
* expect(
* await feedHandle.$$eval('.tweet', nodes => nodes.map(n => n.innerText))
* ).toEqual(['Hello!', 'Hi!']);
* ```
*
* @param selector - The selector to query for.
* @param pageFunction - The function to be evaluated in the element's page's
* context. An array of elements matching the given selector will be passed to
* the function as its first argument.
* @param args - Additional arguments to pass to `pageFunction`.
* @returns A promise to the result of the function.
*/
async $$eval<
Selector extends string, Selector extends string,
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc< Func extends EvaluateFuncWith<
[HandleFor<Array<NodeFor<Selector>>>, ...Params] Array<NodeFor<Selector>>,
> = EvaluateFunc<[HandleFor<Array<NodeFor<Selector>>>, ...Params]> Params
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
>( >(
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
...args: Params ...args: Params
): Promise<Awaited<ReturnType<Func>>> { ): Promise<Awaited<ReturnType<Func>>> {
const {updatedSelector, queryHandler} = const results = await this.$$(selector);
getQueryHandlerAndSelector(selector); const elements = await this.evaluateHandle((_, ...elements) => {
assert(
queryHandler.queryAll,
'Cannot handle queries for a multiple element with the given selector'
);
const handles = (await queryHandler.queryAll(
this,
updatedSelector
)) as Array<HandleFor<NodeFor<Selector>>>;
const elements = (await this.evaluateHandle((_, ...elements) => {
return elements; return elements;
}, ...handles)) as JSHandle<Array<NodeFor<Selector>>>; }, ...results);
const [result] = await Promise.all([ const [result] = await Promise.all([
elements.evaluate(pageFunction, ...args), elements.evaluate(pageFunction, ...args),
...handles.map(handle => { ...results.map(results => {
return handle.dispose(); return results.dispose();
}), }),
]); ]);
await elements.dispose(); await elements.dispose();
return result; return result;
} }
/** override async $x(
* @deprecated Use {@link ElementHandle.$$} with the `xpath` prefix. expression: string
* ): Promise<Array<CDPElementHandle<Node>>> {
* Example: `await elementHandle.$$('xpath/' + xpathExpression)`
*
* The method evaluates the XPath expression relative to the elementHandle.
* If `xpath` starts with `//` instead of `.//`, the dot will be appended
* automatically.
*
* If there are no such elements, the method will resolve to an empty array.
* @param expression - Expression to {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate | evaluate}
*/
async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
if (expression.startsWith('//')) { if (expression.startsWith('//')) {
expression = `.${expression}`; expression = `.${expression}`;
} }
return this.$$(`xpath/${expression}`); return this.$$(`xpath/${expression}`);
} }
/** override async waitForSelector<Selector extends string>(
* Wait for an element matching the given selector to appear in the current
* element.
*
* Unlike {@link Frame.waitForSelector}, this method does not work across
* navigations or if the element is detached from DOM.
*
* @example
*
* ```ts
* import puppeteer from 'puppeteer';
*
* (async () => {
* const browser = await puppeteer.launch();
* const page = await browser.newPage();
* let currentURL;
* page
* .mainFrame()
* .waitForSelector('img')
* .then(() => console.log('First URL with image: ' + currentURL));
*
* for (currentURL of [
* 'https://example.com',
* 'https://google.com',
* 'https://bbc.com',
* ]) {
* await page.goto(currentURL);
* }
* await browser.close();
* })();
* ```
*
* @param selector - The selector to query and wait for.
* @param options - Options for customizing waiting behavior.
* @returns An element matching the given selector.
* @throws Throws if an element matching the given selector doesn't appear.
*/
async waitForSelector<Selector extends string>(
selector: Selector, selector: Selector,
options: WaitForSelectorOptions = {} options: WaitForSelectorOptions = {}
): Promise<ElementHandle<NodeFor<Selector>> | null> { ): Promise<CDPElementHandle<NodeFor<Selector>> | null> {
const {updatedSelector, queryHandler} = const {updatedSelector, QueryHandler} =
getQueryHandlerAndSelector(selector); getQueryHandlerAndSelector(selector);
assert(queryHandler.waitFor, 'Query handler does not support waiting'); return (await QueryHandler.waitFor(
return (await queryHandler.waitFor(
this, this,
updatedSelector, updatedSelector,
options options
)) as ElementHandle<NodeFor<Selector>> | null; )) as CDPElementHandle<NodeFor<Selector>> | null;
} }
/** override async waitForXPath(
* @deprecated Use {@link ElementHandle.waitForSelector} with the `xpath`
* prefix.
*
* Example: `await elementHandle.waitForSelector('xpath/' + xpathExpression)`
*
* The method evaluates the XPath expression relative to the elementHandle.
*
* Wait for the `xpath` within the element. If at the moment of calling the
* method the `xpath` already exists, the method will return immediately. If
* the `xpath` doesn't appear after the `timeout` milliseconds of waiting, the
* function will throw.
*
* If `xpath` starts with `//` instead of `.//`, the dot will be appended
* automatically.
*
* This method works across navigation.
*
* ```ts
* import puppeteer from 'puppeteer';
* (async () => {
* const browser = await puppeteer.launch();
* const page = await browser.newPage();
* let currentURL;
* page
* .waitForXPath('//img')
* .then(() => console.log('First URL with image: ' + currentURL));
* for (currentURL of [
* 'https://example.com',
* 'https://google.com',
* 'https://bbc.com',
* ]) {
* await page.goto(currentURL);
* }
* await browser.close();
* })();
* ```
*
* @param xpath - A
* {@link https://developer.mozilla.org/en-US/docs/Web/XPath | xpath} of an
* element to wait for
* @param options - Optional waiting parameters
* @returns Promise which resolves when element specified by xpath string is
* added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is
* not found in DOM.
* @remarks
* The optional Argument `options` have properties:
*
* - `visible`: A boolean to wait for element to be present in DOM and to be
* visible, i.e. to not have `display: none` or `visibility: hidden` CSS
* properties. Defaults to `false`.
*
* - `hidden`: A boolean wait for element to not be found in the DOM or to be
* hidden, i.e. have `display: none` or `visibility: hidden` CSS properties.
* Defaults to `false`.
*
* - `timeout`: A number which is maximum time to wait for in milliseconds.
* Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The
* default value can be changed by using the {@link Page.setDefaultTimeout}
* method.
*/
async waitForXPath(
xpath: string, xpath: string,
options: { options: {
visible?: boolean; visible?: boolean;
hidden?: boolean; hidden?: boolean;
timeout?: number; timeout?: number;
} = {} } = {}
): Promise<ElementHandle<Node> | null> { ): Promise<CDPElementHandle<Node> | null> {
if (xpath.startsWith('//')) { if (xpath.startsWith('//')) {
xpath = `.${xpath}`; xpath = `.${xpath}`;
} }
return this.waitForSelector(`xpath/${xpath}`, options); return this.waitForSelector(`xpath/${xpath}`, options);
} }
/** override async toElement<
* Converts the current handle to the given element type.
*
* @example
*
* ```ts
* const element: ElementHandle<Element> = await page.$(
* '.class-name-of-anchor'
* );
* // DO NOT DISPOSE `element`, this will be always be the same handle.
* const anchor: ElementHandle<HTMLAnchorElement> = await element.toElement(
* 'a'
* );
* ```
*
* @param tagName - The tag name of the desired element type.
* @throws An error if the handle does not match. **The handle will not be
* automatically disposed.**
*/
async toElement<
K extends keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap K extends keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap
>(tagName: K): Promise<HandleFor<ElementFor<K>>> { >(tagName: K): Promise<HandleFor<ElementFor<K>>> {
const isMatchingTagName = await this.evaluate((node, tagName) => { const isMatchingTagName = await this.evaluate((node, tagName) => {
@ -443,15 +293,7 @@ export class ElementHandle<
return this as unknown as HandleFor<ElementFor<K>>; return this as unknown as HandleFor<ElementFor<K>>;
} }
override asElement(): ElementHandle<ElementType> | null { override async contentFrame(): Promise<Frame | null> {
return this;
}
/**
* Resolves to the content frame for element handles referencing
* iframe nodes, or null otherwise
*/
async contentFrame(): Promise<Frame | null> {
const nodeInfo = await this.client.send('DOM.describeNode', { const nodeInfo = await this.client.send('DOM.describeNode', {
objectId: this.remoteObject().objectId, objectId: this.remoteObject().objectId,
}); });
@ -461,7 +303,9 @@ export class ElementHandle<
return this.#frameManager.frame(nodeInfo.node.frameId); return this.#frameManager.frame(nodeInfo.node.frameId);
} }
async #scrollIntoViewIfNeeded(this: ElementHandle<Element>): Promise<void> { async #scrollIntoViewIfNeeded(
this: CDPElementHandle<Element>
): Promise<void> {
const error = await this.evaluate( const error = await this.evaluate(
async (element): Promise<string | undefined> => { async (element): Promise<string | undefined> => {
if (!element.isConnected) { if (!element.isConnected) {
@ -541,10 +385,7 @@ export class ElementHandle<
return {offsetX, offsetY}; return {offsetX, offsetY};
} }
/** override async clickablePoint(offset?: Offset): Promise<Point> {
* Returns the middle point within an element unless a specific offset is provided.
*/
async clickablePoint(offset?: Offset): Promise<Point> {
const [result, layoutMetrics] = await Promise.all([ const [result, layoutMetrics] = await Promise.all([
this.client this.client
.send('DOM.getContentQuads', { .send('DOM.getContentQuads', {
@ -615,7 +456,7 @@ export class ElementHandle<
#getBoxModel(): Promise<void | Protocol.DOM.GetBoxModelResponse> { #getBoxModel(): Promise<void | Protocol.DOM.GetBoxModelResponse> {
const params: Protocol.DOM.GetBoxModelRequest = { const params: Protocol.DOM.GetBoxModelRequest = {
objectId: this.remoteObject().objectId, objectId: this.id,
}; };
return this.client.send('DOM.getBoxModel', params).catch(error => { return this.client.send('DOM.getBoxModel', params).catch(error => {
return debugError(error); return debugError(error);
@ -649,7 +490,7 @@ export class ElementHandle<
* uses {@link Page.mouse} to hover over the center of the element. * uses {@link Page.mouse} to hover over the center of the element.
* If the element is detached from DOM, the method throws an error. * If the element is detached from DOM, the method throws an error.
*/ */
async hover(this: ElementHandle<Element>): Promise<void> { override async hover(this: CDPElementHandle<Element>): Promise<void> {
await this.#scrollIntoViewIfNeeded(); await this.#scrollIntoViewIfNeeded();
const {x, y} = await this.clickablePoint(); const {x, y} = await this.clickablePoint();
await this.#page.mouse.move(x, y); await this.#page.mouse.move(x, y);
@ -660,8 +501,8 @@ export class ElementHandle<
* uses {@link Page.mouse} to click in the center of the element. * uses {@link Page.mouse} to click in the center of the element.
* If the element is detached from DOM, the method throws an error. * If the element is detached from DOM, the method throws an error.
*/ */
async click( override async click(
this: ElementHandle<Element>, this: CDPElementHandle<Element>,
options: ClickOptions = {} options: ClickOptions = {}
): Promise<void> { ): Promise<void> {
await this.#scrollIntoViewIfNeeded(); await this.#scrollIntoViewIfNeeded();
@ -672,8 +513,8 @@ export class ElementHandle<
/** /**
* This method creates and captures a dragevent from the element. * This method creates and captures a dragevent from the element.
*/ */
async drag( override async drag(
this: ElementHandle<Element>, this: CDPElementHandle<Element>,
target: Point target: Point
): Promise<Protocol.Input.DragData> { ): Promise<Protocol.Input.DragData> {
assert( assert(
@ -685,11 +526,8 @@ export class ElementHandle<
return await this.#page.mouse.drag(start, target); return await this.#page.mouse.drag(start, target);
} }
/** override async dragEnter(
* This method creates a `dragenter` event on the element. this: CDPElementHandle<Element>,
*/
async dragEnter(
this: ElementHandle<Element>,
data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1} data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1}
): Promise<void> { ): Promise<void> {
await this.#scrollIntoViewIfNeeded(); await this.#scrollIntoViewIfNeeded();
@ -697,11 +535,8 @@ export class ElementHandle<
await this.#page.mouse.dragEnter(target, data); await this.#page.mouse.dragEnter(target, data);
} }
/** override async dragOver(
* This method creates a `dragover` event on the element. this: CDPElementHandle<Element>,
*/
async dragOver(
this: ElementHandle<Element>,
data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1} data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1}
): Promise<void> { ): Promise<void> {
await this.#scrollIntoViewIfNeeded(); await this.#scrollIntoViewIfNeeded();
@ -709,11 +544,8 @@ export class ElementHandle<
await this.#page.mouse.dragOver(target, data); await this.#page.mouse.dragOver(target, data);
} }
/** override async drop(
* This method triggers a drop on the element. this: CDPElementHandle<Element>,
*/
async drop(
this: ElementHandle<Element>,
data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1} data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1}
): Promise<void> { ): Promise<void> {
await this.#scrollIntoViewIfNeeded(); await this.#scrollIntoViewIfNeeded();
@ -721,12 +553,9 @@ export class ElementHandle<
await this.#page.mouse.drop(destination, data); await this.#page.mouse.drop(destination, data);
} }
/** override async dragAndDrop(
* This method triggers a dragenter, dragover, and drop on the element. this: CDPElementHandle<Element>,
*/ target: CDPElementHandle<Node>,
async dragAndDrop(
this: ElementHandle<Element>,
target: ElementHandle<Node>,
options?: {delay: number} options?: {delay: number}
): Promise<void> { ): Promise<void> {
await this.#scrollIntoViewIfNeeded(); await this.#scrollIntoViewIfNeeded();
@ -735,23 +564,7 @@ export class ElementHandle<
await this.#page.mouse.dragAndDrop(startPoint, targetPoint, options); await this.#page.mouse.dragAndDrop(startPoint, targetPoint, options);
} }
/** override async select(...values: string[]): Promise<string[]> {
* Triggers a `change` and `input` event once all the provided options have been
* selected. If there's no `<select>` element matching `selector`, the method
* throws an error.
*
* @example
*
* ```ts
* handle.select('blue'); // single selection
* handle.select('red', 'green', 'blue'); // multiple selections
* ```
*
* @param values - Values of options to select. If the `<select>` has the
* `multiple` attribute, all values are considered, otherwise only the first
* one is taken into account.
*/
async select(...values: string[]): Promise<string[]> {
for (const value of values) { for (const value of values) {
assert( assert(
isString(value), isString(value),
@ -795,18 +608,8 @@ export class ElementHandle<
}, values); }, values);
} }
/** override async uploadFile(
* This method expects `elementHandle` to point to an this: CDPElementHandle<HTMLInputElement>,
* {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input | input element}.
*
* @param filePaths - Sets the value of the file input to these paths.
* If a path is relative, then it is resolved against the
* {@link https://nodejs.org/api/process.html#process_process_cwd | current working directory}.
* Note for locals script connecting to remote chrome environments,
* paths must be absolute.
*/
async uploadFile(
this: ElementHandle<HTMLInputElement>,
...filePaths: string[] ...filePaths: string[]
): Promise<void> { ): Promise<void> {
const isMultiple = await this.evaluate(element => { const isMultiple = await this.evaluate(element => {
@ -837,7 +640,9 @@ export class ElementHandle<
} }
}); });
const {objectId} = this.remoteObject(); const {objectId} = this.remoteObject();
const {node} = await this.client.send('DOM.describeNode', {objectId}); const {node} = await this.client.send('DOM.describeNode', {
objectId,
});
const {backendNodeId} = node; const {backendNodeId} = node;
/* The zero-length array is a special case, it seems that /* The zero-length array is a special case, it seems that
@ -861,21 +666,31 @@ export class ElementHandle<
} }
} }
/** override async tap(this: CDPElementHandle<Element>): Promise<void> {
* This method scrolls element into view if needed, and then uses
* {@link Touchscreen.tap} to tap in the center of the element.
* If the element is detached from DOM, the method throws an error.
*/
async tap(this: ElementHandle<Element>): Promise<void> {
await this.#scrollIntoViewIfNeeded(); await this.#scrollIntoViewIfNeeded();
const {x, y} = await this.clickablePoint(); const {x, y} = await this.clickablePoint();
await this.#page.touchscreen.tap(x, y); await this.#page.touchscreen.touchStart(x, y);
await this.#page.touchscreen.touchEnd();
} }
/** override async touchStart(this: CDPElementHandle<Element>): Promise<void> {
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus | focus} on the element. await this.#scrollIntoViewIfNeeded();
*/ const {x, y} = await this.clickablePoint();
async focus(): Promise<void> { await this.#page.touchscreen.touchStart(x, y);
}
override async touchMove(this: CDPElementHandle<Element>): Promise<void> {
await this.#scrollIntoViewIfNeeded();
const {x, y} = await this.clickablePoint();
await this.#page.touchscreen.touchMove(x, y);
}
override async touchEnd(this: CDPElementHandle<Element>): Promise<void> {
await this.#scrollIntoViewIfNeeded();
await this.#page.touchscreen.touchEnd();
}
override async focus(): Promise<void> {
await this.evaluate(element => { await this.evaluate(element => {
if (!(element instanceof HTMLElement)) { if (!(element instanceof HTMLElement)) {
throw new Error('Cannot focus non-HTMLElement'); throw new Error('Cannot focus non-HTMLElement');
@ -884,58 +699,17 @@ export class ElementHandle<
}); });
} }
/** override async type(text: string, options?: {delay: number}): Promise<void> {
* Focuses the element, and then sends a `keydown`, `keypress`/`input`, and
* `keyup` event for each character in the text.
*
* To press a special key, like `Control` or `ArrowDown`,
* use {@link ElementHandle.press}.
*
* @example
*
* ```ts
* await elementHandle.type('Hello'); // Types instantly
* await elementHandle.type('World', {delay: 100}); // Types slower, like a user
* ```
*
* @example
* An example of typing into a text field and then submitting the form:
*
* ```ts
* const elementHandle = await page.$('input');
* await elementHandle.type('some text');
* await elementHandle.press('Enter');
* ```
*/
async type(text: string, options?: {delay: number}): Promise<void> {
await this.focus(); await this.focus();
await this.#page.keyboard.type(text, options); await this.#page.keyboard.type(text, options);
} }
/** override async press(key: KeyInput, options?: PressOptions): Promise<void> {
* Focuses the element, and then uses {@link Keyboard.down} and {@link Keyboard.up}.
*
* @remarks
* If `key` is a single character and no modifier keys besides `Shift`
* are being held down, a `keypress`/`input` event will also be generated.
* The `text` option can be specified to force an input event to be generated.
*
* **NOTE** Modifier keys DO affect `elementHandle.press`. Holding down `Shift`
* will type the text in upper case.
*
* @param key - Name of key to press, such as `ArrowLeft`.
* See {@link KeyInput} for a list of all key names.
*/
async press(key: KeyInput, options?: PressOptions): Promise<void> {
await this.focus(); await this.focus();
await this.#page.keyboard.press(key, options); await this.#page.keyboard.press(key, options);
} }
/** override async boundingBox(): Promise<BoundingBox | null> {
* This method returns the bounding box of the element (relative to the main frame),
* or `null` if the element is not visible.
*/
async boundingBox(): Promise<BoundingBox | null> {
const result = await this.#getBoxModel(); const result = await this.#getBoxModel();
if (!result) { if (!result) {
@ -952,15 +726,7 @@ export class ElementHandle<
return {x: x + offsetX, y: y + offsetY, width, height}; return {x: x + offsetX, y: y + offsetY, width, height};
} }
/** override async boxModel(): Promise<BoxModel | null> {
* This method returns boxes of the element, or `null` if the element is not visible.
*
* @remarks
*
* Boxes are represented as an array of points;
* Each Point is an object `{x, y}`. Box points are sorted clock-wise.
*/
async boxModel(): Promise<BoxModel | null> {
const result = await this.#getBoxModel(); const result = await this.#getBoxModel();
if (!result) { if (!result) {
@ -996,13 +762,8 @@ export class ElementHandle<
}; };
} }
/** override async screenshot(
* This method scrolls element into view if needed, and then uses this: CDPElementHandle<Element>,
* {@link Page.screenshot} to take a screenshot of the element.
* If the element is detached from DOM, the method throws an error.
*/
async screenshot(
this: ElementHandle<Element>,
options: ScreenshotOptions = {} options: ScreenshotOptions = {}
): Promise<string | Buffer> { ): Promise<string | Buffer> {
let needsViewportReset = false; let needsViewportReset = false;
@ -1059,11 +820,8 @@ export class ElementHandle<
return imageData; return imageData;
} }
/** override async isIntersectingViewport(
* Resolves to true if the element is visible in the current viewport. this: CDPElementHandle<Element>,
*/
async isIntersectingViewport(
this: ElementHandle<Element>,
options?: { options?: {
threshold?: number; threshold?: number;
} }

View file

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import {Protocol} from 'devtools-protocol';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {Viewport} from './PuppeteerViewport.js'; import {Viewport} from './PuppeteerViewport.js';
import {Protocol} from 'devtools-protocol';
/** /**
* @internal * @internal

View file

@ -15,10 +15,21 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import type {ElementHandle} from '../api/ElementHandle.js';
import {JSHandle} from '../api/JSHandle.js';
import type PuppeteerUtil from '../injected/injected.js';
import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
import {stringifyFunction} from '../util/Function.js';
import {ARIAQueryHandler} from './AriaQueryHandler.js';
import {Binding} from './Binding.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {CDPElementHandle} from './ElementHandle.js';
import {IsolatedWorld} from './IsolatedWorld.js'; import {IsolatedWorld} from './IsolatedWorld.js';
import {JSHandle} from './JSHandle.js'; import {CDPJSHandle} from './JSHandle.js';
import {LazyArg} from './LazyArg.js'; import {LazyArg} from './LazyArg.js';
import {scriptInjector} from './ScriptInjector.js';
import {EvaluateFunc, HandleFor} from './types.js'; import {EvaluateFunc, HandleFor} from './types.js';
import { import {
createJSHandle, createJSHandle,
@ -71,7 +82,7 @@ export class ExecutionContext {
/** /**
* @internal * @internal
*/ */
_contextName: string; _contextName?: string;
/** /**
* @internal * @internal
@ -84,7 +95,55 @@ export class ExecutionContext {
this._client = client; this._client = client;
this._world = world; this._world = world;
this._contextId = contextPayload.id; this._contextId = contextPayload.id;
this._contextName = contextPayload.name; if (contextPayload.name) {
this._contextName = contextPayload.name;
}
}
#puppeteerUtil?: Promise<JSHandle<PuppeteerUtil>>;
get puppeteerUtil(): Promise<JSHandle<PuppeteerUtil>> {
scriptInjector.inject(script => {
if (this.#puppeteerUtil) {
this.#puppeteerUtil.then(handle => {
handle.dispose();
});
}
this.#puppeteerUtil = Promise.all([
this.#installGlobalBinding(
new Binding(
'__ariaQuerySelector',
ARIAQueryHandler.queryOne as (...args: unknown[]) => unknown
)
),
this.#installGlobalBinding(
new Binding('__ariaQuerySelectorAll', (async (
element: ElementHandle<Node>,
selector: string
): Promise<JSHandle<Node[]>> => {
const results = ARIAQueryHandler.queryAll(element, selector);
return element.executionContext().evaluateHandle((...elements) => {
return elements;
}, ...(await AsyncIterableUtil.collect(results)));
}) as (...args: unknown[]) => unknown)
),
]).then(() => {
return this.evaluateHandle(script) as Promise<JSHandle<PuppeteerUtil>>;
});
}, !this.#puppeteerUtil);
return this.#puppeteerUtil as Promise<JSHandle<PuppeteerUtil>>;
}
async #installGlobalBinding(binding: Binding) {
try {
if (this._world) {
this._world._bindings.set(binding.name, binding);
await this._world._addBindingToContext(this, binding.name);
}
} catch {
// If the binding cannot be added, then either the browser doesn't support
// bindings (e.g. Firefox) or the context is broken. Either breakage is
// okay, so we ignore the error.
}
} }
/** /**
@ -250,29 +309,10 @@ export class ExecutionContext {
: createJSHandle(this, remoteObject); : createJSHandle(this, remoteObject);
} }
let functionText = pageFunction.toString();
try {
new Function('(' + functionText + ')');
} catch (error) {
// 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;
}
try {
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!');
}
}
let callFunctionOnPromise; let callFunctionOnPromise;
try { try {
callFunctionOnPromise = this._client.send('Runtime.callFunctionOn', { callFunctionOnPromise = this._client.send('Runtime.callFunctionOn', {
functionDeclaration: functionText + '\n' + suffix + '\n', functionDeclaration: `${stringifyFunction(pageFunction)}\n${suffix}\n`,
executionContextId: this._contextId, executionContextId: this._contextId,
arguments: await Promise.all(args.map(convertArgument.bind(this))), arguments: await Promise.all(args.map(convertArgument.bind(this))),
returnByValue, returnByValue,
@ -304,7 +344,7 @@ export class ExecutionContext {
arg: unknown arg: unknown
): Promise<Protocol.Runtime.CallArgument> { ): Promise<Protocol.Runtime.CallArgument> {
if (arg instanceof LazyArg) { if (arg instanceof LazyArg) {
arg = await arg.get(); arg = await arg.get(this);
} }
if (typeof arg === 'bigint') { if (typeof arg === 'bigint') {
// eslint-disable-line valid-typeof // eslint-disable-line valid-typeof
@ -322,7 +362,10 @@ export class ExecutionContext {
if (Object.is(arg, NaN)) { if (Object.is(arg, NaN)) {
return {unserializableValue: 'NaN'}; return {unserializableValue: 'NaN'};
} }
const objectHandle = arg && arg instanceof JSHandle ? arg : null; const objectHandle =
arg && (arg instanceof CDPJSHandle || arg instanceof CDPElementHandle)
? arg
: null;
if (objectHandle) { if (objectHandle) {
if (objectHandle.executionContext() !== this) { if (objectHandle.executionContext() !== this) {
throw new Error( throw new Error(

View file

@ -15,8 +15,9 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {ElementHandle} from '../api/ElementHandle.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {ElementHandle} from './ElementHandle.js';
/** /**
* File choosers let you react to the page requesting for a file. * File choosers let you react to the page requesting for a file.

View file

@ -15,17 +15,19 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js';
import {CDPSession, Connection} from './Connection.js';
import {Target} from './Target.js';
import {TargetFilterCallback} from '../api/Browser.js'; import {TargetFilterCallback} from '../api/Browser.js';
import {assert} from '../util/assert.js';
import {CDPSession, Connection} from './Connection.js';
import {EventEmitter} from './EventEmitter.js';
import {Target} from './Target.js';
import { import {
TargetFactory, TargetFactory,
TargetInterceptor, TargetInterceptor,
TargetManagerEmittedEvents, TargetManagerEmittedEvents,
TargetManager, TargetManager,
} from './TargetManager.js'; } from './TargetManager.js';
import {EventEmitter} from './EventEmitter.js';
/** /**
* FirefoxTargetManager implements target management using * FirefoxTargetManager implements target management using

View file

@ -15,12 +15,15 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js';
import {ElementHandle} from '../api/ElementHandle.js';
import {Page} from '../api/Page.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {ElementHandle} from './ElementHandle.js';
import {ExecutionContext} from './ExecutionContext.js'; import {ExecutionContext} from './ExecutionContext.js';
import {FrameManager} from './FrameManager.js'; import {FrameManager} from './FrameManager.js';
import {getQueryHandlerAndSelector} from './GetQueryHandler.js';
import {HTTPResponse} from './HTTPResponse.js'; import {HTTPResponse} from './HTTPResponse.js';
import {MouseButton} from './Input.js'; import {MouseButton} from './Input.js';
import { import {
@ -29,10 +32,9 @@ import {
WaitForSelectorOptions, WaitForSelectorOptions,
} from './IsolatedWorld.js'; } from './IsolatedWorld.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {LazyArg} from './LazyArg.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js'; import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
import {Page} from '../api/Page.js'; import {EvaluateFunc, EvaluateFuncWith, HandleFor, NodeFor} from './types.js';
import {getQueryHandlerAndSelector} from './QueryHandler.js';
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
import {importFS} from './util.js'; import {importFS} from './util.js';
/** /**
@ -353,6 +355,9 @@ export class Frame {
referrerPolicy, referrerPolicy,
}); });
ensureNewDocumentNavigation = !!response.loaderId; ensureNewDocumentNavigation = !!response.loaderId;
if (response.errorText === 'net::ERR_HTTP_RESPONSE_CODE_FAILURE') {
return null;
}
return response.errorText return response.errorText
? new Error(`${response.errorText} at ${url}`) ? new Error(`${response.errorText} at ${url}`)
: null; : null;
@ -514,9 +519,10 @@ export class Frame {
async $eval< async $eval<
Selector extends string, Selector extends string,
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc< Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
[ElementHandle<NodeFor<Selector>>, ...Params] NodeFor<Selector>,
> = EvaluateFunc<[ElementHandle<NodeFor<Selector>>, ...Params]> Params
>
>( >(
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
@ -548,9 +554,10 @@ export class Frame {
async $$eval< async $$eval<
Selector extends string, Selector extends string,
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc< Func extends EvaluateFuncWith<
[Array<NodeFor<Selector>>, ...Params] Array<NodeFor<Selector>>,
> = EvaluateFunc<[Array<NodeFor<Selector>>, ...Params]> Params
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
>( >(
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
@ -612,10 +619,9 @@ export class Frame {
selector: Selector, selector: Selector,
options: WaitForSelectorOptions = {} options: WaitForSelectorOptions = {}
): Promise<ElementHandle<NodeFor<Selector>> | null> { ): Promise<ElementHandle<NodeFor<Selector>> | null> {
const {updatedSelector, queryHandler} = const {updatedSelector, QueryHandler} =
getQueryHandlerAndSelector(selector); getQueryHandlerAndSelector(selector);
assert(queryHandler.waitFor, 'Query handler does not support waiting'); return (await QueryHandler.waitFor(
return (await queryHandler.waitFor(
this, this,
updatedSelector, updatedSelector,
options options
@ -839,7 +845,9 @@ export class Frame {
await promise; await promise;
return script; return script;
}, },
await this.worlds[PUPPETEER_WORLD].puppeteerUtil, LazyArg.create(context => {
return context.puppeteerUtil;
}),
{...options, type, content} {...options, type, content}
) )
); );
@ -923,7 +931,9 @@ export class Frame {
await promise; await promise;
return element; return element;
}, },
await this.worlds[PUPPETEER_WORLD].puppeteerUtil, LazyArg.create(context => {
return context.puppeteerUtil;
}),
options options
) )
); );

View file

@ -15,8 +15,11 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {Page} from '../api/Page.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {CDPSession, isTargetClosedError} from './Connection.js'; import {CDPSession, isTargetClosedError} from './Connection.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
import {EVALUATION_SCRIPT_URL, ExecutionContext} from './ExecutionContext.js'; import {EVALUATION_SCRIPT_URL, ExecutionContext} from './ExecutionContext.js';
@ -25,7 +28,6 @@ import {FrameTree} from './FrameTree.js';
import {IsolatedWorld} from './IsolatedWorld.js'; import {IsolatedWorld} from './IsolatedWorld.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {NetworkManager} from './NetworkManager.js'; import {NetworkManager} from './NetworkManager.js';
import {Page} from '../api/Page.js';
import {Target} from './Target.js'; import {Target} from './Target.js';
import {TimeoutSettings} from './TimeoutSettings.js'; import {TimeoutSettings} from './TimeoutSettings.js';
import {debugError} from './util.js'; import {debugError} from './util.js';
@ -174,12 +176,18 @@ export class FrameManager extends EventEmitter {
contextId: number, contextId: number,
session: CDPSession = this.#client session: CDPSession = this.#client
): ExecutionContext { ): ExecutionContext {
const key = `${session.id()}:${contextId}`; const context = this.getExecutionContextById(contextId, session);
const context = this.#contextIdToContext.get(key);
assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId); assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId);
return context; return context;
} }
getExecutionContextById(
contextId: number,
session: CDPSession = this.#client
): ExecutionContext | undefined {
return this.#contextIdToContext.get(`${session.id()}:${contextId}`);
}
page(): Page { page(): Page {
return this.#page; return this.#page;
} }

View file

@ -18,6 +18,7 @@ import {
createDeferredPromise, createDeferredPromise,
DeferredPromise, DeferredPromise,
} from '../util/DeferredPromise.js'; } from '../util/DeferredPromise.js';
import type {Frame} from './Frame.js'; import type {Frame} from './Frame.js';
/** /**

View file

@ -0,0 +1,70 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {ARIAQueryHandler} from './AriaQueryHandler.js';
import {customQueryHandlers} from './CustomQueryHandler.js';
import {PierceQueryHandler} from './PierceQueryHandler.js';
import {PQueryHandler} from './PQueryHandler.js';
import type {QueryHandler} from './QueryHandler.js';
import {TextQueryHandler} from './TextQueryHandler.js';
import {XPathQueryHandler} from './XPathQueryHandler.js';
export const BUILTIN_QUERY_HANDLERS = Object.freeze({
aria: ARIAQueryHandler,
pierce: PierceQueryHandler,
xpath: XPathQueryHandler,
text: TextQueryHandler,
});
const QUERY_SEPARATORS = ['=', '/'];
/**
* @internal
*/
export function getQueryHandlerByName(
name: string
): typeof QueryHandler | undefined {
if (name in BUILTIN_QUERY_HANDLERS) {
return BUILTIN_QUERY_HANDLERS[name as 'aria'];
}
return customQueryHandlers.get(name);
}
/**
* @internal
*/
export function getQueryHandlerAndSelector(selector: string): {
updatedSelector: string;
QueryHandler: typeof QueryHandler;
} {
for (const handlerMap of [
customQueryHandlers.names().map(name => {
return [name, customQueryHandlers.get(name)!] as const;
}),
Object.entries(BUILTIN_QUERY_HANDLERS),
]) {
for (const [name, QueryHandler] of handlerMap) {
for (const separator of QUERY_SEPARATORS) {
const prefix = `${name}${separator}`;
if (selector.startsWith(prefix)) {
selector = selector.slice(prefix.length);
return {updatedSelector: selector, QueryHandler};
}
}
}
}
return {updatedSelector: selector, QueryHandler: PQueryHandler};
}

View file

@ -15,12 +15,14 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js'; import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {ProtocolError} from './Errors.js'; import {ProtocolError} from './Errors.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
import {Frame} from './Frame.js'; import {Frame} from './Frame.js';
import {debugError, isString} from './util.js';
import {HTTPResponse} from './HTTPResponse.js'; import {HTTPResponse} from './HTTPResponse.js';
import {debugError, isString} from './util.js';
/** /**
* @public * @public

View file

@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import {Protocol} from 'devtools-protocol';
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js'; import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
import {ProtocolError} from './Errors.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
import {Frame} from './Frame.js'; import {Frame} from './Frame.js';
import {HTTPRequest} from './HTTPRequest.js'; import {HTTPRequest} from './HTTPRequest.js';
import {SecurityDetails} from './SecurityDetails.js'; import {SecurityDetails} from './SecurityDetails.js';
import {Protocol} from 'devtools-protocol';
import {ProtocolError} from './Errors.js';
/** /**
* @public * @public

View file

@ -0,0 +1,81 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {JSHandle} from '../api/JSHandle.js';
import {AwaitableIterable, HandleFor} from './types.js';
const DEFAULT_BATCH_SIZE = 20;
/**
* This will transpose an iterator JSHandle into a fast, Puppeteer-side iterator
* of JSHandles.
*
* @param size - The number of elements to transpose. This should be something
* reasonable.
*/
async function* fastTransposeIteratorHandle<T>(
iterator: JSHandle<AwaitableIterator<T>>,
size = DEFAULT_BATCH_SIZE
) {
const array = await iterator.evaluateHandle(async (iterator, size) => {
const results = [];
while (results.length < size) {
const result = await iterator.next();
if (result.done) {
break;
}
results.push(result.value);
}
return results;
}, size);
const properties = (await array.getProperties()) as Map<string, HandleFor<T>>;
await array.dispose();
yield* properties.values();
return properties.size === 0;
}
/**
* This will transpose an iterator JSHandle in batches based on the default size
* of {@link fastTransposeIteratorHandle}.
*/
async function* transposeIteratorHandle<T>(
iterator: JSHandle<AwaitableIterator<T>>
) {
try {
while (!(yield* fastTransposeIteratorHandle(iterator))) {}
} finally {
await iterator.dispose();
}
}
type AwaitableIterator<T> = Iterator<T> | AsyncIterator<T>;
/**
* @internal
*/
export async function* transposeIterableHandle<T>(
handle: JSHandle<AwaitableIterable<T>>
): AsyncIterableIterator<HandleFor<T>> {
yield* transposeIteratorHandle(
await handle.evaluateHandle(iterable => {
return (async function* () {
yield* iterable;
})();
})
);
}

View file

@ -14,11 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
import {Protocol} from 'devtools-protocol';
import {Point} from '../api/ElementHandle.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {_keyDefinitions, KeyDefinition, KeyInput} from './USKeyboardLayout.js'; import {_keyDefinitions, KeyDefinition, KeyInput} from './USKeyboardLayout.js';
import {Protocol} from 'devtools-protocol';
import {Point} from './JSHandle.js';
type KeyDescription = Required< type KeyDescription = Required<
Pick<KeyDefinition, 'keyCode' | 'key' | 'text' | 'code' | 'location'> Pick<KeyDefinition, 'keyCode' | 'key' | 'text' | 'code' | 'location'>
@ -670,12 +672,40 @@ export class Touchscreen {
* @param y - Vertical position of the tap. * @param y - Vertical position of the tap.
*/ */
async tap(x: number, y: number): Promise<void> { async tap(x: number, y: number): Promise<void> {
await this.touchStart(x, y);
await this.touchEnd();
}
/**
* Dispatches a `touchstart` event.
* @param x - Horizontal position of the tap.
* @param y - Vertical position of the tap.
*/
async touchStart(x: number, y: number): Promise<void> {
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', { await this.#client.send('Input.dispatchTouchEvent', {
type: 'touchStart', type: 'touchStart',
touchPoints, touchPoints,
modifiers: this.#keyboard._modifiers, modifiers: this.#keyboard._modifiers,
}); });
}
/**
* Dispatches a `touchMove` event.
* @param x - Horizontal position of the move.
* @param y - Vertical position of the move.
*/
async touchMove(x: number, y: number): Promise<void> {
const movePoints = [{x: Math.round(x), y: Math.round(y)}];
await this.#client.send('Input.dispatchTouchEvent', {
type: 'touchMove',
touchPoints: movePoints,
modifiers: this.#keyboard._modifiers,
});
}
/**
* Dispatches a `touchend` event.
*/
async touchEnd(): Promise<void> {
await this.#client.send('Input.dispatchTouchEvent', { await this.#client.send('Input.dispatchTouchEvent', {
type: 'touchEnd', type: 'touchEnd',
touchPoints: [], touchPoints: [],

View file

@ -15,26 +15,31 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {source as injectedSource} from '../generated/injected.js';
import type {ElementHandle} from '../api/ElementHandle.js';
import {JSHandle} from '../api/JSHandle.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {createDeferredPromise} from '../util/DeferredPromise.js'; import {createDeferredPromise} from '../util/DeferredPromise.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {Binding} from './Binding.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {ExecutionContext} from './ExecutionContext.js'; import {ExecutionContext} from './ExecutionContext.js';
import {Frame} from './Frame.js'; import {Frame} from './Frame.js';
import {FrameManager} from './FrameManager.js'; import {FrameManager} from './FrameManager.js';
import {MouseButton} from './Input.js'; import {MouseButton} from './Input.js';
import {JSHandle} from './JSHandle.js'; import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {LazyArg} from './LazyArg.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js'; import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
import {TimeoutSettings} from './TimeoutSettings.js'; import {TimeoutSettings} from './TimeoutSettings.js';
import {EvaluateFunc, HandleFor, InnerLazyParams, NodeFor} from './types.js'; import {
import {createJSHandle, debugError, pageBindingInitString} from './util.js'; BindingPayload,
EvaluateFunc,
EvaluateFuncWith,
HandleFor,
InnerLazyParams,
NodeFor,
} from './types.js';
import {addPageBinding, createJSHandle, debugError} from './util.js';
import {TaskManager, WaitTask} from './WaitTask.js'; import {TaskManager, WaitTask} from './WaitTask.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import type PuppeteerUtil from '../injected/injected.js';
import type {ElementHandle} from './ElementHandle.js';
/** /**
* @public * @public
@ -91,29 +96,20 @@ export class IsolatedWorld {
#detached = false; #detached = false;
// Set of bindings that have been registered in the current context. // Set of bindings that have been registered in the current context.
#ctxBindings = new Set<string>(); #contextBindings = new Set<string>();
// Contains mapping from functions that should be bound to Puppeteer functions. // Contains mapping from functions that should be bound to Puppeteer functions.
#boundFunctions = new Map<string, Function>(); #bindings = new Map<string, Binding>();
#taskManager = new TaskManager(); #taskManager = new TaskManager();
#puppeteerUtil = createDeferredPromise<JSHandle<PuppeteerUtil>>();
get puppeteerUtil(): Promise<JSHandle<PuppeteerUtil>> {
return this.#puppeteerUtil;
}
get taskManager(): TaskManager { get taskManager(): TaskManager {
return this.#taskManager; return this.#taskManager;
} }
get _boundFunctions(): Map<string, Function> { get _bindings(): Map<string, Binding> {
return this.#boundFunctions; return this.#bindings;
} }
static #bindingIdentifier = (name: string, contextId: number) => {
return `${name}_${contextId}`;
};
constructor(frame: Frame) { constructor(frame: Frame) {
// Keep own reference to client because it might differ from the FrameManager's // Keep own reference to client because it might differ from the FrameManager's
// client for OOP iframes. // client for OOP iframes.
@ -139,31 +135,13 @@ export class IsolatedWorld {
clearContext(): void { clearContext(): void {
this.#document = undefined; this.#document = undefined;
this.#puppeteerUtil = createDeferredPromise();
this.#context = createDeferredPromise(); this.#context = createDeferredPromise();
} }
setContext(context: ExecutionContext): void { setContext(context: ExecutionContext): void {
this.#injectPuppeteerUtil(context); this.#contextBindings.clear();
this.#ctxBindings.clear();
this.#context.resolve(context); this.#context.resolve(context);
} this.#taskManager.rerunAll();
async #injectPuppeteerUtil(context: ExecutionContext): Promise<void> {
try {
this.#puppeteerUtil.resolve(
(await context.evaluateHandle(
`(() => {
const module = {};
${injectedSource}
return module.exports.default;
})()`
)) as JSHandle<PuppeteerUtil>
);
this.#taskManager.rerunAll();
} catch (error: unknown) {
debugError(error);
}
} }
hasContext(): boolean { hasContext(): boolean {
@ -245,9 +223,10 @@ export class IsolatedWorld {
async $eval< async $eval<
Selector extends string, Selector extends string,
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc< Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
[ElementHandle<NodeFor<Selector>>, ...Params] NodeFor<Selector>,
> = EvaluateFunc<[ElementHandle<NodeFor<Selector>>, ...Params]> Params
>
>( >(
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
@ -260,9 +239,10 @@ export class IsolatedWorld {
async $$eval< async $$eval<
Selector extends string, Selector extends string,
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc< Func extends EvaluateFuncWith<
[Array<NodeFor<Selector>>, ...Params] Array<NodeFor<Selector>>,
> = EvaluateFunc<[Array<NodeFor<Selector>>, ...Params]> Params
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
>( >(
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
@ -371,71 +351,50 @@ export class IsolatedWorld {
// If multiple waitFor are set up asynchronously, we need to wait for the // If multiple waitFor are set up asynchronously, we need to wait for the
// first one to set up the binding in the page before running the others. // first one to set up the binding in the page before running the others.
#settingUpBinding: Promise<void> | null = null; #mutex = new Mutex();
async _addBindingToContext( async _addBindingToContext(
context: ExecutionContext, context: ExecutionContext,
name: string name: string
): Promise<void> { ): Promise<void> {
// Previous operation added the binding so we are done. if (this.#contextBindings.has(name)) {
if (
this.#ctxBindings.has(
IsolatedWorld.#bindingIdentifier(name, context._contextId)
)
) {
return; return;
} }
// Wait for other operation to finish
if (this.#settingUpBinding) {
await this.#settingUpBinding;
return this._addBindingToContext(context, name);
}
const bind = async (name: string) => { await this.#mutex.acquire();
const expression = pageBindingInitString('internal', name); try {
try { await context._client.send('Runtime.addBinding', {
// TODO: In theory, it would be enough to call this just once name,
await context._client.send('Runtime.addBinding', { executionContextName: context._contextName,
name, });
executionContextName: context._contextName,
}); await context.evaluate(addPageBinding, 'internal', name);
await context.evaluate(expression);
} catch (error) { this.#contextBindings.add(name);
// We could have tried to evaluate in a context which was already } catch (error) {
// destroyed. This happens, for example, if the page is navigated while // We could have tried to evaluate in a context which was already
// we are trying to add the binding // destroyed. This happens, for example, if the page is navigated while
if (error instanceof Error) { // we are trying to add the binding
// Destroyed context. if (error instanceof Error) {
if (error.message.includes('Execution context was destroyed')) { // Destroyed context.
return; if (error.message.includes('Execution context was destroyed')) {
} return;
// Missing context. }
if (error.message.includes('Cannot find context with specified id')) { // Missing context.
return; if (error.message.includes('Cannot find context with specified id')) {
} return;
} }
debugError(error);
return;
} }
this.#ctxBindings.add(
IsolatedWorld.#bindingIdentifier(name, context._contextId)
);
};
this.#settingUpBinding = bind(name); debugError(error);
await this.#settingUpBinding; } finally {
this.#settingUpBinding = null; this.#mutex.release();
}
} }
#onBindingCalled = async ( #onBindingCalled = async (
event: Protocol.Runtime.BindingCalledEvent event: Protocol.Runtime.BindingCalledEvent
): Promise<void> => { ): Promise<void> => {
let payload: {type: string; name: string; seq: number; args: unknown[]}; let payload: BindingPayload;
if (!this.hasContext()) {
return;
}
const context = await this.executionContext();
try { try {
payload = JSON.parse(event.payload); payload = JSON.parse(event.payload);
} catch { } catch {
@ -443,108 +402,23 @@ export class IsolatedWorld {
// called before our wrapper was initialized. // called before our wrapper was initialized.
return; return;
} }
const {type, name, seq, args} = payload; const {type, name, seq, args, isTrivial} = payload;
if ( if (type !== 'internal') {
type !== 'internal' ||
!this.#ctxBindings.has(
IsolatedWorld.#bindingIdentifier(name, context._contextId)
)
) {
return; return;
} }
if (context._contextId !== event.executionContextId) { if (!this.#contextBindings.has(name)) {
return; return;
} }
try {
const fn = this._boundFunctions.get(name); const context = await this.#context;
if (!fn) { if (event.executionContextId !== context._contextId) {
throw new Error(`Bound function $name is not found`); return;
}
const result = await fn(...args);
await context.evaluate(
(name: string, seq: number, result: unknown) => {
// @ts-expect-error Code is evaluated in a different context.
const callbacks = self[name].callbacks;
callbacks.get(seq).resolve(result);
callbacks.delete(seq);
},
name,
seq,
result
);
} catch (error) {
// The WaitTask may already have been resolved by timing out, or the
// execution context may have been destroyed.
// In both caes, the promises above are rejected with a protocol error.
// We can safely ignores these, as the WaitTask is re-installed in
// the next execution context if needed.
if ((error as Error).message.includes('Protocol error')) {
return;
}
debugError(error);
} }
const binding = this._bindings.get(name);
await binding?.run(context, seq, args, isTrivial);
}; };
async _waitForSelectorInPage(
queryOne: Function,
root: ElementHandle<Node> | undefined,
selector: string,
options: WaitForSelectorOptions,
bindings = new Map<string, (...args: never[]) => unknown>()
): Promise<JSHandle<unknown> | null> {
const {
visible: waitForVisible = false,
hidden: waitForHidden = false,
timeout = this.#timeoutSettings.timeout(),
} = options;
try {
const handle = await this.waitForFunction(
async (PuppeteerUtil, query, selector, root, visible) => {
if (!PuppeteerUtil) {
return;
}
const node = (await PuppeteerUtil.createFunction(query)(
root || document,
selector,
PuppeteerUtil
)) as Node | null;
return PuppeteerUtil.checkVisibility(node, visible);
},
{
bindings,
polling: waitForVisible || waitForHidden ? 'raf' : 'mutation',
root,
timeout,
},
new LazyArg(async () => {
try {
// In case CDP fails.
return await this.puppeteerUtil;
} catch {
return undefined;
}
}),
queryOne.toString(),
selector,
root,
waitForVisible ? true : waitForHidden ? false : undefined
);
const elementHandle = handle.asElement();
if (!elementHandle) {
await handle.dispose();
return null;
}
return elementHandle;
} catch (error) {
if (!isErrorLike(error)) {
throw error;
}
error.message = `Waiting for selector \`${selector}\` failed: ${error.message}`;
throw error;
}
}
waitForFunction< waitForFunction<
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc<InnerLazyParams<Params>> = EvaluateFunc< Func extends EvaluateFunc<InnerLazyParams<Params>> = EvaluateFunc<
@ -556,14 +430,12 @@ export class IsolatedWorld {
polling?: 'raf' | 'mutation' | number; polling?: 'raf' | 'mutation' | number;
timeout?: number; timeout?: number;
root?: ElementHandle<Node>; root?: ElementHandle<Node>;
bindings?: Map<string, (...args: never[]) => unknown>;
} = {}, } = {},
...args: Params ...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>> { ): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
const { const {
polling = 'raf', polling = 'raf',
timeout = this.#timeoutSettings.timeout(), timeout = this.#timeoutSettings.timeout(),
bindings,
root, root,
} = options; } = options;
if (typeof polling === 'number' && polling < 0) { if (typeof polling === 'number' && polling < 0) {
@ -572,7 +444,6 @@ export class IsolatedWorld {
const waitTask = new WaitTask( const waitTask = new WaitTask(
this, this,
{ {
bindings,
polling, polling,
root, root,
timeout, timeout,
@ -603,20 +474,57 @@ export class IsolatedWorld {
} }
async adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T> { async adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
const executionContext = await this.executionContext(); const context = await this.executionContext();
assert( assert(
handle.executionContext() !== executionContext, handle.executionContext() !== context,
'Cannot adopt handle that already belongs to this execution context' 'Cannot adopt handle that already belongs to this execution context'
); );
const nodeInfo = await this.#client.send('DOM.describeNode', { const nodeInfo = await this.#client.send('DOM.describeNode', {
objectId: handle.remoteObject().objectId, objectId: handle.id,
}); });
return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T; return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T;
} }
async transferHandle<T extends JSHandle<Node>>(handle: T): Promise<T> { async transferHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
const result = await this.adoptHandle(handle); const context = await this.executionContext();
if (handle.executionContext() === context) {
return handle;
}
const info = await this.#client.send('DOM.describeNode', {
objectId: handle.remoteObject().objectId,
});
const newHandle = (await this.adoptBackendNode(
info.node.backendNodeId
)) as T;
await handle.dispose(); await handle.dispose();
return result; return newHandle;
}
}
class Mutex {
#locked = false;
#acquirers: Array<() => void> = [];
// This is FIFO.
acquire(): Promise<void> {
if (!this.#locked) {
this.#locked = true;
return Promise.resolve();
}
let resolve!: () => void;
const promise = new Promise<void>(res => {
resolve = res;
});
this.#acquirers.push(resolve);
return promise;
}
release(): void {
const resolve = this.#acquirers.shift();
if (!resolve) {
this.#locked = false;
return;
}
resolve();
} }
} }

View file

@ -15,64 +15,22 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {JSHandle} from '../api/JSHandle.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import type {ElementHandle} from './ElementHandle.js'; import type {CDPElementHandle} from './ElementHandle.js';
import {ExecutionContext} from './ExecutionContext.js'; import {ExecutionContext} from './ExecutionContext.js';
import {MouseButton} from './Input.js'; import {EvaluateFuncWith, HandleFor, HandleOr} from './types.js';
import {EvaluateFunc, HandleFor, HandleOr} from './types.js';
import {createJSHandle, releaseObject, valueFromRemoteObject} from './util.js'; import {createJSHandle, releaseObject, valueFromRemoteObject} from './util.js';
declare const __JSHandleSymbol: unique symbol; declare const __JSHandleSymbol: unique symbol;
/** /**
* @public * @internal
*/ */
export interface BoxModel { export class CDPJSHandle<T = unknown> extends JSHandle<T> {
content: Point[];
padding: Point[];
border: Point[];
margin: Point[];
width: number;
height: number;
}
/**
* @public
*/
export interface BoundingBox extends Point {
/**
* the width of the element in pixels.
*/
width: number;
/**
* the height of the element in pixels.
*/
height: number;
}
/**
* Represents a reference to a JavaScript object. Instances can be created using
* {@link Page.evaluateHandle}.
*
* Handles prevent the referenced JavaScript object from being garbage-collected
* unless the handle is purposely {@link JSHandle.dispose | disposed}. JSHandles
* are auto-disposed when their associated frame is navigated away or the parent
* context gets destroyed.
*
* Handles can be used as arguments for any evaluation function such as
* {@link Page.$eval}, {@link Page.evaluate}, and {@link Page.evaluateHandle}.
* They are resolved to their referenced object.
*
* @example
*
* ```ts
* const windowHandle = await page.evaluateHandle(() => window);
* ```
*
* @public
*/
export class JSHandle<T = unknown> {
/** /**
* Used for nominally typing {@link JSHandle}. * Used for nominally typing {@link JSHandle}.
*/ */
@ -82,73 +40,50 @@ export class JSHandle<T = unknown> {
#context: ExecutionContext; #context: ExecutionContext;
#remoteObject: Protocol.Runtime.RemoteObject; #remoteObject: Protocol.Runtime.RemoteObject;
/** override get disposed(): boolean {
* @internal
*/
get client(): CDPSession {
return this.#context._client;
}
/**
* @internal
*/
get disposed(): boolean {
return this.#disposed; return this.#disposed;
} }
/**
* @internal
*/
constructor( constructor(
context: ExecutionContext, context: ExecutionContext,
remoteObject: Protocol.Runtime.RemoteObject remoteObject: Protocol.Runtime.RemoteObject
) { ) {
super();
this.#context = context; this.#context = context;
this.#remoteObject = remoteObject; this.#remoteObject = remoteObject;
} }
/** override executionContext(): ExecutionContext {
* @internal
*/
executionContext(): ExecutionContext {
return this.#context; return this.#context;
} }
override get client(): CDPSession {
return this.#context._client;
}
/** /**
* Evaluates the given function with the current handle as its first argument.
*
* @see {@link ExecutionContext.evaluate} for more details. * @see {@link ExecutionContext.evaluate} for more details.
*/ */
async evaluate< override async evaluate<
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc<[this, ...Params]> = EvaluateFunc< Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>
[this, ...Params]
>
>( >(
pageFunction: Func | string, pageFunction: Func | string,
...args: Params ...args: Params
): // @ts-expect-error Circularity here is okay because we only need the return ): Promise<Awaited<ReturnType<Func>>> {
// type which doesn't use `this`.
Promise<Awaited<ReturnType<Func>>> {
return await this.executionContext().evaluate(pageFunction, this, ...args); return await this.executionContext().evaluate(pageFunction, this, ...args);
} }
/** /**
* Evaluates the given function with the current handle as its first argument.
*
* @see {@link ExecutionContext.evaluateHandle} for more details. * @see {@link ExecutionContext.evaluateHandle} for more details.
*/ */
async evaluateHandle< override async evaluateHandle<
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc<[this, ...Params]> = EvaluateFunc< Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>
[this, ...Params]
>
>( >(
pageFunction: Func | string, pageFunction: Func | string,
...args: Params ...args: Params
): // @ts-expect-error Circularity here is okay because we only need the return ): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
// type which doesn't use `this`.
Promise<HandleFor<Awaited<ReturnType<Func>>>> {
return await this.executionContext().evaluateHandle( return await this.executionContext().evaluateHandle(
pageFunction, pageFunction,
this, this,
@ -156,14 +91,11 @@ export class JSHandle<T = unknown> {
); );
} }
/** override async getProperty<K extends keyof T>(
* Fetches a single property from the referenced object.
*/
async getProperty<K extends keyof T>(
propertyName: HandleOr<K> propertyName: HandleOr<K>
): Promise<HandleFor<T[K]>>; ): Promise<HandleFor<T[K]>>;
async getProperty(propertyName: string): Promise<JSHandle<unknown>>; override async getProperty(propertyName: string): Promise<JSHandle<unknown>>;
async getProperty<K extends keyof T>( override async getProperty<K extends keyof T>(
propertyName: HandleOr<K> propertyName: HandleOr<K>
): Promise<HandleFor<T[K]>> { ): Promise<HandleFor<T[K]>> {
return this.evaluateHandle((object, propertyName) => { return this.evaluateHandle((object, propertyName) => {
@ -171,25 +103,7 @@ export class JSHandle<T = unknown> {
}, propertyName); }, propertyName);
} }
/** override async getProperties(): Promise<Map<string, JSHandle>> {
* Gets a map of handles representing the properties of the current handle.
*
* @example
*
* ```ts
* const listHandle = await page.evaluateHandle(() => document.body.children);
* const properties = await listHandle.getProperties();
* const children = [];
* for (const property of properties.values()) {
* const element = property.asElement();
* if (element) {
* children.push(element);
* }
* }
* children; // holds elementHandles to all children of document.body
* ```
*/
async getProperties(): Promise<Map<string, JSHandle>> {
assert(this.#remoteObject.objectId); assert(this.#remoteObject.objectId);
// We use Runtime.getProperties rather than iterative building because the // We use Runtime.getProperties rather than iterative building because the
// iterative approach might create a distorted snapshot. // iterative approach might create a distorted snapshot.
@ -207,15 +121,7 @@ export class JSHandle<T = unknown> {
return result; return result;
} }
/** override async jsonValue(): Promise<T> {
* @returns A vanilla object representing the serializable portions of the
* referenced object.
* @throws Throws if the object cannot be serialized due to circularity.
*
* @remarks
* If the object has a `toJSON` function, it **will not** be called.
*/
async jsonValue(): Promise<T> {
if (!this.#remoteObject.objectId) { if (!this.#remoteObject.objectId) {
return valueFromRemoteObject(this.#remoteObject); return valueFromRemoteObject(this.#remoteObject);
} }
@ -232,14 +138,11 @@ export class JSHandle<T = unknown> {
* @returns Either `null` or the handle itself if the handle is an * @returns Either `null` or the handle itself if the handle is an
* instance of {@link ElementHandle}. * instance of {@link ElementHandle}.
*/ */
asElement(): ElementHandle<Node> | null { override asElement(): CDPElementHandle<Node> | null {
return null; return null;
} }
/** override async dispose(): Promise<void> {
* Releases the object referenced by the handle for garbage collection.
*/
async dispose(): Promise<void> {
if (this.#disposed) { if (this.#disposed) {
return; return;
} }
@ -247,13 +150,7 @@ export class JSHandle<T = unknown> {
await releaseObject(this.client, this.#remoteObject); await releaseObject(this.client, this.#remoteObject);
} }
/** override toString(): string {
* Returns a string representation of the JSHandle.
*
* @remarks
* Useful during debugging.
*/
toString(): string {
if (!this.#remoteObject.objectId) { if (!this.#remoteObject.objectId) {
return 'JSHandle:' + valueFromRemoteObject(this.#remoteObject); return 'JSHandle:' + valueFromRemoteObject(this.#remoteObject);
} }
@ -261,72 +158,11 @@ export class JSHandle<T = unknown> {
return 'JSHandle@' + type; return 'JSHandle@' + type;
} }
/** override get id(): string | undefined {
* Provides access to the return this.#remoteObject.objectId;
* [Protocol.Runtime.RemoteObject](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObject) }
* backing this handle.
*/ override remoteObject(): Protocol.Runtime.RemoteObject {
remoteObject(): Protocol.Runtime.RemoteObject {
return this.#remoteObject; return this.#remoteObject;
} }
} }
/**
* @public
*/
export interface Offset {
/**
* x-offset for the clickable point relative to the top-left corner of the border box.
*/
x: number;
/**
* y-offset for the clickable point relative to the top-left corner of the border box.
*/
y: number;
}
/**
* @public
*/
export interface ClickOptions {
/**
* Time to wait between `mousedown` and `mouseup` in milliseconds.
*
* @defaultValue 0
*/
delay?: number;
/**
* @defaultValue 'left'
*/
button?: MouseButton;
/**
* @defaultValue 1
*/
clickCount?: number;
/**
* Offset for the clickable point relative to the top-left corner of the border box.
*/
offset?: Offset;
}
/**
* @public
*/
export interface PressOptions {
/**
* Time to wait between `keydown` and `keyup` in milliseconds. Defaults to 0.
*/
delay?: number;
/**
* If specified, generates an input event with this text.
*/
text?: string;
}
/**
* @public
*/
export interface Point {
x: number;
y: number;
}

View file

@ -14,16 +14,26 @@
* limitations under the License. * limitations under the License.
*/ */
import {ExecutionContext} from './ExecutionContext.js';
/** /**
* @internal * @internal
*/ */
export class LazyArg<T> { export class LazyArg<T> {
#get: () => Promise<T>; static create = <T>(
constructor(get: () => Promise<T>) { get: (context: ExecutionContext) => Promise<T> | T
): T => {
// We don't want to introduce LazyArg to the type system, otherwise we would
// have to make it public.
return new LazyArg(get) as unknown as T;
};
#get: (context: ExecutionContext) => Promise<T> | T;
private constructor(get: (context: ExecutionContext) => Promise<T> | T) {
this.#get = get; this.#get = get;
} }
get(): Promise<T> { async get(context: ExecutionContext): Promise<T> {
return this.#get(); return this.#get(context);
} }
} }

View file

@ -15,22 +15,23 @@
*/ */
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {
DeferredPromise,
createDeferredPromise,
} from '../util/DeferredPromise.js';
import {CDPSessionEmittedEvents} from './Connection.js';
import {TimeoutError} from './Errors.js';
import {Frame} from './Frame.js';
import {FrameManager, FrameManagerEmittedEvents} from './FrameManager.js';
import {HTTPRequest} from './HTTPRequest.js';
import {HTTPResponse} from './HTTPResponse.js';
import {NetworkManagerEmittedEvents} from './NetworkManager.js';
import { import {
addEventListener, addEventListener,
PuppeteerEventListener, PuppeteerEventListener,
removeEventListeners, removeEventListeners,
} from './util.js'; } from './util.js';
import {
DeferredPromise,
createDeferredPromise,
} from '../util/DeferredPromise.js';
import {TimeoutError} from './Errors.js';
import {FrameManager, FrameManagerEmittedEvents} from './FrameManager.js';
import {Frame} from './Frame.js';
import {HTTPRequest} from './HTTPRequest.js';
import {HTTPResponse} from './HTTPResponse.js';
import {NetworkManagerEmittedEvents} from './NetworkManager.js';
import {CDPSessionEmittedEvents} from './Connection.js';
/** /**
* @public * @public
*/ */

View file

@ -15,6 +15,7 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {HTTPRequest} from './HTTPRequest.js'; import {HTTPRequest} from './HTTPRequest.js';
/** /**

View file

@ -15,16 +15,18 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {createDebuggableDeferredPromise} from '../util/DebuggableDeferredPromise.js';
import {DeferredPromise} from '../util/DeferredPromise.js';
import {CDPSession} from './Connection.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
import {Frame} from './Frame.js'; import {Frame} from './Frame.js';
import {HTTPRequest} from './HTTPRequest.js'; import {HTTPRequest} from './HTTPRequest.js';
import {HTTPResponse} from './HTTPResponse.js'; import {HTTPResponse} from './HTTPResponse.js';
import {FetchRequestId, NetworkEventManager} from './NetworkEventManager.js'; import {FetchRequestId, NetworkEventManager} from './NetworkEventManager.js';
import {debugError, isString} from './util.js'; import {debugError, isString} from './util.js';
import {DeferredPromise} from '../util/DeferredPromise.js';
import {createDebuggableDeferredPromise} from '../util/DebuggableDeferredPromise.js';
import {CDPSession} from './Connection.js';
/** /**
* @public * @public

View file

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import NodeWebSocket from 'ws'; import NodeWebSocket from 'ws';
import {ConnectionTransport} from '../common/ConnectionTransport.js'; import {ConnectionTransport} from '../common/ConnectionTransport.js';
import {packageVersion} from '../generated/version.js'; import {packageVersion} from '../generated/version.js';

View file

@ -0,0 +1,37 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {QueryHandler, QuerySelector, QuerySelectorAll} from './QueryHandler.js';
/**
* @internal
*/
export class PQueryHandler extends QueryHandler {
static override querySelectorAll: QuerySelectorAll = (
element,
selector,
{pQuerySelectorAll}
) => {
return pQuerySelectorAll(element, selector);
};
static override querySelector: QuerySelector = (
element,
selector,
{pQuerySelector}
) => {
return pQuerySelector(element, selector);
};
}

View file

@ -14,10 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
import {Protocol} from 'devtools-protocol';
import type {Readable} from 'stream'; import type {Readable} from 'stream';
import {Protocol} from 'devtools-protocol';
import type {Browser} from '../api/Browser.js'; import type {Browser} from '../api/Browser.js';
import type {BrowserContext} from '../api/BrowserContext.js'; import type {BrowserContext} from '../api/BrowserContext.js';
import {ElementHandle} from '../api/ElementHandle.js';
import {JSHandle} from '../api/JSHandle.js';
import { import {
GeolocationOptions, GeolocationOptions,
MediaFeature, MediaFeature,
@ -35,7 +39,9 @@ import {
DeferredPromise, DeferredPromise,
} from '../util/DeferredPromise.js'; } from '../util/DeferredPromise.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {Accessibility} from './Accessibility.js'; import {Accessibility} from './Accessibility.js';
import {Binding} from './Binding.js';
import { import {
CDPSession, CDPSession,
CDPSessionEmittedEvents, CDPSessionEmittedEvents,
@ -44,7 +50,6 @@ import {
import {ConsoleMessage, ConsoleMessageType} from './ConsoleMessage.js'; import {ConsoleMessage, ConsoleMessageType} from './ConsoleMessage.js';
import {Coverage} from './Coverage.js'; import {Coverage} from './Coverage.js';
import {Dialog} from './Dialog.js'; import {Dialog} from './Dialog.js';
import {ElementHandle} from './ElementHandle.js';
import {EmulationManager} from './EmulationManager.js'; import {EmulationManager} from './EmulationManager.js';
import {FileChooser} from './FileChooser.js'; import {FileChooser} from './FileChooser.js';
import { import {
@ -59,7 +64,6 @@ import {HTTPResponse} from './HTTPResponse.js';
import {Keyboard, Mouse, MouseButton, Touchscreen} from './Input.js'; import {Keyboard, Mouse, MouseButton, Touchscreen} from './Input.js';
import {WaitForSelectorOptions} from './IsolatedWorld.js'; import {WaitForSelectorOptions} from './IsolatedWorld.js';
import {MAIN_WORLD} from './IsolatedWorlds.js'; import {MAIN_WORLD} from './IsolatedWorlds.js';
import {JSHandle} from './JSHandle.js';
import { import {
Credentials, Credentials,
NetworkConditions, NetworkConditions,
@ -72,7 +76,13 @@ import {TargetManagerEmittedEvents} from './TargetManager.js';
import {TaskQueue} from './TaskQueue.js'; import {TaskQueue} from './TaskQueue.js';
import {TimeoutSettings} from './TimeoutSettings.js'; import {TimeoutSettings} from './TimeoutSettings.js';
import {Tracing} from './Tracing.js'; import {Tracing} from './Tracing.js';
import {EvaluateFunc, HandleFor, NodeFor} from './types.js'; import {
BindingPayload,
EvaluateFunc,
EvaluateFuncWith,
HandleFor,
NodeFor,
} from './types.js';
import { import {
createJSHandle, createJSHandle,
debugError, debugError,
@ -83,9 +93,6 @@ import {
importFS, importFS,
isNumber, isNumber,
isString, isString,
pageBindingDeliverErrorString,
pageBindingDeliverErrorValueString,
pageBindingDeliverResultString,
pageBindingInitString, pageBindingInitString,
releaseObject, releaseObject,
valueFromRemoteObject, valueFromRemoteObject,
@ -140,7 +147,7 @@ export class CDPPage extends Page {
#frameManager: FrameManager; #frameManager: FrameManager;
#emulationManager: EmulationManager; #emulationManager: EmulationManager;
#tracing: Tracing; #tracing: Tracing;
#pageBindings = new Map<string, Function>(); #bindings = new Map<string, Binding>();
#coverage: Coverage; #coverage: Coverage;
#javascriptEnabled = true; #javascriptEnabled = true;
#viewport: Viewport | null; #viewport: Viewport | null;
@ -521,13 +528,12 @@ export class CDPPage extends Page {
): Promise<JSHandle<Prototype[]>> { ): Promise<JSHandle<Prototype[]>> {
const context = await this.mainFrame().executionContext(); const context = await this.mainFrame().executionContext();
assert(!prototypeHandle.disposed, 'Prototype JSHandle is disposed!'); assert(!prototypeHandle.disposed, 'Prototype JSHandle is disposed!');
const remoteObject = prototypeHandle.remoteObject();
assert( assert(
remoteObject.objectId, prototypeHandle.id,
'Prototype JSHandle must not be referencing primitive value' 'Prototype JSHandle must not be referencing primitive value'
); );
const response = await context._client.send('Runtime.queryObjects', { const response = await context._client.send('Runtime.queryObjects', {
prototypeObjectId: remoteObject.objectId, prototypeObjectId: prototypeHandle.id,
}); });
return createJSHandle(context, response.objects) as HandleFor<Prototype[]>; return createJSHandle(context, response.objects) as HandleFor<Prototype[]>;
} }
@ -535,9 +541,10 @@ export class CDPPage extends Page {
override async $eval< override async $eval<
Selector extends string, Selector extends string,
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc< Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
[ElementHandle<NodeFor<Selector>>, ...Params] NodeFor<Selector>,
> = EvaluateFunc<[ElementHandle<NodeFor<Selector>>, ...Params]> Params
>
>( >(
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
@ -549,9 +556,10 @@ export class CDPPage extends Page {
override async $$eval< override async $$eval<
Selector extends string, Selector extends string,
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc< Func extends EvaluateFuncWith<
[Array<NodeFor<Selector>>, ...Params] Array<NodeFor<Selector>>,
> = EvaluateFunc<[Array<NodeFor<Selector>>, ...Params]> Params
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
>( >(
selector: Selector, selector: Selector,
pageFunction: Func | string, pageFunction: Func | string,
@ -646,23 +654,29 @@ export class CDPPage extends Page {
name: string, name: string,
pptrFunction: Function | {default: Function} pptrFunction: Function | {default: Function}
): Promise<void> { ): Promise<void> {
if (this.#pageBindings.has(name)) { if (this.#bindings.has(name)) {
throw new Error( throw new Error(
`Failed to add page binding with name ${name}: window['${name}'] already exists!` `Failed to add page binding with name ${name}: window['${name}'] already exists!`
); );
} }
let exposedFunction: Function; let binding: Binding;
switch (typeof pptrFunction) { switch (typeof pptrFunction) {
case 'function': case 'function':
exposedFunction = pptrFunction; binding = new Binding(
name,
pptrFunction as (...args: unknown[]) => unknown
);
break; break;
default: default:
exposedFunction = pptrFunction.default; binding = new Binding(
name,
pptrFunction.default as (...args: unknown[]) => unknown
);
break; break;
} }
this.#pageBindings.set(name, exposedFunction); this.#bindings.set(name, binding);
const expression = pageBindingInitString('exposedFun', name); const expression = pageBindingInitString('exposedFun', name);
await this.#client.send('Runtime.addBinding', {name: name}); await this.#client.send('Runtime.addBinding', {name: name});
@ -747,10 +761,20 @@ export class CDPPage extends Page {
// @see https://github.com/puppeteer/puppeteer/issues/3865 // @see https://github.com/puppeteer/puppeteer/issues/3865
return; return;
} }
const context = this.#frameManager.executionContextById( const context = this.#frameManager.getExecutionContextById(
event.executionContextId, event.executionContextId,
this.#client this.#client
); );
if (!context) {
debugError(
new Error(
`ExecutionContext not found for a console message: ${JSON.stringify(
event
)}`
)
);
return;
}
const values = event.args.map(arg => { const values = event.args.map(arg => {
return createJSHandle(context, arg); return createJSHandle(context, arg);
}); });
@ -760,7 +784,7 @@ export class CDPPage extends Page {
async #onBindingCalled( async #onBindingCalled(
event: Protocol.Runtime.BindingCalledEvent event: Protocol.Runtime.BindingCalledEvent
): Promise<void> { ): Promise<void> {
let payload: {type: string; name: string; seq: number; args: unknown[]}; let payload: BindingPayload;
try { try {
payload = JSON.parse(event.payload); payload = JSON.parse(event.payload);
} catch { } catch {
@ -768,34 +792,21 @@ export class CDPPage extends Page {
// called before our wrapper was initialized. // called before our wrapper was initialized.
return; return;
} }
const {type, name, seq, args} = payload; const {type, name, seq, args, isTrivial} = payload;
if (type !== 'exposedFun' || !this.#pageBindings.has(name)) { if (type !== 'exposedFun') {
return; return;
} }
let expression = null;
try { const context = this.#frameManager.executionContextById(
const pageBinding = this.#pageBindings.get(name); event.executionContextId,
assert(pageBinding); this.#client
const result = await pageBinding(...args); );
expression = pageBindingDeliverResultString(name, seq, result); if (!context) {
} catch (error) { return;
if (isErrorLike(error)) {
expression = pageBindingDeliverErrorString(
name,
seq,
error.message,
error.stack
);
} else {
expression = pageBindingDeliverErrorValueString(name, seq, error);
}
} }
this.#client
.send('Runtime.evaluate', { const binding = this.#bindings.get(name);
expression, await binding?.run(context, seq, args, isTrivial);
contextId: event.executionContextId,
})
.catch(debugError);
} }
#addConsoleMessage( #addConsoleMessage(
@ -811,7 +822,7 @@ export class CDPPage extends Page {
} }
const textTokens = []; const textTokens = [];
for (const arg of args) { for (const arg of args) {
const remoteObject = arg.remoteObject(); const remoteObject = arg.remoteObject() as Protocol.Runtime.RemoteObject;
if (remoteObject.objectId) { if (remoteObject.objectId) {
textTokens.push(arg.toString()); textTokens.push(arg.toString());
} else { } else {
@ -1621,11 +1632,7 @@ export class CDPPage extends Page {
override waitForXPath( override waitForXPath(
xpath: string, xpath: string,
options: { options: WaitForSelectorOptions = {}
visible?: boolean;
hidden?: boolean;
timeout?: number;
} = {}
): Promise<ElementHandle<Node> | null> { ): Promise<ElementHandle<Node> | null> {
return this.mainFrame().waitForXPath(xpath, options); return this.mainFrame().waitForXPath(xpath, options);
} }

View file

@ -0,0 +1,39 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type PuppeteerUtil from '../injected/injected.js';
import {QueryHandler} from './QueryHandler.js';
/**
* @internal
*/
export class PierceQueryHandler extends QueryHandler {
static override querySelector = (
element: Node,
selector: string,
{pierceQuerySelector}: PuppeteerUtil
): Node | null => {
return pierceQuerySelector(element, selector);
};
static override querySelectorAll = (
element: Node,
selector: string,
{pierceQuerySelectorAll}: PuppeteerUtil
): Iterable<Node> => {
return pierceQuerySelectorAll(element, selector);
};
}

View file

@ -13,19 +13,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import {Browser} from '../api/Browser.js'; import {Browser} from '../api/Browser.js';
import { import {
BrowserConnectOptions, BrowserConnectOptions,
_connectToCDPBrowser, _connectToCDPBrowser,
} from './BrowserConnector.js'; } from './BrowserConnector.js';
import {ConnectionTransport} from './ConnectionTransport.js'; import {ConnectionTransport} from './ConnectionTransport.js';
import { import {CustomQueryHandler, customQueryHandlers} from './CustomQueryHandler.js';
clearCustomQueryHandlers,
CustomQueryHandler,
customQueryHandlerNames,
registerCustomQueryHandler,
unregisterCustomQueryHandler,
} from './QueryHandler.js';
/** /**
* Settings that are common to the Puppeteer class, regardless of environment. * Settings that are common to the Puppeteer class, regardless of environment.
@ -57,9 +53,18 @@ export interface ConnectOptions extends BrowserConnectOptions {
* instance of {@link PuppeteerNode} when you import or require `puppeteer`. * instance of {@link PuppeteerNode} when you import or require `puppeteer`.
* That class extends `Puppeteer`, so has all the methods documented below as * That class extends `Puppeteer`, so has all the methods documented below as
* well as all that are defined on {@link PuppeteerNode}. * well as all that are defined on {@link PuppeteerNode}.
*
* @public * @public
*/ */
export class Puppeteer { export class Puppeteer {
/**
* Operations for {@link CustomQueryHandler | custom query handlers}. See
* {@link CustomQueryHandlerRegistry}.
*
* @internal
*/
static customQueryHandlers = customQueryHandlers;
/** /**
* Registers a {@link CustomQueryHandler | custom query handler}. * Registers a {@link CustomQueryHandler | custom query handler}.
* *
@ -86,28 +91,28 @@ export class Puppeteer {
name: string, name: string,
queryHandler: CustomQueryHandler queryHandler: CustomQueryHandler
): void { ): void {
return registerCustomQueryHandler(name, queryHandler); return this.customQueryHandlers.register(name, queryHandler);
} }
/** /**
* Unregisters a custom query handler for a given name. * Unregisters a custom query handler for a given name.
*/ */
static unregisterCustomQueryHandler(name: string): void { static unregisterCustomQueryHandler(name: string): void {
return unregisterCustomQueryHandler(name); return this.customQueryHandlers.unregister(name);
} }
/** /**
* Gets the names of all custom query handlers. * Gets the names of all custom query handlers.
*/ */
static customQueryHandlerNames(): string[] { static customQueryHandlerNames(): string[] {
return customQueryHandlerNames(); return this.customQueryHandlers.names();
} }
/** /**
* Unregisters all custom query handlers. * Unregisters all custom query handlers.
*/ */
static clearCustomQueryHandlers(): void { static clearCustomQueryHandlers(): void {
return clearCustomQueryHandlers(); return this.customQueryHandlers.clear();
} }
/** /**

View file

@ -1,5 +1,5 @@
/** /**
* Copyright 2020 Google Inc. All rights reserved. * Copyright 2023 Google Inc. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,305 +14,202 @@
* limitations under the License. * limitations under the License.
*/ */
import PuppeteerUtil from '../injected/injected.js'; import {ElementHandle} from '../api/ElementHandle.js';
import {ariaHandler} from './AriaQueryHandler.js'; import type PuppeteerUtil from '../injected/injected.js';
import {ElementHandle} from './ElementHandle.js'; import {assert} from '../util/assert.js';
import {Frame} from './Frame.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {WaitForSelectorOptions} from './IsolatedWorld.js'; import {interpolateFunction, stringifyFunction} from '../util/Function.js';
import type {Frame} from './Frame.js';
import {transposeIterableHandle} from './HandleIterator.js';
import type {WaitForSelectorOptions} from './IsolatedWorld.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {LazyArg} from './LazyArg.js';
/** import type {Awaitable, AwaitableIterable} from './types.js';
* @public
*/
export interface CustomQueryHandler {
/**
* @returns A {@link Node} matching the given `selector` from {@link node}.
*/
queryOne?: (node: Node, selector: string) => Node | null;
/**
* @returns Some {@link Node}s matching the given `selector` from {@link node}.
*/
queryAll?: (node: Node, selector: string) => Node[];
}
/** /**
* @internal * @internal
*/ */
export interface InternalQueryHandler { export type QuerySelectorAll = (
/** node: Node,
* @returns A {@link Node} matching the given `selector` from {@link node}. selector: string,
*/ PuppeteerUtil: PuppeteerUtil
queryOne?: ( ) => AwaitableIterable<Node>;
node: Node,
selector: string,
PuppeteerUtil: PuppeteerUtil
) => Node | null;
/**
* @returns Some {@link Node}s matching the given `selector` from {@link node}.
*/
queryAll?: (
node: Node,
selector: string,
PuppeteerUtil: PuppeteerUtil
) => Node[];
}
/** /**
* @internal * @internal
*/ */
export interface PuppeteerQueryHandler { export type QuerySelector = (
/** node: Node,
* Queries for a single node given a selector and {@link ElementHandle}. selector: string,
* PuppeteerUtil: PuppeteerUtil
* Akin to {@link Window.prototype.querySelector}. ) => Awaitable<Node | null>;
*/
queryOne?: ( /**
element: ElementHandle<Node>, * @internal
selector: string */
) => Promise<ElementHandle<Node> | null>; export class QueryHandler {
// Either one of these may be implemented, but at least one must be.
static querySelectorAll?: QuerySelectorAll;
static querySelector?: QuerySelector;
static get _querySelector(): QuerySelector {
if (this.querySelector) {
return this.querySelector;
}
if (!this.querySelectorAll) {
throw new Error('Cannot create default `querySelector`.');
}
return (this.querySelector = interpolateFunction(
async (node, selector, PuppeteerUtil) => {
const querySelectorAll: QuerySelectorAll =
PLACEHOLDER('querySelectorAll');
const results = querySelectorAll(node, selector, PuppeteerUtil);
for await (const result of results) {
return result;
}
return null;
},
{
querySelectorAll: stringifyFunction(this.querySelectorAll),
}
));
}
static get _querySelectorAll(): QuerySelectorAll {
if (this.querySelectorAll) {
return this.querySelectorAll;
}
if (!this.querySelector) {
throw new Error('Cannot create default `querySelectorAll`.');
}
return (this.querySelectorAll = interpolateFunction(
async function* (node, selector, PuppeteerUtil) {
const querySelector: QuerySelector = PLACEHOLDER('querySelector');
const result = await querySelector(node, selector, PuppeteerUtil);
if (result) {
yield result;
}
},
{
querySelector: stringifyFunction(this.querySelector),
}
));
}
/** /**
* Queries for multiple nodes given a selector and {@link ElementHandle}. * Queries for multiple nodes given a selector and {@link ElementHandle}.
* *
* Akin to {@link Window.prototype.querySelectorAll}. * Akin to {@link Document.prototype.querySelectorAll}.
*/ */
queryAll?: ( static async *queryAll(
element: ElementHandle<Node>, element: ElementHandle<Node>,
selector: string selector: string
) => Promise<Array<ElementHandle<Node>>>; ): AwaitableIterable<ElementHandle<Node>> {
const world = element.executionContext()._world;
assert(world);
const handle = await element.evaluateHandle(
this._querySelectorAll,
selector,
LazyArg.create(context => {
return context.puppeteerUtil;
})
);
yield* transposeIterableHandle(handle);
}
/**
* Queries for a single node given a selector and {@link ElementHandle}.
*
* Akin to {@link Document.prototype.querySelector}.
*/
static async queryOne(
element: ElementHandle<Node>,
selector: string
): Promise<ElementHandle<Node> | null> {
const world = element.executionContext()._world;
assert(world);
const result = await element.evaluateHandle(
this._querySelector,
selector,
LazyArg.create(context => {
return context.puppeteerUtil;
})
);
if (!(result instanceof ElementHandle)) {
await result.dispose();
return null;
}
return result;
}
/** /**
* Waits until a single node appears for a given selector and * Waits until a single node appears for a given selector and
* {@link ElementHandle}. * {@link ElementHandle}.
*
* This will always query the handle in the Puppeteer world and migrate the
* result to the main world.
*/ */
waitFor?: ( static async waitFor(
elementOrFrame: ElementHandle<Node> | Frame, elementOrFrame: ElementHandle<Node> | Frame,
selector: string, selector: string,
options: WaitForSelectorOptions options: WaitForSelectorOptions
) => Promise<ElementHandle<Node> | null>; ): Promise<ElementHandle<Node> | null> {
} let frame: Frame;
let element: ElementHandle<Node> | undefined;
if (!(elementOrFrame instanceof ElementHandle)) {
frame = elementOrFrame;
} else {
frame = elementOrFrame.frame;
element = await frame.worlds[PUPPETEER_WORLD].adoptHandle(elementOrFrame);
}
function createPuppeteerQueryHandler( const {visible = false, hidden = false, timeout} = options;
handler: InternalQueryHandler
): PuppeteerQueryHandler {
const internalHandler: PuppeteerQueryHandler = {};
if (handler.queryOne) { try {
const queryOne = handler.queryOne; const handle = await frame.worlds[PUPPETEER_WORLD].waitForFunction(
internalHandler.queryOne = async (element, selector) => { async (PuppeteerUtil, query, selector, root, visible) => {
const jsHandle = await element.evaluateHandle( const querySelector = PuppeteerUtil.createFunction(
queryOne, query
) as QuerySelector;
const node = await querySelector(
root ?? document,
selector,
PuppeteerUtil
);
return PuppeteerUtil.checkVisibility(node, visible);
},
{
polling: visible || hidden ? 'raf' : 'mutation',
root: element,
timeout,
},
LazyArg.create(context => {
return context.puppeteerUtil;
}),
stringifyFunction(this._querySelector),
selector, selector,
await element.executionContext()._world!.puppeteerUtil
);
const elementHandle = jsHandle.asElement();
if (elementHandle) {
return elementHandle;
}
await jsHandle.dispose();
return null;
};
internalHandler.waitFor = async (elementOrFrame, selector, options) => {
let frame: Frame;
let element: ElementHandle<Node> | undefined;
if (elementOrFrame instanceof Frame) {
frame = elementOrFrame;
} else {
frame = elementOrFrame.frame;
element = await frame.worlds[PUPPETEER_WORLD].adoptHandle(
elementOrFrame
);
}
const result = await frame.worlds[PUPPETEER_WORLD]._waitForSelectorInPage(
queryOne,
element, element,
selector, visible ? true : hidden ? false : undefined
options
); );
if (!(handle instanceof ElementHandle)) {
await handle.dispose();
return null;
}
return frame.worlds[MAIN_WORLD].transferHandle(handle);
} catch (error) {
if (!isErrorLike(error)) {
throw error;
}
error.message = `Waiting for selector \`${selector}\` failed: ${error.message}`;
throw error;
} finally {
if (element) { if (element) {
await element.dispose(); await element.dispose();
} }
if (!result) {
return null;
}
if (!(result instanceof ElementHandle)) {
await result.dispose();
return null;
}
return frame.worlds[MAIN_WORLD].transferHandle(result);
};
}
if (handler.queryAll) {
const queryAll = handler.queryAll;
internalHandler.queryAll = async (element, selector) => {
const jsHandle = await element.evaluateHandle(
queryAll,
selector,
await element.executionContext()._world!.puppeteerUtil
);
const properties = await jsHandle.getProperties();
await jsHandle.dispose();
const result = [];
for (const property of properties.values()) {
const elementHandle = property.asElement();
if (elementHandle) {
result.push(elementHandle);
}
}
return result;
};
}
return internalHandler;
}
const defaultHandler = createPuppeteerQueryHandler({
queryOne: (element, selector) => {
if (!('querySelector' in element)) {
throw new Error(
`Could not invoke \`querySelector\` on node of type ${element.nodeName}.`
);
}
return (
element as unknown as {querySelector(selector: string): Element}
).querySelector(selector);
},
queryAll: (element, selector) => {
if (!('querySelectorAll' in element)) {
throw new Error(
`Could not invoke \`querySelectorAll\` on node of type ${element.nodeName}.`
);
}
return [
...(
element as unknown as {
querySelectorAll(selector: string): NodeList;
}
).querySelectorAll(selector),
];
},
});
const pierceHandler = createPuppeteerQueryHandler({
queryOne: (element, selector, {pierceQuerySelector}) => {
return pierceQuerySelector(element, selector);
},
queryAll: (element, selector, {pierceQuerySelectorAll}) => {
return pierceQuerySelectorAll(element, selector);
},
});
const xpathHandler = createPuppeteerQueryHandler({
queryOne: (element, selector, {xpathQuerySelector}) => {
return xpathQuerySelector(element, selector);
},
queryAll: (element, selector, {xpathQuerySelectorAll}) => {
return xpathQuerySelectorAll(element, selector);
},
});
const textQueryHandler = createPuppeteerQueryHandler({
queryOne: (element, selector, {textQuerySelector}) => {
return textQuerySelector(element, selector);
},
queryAll: (element, selector, {textQuerySelectorAll}) => {
return textQuerySelectorAll(element, selector);
},
});
interface RegisteredQueryHandler {
handler: PuppeteerQueryHandler;
transformSelector?: (selector: string) => string;
}
const INTERNAL_QUERY_HANDLERS = new Map<string, RegisteredQueryHandler>([
['aria', {handler: ariaHandler}],
['pierce', {handler: pierceHandler}],
['xpath', {handler: xpathHandler}],
['text', {handler: textQueryHandler}],
]);
const QUERY_HANDLERS = new Map<string, RegisteredQueryHandler>();
/**
* @deprecated Import {@link Puppeteer} and use the static method
* {@link Puppeteer.registerCustomQueryHandler}
*
* @public
*/
export function registerCustomQueryHandler(
name: string,
handler: CustomQueryHandler
): void {
if (INTERNAL_QUERY_HANDLERS.has(name)) {
throw new Error(`A query handler named "${name}" already exists`);
}
if (QUERY_HANDLERS.has(name)) {
throw new Error(`A custom query handler named "${name}" already exists`);
}
const isValidName = /^[a-zA-Z]+$/.test(name);
if (!isValidName) {
throw new Error(`Custom query handler names may only contain [a-zA-Z]`);
}
QUERY_HANDLERS.set(name, {handler: createPuppeteerQueryHandler(handler)});
}
/**
* @deprecated Import {@link Puppeteer} and use the static method
* {@link Puppeteer.unregisterCustomQueryHandler}
*
* @public
*/
export function unregisterCustomQueryHandler(name: string): void {
QUERY_HANDLERS.delete(name);
}
/**
* @deprecated Import {@link Puppeteer} and use the static method
* {@link Puppeteer.customQueryHandlerNames}
*
* @public
*/
export function customQueryHandlerNames(): string[] {
return [...QUERY_HANDLERS.keys()];
}
/**
* @deprecated Import {@link Puppeteer} and use the static method
* {@link Puppeteer.clearCustomQueryHandlers}
*
* @public
*/
export function clearCustomQueryHandlers(): void {
QUERY_HANDLERS.clear();
}
const CUSTOM_QUERY_SEPARATORS = ['=', '/'];
/**
* @internal
*/
export function getQueryHandlerAndSelector(selector: string): {
updatedSelector: string;
queryHandler: PuppeteerQueryHandler;
} {
for (const handlerMap of [QUERY_HANDLERS, INTERNAL_QUERY_HANDLERS]) {
for (const [
name,
{handler: queryHandler, transformSelector},
] of handlerMap) {
for (const separator of CUSTOM_QUERY_SEPARATORS) {
const prefix = `${name}${separator}`;
if (selector.startsWith(prefix)) {
selector = selector.slice(prefix.length);
if (transformSelector) {
selector = transformSelector(selector);
}
return {updatedSelector: selector, queryHandler};
}
}
} }
} }
return {updatedSelector: selector, queryHandler: defaultHandler};
} }

View file

@ -0,0 +1,49 @@
import {source as injectedSource} from '../generated/injected.js';
class ScriptInjector {
#updated = false;
#amendments = new Set<string>();
// Appends a statement of the form `(PuppeteerUtil) => {...}`.
append(statement: string): void {
this.#update(() => {
this.#amendments.add(statement);
});
}
pop(statement: string): void {
this.#update(() => {
this.#amendments.delete(statement);
});
}
inject(inject: (script: string) => void, force = false) {
if (this.#updated || force) {
inject(this.#get());
}
this.#updated = false;
}
#update(callback: () => void): void {
callback();
this.#updated = true;
}
#get(): string {
return `(() => {
const module = {};
${injectedSource}
${[...this.#amendments]
.map(statement => {
return `(${statement})(module.exports.default);`;
})
.join('')}
return module.exports.default;
})()`;
}
}
/**
* @internal
*/
export const scriptInjector = new ScriptInjector();

View file

@ -14,16 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
import {Page, PageEmittedEvents} from '../api/Page.js'; import {Protocol} from 'devtools-protocol';
import {WebWorker} from './WebWorker.js';
import {CDPSession} from './Connection.js';
import type {Browser, IsPageTargetCallback} from '../api/Browser.js'; import type {Browser, IsPageTargetCallback} from '../api/Browser.js';
import type {BrowserContext} from '../api/BrowserContext.js'; import type {BrowserContext} from '../api/BrowserContext.js';
import {Viewport} from './PuppeteerViewport.js'; import {Page, PageEmittedEvents} from '../api/Page.js';
import {Protocol} from 'devtools-protocol';
import {TaskQueue} from './TaskQueue.js'; import {CDPSession} from './Connection.js';
import {TargetManager} from './TargetManager.js';
import {CDPPage} from './Page.js'; import {CDPPage} from './Page.js';
import {Viewport} from './PuppeteerViewport.js';
import {TargetManager} from './TargetManager.js';
import {TaskQueue} from './TaskQueue.js';
import {WebWorker} from './WebWorker.js';
/** /**
* Target represents a * Target represents a

View file

@ -15,6 +15,7 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
import {Target} from './Target.js'; import {Target} from './Target.js';

View file

@ -0,0 +1,30 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {QueryHandler, QuerySelectorAll} from './QueryHandler.js';
/**
* @internal
*/
export class TextQueryHandler extends QueryHandler {
static override querySelectorAll: QuerySelectorAll = (
element,
selector,
{textQuerySelectorAll}
) => {
return textQuerySelectorAll(element, selector);
};
}

View file

@ -14,9 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {getReadableAsBuffer, getReadableFromProtocolStream} from './util.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {getReadableAsBuffer, getReadableFromProtocolStream} from './util.js';
/** /**
* @public * @public

View file

@ -14,19 +14,21 @@
* limitations under the License. * limitations under the License.
*/ */
import {ElementHandle} from '../api/ElementHandle.js';
import {JSHandle} from '../api/JSHandle.js';
import type {Poller} from '../injected/Poller.js'; import type {Poller} from '../injected/Poller.js';
import {createDeferredPromise} from '../util/DeferredPromise.js'; import {createDeferredPromise} from '../util/DeferredPromise.js';
import {ElementHandle} from './ElementHandle.js'; import {stringifyFunction} from '../util/Function.js';
import {TimeoutError} from './Errors.js'; import {TimeoutError} from './Errors.js';
import {IsolatedWorld} from './IsolatedWorld.js'; import {IsolatedWorld} from './IsolatedWorld.js';
import {JSHandle} from './JSHandle.js'; import {LazyArg} from './LazyArg.js';
import {HandleFor} from './types.js'; import {HandleFor} from './types.js';
/** /**
* @internal * @internal
*/ */
export interface WaitTaskOptions { export interface WaitTaskOptions {
bindings?: Map<string, (...args: never[]) => unknown>;
polling: 'raf' | 'mutation' | number; polling: 'raf' | 'mutation' | number;
root?: ElementHandle<Node>; root?: ElementHandle<Node>;
timeout: number; timeout: number;
@ -37,7 +39,6 @@ export interface WaitTaskOptions {
*/ */
export class WaitTask<T = unknown> { export class WaitTask<T = unknown> {
#world: IsolatedWorld; #world: IsolatedWorld;
#bindings: Map<string, (...args: never[]) => unknown>;
#polling: 'raf' | 'mutation' | number; #polling: 'raf' | 'mutation' | number;
#root?: ElementHandle<Node>; #root?: ElementHandle<Node>;
@ -57,7 +58,6 @@ export class WaitTask<T = unknown> {
...args: unknown[] ...args: unknown[]
) { ) {
this.#world = world; this.#world = world;
this.#bindings = options.bindings ?? new Map();
this.#polling = options.polling; this.#polling = options.polling;
this.#root = options.root; this.#root = options.root;
@ -66,7 +66,7 @@ export class WaitTask<T = unknown> {
this.#fn = `() => {return (${fn});}`; this.#fn = `() => {return (${fn});}`;
break; break;
default: default:
this.#fn = fn.toString(); this.#fn = stringifyFunction(fn);
break; break;
} }
this.#args = args; this.#args = args;
@ -81,12 +81,6 @@ export class WaitTask<T = unknown> {
}, options.timeout); }, options.timeout);
} }
if (this.#bindings.size !== 0) {
for (const [name, fn] of this.#bindings) {
this.#world._boundFunctions.set(name, fn);
}
}
this.rerun(); this.rerun();
} }
@ -96,15 +90,6 @@ export class WaitTask<T = unknown> {
async rerun(): Promise<void> { async rerun(): Promise<void> {
try { try {
if (this.#bindings.size !== 0) {
const context = await this.#world.executionContext();
await Promise.all(
[...this.#bindings].map(async ([name]) => {
return await this.#world._addBindingToContext(context, name);
})
);
}
switch (this.#polling) { switch (this.#polling) {
case 'raf': case 'raf':
this.#poller = await this.#world.evaluateHandle( this.#poller = await this.#world.evaluateHandle(
@ -114,7 +99,9 @@ export class WaitTask<T = unknown> {
return fun(...args) as Promise<T>; return fun(...args) as Promise<T>;
}); });
}, },
await this.#world.puppeteerUtil, LazyArg.create(context => {
return context.puppeteerUtil;
}),
this.#fn, this.#fn,
...this.#args ...this.#args
); );
@ -127,7 +114,9 @@ export class WaitTask<T = unknown> {
return fun(...args) as Promise<T>; return fun(...args) as Promise<T>;
}, root || document); }, root || document);
}, },
await this.#world.puppeteerUtil, LazyArg.create(context => {
return context.puppeteerUtil;
}),
this.#root, this.#root,
this.#fn, this.#fn,
...this.#args ...this.#args
@ -141,7 +130,9 @@ export class WaitTask<T = unknown> {
return fun(...args) as Promise<T>; return fun(...args) as Promise<T>;
}, ms); }, ms);
}, },
await this.#world.puppeteerUtil, LazyArg.create(context => {
return context.puppeteerUtil;
}),
this.#polling, this.#polling,
this.#fn, this.#fn,
...this.#args ...this.#args

View file

@ -14,21 +14,23 @@
* limitations under the License. * limitations under the License.
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {createDeferredPromise} from '../util/DeferredPromise.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {ConsoleMessageType} from './ConsoleMessage.js'; import {ConsoleMessageType} from './ConsoleMessage.js';
import {EvaluateFunc, HandleFor} from './types.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
import {ExecutionContext} from './ExecutionContext.js'; import {ExecutionContext} from './ExecutionContext.js';
import {JSHandle} from './JSHandle.js'; import {CDPJSHandle} from './JSHandle.js';
import {EvaluateFunc, HandleFor} from './types.js';
import {debugError} from './util.js'; import {debugError} from './util.js';
import {createDeferredPromise} from '../util/DeferredPromise.js';
/** /**
* @internal * @internal
*/ */
export type ConsoleAPICalledCallback = ( export type ConsoleAPICalledCallback = (
eventType: ConsoleMessageType, eventType: ConsoleMessageType,
handles: JSHandle[], handles: CDPJSHandle[],
trace: Protocol.Runtime.StackTrace trace: Protocol.Runtime.StackTrace
) => void; ) => void;
@ -93,7 +95,7 @@ export class WebWorker extends EventEmitter {
return consoleAPICalled( return consoleAPICalled(
event.type, event.type,
event.args.map((object: Protocol.Runtime.RemoteObject) => { event.args.map((object: Protocol.Runtime.RemoteObject) => {
return new JSHandle(context, object); return new CDPJSHandle(context, object);
}), }),
event.stackTrace event.stackTrace
); );

View file

@ -0,0 +1,30 @@
/**
* Copyright 2023 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {QueryHandler, QuerySelectorAll} from './QueryHandler.js';
/**
* @internal
*/
export class XPathQueryHandler extends QueryHandler {
static override querySelectorAll: QuerySelectorAll = (
element,
selector,
{xpathQuerySelectorAll}
) => {
return xpathQuerySelectorAll(element, selector);
};
}

View file

@ -1,9 +1,12 @@
import {CDPSession, Connection as CDPPPtrConnection} from '../Connection.js'; import * as BidiMapper from 'chromium-bidi/lib/cjs/bidiMapper/bidiMapper.js';
import {Connection as BidiPPtrConnection} from './Connection.js'; import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import {Bidi, BidiMapper} from '../../../third_party/chromium-bidi/index.js';
import type {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js'; import type {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
import {CDPSession, Connection as CDPPPtrConnection} from '../Connection.js';
import {Handler} from '../EventEmitter.js'; import {Handler} from '../EventEmitter.js';
import {Connection as BidiPPtrConnection} from './Connection.js';
type CdpEvents = { type CdpEvents = {
[Property in keyof ProtocolMapping.Events]: ProtocolMapping.Events[Property][0]; [Property in keyof ProtocolMapping.Events]: ProtocolMapping.Events[Property][0];
}; };
@ -99,9 +102,9 @@ class CDPClientAdapter<
this.#client.on('*', this.#forwardMessage as Handler<any>); this.#client.on('*', this.#forwardMessage as Handler<any>);
} }
#forwardMessage = ( #forwardMessage = <T extends keyof CdpEvents>(
method: keyof ProtocolMapping.Events, method: T,
event: ProtocolMapping.Events[keyof ProtocolMapping.Events] event: CdpEvents[T]
) => { ) => {
this.emit(method, event); this.emit(method, event);
}; };

View file

@ -14,15 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
import {ChildProcess} from 'child_process';
import { import {
Browser as BrowserBase, Browser as BrowserBase,
BrowserCloseCallback, BrowserCloseCallback,
BrowserContextOptions, BrowserContextOptions,
} from '../../api/Browser.js'; } from '../../api/Browser.js';
import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js'; import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js';
import {Connection} from './Connection.js';
import {ChildProcess} from 'child_process';
import {BrowserContext} from './BrowserContext.js'; import {BrowserContext} from './BrowserContext.js';
import {Connection} from './Connection.js';
/** /**
* @internal * @internal
@ -34,7 +36,7 @@ export class Browser extends BrowserBase {
static async create(opts: Options): Promise<Browser> { static async create(opts: Options): Promise<Browser> {
// TODO: await until the connection is established. // TODO: await until the connection is established.
try { try {
(await opts.connection.send('session.new', {})) as {sessionId: string}; await opts.connection.send('session.new', {});
} catch {} } catch {}
return new Browser(opts); return new Browser(opts);
} }

View file

@ -16,6 +16,7 @@
import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js'; import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js';
import {Page as PageBase} from '../../api/Page.js'; import {Page as PageBase} from '../../api/Page.js';
import {Connection} from './Connection.js'; import {Connection} from './Connection.js';
import {Page} from './Page.js'; import {Page} from './Page.js';
@ -31,10 +32,10 @@ export class BrowserContext extends BrowserContextBase {
} }
override async newPage(): Promise<PageBase> { override async newPage(): Promise<PageBase> {
const result = (await this.#connection.send('browsingContext.create', { const response = await this.#connection.send('browsingContext.create', {
type: 'tab', type: 'tab',
})) as {context: string}; });
return new Page(this.#connection, result.context); return new Page(this.#connection, response.result.context);
} }
override async close(): Promise<void> {} override async close(): Promise<void> {}

Some files were not shown because too many files have changed in this diff Show more