forked from mirrors/gecko-dev
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:
parent
13840898bb
commit
33c84b1801
213 changed files with 7845 additions and 35529 deletions
|
|
@ -117,10 +117,14 @@ _OPT\.OBJ/
|
|||
|
||||
# Ignore node_module directories and npm artifacts
|
||||
^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/\.husky
|
||||
^remote/test/puppeteer/\.wireit/
|
||||
^remote/test/puppeteer/coverage/
|
||||
^remote/test/puppeteer/.devcontainer/
|
||||
^remote/test/puppeteer/docker/
|
||||
^remote/test/puppeteer/docs/puppeteer-core\.api\.json
|
||||
^remote/test/puppeteer/docs/puppeteer\.api\.json
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ generated/
|
|||
|
||||
# IDE Artifacts
|
||||
.vscode
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.template.json
|
||||
.devcontainer
|
||||
|
||||
# Misc
|
||||
|
|
|
|||
|
|
@ -110,6 +110,14 @@ module.exports = {
|
|||
],
|
||||
'import/extensions': ['error', 'ignorePackages'],
|
||||
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
'newlines-between': 'always',
|
||||
alphabetize: {order: 'asc', caseInsensitive: true},
|
||||
},
|
||||
],
|
||||
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
// Don't allow underscored declarations on camelCased variables/properties.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ generated/
|
|||
# IDE Artifacts
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.template.json
|
||||
.devcontainer
|
||||
|
||||
# Misc
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"packages/puppeteer": "19.6.0",
|
||||
"packages/puppeteer-core": "19.6.0",
|
||||
"packages/puppeteer": "19.7.2",
|
||||
"packages/puppeteer-core": "19.7.2",
|
||||
"packages/testserver": "0.6.0",
|
||||
"packages/ng-schematics": "0.1.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,9 +44,17 @@ npm i puppeteer
|
|||
```
|
||||
|
||||
When you install Puppeteer, it automatically downloads a recent version of
|
||||
Chromium (~170MB macOS, ~282MB Linux, ~280MB Windows) that is
|
||||
[guaranteed to work](https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy)
|
||||
with Puppeteer. For a version of Puppeteer without installation, see
|
||||
Chromium (~170MB macOS, ~282MB Linux, ~280MB Windows) that is [guaranteed to
|
||||
work](https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy)
|
||||
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).
|
||||
|
||||
#### Configuration
|
||||
|
|
@ -149,7 +157,7 @@ import puppeteer from 'puppeteer';
|
|||
await page.waitForSelector(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(
|
||||
'text/Customize and automate'
|
||||
);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -5,6 +5,6 @@ origin:
|
|||
description: Headless Chrome Node API
|
||||
license: Apache-2.0
|
||||
name: puppeteer
|
||||
release: e13e9647fc0d917da94af8851a09ed318fb0e07c
|
||||
url: /Users/alexandraborovova/Projects/puppeteer
|
||||
release: puppeteer-v19.7.2
|
||||
url: https://github.com/puppeteer/puppeteer.git
|
||||
schema: 1
|
||||
|
|
|
|||
483
remote/test/puppeteer/package-lock.json
generated
483
remote/test/puppeteer/package-lock.json
generated
|
|
@ -19,7 +19,7 @@
|
|||
"@microsoft/api-extractor": "7.33.7",
|
||||
"@microsoft/api-extractor-model": "7.25.3",
|
||||
"@pptr/testserver": "file:packages/testserver",
|
||||
"@rollup/plugin-commonjs": "24.0.0",
|
||||
"@rollup/plugin-commonjs": "24.0.1",
|
||||
"@rollup/plugin-node-resolve": "15.0.1",
|
||||
"@types/debug": "4.1.7",
|
||||
"@types/diff": "5.0.2",
|
||||
|
|
@ -40,7 +40,6 @@
|
|||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||
"@typescript-eslint/parser": "5.46.1",
|
||||
"c8": "7.12.0",
|
||||
"chromium-bidi": "0.4.3",
|
||||
"commonmark": "0.30.0",
|
||||
"cross-env": "7.0.3",
|
||||
"diff": "5.1.0",
|
||||
|
|
@ -70,8 +69,7 @@
|
|||
"pngjs": "6.0.0",
|
||||
"prettier": "2.8.1",
|
||||
"puppeteer": "file:packages/puppeteer",
|
||||
"rollup": "2.79.1",
|
||||
"rollup-plugin-dts": "4.2.2",
|
||||
"rollup": "3.12.1",
|
||||
"semver": "7.3.8",
|
||||
"sinon": "15.0.1",
|
||||
"source-map-support": "0.5.21",
|
||||
|
|
@ -103,11 +101,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/architect": {
|
||||
"version": "0.1501.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1501.2.tgz",
|
||||
"integrity": "sha512-AfORVGLN0FBIUXO3FkfGOKu+Gz6oJjF8Bu8cPn27duiI0wszxGNY3fATKwbSg7JcKx1oQS/G7RjyC5OiTA6a0Q==",
|
||||
"version": "0.1501.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1501.6.tgz",
|
||||
"integrity": "sha512-u07zZFlfrg0Qn4mu5M9Nz0pH2Yd2028XF/73980PsZMxwkSm4diF08v4bHk3UyR7yPT7phwvt4znj6ryZhx1gw==",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "15.1.2",
|
||||
"@angular-devkit/core": "15.1.6",
|
||||
"rxjs": "6.6.7"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -116,10 +114,10 @@
|
|||
"yarn": ">= 1.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/architect/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==",
|
||||
"node_modules/@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==",
|
||||
"dependencies": {
|
||||
"ajv": "8.12.0",
|
||||
"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",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
||||
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
|
||||
|
|
@ -156,7 +154,7 @@
|
|||
"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",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
|
||||
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
|
||||
|
|
@ -1386,14 +1384,18 @@
|
|||
"resolved": "test",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@puppeteer/browsers": {
|
||||
"resolved": "packages/browsers",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@puppeteer/ng-schematics": {
|
||||
"resolved": "packages/ng-schematics",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@rollup/plugin-commonjs": {
|
||||
"version": "24.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.0.tgz",
|
||||
"integrity": "sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g==",
|
||||
"version": "24.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz",
|
||||
"integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
|
|
@ -1421,18 +1423,6 @@
|
|||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"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": {
|
||||
"version": "15.0.1",
|
||||
"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=="
|
||||
},
|
||||
"node_modules/chromium-bidi": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.3.tgz",
|
||||
"integrity": "sha512-A40H1rdpJqkTdnGhnYDzMhtDdIbkXNFj2wgIfivMXL7LyHFDmBtv1hdyycDhnxtYunbPLDZtTs/n+ZT5j7Vnew==",
|
||||
"dev": true,
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.4.tgz",
|
||||
"integrity": "sha512-4BX5cSaponuvVT1+SbLYTOAgDoVtX/Khoc9UsbFJ/AsPVUeFAM3RiIDFI6XFhLYMi9WmVJqh1ZH+dRpNKkKwiQ==",
|
||||
"dependencies": {
|
||||
"mitt": "3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"devtools-protocol": "*",
|
||||
"mitt": "*"
|
||||
"devtools-protocol": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
|
|
@ -2717,7 +2708,6 @@
|
|||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
|
|
@ -3036,9 +3026,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/devtools-protocol": {
|
||||
"version": "0.0.1082910",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1082910.tgz",
|
||||
"integrity": "sha512-RqoZ2GmqaNxyx+99L/RemY5CkwG9D0WEfOKxekwCRXOGrDCep62ngezEJUVMq6rISYQ+085fJnWDQqGHlxVNww=="
|
||||
"version": "0.0.1094867",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz",
|
||||
"integrity": "sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ=="
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "5.1.0",
|
||||
|
|
@ -5617,9 +5607,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
|
|
@ -5851,12 +5841,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.26.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz",
|
||||
"integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==",
|
||||
"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": {
|
||||
"sourcemap-codec": "^1.4.8"
|
||||
"@jridgewell/sourcemap-codec": "^1.4.13"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
|
|
@ -6049,8 +6039,7 @@
|
|||
"node_modules/mitt": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz",
|
||||
"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ=="
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
|
|
@ -7363,55 +7352,21 @@
|
|||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "2.79.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||
"version": "3.12.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz",
|
||||
"integrity": "sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
"node": ">=14.18.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"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": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
|
||||
|
|
@ -8040,9 +7995,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tsconfig-paths/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
|
|
@ -8192,7 +8147,7 @@
|
|||
"version": "4.9.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
|
||||
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
|
@ -8469,10 +8424,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.6.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz",
|
||||
"integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==",
|
||||
"dev": true,
|
||||
"version": "17.7.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.0.tgz",
|
||||
"integrity": "sha512-dwqOPg5trmrre9+v8SUo2q/hAwyKoVfu8OC1xPHKJGNdxAvPl4sKxL4vBnh3bQz/ZvvGAFeA5H3ou2kcOY8sQQ==",
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
|
|
@ -8480,7 +8434,7 @@
|
|||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^21.0.0"
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
|
|
@ -8543,7 +8497,6 @@
|
|||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
|
|
@ -8606,14 +8559,62 @@
|
|||
"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": {
|
||||
"name": "@puppeteer/ng-schematics",
|
||||
"version": "0.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "^0.1501.2",
|
||||
"@angular-devkit/core": "^15.1.2",
|
||||
"@angular-devkit/schematics": "^15.1.2"
|
||||
"@angular-devkit/architect": "^0.1501.6",
|
||||
"@angular-devkit/core": "^15.1.6",
|
||||
"@angular-devkit/schematics": "^15.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@schematics/angular": "^14.2.8",
|
||||
|
|
@ -8623,37 +8624,12 @@
|
|||
"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": {
|
||||
"version": "15.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.1.2.tgz",
|
||||
"integrity": "sha512-HjJPm+4SS5TdAHHvdpXLv25wsvwVOn5RYs0A9MazTndlm80ct3PKeYUgakNDRFjRj8uORNlJMKmQIIhUSDjFsw==",
|
||||
"version": "15.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.1.6.tgz",
|
||||
"integrity": "sha512-cwmJFpS43zrdlmfwfHIxG/Nzg5rzFdtKrHx64ZXxNFm6JdyK2JTs/qrHUwv1FYWAcqhdiHn+00jYklMmvsvPOA==",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "15.1.2",
|
||||
"@angular-devkit/core": "15.1.6",
|
||||
"jsonc-parser": "3.2.0",
|
||||
"magic-string": "0.27.0",
|
||||
"ora": "5.4.1",
|
||||
|
|
@ -8671,21 +8647,6 @@
|
|||
"integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==",
|
||||
"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": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
|
||||
|
|
@ -8697,16 +8658,8 @@
|
|||
"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": {
|
||||
"version": "19.6.0",
|
||||
"version": "19.7.2",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
|
|
@ -8714,19 +8667,20 @@
|
|||
"https-proxy-agent": "5.0.1",
|
||||
"progress": "2.0.3",
|
||||
"proxy-from-env": "1.1.0",
|
||||
"puppeteer-core": "19.6.0"
|
||||
"puppeteer-core": "19.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.1.0"
|
||||
}
|
||||
},
|
||||
"packages/puppeteer-core": {
|
||||
"version": "19.6.0",
|
||||
"version": "19.7.2",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"chromium-bidi": "0.4.4",
|
||||
"cross-fetch": "3.1.5",
|
||||
"debug": "4.3.4",
|
||||
"devtools-protocol": "0.0.1082910",
|
||||
"devtools-protocol": "0.0.1094867",
|
||||
"extract-zip": "2.0.1",
|
||||
"https-proxy-agent": "5.0.1",
|
||||
"proxy-from-env": "1.1.0",
|
||||
|
|
@ -8737,6 +8691,14 @@
|
|||
},
|
||||
"engines": {
|
||||
"node": ">=14.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">= 4.7.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"packages/puppeteer/node_modules/argparse": {
|
||||
|
|
@ -8982,26 +8944,26 @@
|
|||
}
|
||||
},
|
||||
"@angular-devkit/architect": {
|
||||
"version": "0.1501.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1501.2.tgz",
|
||||
"integrity": "sha512-AfORVGLN0FBIUXO3FkfGOKu+Gz6oJjF8Bu8cPn27duiI0wszxGNY3fATKwbSg7JcKx1oQS/G7RjyC5OiTA6a0Q==",
|
||||
"version": "0.1501.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1501.6.tgz",
|
||||
"integrity": "sha512-u07zZFlfrg0Qn4mu5M9Nz0pH2Yd2028XF/73980PsZMxwkSm4diF08v4bHk3UyR7yPT7phwvt4znj6ryZhx1gw==",
|
||||
"requires": {
|
||||
"@angular-devkit/core": "15.1.2",
|
||||
"@angular-devkit/core": "15.1.6",
|
||||
"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": {
|
||||
"@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": {
|
||||
"version": "8.12.0",
|
||||
"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": {
|
||||
"version": "file:packages/ng-schematics",
|
||||
"requires": {
|
||||
"@angular-devkit/architect": "^0.1501.2",
|
||||
"@angular-devkit/core": "^15.1.2",
|
||||
"@angular-devkit/schematics": "^15.1.2",
|
||||
"@angular-devkit/architect": "^0.1501.6",
|
||||
"@angular-devkit/core": "^15.1.6",
|
||||
"@angular-devkit/schematics": "^15.1.6",
|
||||
"@schematics/angular": "^14.2.8",
|
||||
"@types/node": "^14.15.0"
|
||||
},
|
||||
"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": {
|
||||
"version": "15.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.1.2.tgz",
|
||||
"integrity": "sha512-HjJPm+4SS5TdAHHvdpXLv25wsvwVOn5RYs0A9MazTndlm80ct3PKeYUgakNDRFjRj8uORNlJMKmQIIhUSDjFsw==",
|
||||
"version": "15.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.1.6.tgz",
|
||||
"integrity": "sha512-cwmJFpS43zrdlmfwfHIxG/Nzg5rzFdtKrHx64ZXxNFm6JdyK2JTs/qrHUwv1FYWAcqhdiHn+00jYklMmvsvPOA==",
|
||||
"requires": {
|
||||
"@angular-devkit/core": "15.1.2",
|
||||
"@angular-devkit/core": "15.1.6",
|
||||
"jsonc-parser": "3.2.0",
|
||||
"magic-string": "0.27.0",
|
||||
"ora": "5.4.1",
|
||||
|
|
@ -10047,17 +10029,6 @@
|
|||
"integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==",
|
||||
"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": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
|
||||
|
|
@ -10065,18 +10036,13 @@
|
|||
"requires": {
|
||||
"@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": {
|
||||
"version": "24.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.0.tgz",
|
||||
"integrity": "sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g==",
|
||||
"version": "24.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz",
|
||||
"integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
|
|
@ -10092,15 +10058,6 @@
|
|||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"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=="
|
||||
},
|
||||
"chromium-bidi": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.3.tgz",
|
||||
"integrity": "sha512-A40H1rdpJqkTdnGhnYDzMhtDdIbkXNFj2wgIfivMXL7LyHFDmBtv1hdyycDhnxtYunbPLDZtTs/n+ZT5j7Vnew==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.4.tgz",
|
||||
"integrity": "sha512-4BX5cSaponuvVT1+SbLYTOAgDoVtX/Khoc9UsbFJ/AsPVUeFAM3RiIDFI6XFhLYMi9WmVJqh1ZH+dRpNKkKwiQ==",
|
||||
"requires": {
|
||||
"mitt": "3.0.0"
|
||||
}
|
||||
},
|
||||
"cli-cursor": {
|
||||
"version": "3.1.0",
|
||||
|
|
@ -11085,7 +11043,6 @@
|
|||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
|
|
@ -11320,9 +11277,9 @@
|
|||
}
|
||||
},
|
||||
"devtools-protocol": {
|
||||
"version": "0.0.1082910",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1082910.tgz",
|
||||
"integrity": "sha512-RqoZ2GmqaNxyx+99L/RemY5CkwG9D0WEfOKxekwCRXOGrDCep62ngezEJUVMq6rISYQ+085fJnWDQqGHlxVNww=="
|
||||
"version": "0.0.1094867",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz",
|
||||
"integrity": "sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ=="
|
||||
},
|
||||
"diff": {
|
||||
"version": "5.1.0",
|
||||
|
|
@ -13126,9 +13083,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true
|
||||
},
|
||||
"jsonc-parser": {
|
||||
|
|
@ -13318,12 +13275,12 @@
|
|||
}
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.26.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz",
|
||||
"integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==",
|
||||
"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": {
|
||||
"sourcemap-codec": "^1.4.8"
|
||||
"@jridgewell/sourcemap-codec": "^1.4.13"
|
||||
}
|
||||
},
|
||||
"make-dir": {
|
||||
|
|
@ -13460,8 +13417,7 @@
|
|||
"mitt": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz",
|
||||
"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ=="
|
||||
},
|
||||
"mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
|
|
@ -14130,7 +14086,7 @@
|
|||
"https-proxy-agent": "5.0.1",
|
||||
"progress": "2.0.3",
|
||||
"proxy-from-env": "1.1.0",
|
||||
"puppeteer-core": "19.6.0"
|
||||
"puppeteer-core": "19.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"argparse": {
|
||||
|
|
@ -14162,9 +14118,10 @@
|
|||
"puppeteer-core": {
|
||||
"version": "file:packages/puppeteer-core",
|
||||
"requires": {
|
||||
"chromium-bidi": "0.4.4",
|
||||
"cross-fetch": "3.1.5",
|
||||
"debug": "4.3.4",
|
||||
"devtools-protocol": "0.0.1082910",
|
||||
"devtools-protocol": "0.0.1094867",
|
||||
"extract-zip": "2.0.1",
|
||||
"https-proxy-agent": "5.0.1",
|
||||
"proxy-from-env": "1.1.0",
|
||||
|
|
@ -14482,36 +14439,14 @@
|
|||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "2.79.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||
"version": "3.12.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz",
|
||||
"integrity": "sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"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": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
|
||||
|
|
@ -14981,9 +14916,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
|
|
@ -15090,7 +15025,7 @@
|
|||
"version": "4.9.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
|
||||
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"unbox-primitive": {
|
||||
"version": "1.0.2",
|
||||
|
|
@ -15300,10 +15235,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "17.6.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz",
|
||||
"integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==",
|
||||
"dev": true,
|
||||
"version": "17.7.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.0.tgz",
|
||||
"integrity": "sha512-dwqOPg5trmrre9+v8SUo2q/hAwyKoVfu8OC1xPHKJGNdxAvPl4sKxL4vBnh3bQz/ZvvGAFeA5H3ou2kcOY8sQQ==",
|
||||
"requires": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
|
|
@ -15311,14 +15245,13 @@
|
|||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^21.0.0"
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@
|
|||
},
|
||||
"scripts": {
|
||||
"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": "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",
|
||||
"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:prettier": "prettier --write .",
|
||||
"format": "run-s format:*",
|
||||
|
|
@ -25,17 +26,93 @@
|
|||
"prepare": "husky install",
|
||||
"test-install": "npm run test --workspace @puppeteer-test/installation",
|
||||
"test-types": "tsd -t packages/puppeteer",
|
||||
"test:chrome:headful": "npm test -- --test-suite chrome-headful",
|
||||
"test:chrome:headless-chrome": "npm test -- --test-suite chrome-new-headless",
|
||||
"test:chrome:headless": "npm test -- --test-suite chrome-headless",
|
||||
"test:chrome:bidi": "npm test -- --test-suite chrome-bidi",
|
||||
"test:chrome": "run-s test:chrome:*",
|
||||
"test:firefox:bidi": "npm test -- --test-suite firefox-bidi",
|
||||
"test:firefox:headful": "npm test -- --test-suite firefox-headful",
|
||||
"test:firefox:headless": "npm test -- --test-suite firefox-headless",
|
||||
"test:firefox": "run-s test:firefox:*",
|
||||
"test:chrome:headful": "wireit",
|
||||
"test:chrome:new-headless": "wireit",
|
||||
"test:chrome:headless": "wireit",
|
||||
"test:chrome:bidi": "wireit",
|
||||
"test:chrome": "wireit",
|
||||
"test:firefox:bidi": "wireit",
|
||||
"test:firefox:headful": "wireit",
|
||||
"test:firefox:headless": "wireit",
|
||||
"test:firefox": "wireit",
|
||||
"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": {
|
||||
"@actions/core": "1.10.0",
|
||||
"@commitlint/cli": "17.3.0",
|
||||
|
|
@ -44,7 +121,7 @@
|
|||
"@microsoft/api-extractor": "7.33.7",
|
||||
"@microsoft/api-extractor-model": "7.25.3",
|
||||
"@pptr/testserver": "file:packages/testserver",
|
||||
"@rollup/plugin-commonjs": "24.0.0",
|
||||
"@rollup/plugin-commonjs": "24.0.1",
|
||||
"@rollup/plugin-node-resolve": "15.0.1",
|
||||
"@types/debug": "4.1.7",
|
||||
"@types/diff": "5.0.2",
|
||||
|
|
@ -65,7 +142,6 @@
|
|||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||
"@typescript-eslint/parser": "5.46.1",
|
||||
"c8": "7.12.0",
|
||||
"chromium-bidi": "0.4.3",
|
||||
"commonmark": "0.30.0",
|
||||
"cross-env": "7.0.3",
|
||||
"diff": "5.1.0",
|
||||
|
|
@ -95,8 +171,7 @@
|
|||
"pngjs": "6.0.0",
|
||||
"prettier": "2.8.1",
|
||||
"puppeteer": "file:packages/puppeteer",
|
||||
"rollup": "2.79.1",
|
||||
"rollup-plugin-dts": "4.2.2",
|
||||
"rollup": "3.12.1",
|
||||
"semver": "7.3.8",
|
||||
"sinon": "15.0.1",
|
||||
"source-map-support": "0.5.21",
|
||||
|
|
|
|||
6
remote/test/puppeteer/packages/browsers/.mocharc.cjs
Normal file
6
remote/test/puppeteer/packages/browsers/.mocharc.cjs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
logLevel: 'debug',
|
||||
spec: 'test/build/**/*.spec.js',
|
||||
exit: !!process.env.CI,
|
||||
reporter: 'spec',
|
||||
};
|
||||
3
remote/test/puppeteer/packages/browsers/README.md
Normal file
3
remote/test/puppeteer/packages/browsers/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# @puppeteer/browsers
|
||||
|
||||
TODO
|
||||
87
remote/test/puppeteer/packages/browsers/package.json
Normal file
87
remote/test/puppeteer/packages/browsers/package.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
176
remote/test/puppeteer/packages/browsers/src/CLI.ts
Normal file
176
remote/test/puppeteer/packages/browsers/src/CLI.ts
Normal 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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -14,5 +16,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * as BidiMapper from 'chromium-bidi/lib/cjs/bidiMapper/bidiMapper.js';
|
||||
export * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||
import {CLI} from './CLI.js';
|
||||
|
||||
new CLI().run(process.argv);
|
||||
|
|
@ -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};
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
19
remote/test/puppeteer/packages/browsers/src/debug.ts
Normal file
19
remote/test/puppeteer/packages/browsers/src/debug.ts
Normal 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};
|
||||
|
|
@ -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;
|
||||
}
|
||||
140
remote/test/puppeteer/packages/browsers/src/fetch.ts
Normal file
140
remote/test/puppeteer/packages/browsers/src/fetch.ts
Normal 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));
|
||||
}
|
||||
89
remote/test/puppeteer/packages/browsers/src/fileUtil.ts
Normal file
89
remote/test/puppeteer/packages/browsers/src/fileUtil.ts
Normal 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`);
|
||||
}
|
||||
}
|
||||
139
remote/test/puppeteer/packages/browsers/src/httpUtil.ts
Normal file
139
remote/test/puppeteer/packages/browsers/src/httpUtil.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
295
remote/test/puppeteer/packages/browsers/src/launcher.ts
Normal file
295
remote/test/puppeteer/packages/browsers/src/launcher.ts
Normal 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)
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"outDir": "../lib/cjs"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../lib/esm"
|
||||
}
|
||||
}
|
||||
|
|
@ -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')
|
||||
);
|
||||
});
|
||||
});
|
||||
75
remote/test/puppeteer/packages/browsers/test/src/cli.spec.ts
Normal file
75
remote/test/puppeteer/packages/browsers/test/src/cli.spec.ts
Normal 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')
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
228
remote/test/puppeteer/packages/browsers/test/src/fetch.spec.ts
Normal file
228
remote/test/puppeteer/packages/browsers/test/src/fetch.spec.ts
Normal 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',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"outDir": "../build"
|
||||
},
|
||||
"references": [{"path": "../../tsconfig.json"}]
|
||||
}
|
||||
8
remote/test/puppeteer/packages/browsers/tsconfig.json
Normal file
8
remote/test/puppeteer/packages/browsers/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"references": [
|
||||
{"path": "src/tsconfig.esm.json"},
|
||||
{"path": "src/tsconfig.cjs.json"}
|
||||
]
|
||||
}
|
||||
|
|
@ -3,18 +3,16 @@
|
|||
"version": "0.1.0",
|
||||
"description": "Puppeteer Angular schematics",
|
||||
"scripts": {
|
||||
"dev": "npm run build --watch",
|
||||
"dev:test": "npm run test --watch",
|
||||
"copy": "wireit",
|
||||
"build:tsc": "wireit",
|
||||
"build": "wireit",
|
||||
"clean": "tsc --build --clean && rimraf lib",
|
||||
"clean:test": "rimraf test/build",
|
||||
"clean": "tsc -b --clean && rimraf lib && rimraf test/build",
|
||||
"dev:test": "npm run test --watch",
|
||||
"dev": "npm run build --watch",
|
||||
"test": "wireit"
|
||||
},
|
||||
"wireit": {
|
||||
"copy": {
|
||||
"clean": "if-file-deleted",
|
||||
"command": "node copySchemaFiles.js",
|
||||
"build": {
|
||||
"command": "node tools/copySchemaFiles.js",
|
||||
"files": [
|
||||
"src/**/files/**",
|
||||
"src/**/*.json"
|
||||
|
|
@ -24,29 +22,25 @@
|
|||
"lib/**/*.json"
|
||||
],
|
||||
"dependencies": [
|
||||
"clean"
|
||||
"build:tsc"
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
"build:tsc": {
|
||||
"command": "tsc -b",
|
||||
"clean": "if-file-deleted",
|
||||
"files": [
|
||||
"src/**/*.ts",
|
||||
"!src/**/files",
|
||||
"!src/**/*.json"
|
||||
"**/tsconfig.*.json",
|
||||
"**/tsconfig.json",
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"output": [
|
||||
"lib/**",
|
||||
"!lib/**/files",
|
||||
"!lib/**/*.json"
|
||||
],
|
||||
"dependencies": [
|
||||
"copy"
|
||||
"lib/**/*.{ts,js}",
|
||||
"lib/**/*.{ts,js}.map"
|
||||
]
|
||||
},
|
||||
"test": {
|
||||
"command": "mocha",
|
||||
"dependencies": [
|
||||
"clean:test",
|
||||
"build"
|
||||
]
|
||||
}
|
||||
|
|
@ -66,9 +60,9 @@
|
|||
"node": ">=14.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "^0.1501.2",
|
||||
"@angular-devkit/core": "^15.1.2",
|
||||
"@angular-devkit/schematics": "^15.1.2"
|
||||
"@angular-devkit/architect": "^0.1501.6",
|
||||
"@angular-devkit/core": "^15.1.6",
|
||||
"@angular-devkit/schematics": "^15.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.15.0",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import {spawn} from 'child_process';
|
||||
|
||||
import {
|
||||
createBuilder,
|
||||
BuilderContext,
|
||||
|
|
@ -6,7 +8,6 @@ import {
|
|||
BuilderRun,
|
||||
} from '@angular-devkit/architect';
|
||||
import {JsonObject} from '@angular-devkit/core';
|
||||
import {spawn} from 'child_process';
|
||||
|
||||
import {PuppeteerBuilderOptions} from './types.js';
|
||||
|
||||
|
|
|
|||
|
|
@ -16,14 +16,15 @@
|
|||
|
||||
import {chain, Rule, SchematicContext, Tree} from '@angular-devkit/schematics';
|
||||
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
|
||||
|
||||
import {concatMap, map, scan} from 'rxjs/operators';
|
||||
import {of} from 'rxjs';
|
||||
import {concatMap, map, scan} from 'rxjs/operators';
|
||||
|
||||
import {
|
||||
addBaseFiles,
|
||||
addFrameworkFiles,
|
||||
getNgCommandName,
|
||||
} from '../utils/files.js';
|
||||
import {getAngularConfig} from '../utils/json.js';
|
||||
import {
|
||||
addPackageJsonDependencies,
|
||||
addPackageJsonScripts,
|
||||
|
|
@ -33,9 +34,7 @@ import {
|
|||
type NodePackage,
|
||||
updateAngularJsonScripts,
|
||||
} from '../utils/packages.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
|
||||
// factory per file.
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {relative, resolve} from 'path';
|
||||
|
||||
import {getSystemPath, normalize, strings} from '@angular-devkit/core';
|
||||
import {
|
||||
SchematicContext,
|
||||
|
|
@ -26,7 +28,7 @@ import {
|
|||
move,
|
||||
url,
|
||||
} from '@angular-devkit/schematics';
|
||||
import {relative, resolve} from 'path';
|
||||
|
||||
import {SchematicsOptions, TestingFramework} from './types.js';
|
||||
|
||||
export interface FilesOptions {
|
||||
|
|
|
|||
|
|
@ -14,15 +14,17 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Tree} from '@angular-devkit/schematics';
|
||||
import {get} from 'https';
|
||||
import {SchematicsOptions, TestingFramework} from './types.js';
|
||||
|
||||
import {Tree} from '@angular-devkit/schematics';
|
||||
|
||||
import {getNgCommandName, getScriptFromOptions} from './files.js';
|
||||
import {
|
||||
getAngularConfig,
|
||||
getJsonFileAsObject,
|
||||
getObjectAsJson,
|
||||
} from './json.js';
|
||||
import {getNgCommandName, getScriptFromOptions} from './files.js';
|
||||
import {SchematicsOptions, TestingFramework} from './types.js';
|
||||
export interface NodePackage {
|
||||
name: string;
|
||||
version: string;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import expect from 'expect';
|
||||
import sinon from 'sinon';
|
||||
import https from 'https';
|
||||
import {join} from 'path';
|
||||
|
||||
import {JsonObject} from '@angular-devkit/core';
|
||||
import {
|
||||
SchematicTestRunner,
|
||||
UnitTestTree,
|
||||
} 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 = {
|
||||
name: 'workspace',
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@
|
|||
*/
|
||||
|
||||
const fs = require('fs/promises');
|
||||
const {join} = require('path');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} directory
|
||||
|
|
@ -42,8 +44,8 @@ async function findSchemaFiles(directory, files = []) {
|
|||
}
|
||||
|
||||
async function copySchemaFiles() {
|
||||
const srcDir = './src';
|
||||
const outputDir = './lib';
|
||||
const srcDir = join(__dirname, '..', 'src');
|
||||
const outputDir = join(__dirname, '..', 'lib');
|
||||
const files = await findSchemaFiles(srcDir);
|
||||
|
||||
const moves = files.map(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.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,7 @@
|
|||
},
|
||||
|
||||
"docModel": {
|
||||
"enabled": true,
|
||||
"apiJsonFilePath": "<projectFolder>/../../docs/<unscopedPackageName>.api.json"
|
||||
"enabled": false
|
||||
},
|
||||
|
||||
"dtsRollup": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "puppeteer-core",
|
||||
"version": "19.6.0",
|
||||
"version": "19.7.2",
|
||||
"description": "A high-level API to control headless Chrome over the DevTools Protocol",
|
||||
"keywords": [
|
||||
"puppeteer",
|
||||
|
|
@ -34,17 +34,15 @@
|
|||
"node": ">=14.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build:third_party": "wireit",
|
||||
"build:docs": "wireit",
|
||||
"build:tsc": "wireit",
|
||||
"build:types": "wireit",
|
||||
"build": "wireit",
|
||||
"check": "tsx tools/ensure-correct-devtools-protocol-package",
|
||||
"format:types": "wireit",
|
||||
"clean": "tsc -b --clean && rimraf lib src/generated",
|
||||
"generate:package-json": "wireit",
|
||||
"generate:sources": "wireit",
|
||||
"prepack": "wireit",
|
||||
"clean": "tsc -b --clean && rimraf lib src/generated",
|
||||
"clean:third_party": "wireit"
|
||||
"prepack": "wireit"
|
||||
},
|
||||
"wireit": {
|
||||
"prepack": {
|
||||
|
|
@ -58,93 +56,71 @@
|
|||
},
|
||||
"build": {
|
||||
"dependencies": [
|
||||
"build:third_party",
|
||||
"format:types",
|
||||
"generate:package-json"
|
||||
"build:tsc",
|
||||
"build:types"
|
||||
]
|
||||
},
|
||||
"generate:sources": {
|
||||
"command": "tsx tools/generate_sources.ts",
|
||||
"clean": "if-file-deleted",
|
||||
"files": [
|
||||
"tools/generate_sources.ts",
|
||||
"src/templates/**"
|
||||
"../../versions.js",
|
||||
"src/{injected,templates}/**",
|
||||
"tools/generate_sources.ts"
|
||||
],
|
||||
"output": [
|
||||
"src/generated/**"
|
||||
]
|
||||
},
|
||||
"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/**"
|
||||
"src/generated/*.ts"
|
||||
]
|
||||
},
|
||||
"generate:package-json": {
|
||||
"command": "tsx ../../tools/generate_module_package_json.ts lib/esm/package.json",
|
||||
"clean": "if-file-deleted",
|
||||
"dependencies": [
|
||||
"build:tsc"
|
||||
"files": [
|
||||
"../../tools/generate_module_package_json.ts"
|
||||
],
|
||||
"output": [
|
||||
"lib/esm/package.json"
|
||||
]
|
||||
},
|
||||
"build:types": {
|
||||
"command": "api-extractor run --local",
|
||||
"build:docs": {
|
||||
"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": [
|
||||
"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": {
|
||||
"command": "tsc -b",
|
||||
"command": "tsc -b && rollup --config rollup.third_party.config.mjs",
|
||||
"clean": "if-file-deleted",
|
||||
"dependencies": [
|
||||
"clean:third_party",
|
||||
"generate:package-json",
|
||||
"generate:sources"
|
||||
],
|
||||
"files": [
|
||||
"src/**",
|
||||
"compat/**",
|
||||
"third_party/**",
|
||||
"**/tsconfig.*.json"
|
||||
"{compat,src,third_party}/**",
|
||||
"rollup.third_party.config.mjs"
|
||||
],
|
||||
"output": [
|
||||
"lib/esm/**",
|
||||
"lib/cjs/**"
|
||||
"lib/{cjs,esm}/**",
|
||||
"!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",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"chromium-bidi": "0.4.4",
|
||||
"cross-fetch": "3.1.5",
|
||||
"debug": "4.3.4",
|
||||
"devtools-protocol": "0.0.1082910",
|
||||
"devtools-protocol": "0.0.1094867",
|
||||
"extract-zip": "2.0.1",
|
||||
"https-proxy-agent": "5.0.1",
|
||||
"proxy-from-env": "1.1.0",
|
||||
|
|
@ -165,5 +142,13 @@
|
|||
"tar-fs": "2.1.1",
|
||||
"unbzip2-stream": "1.4.3",
|
||||
"ws": "8.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">= 4.7.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,29 +13,23 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* 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 {nodeResolve} from '@rollup/plugin-node-resolve';
|
||||
import glob from 'glob';
|
||||
|
||||
export default ['cjs', 'esm'].flatMap(outputType => {
|
||||
const configs = [];
|
||||
// Note we don't use path.join here. We cannot since `glob` does not support
|
||||
// the backslash path separator.
|
||||
const thirdPartyPath = `lib/${outputType}/third_party`;
|
||||
for (const jsFile of glob.sync(`${thirdPartyPath}/**/*.js`)) {
|
||||
for (const file of glob.sync(`lib/${outputType}/third_party/**/*.js`)) {
|
||||
configs.push({
|
||||
input: jsFile,
|
||||
output: {file: jsFile, format: outputType},
|
||||
input: file,
|
||||
output: {
|
||||
file,
|
||||
format: outputType,
|
||||
},
|
||||
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;
|
||||
});
|
||||
|
|
@ -17,11 +17,14 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import {ChildProcess} from 'child_process';
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
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 {BrowserContext} from './BrowserContext.js';
|
||||
import type {Page} from './Page.js'; // TODO: move to ./api
|
||||
|
||||
/**
|
||||
* BrowserContext options.
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@
|
|||
*/
|
||||
|
||||
import {EventEmitter} from '../common/EventEmitter.js';
|
||||
import {Page} from './Page.js';
|
||||
import {Target} from '../common/Target.js';
|
||||
|
||||
import type {Permission, Browser} from './Browser.js';
|
||||
import {Page} from './Page.js';
|
||||
|
||||
/**
|
||||
* BrowserContexts provide a way to operate multiple independent browser
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -14,14 +14,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
import type {Readable} from 'stream';
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import type {Accessibility} from '../common/Accessibility.js';
|
||||
import type {ConsoleMessage} from '../common/ConsoleMessage.js';
|
||||
import type {Coverage} from '../common/Coverage.js';
|
||||
import {Device} from '../common/Device.js';
|
||||
import type {Dialog} from '../common/Dialog.js';
|
||||
import type {ElementHandle} from '../common/ElementHandle.js';
|
||||
import {EventEmitter, Handler} from '../common/EventEmitter.js';
|
||||
import type {FileChooser} from '../common/FileChooser.js';
|
||||
import type {
|
||||
|
|
@ -39,17 +40,24 @@ import type {
|
|||
Touchscreen,
|
||||
} from '../common/Input.js';
|
||||
import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
|
||||
import type {JSHandle} from '../common/JSHandle.js';
|
||||
import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js';
|
||||
import type {Credentials, NetworkConditions} from '../common/NetworkManager.js';
|
||||
import type {PDFOptions} from '../common/PDFOptions.js';
|
||||
import type {Viewport} from '../common/PuppeteerViewport.js';
|
||||
import type {Target} from '../common/Target.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 {Browser} from './Browser.js';
|
||||
import type {BrowserContext} from './BrowserContext.js';
|
||||
import type {ElementHandle} from './ElementHandle.js';
|
||||
import type {JSHandle} from './JSHandle.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
|
@ -956,21 +964,16 @@ export class Page extends EventEmitter {
|
|||
async $eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[ElementHandle<NodeFor<Selector>>, ...Params]
|
||||
> = EvaluateFunc<[ElementHandle<NodeFor<Selector>>, ...Params]>
|
||||
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
|
||||
NodeFor<Selector>,
|
||||
Params
|
||||
>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[ElementHandle<NodeFor<Selector>>, ...Params]
|
||||
> = EvaluateFunc<[ElementHandle<NodeFor<Selector>>, ...Params]>
|
||||
>(): Promise<Awaited<ReturnType<Func>>> {
|
||||
async $eval(): Promise<unknown> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
|
|
@ -1039,21 +1042,16 @@ export class Page extends EventEmitter {
|
|||
async $$eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[Array<NodeFor<Selector>>, ...Params]
|
||||
> = EvaluateFunc<[Array<NodeFor<Selector>>, ...Params]>
|
||||
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<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[Array<NodeFor<Selector>>, ...Params]
|
||||
> = EvaluateFunc<[Array<NodeFor<Selector>>, ...Params]>
|
||||
>(): Promise<Awaited<ReturnType<Func>>> {
|
||||
async $$eval(): Promise<unknown> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
|
|
@ -2465,7 +2463,7 @@ export class Page extends EventEmitter {
|
|||
* @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.
|
||||
* not found in DOM, otherwise resolves to `ElementHandle`.
|
||||
* @remarks
|
||||
* The optional Argument `options` have properties:
|
||||
*
|
||||
|
|
@ -2483,11 +2481,7 @@ export class Page extends EventEmitter {
|
|||
*/
|
||||
waitForXPath(
|
||||
xpath: string,
|
||||
options?: {
|
||||
visible?: boolean;
|
||||
hidden?: boolean;
|
||||
timeout?: number;
|
||||
}
|
||||
options?: WaitForSelectorOptions
|
||||
): Promise<ElementHandle<Node> | null>;
|
||||
waitForXPath(): Promise<ElementHandle<Node> | null> {
|
||||
throw new Error('Not implemented');
|
||||
|
|
|
|||
|
|
@ -17,3 +17,5 @@
|
|||
export * from './Browser.js';
|
||||
export * from './BrowserContext.js';
|
||||
export * from './Page.js';
|
||||
export * from './JSHandle.js';
|
||||
export * from './ElementHandle.js';
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@
|
|||
*/
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {ElementHandle} from '../api/ElementHandle.js';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
|
||||
/**
|
||||
* Represents a Node and the properties of it that are relevant to Accessibility.
|
||||
|
|
@ -186,7 +188,7 @@ export class Accessibility {
|
|||
let backendNodeId: number | undefined;
|
||||
if (root) {
|
||||
const {node} = await this.#client.send('DOM.describeNode', {
|
||||
objectId: root.remoteObject().objectId,
|
||||
objectId: root.id,
|
||||
});
|
||||
backendNodeId = node.backendNodeId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,46 +16,42 @@
|
|||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {ElementHandle} from '../api/ElementHandle.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {AsyncIterableUtil} from '../util/AsyncIterableUtil.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';
|
||||
import type {PuppeteerQueryHandler} from './QueryHandler.js';
|
||||
import type {Frame} from './Frame.js';
|
||||
|
||||
async function queryAXTree(
|
||||
const queryAXTree = async (
|
||||
client: CDPSession,
|
||||
element: ElementHandle<Node>,
|
||||
accessibleName?: string,
|
||||
role?: string
|
||||
): Promise<Protocol.Accessibility.AXNode[]> {
|
||||
): Promise<Protocol.Accessibility.AXNode[]> => {
|
||||
const {nodes} = await client.send('Accessibility.queryAXTree', {
|
||||
objectId: element.remoteObject().objectId,
|
||||
objectId: element.id,
|
||||
accessibleName,
|
||||
role,
|
||||
});
|
||||
const filteredNodes: Protocol.Accessibility.AXNode[] = nodes.filter(
|
||||
(node: Protocol.Accessibility.AXNode) => {
|
||||
return !node.role || node.role.value !== 'StaticText';
|
||||
}
|
||||
);
|
||||
return filteredNodes;
|
||||
}
|
||||
return nodes.filter((node: Protocol.Accessibility.AXNode) => {
|
||||
return !node.role || node.role.value !== 'StaticText';
|
||||
});
|
||||
};
|
||||
|
||||
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 => {
|
||||
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
|
||||
|
|
@ -68,11 +64,13 @@ function isKnownAttribute(
|
|||
* - 'label' queries for elements with name 'label' and any role.
|
||||
* - '[name=""][role="button"]' queries for elements with no name and role 'button'.
|
||||
*/
|
||||
function parseAriaSelector(selector: string): ARIAQueryOption {
|
||||
const queryOptions: ARIAQueryOption = {};
|
||||
const ATTRIBUTE_REGEXP =
|
||||
/\[\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(
|
||||
attributeRegexp,
|
||||
(_, attribute: string, _quote: string, value: string) => {
|
||||
ATTRIBUTE_REGEXP,
|
||||
(_, attribute, __, value) => {
|
||||
attribute = attribute.trim();
|
||||
assert(
|
||||
isKnownAttribute(attribute),
|
||||
|
|
@ -86,104 +84,41 @@ function parseAriaSelector(selector: string): ARIAQueryOption {
|
|||
queryOptions.name = normalizeValue(defaultName);
|
||||
}
|
||||
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
|
||||
*/
|
||||
export const ariaHandler: PuppeteerQueryHandler = {
|
||||
queryOne,
|
||||
waitFor,
|
||||
queryAll,
|
||||
};
|
||||
export class ARIAQueryHandler extends QueryHandler {
|
||||
static override querySelector: QuerySelector = async (
|
||||
node,
|
||||
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
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,17 +15,9 @@
|
|||
*/
|
||||
|
||||
import {ChildProcess} from 'child_process';
|
||||
|
||||
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 {
|
||||
Browser as BrowserBase,
|
||||
BrowserCloseCallback,
|
||||
|
|
@ -39,6 +31,17 @@ import {
|
|||
Permission,
|
||||
} from '../api/Browser.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
|
||||
|
|
|
|||
|
|
@ -14,18 +14,18 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {debugError} from './util.js';
|
||||
import {isErrorLike} from '../util/ErrorLike.js';
|
||||
import {IsPageTargetCallback, TargetFilterCallback} from '../api/Browser.js';
|
||||
import {isNode} from '../environment.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 {Connection} from './Connection.js';
|
||||
import {ConnectionTransport} from './ConnectionTransport.js';
|
||||
import {getFetch} from './fetch.js';
|
||||
import {Viewport} from './PuppeteerViewport.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
|
||||
* connecting to an existing browser instance.
|
||||
|
|
|
|||
|
|
@ -15,18 +15,20 @@
|
|||
*/
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
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 {debugError} from './util.js';
|
||||
import {TargetFilterCallback} from '../api/Browser.js';
|
||||
import {
|
||||
TargetInterceptor,
|
||||
TargetFactory,
|
||||
TargetManager,
|
||||
TargetManagerEmittedEvents,
|
||||
} from './TargetManager.js';
|
||||
import {debugError} from './util.js';
|
||||
|
||||
/**
|
||||
* ChromeTargetManager uses the CDP's auto-attach mechanism to intercept
|
||||
|
|
|
|||
|
|
@ -13,16 +13,20 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable import/order */
|
||||
import {assert} from '../util/assert.js';
|
||||
|
||||
import {debug} from './Debug.js';
|
||||
const debugProtocolSend = debug('puppeteer:protocol:SEND ►');
|
||||
const debugProtocolReceive = debug('puppeteer:protocol:RECV ◀');
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
|
||||
|
||||
import {ConnectionTransport} from './ConnectionTransport.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
import {ProtocolError} from './Errors.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {JSHandle} from './JSHandle.js';
|
||||
import {JSHandle} from '../api/JSHandle.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
|
|
|||
|
|
@ -14,12 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {assert} from '../util/assert.js';
|
||||
import {addEventListener, debugError, PuppeteerEventListener} from './util.js';
|
||||
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 {addEventListener, debugError, PuppeteerEventListener} from './util.js';
|
||||
import {removeEventListeners} from './util.js';
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -14,10 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {assert} from '../util/assert.js';
|
||||
import {CDPSession} from './Connection.js';
|
||||
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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -15,26 +15,38 @@
|
|||
*/
|
||||
|
||||
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 {
|
||||
BoundingBox,
|
||||
BoxModel,
|
||||
ClickOptions,
|
||||
JSHandle,
|
||||
ElementHandle,
|
||||
Offset,
|
||||
Point,
|
||||
PressOptions,
|
||||
} from './JSHandle.js';
|
||||
} from '../api/ElementHandle.js';
|
||||
import {JSHandle} from '../api/JSHandle.js';
|
||||
import {Page, ScreenshotOptions} from '../api/Page.js';
|
||||
import {getQueryHandlerAndSelector} from './QueryHandler.js';
|
||||
import {ElementFor, EvaluateFunc, HandleFor, NodeFor} from './types.js';
|
||||
import {assert} from '../util/assert.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 {debugError, isString} from './util.js';
|
||||
import {CDPPage} from './Page.js';
|
||||
|
||||
const applyOffsetsToQuad = (
|
||||
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
|
||||
* 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
|
||||
* @internal
|
||||
*/
|
||||
|
||||
export class ElementHandle<
|
||||
export class CDPElementHandle<
|
||||
ElementType extends Node = Element
|
||||
> extends JSHandle<ElementType> {
|
||||
> extends ElementHandle<ElementType> {
|
||||
#frame: Frame;
|
||||
#jsHandle: CDPJSHandle<ElementType>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor(
|
||||
context: ExecutionContext,
|
||||
remoteObject: Protocol.Runtime.RemoteObject,
|
||||
frame: Frame
|
||||
) {
|
||||
super(context, remoteObject);
|
||||
super();
|
||||
this.#jsHandle = new CDPJSHandle(context, remoteObject);
|
||||
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 {
|
||||
return this.#frame._frameManager;
|
||||
}
|
||||
|
|
@ -105,85 +137,72 @@ export class ElementHandle<
|
|||
return this.#frame.page();
|
||||
}
|
||||
|
||||
get frame(): Frame {
|
||||
override get frame(): Frame {
|
||||
return this.#frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>(
|
||||
override get disposed(): boolean {
|
||||
return this.#jsHandle.disposed;
|
||||
}
|
||||
|
||||
override async getProperty<K extends keyof ElementType>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<ElementType[K]>>;
|
||||
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
|
||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||
const {updatedSelector, queryHandler} =
|
||||
): Promise<CDPElementHandle<NodeFor<Selector>> | null> {
|
||||
const {updatedSelector, QueryHandler} =
|
||||
getQueryHandlerAndSelector(selector);
|
||||
assert(
|
||||
queryHandler.queryOne,
|
||||
'Cannot handle queries for a single element with the given selector'
|
||||
);
|
||||
return (await queryHandler.queryOne(
|
||||
return (await QueryHandler.queryOne(
|
||||
this,
|
||||
updatedSelector
|
||||
)) as ElementHandle<NodeFor<Selector>> | null;
|
||||
)) as CDPElementHandle<NodeFor<Selector>> | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>(
|
||||
override async $$<Selector extends string>(
|
||||
selector: Selector
|
||||
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
|
||||
const {updatedSelector, queryHandler} =
|
||||
): Promise<Array<CDPElementHandle<NodeFor<Selector>>>> {
|
||||
const {updatedSelector, QueryHandler} =
|
||||
getQueryHandlerAndSelector(selector);
|
||||
assert(
|
||||
queryHandler.queryAll,
|
||||
'Cannot handle queries for a multiple element with the given selector'
|
||||
);
|
||||
return (await queryHandler.queryAll(this, updatedSelector)) as Array<
|
||||
ElementHandle<NodeFor<Selector>>
|
||||
>;
|
||||
return AsyncIterableUtil.collect(
|
||||
QueryHandler.queryAll(this, updatedSelector)
|
||||
) as Promise<Array<CDPElementHandle<NodeFor<Selector>>>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<
|
||||
override async $eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[ElementHandle<NodeFor<Selector>>, ...Params]
|
||||
> = EvaluateFunc<[ElementHandle<NodeFor<Selector>>, ...Params]>
|
||||
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
|
||||
NodeFor<Selector>,
|
||||
Params
|
||||
>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
|
|
@ -200,238 +219,69 @@ export class ElementHandle<
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<
|
||||
override async $$eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[HandleFor<Array<NodeFor<Selector>>>, ...Params]
|
||||
> = EvaluateFunc<[HandleFor<Array<NodeFor<Selector>>>, ...Params]>
|
||||
Func extends EvaluateFuncWith<
|
||||
Array<NodeFor<Selector>>,
|
||||
Params
|
||||
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
const {updatedSelector, queryHandler} =
|
||||
getQueryHandlerAndSelector(selector);
|
||||
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) => {
|
||||
const results = await this.$$(selector);
|
||||
const elements = await this.evaluateHandle((_, ...elements) => {
|
||||
return elements;
|
||||
}, ...handles)) as JSHandle<Array<NodeFor<Selector>>>;
|
||||
}, ...results);
|
||||
const [result] = await Promise.all([
|
||||
elements.evaluate(pageFunction, ...args),
|
||||
...handles.map(handle => {
|
||||
return handle.dispose();
|
||||
...results.map(results => {
|
||||
return results.dispose();
|
||||
}),
|
||||
]);
|
||||
await elements.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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>>> {
|
||||
override async $x(
|
||||
expression: string
|
||||
): Promise<Array<CDPElementHandle<Node>>> {
|
||||
if (expression.startsWith('//')) {
|
||||
expression = `.${expression}`;
|
||||
}
|
||||
return this.$$(`xpath/${expression}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>(
|
||||
override async waitForSelector<Selector extends string>(
|
||||
selector: Selector,
|
||||
options: WaitForSelectorOptions = {}
|
||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||
const {updatedSelector, queryHandler} =
|
||||
): Promise<CDPElementHandle<NodeFor<Selector>> | null> {
|
||||
const {updatedSelector, QueryHandler} =
|
||||
getQueryHandlerAndSelector(selector);
|
||||
assert(queryHandler.waitFor, 'Query handler does not support waiting');
|
||||
return (await queryHandler.waitFor(
|
||||
return (await QueryHandler.waitFor(
|
||||
this,
|
||||
updatedSelector,
|
||||
options
|
||||
)) as ElementHandle<NodeFor<Selector>> | null;
|
||||
)) as CDPElementHandle<NodeFor<Selector>> | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(
|
||||
override async waitForXPath(
|
||||
xpath: string,
|
||||
options: {
|
||||
visible?: boolean;
|
||||
hidden?: boolean;
|
||||
timeout?: number;
|
||||
} = {}
|
||||
): Promise<ElementHandle<Node> | null> {
|
||||
): Promise<CDPElementHandle<Node> | null> {
|
||||
if (xpath.startsWith('//')) {
|
||||
xpath = `.${xpath}`;
|
||||
}
|
||||
return this.waitForSelector(`xpath/${xpath}`, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<
|
||||
override async toElement<
|
||||
K extends keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap
|
||||
>(tagName: K): Promise<HandleFor<ElementFor<K>>> {
|
||||
const isMatchingTagName = await this.evaluate((node, tagName) => {
|
||||
|
|
@ -443,15 +293,7 @@ export class ElementHandle<
|
|||
return this as unknown as HandleFor<ElementFor<K>>;
|
||||
}
|
||||
|
||||
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> {
|
||||
override async contentFrame(): Promise<Frame | null> {
|
||||
const nodeInfo = await this.client.send('DOM.describeNode', {
|
||||
objectId: this.remoteObject().objectId,
|
||||
});
|
||||
|
|
@ -461,7 +303,9 @@ export class ElementHandle<
|
|||
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(
|
||||
async (element): Promise<string | undefined> => {
|
||||
if (!element.isConnected) {
|
||||
|
|
@ -541,10 +385,7 @@ export class ElementHandle<
|
|||
return {offsetX, offsetY};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the middle point within an element unless a specific offset is provided.
|
||||
*/
|
||||
async clickablePoint(offset?: Offset): Promise<Point> {
|
||||
override async clickablePoint(offset?: Offset): Promise<Point> {
|
||||
const [result, layoutMetrics] = await Promise.all([
|
||||
this.client
|
||||
.send('DOM.getContentQuads', {
|
||||
|
|
@ -615,7 +456,7 @@ export class ElementHandle<
|
|||
|
||||
#getBoxModel(): Promise<void | Protocol.DOM.GetBoxModelResponse> {
|
||||
const params: Protocol.DOM.GetBoxModelRequest = {
|
||||
objectId: this.remoteObject().objectId,
|
||||
objectId: this.id,
|
||||
};
|
||||
return this.client.send('DOM.getBoxModel', params).catch(error => {
|
||||
return debugError(error);
|
||||
|
|
@ -649,7 +490,7 @@ export class ElementHandle<
|
|||
* 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> {
|
||||
override async hover(this: CDPElementHandle<Element>): Promise<void> {
|
||||
await this.#scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this.clickablePoint();
|
||||
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.
|
||||
* If the element is detached from DOM, the method throws an error.
|
||||
*/
|
||||
async click(
|
||||
this: ElementHandle<Element>,
|
||||
override async click(
|
||||
this: CDPElementHandle<Element>,
|
||||
options: ClickOptions = {}
|
||||
): Promise<void> {
|
||||
await this.#scrollIntoViewIfNeeded();
|
||||
|
|
@ -672,8 +513,8 @@ export class ElementHandle<
|
|||
/**
|
||||
* This method creates and captures a dragevent from the element.
|
||||
*/
|
||||
async drag(
|
||||
this: ElementHandle<Element>,
|
||||
override async drag(
|
||||
this: CDPElementHandle<Element>,
|
||||
target: Point
|
||||
): Promise<Protocol.Input.DragData> {
|
||||
assert(
|
||||
|
|
@ -685,11 +526,8 @@ export class ElementHandle<
|
|||
return await this.#page.mouse.drag(start, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a `dragenter` event on the element.
|
||||
*/
|
||||
async dragEnter(
|
||||
this: ElementHandle<Element>,
|
||||
override async dragEnter(
|
||||
this: CDPElementHandle<Element>,
|
||||
data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1}
|
||||
): Promise<void> {
|
||||
await this.#scrollIntoViewIfNeeded();
|
||||
|
|
@ -697,11 +535,8 @@ export class ElementHandle<
|
|||
await this.#page.mouse.dragEnter(target, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a `dragover` event on the element.
|
||||
*/
|
||||
async dragOver(
|
||||
this: ElementHandle<Element>,
|
||||
override async dragOver(
|
||||
this: CDPElementHandle<Element>,
|
||||
data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1}
|
||||
): Promise<void> {
|
||||
await this.#scrollIntoViewIfNeeded();
|
||||
|
|
@ -709,11 +544,8 @@ export class ElementHandle<
|
|||
await this.#page.mouse.dragOver(target, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method triggers a drop on the element.
|
||||
*/
|
||||
async drop(
|
||||
this: ElementHandle<Element>,
|
||||
override async drop(
|
||||
this: CDPElementHandle<Element>,
|
||||
data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1}
|
||||
): Promise<void> {
|
||||
await this.#scrollIntoViewIfNeeded();
|
||||
|
|
@ -721,12 +553,9 @@ export class ElementHandle<
|
|||
await this.#page.mouse.drop(destination, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method triggers a dragenter, dragover, and drop on the element.
|
||||
*/
|
||||
async dragAndDrop(
|
||||
this: ElementHandle<Element>,
|
||||
target: ElementHandle<Node>,
|
||||
override async dragAndDrop(
|
||||
this: CDPElementHandle<Element>,
|
||||
target: CDPElementHandle<Node>,
|
||||
options?: {delay: number}
|
||||
): Promise<void> {
|
||||
await this.#scrollIntoViewIfNeeded();
|
||||
|
|
@ -735,23 +564,7 @@ export class ElementHandle<
|
|||
await this.#page.mouse.dragAndDrop(startPoint, targetPoint, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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[]> {
|
||||
override async select(...values: string[]): Promise<string[]> {
|
||||
for (const value of values) {
|
||||
assert(
|
||||
isString(value),
|
||||
|
|
@ -795,18 +608,8 @@ export class ElementHandle<
|
|||
}, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>,
|
||||
override async uploadFile(
|
||||
this: CDPElementHandle<HTMLInputElement>,
|
||||
...filePaths: string[]
|
||||
): Promise<void> {
|
||||
const isMultiple = await this.evaluate(element => {
|
||||
|
|
@ -837,7 +640,9 @@ export class ElementHandle<
|
|||
}
|
||||
});
|
||||
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;
|
||||
|
||||
/* The zero-length array is a special case, it seems that
|
||||
|
|
@ -861,21 +666,31 @@ export class ElementHandle<
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
override async tap(this: CDPElementHandle<Element>): Promise<void> {
|
||||
await this.#scrollIntoViewIfNeeded();
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus | focus} on the element.
|
||||
*/
|
||||
async focus(): Promise<void> {
|
||||
override async touchStart(this: CDPElementHandle<Element>): Promise<void> {
|
||||
await this.#scrollIntoViewIfNeeded();
|
||||
const {x, y} = await this.clickablePoint();
|
||||
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 => {
|
||||
if (!(element instanceof HTMLElement)) {
|
||||
throw new Error('Cannot focus non-HTMLElement');
|
||||
|
|
@ -884,58 +699,17 @@ export class ElementHandle<
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
override async type(text: string, options?: {delay: number}): Promise<void> {
|
||||
await this.focus();
|
||||
await this.#page.keyboard.type(text, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
override async press(key: KeyInput, options?: PressOptions): Promise<void> {
|
||||
await this.focus();
|
||||
await this.#page.keyboard.press(key, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
override async boundingBox(): Promise<BoundingBox | null> {
|
||||
const result = await this.#getBoxModel();
|
||||
|
||||
if (!result) {
|
||||
|
|
@ -952,15 +726,7 @@ export class ElementHandle<
|
|||
return {x: x + offsetX, y: y + offsetY, width, height};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
override async boxModel(): Promise<BoxModel | null> {
|
||||
const result = await this.#getBoxModel();
|
||||
|
||||
if (!result) {
|
||||
|
|
@ -996,13 +762,8 @@ export class ElementHandle<
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>,
|
||||
override async screenshot(
|
||||
this: CDPElementHandle<Element>,
|
||||
options: ScreenshotOptions = {}
|
||||
): Promise<string | Buffer> {
|
||||
let needsViewportReset = false;
|
||||
|
|
@ -1059,11 +820,8 @@ export class ElementHandle<
|
|||
return imageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves to true if the element is visible in the current viewport.
|
||||
*/
|
||||
async isIntersectingViewport(
|
||||
this: ElementHandle<Element>,
|
||||
override async isIntersectingViewport(
|
||||
this: CDPElementHandle<Element>,
|
||||
options?: {
|
||||
threshold?: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,10 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {Viewport} from './PuppeteerViewport.js';
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
|
|
|||
|
|
@ -15,10 +15,21 @@
|
|||
*/
|
||||
|
||||
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 {CDPElementHandle} from './ElementHandle.js';
|
||||
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||
import {JSHandle} from './JSHandle.js';
|
||||
import {CDPJSHandle} from './JSHandle.js';
|
||||
import {LazyArg} from './LazyArg.js';
|
||||
import {scriptInjector} from './ScriptInjector.js';
|
||||
import {EvaluateFunc, HandleFor} from './types.js';
|
||||
import {
|
||||
createJSHandle,
|
||||
|
|
@ -71,7 +82,7 @@ export class ExecutionContext {
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
_contextName: string;
|
||||
_contextName?: string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
|
@ -84,7 +95,55 @@ export class ExecutionContext {
|
|||
this._client = client;
|
||||
this._world = world;
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
try {
|
||||
callFunctionOnPromise = this._client.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: functionText + '\n' + suffix + '\n',
|
||||
functionDeclaration: `${stringifyFunction(pageFunction)}\n${suffix}\n`,
|
||||
executionContextId: this._contextId,
|
||||
arguments: await Promise.all(args.map(convertArgument.bind(this))),
|
||||
returnByValue,
|
||||
|
|
@ -304,7 +344,7 @@ export class ExecutionContext {
|
|||
arg: unknown
|
||||
): Promise<Protocol.Runtime.CallArgument> {
|
||||
if (arg instanceof LazyArg) {
|
||||
arg = await arg.get();
|
||||
arg = await arg.get(this);
|
||||
}
|
||||
if (typeof arg === 'bigint') {
|
||||
// eslint-disable-line valid-typeof
|
||||
|
|
@ -322,7 +362,10 @@ export class ExecutionContext {
|
|||
if (Object.is(arg, 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.executionContext() !== this) {
|
||||
throw new Error(
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@
|
|||
*/
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {ElementHandle} from '../api/ElementHandle.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
|
||||
/**
|
||||
* File choosers let you react to the page requesting for a file.
|
||||
|
|
|
|||
|
|
@ -15,17 +15,19 @@
|
|||
*/
|
||||
|
||||
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 {assert} from '../util/assert.js';
|
||||
|
||||
import {CDPSession, Connection} from './Connection.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
import {Target} from './Target.js';
|
||||
import {
|
||||
TargetFactory,
|
||||
TargetInterceptor,
|
||||
TargetManagerEmittedEvents,
|
||||
TargetManager,
|
||||
} from './TargetManager.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
|
||||
/**
|
||||
* FirefoxTargetManager implements target management using
|
||||
|
|
|
|||
|
|
@ -15,12 +15,15 @@
|
|||
*/
|
||||
|
||||
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 {CDPSession} from './Connection.js';
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {FrameManager} from './FrameManager.js';
|
||||
import {getQueryHandlerAndSelector} from './GetQueryHandler.js';
|
||||
import {HTTPResponse} from './HTTPResponse.js';
|
||||
import {MouseButton} from './Input.js';
|
||||
import {
|
||||
|
|
@ -29,10 +32,9 @@ import {
|
|||
WaitForSelectorOptions,
|
||||
} from './IsolatedWorld.js';
|
||||
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
|
||||
import {LazyArg} from './LazyArg.js';
|
||||
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||
import {Page} from '../api/Page.js';
|
||||
import {getQueryHandlerAndSelector} from './QueryHandler.js';
|
||||
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
|
||||
import {EvaluateFunc, EvaluateFuncWith, HandleFor, NodeFor} from './types.js';
|
||||
import {importFS} from './util.js';
|
||||
|
||||
/**
|
||||
|
|
@ -353,6 +355,9 @@ export class Frame {
|
|||
referrerPolicy,
|
||||
});
|
||||
ensureNewDocumentNavigation = !!response.loaderId;
|
||||
if (response.errorText === 'net::ERR_HTTP_RESPONSE_CODE_FAILURE') {
|
||||
return null;
|
||||
}
|
||||
return response.errorText
|
||||
? new Error(`${response.errorText} at ${url}`)
|
||||
: null;
|
||||
|
|
@ -514,9 +519,10 @@ export class Frame {
|
|||
async $eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[ElementHandle<NodeFor<Selector>>, ...Params]
|
||||
> = EvaluateFunc<[ElementHandle<NodeFor<Selector>>, ...Params]>
|
||||
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
|
||||
NodeFor<Selector>,
|
||||
Params
|
||||
>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
|
|
@ -548,9 +554,10 @@ export class Frame {
|
|||
async $$eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[Array<NodeFor<Selector>>, ...Params]
|
||||
> = EvaluateFunc<[Array<NodeFor<Selector>>, ...Params]>
|
||||
Func extends EvaluateFuncWith<
|
||||
Array<NodeFor<Selector>>,
|
||||
Params
|
||||
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
|
|
@ -612,10 +619,9 @@ export class Frame {
|
|||
selector: Selector,
|
||||
options: WaitForSelectorOptions = {}
|
||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||
const {updatedSelector, queryHandler} =
|
||||
const {updatedSelector, QueryHandler} =
|
||||
getQueryHandlerAndSelector(selector);
|
||||
assert(queryHandler.waitFor, 'Query handler does not support waiting');
|
||||
return (await queryHandler.waitFor(
|
||||
return (await QueryHandler.waitFor(
|
||||
this,
|
||||
updatedSelector,
|
||||
options
|
||||
|
|
@ -839,7 +845,9 @@ export class Frame {
|
|||
await promise;
|
||||
return script;
|
||||
},
|
||||
await this.worlds[PUPPETEER_WORLD].puppeteerUtil,
|
||||
LazyArg.create(context => {
|
||||
return context.puppeteerUtil;
|
||||
}),
|
||||
{...options, type, content}
|
||||
)
|
||||
);
|
||||
|
|
@ -923,7 +931,9 @@ export class Frame {
|
|||
await promise;
|
||||
return element;
|
||||
},
|
||||
await this.worlds[PUPPETEER_WORLD].puppeteerUtil,
|
||||
LazyArg.create(context => {
|
||||
return context.puppeteerUtil;
|
||||
}),
|
||||
options
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,8 +15,11 @@
|
|||
*/
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {Page} from '../api/Page.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {isErrorLike} from '../util/ErrorLike.js';
|
||||
|
||||
import {CDPSession, isTargetClosedError} from './Connection.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
import {EVALUATION_SCRIPT_URL, ExecutionContext} from './ExecutionContext.js';
|
||||
|
|
@ -25,7 +28,6 @@ import {FrameTree} from './FrameTree.js';
|
|||
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
|
||||
import {NetworkManager} from './NetworkManager.js';
|
||||
import {Page} from '../api/Page.js';
|
||||
import {Target} from './Target.js';
|
||||
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||
import {debugError} from './util.js';
|
||||
|
|
@ -174,12 +176,18 @@ export class FrameManager extends EventEmitter {
|
|||
contextId: number,
|
||||
session: CDPSession = this.#client
|
||||
): ExecutionContext {
|
||||
const key = `${session.id()}:${contextId}`;
|
||||
const context = this.#contextIdToContext.get(key);
|
||||
const context = this.getExecutionContextById(contextId, session);
|
||||
assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId);
|
||||
return context;
|
||||
}
|
||||
|
||||
getExecutionContextById(
|
||||
contextId: number,
|
||||
session: CDPSession = this.#client
|
||||
): ExecutionContext | undefined {
|
||||
return this.#contextIdToContext.get(`${session.id()}:${contextId}`);
|
||||
}
|
||||
|
||||
page(): Page {
|
||||
return this.#page;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
createDeferredPromise,
|
||||
DeferredPromise,
|
||||
} from '../util/DeferredPromise.js';
|
||||
|
||||
import type {Frame} from './Frame.js';
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
}
|
||||
|
|
@ -15,12 +15,14 @@
|
|||
*/
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
|
||||
|
||||
import {assert} from '../util/assert.js';
|
||||
|
||||
import {ProtocolError} from './Errors.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {debugError, isString} from './util.js';
|
||||
import {HTTPResponse} from './HTTPResponse.js';
|
||||
import {debugError, isString} from './util.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
|
|
|||
|
|
@ -13,14 +13,14 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
|
||||
|
||||
import {ProtocolError} from './Errors.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {HTTPRequest} from './HTTPRequest.js';
|
||||
import {SecurityDetails} from './SecurityDetails.js';
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
import {ProtocolError} from './Errors.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
})();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
@ -14,11 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {Point} from '../api/ElementHandle.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {_keyDefinitions, KeyDefinition, KeyInput} from './USKeyboardLayout.js';
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
import {Point} from './JSHandle.js';
|
||||
|
||||
type KeyDescription = Required<
|
||||
Pick<KeyDefinition, 'keyCode' | 'key' | 'text' | 'code' | 'location'>
|
||||
|
|
@ -670,12 +672,40 @@ export class Touchscreen {
|
|||
* @param y - Vertical position of the tap.
|
||||
*/
|
||||
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)}];
|
||||
await this.#client.send('Input.dispatchTouchEvent', {
|
||||
type: 'touchStart',
|
||||
touchPoints,
|
||||
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', {
|
||||
type: 'touchEnd',
|
||||
touchPoints: [],
|
||||
|
|
|
|||
|
|
@ -15,26 +15,31 @@
|
|||
*/
|
||||
|
||||
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 {createDeferredPromise} from '../util/DeferredPromise.js';
|
||||
import {isErrorLike} from '../util/ErrorLike.js';
|
||||
|
||||
import {Binding} from './Binding.js';
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {FrameManager} from './FrameManager.js';
|
||||
import {MouseButton} from './Input.js';
|
||||
import {JSHandle} from './JSHandle.js';
|
||||
import {LazyArg} from './LazyArg.js';
|
||||
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
|
||||
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||
import {EvaluateFunc, HandleFor, InnerLazyParams, NodeFor} from './types.js';
|
||||
import {createJSHandle, debugError, pageBindingInitString} from './util.js';
|
||||
import {
|
||||
BindingPayload,
|
||||
EvaluateFunc,
|
||||
EvaluateFuncWith,
|
||||
HandleFor,
|
||||
InnerLazyParams,
|
||||
NodeFor,
|
||||
} from './types.js';
|
||||
import {addPageBinding, createJSHandle, debugError} from './util.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
|
||||
|
|
@ -91,29 +96,20 @@ export class IsolatedWorld {
|
|||
#detached = false;
|
||||
|
||||
// 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.
|
||||
#boundFunctions = new Map<string, Function>();
|
||||
#bindings = new Map<string, Binding>();
|
||||
#taskManager = new TaskManager();
|
||||
#puppeteerUtil = createDeferredPromise<JSHandle<PuppeteerUtil>>();
|
||||
|
||||
get puppeteerUtil(): Promise<JSHandle<PuppeteerUtil>> {
|
||||
return this.#puppeteerUtil;
|
||||
}
|
||||
|
||||
get taskManager(): TaskManager {
|
||||
return this.#taskManager;
|
||||
}
|
||||
|
||||
get _boundFunctions(): Map<string, Function> {
|
||||
return this.#boundFunctions;
|
||||
get _bindings(): Map<string, Binding> {
|
||||
return this.#bindings;
|
||||
}
|
||||
|
||||
static #bindingIdentifier = (name: string, contextId: number) => {
|
||||
return `${name}_${contextId}`;
|
||||
};
|
||||
|
||||
constructor(frame: Frame) {
|
||||
// Keep own reference to client because it might differ from the FrameManager's
|
||||
// client for OOP iframes.
|
||||
|
|
@ -139,31 +135,13 @@ export class IsolatedWorld {
|
|||
|
||||
clearContext(): void {
|
||||
this.#document = undefined;
|
||||
this.#puppeteerUtil = createDeferredPromise();
|
||||
this.#context = createDeferredPromise();
|
||||
}
|
||||
|
||||
setContext(context: ExecutionContext): void {
|
||||
this.#injectPuppeteerUtil(context);
|
||||
this.#ctxBindings.clear();
|
||||
this.#contextBindings.clear();
|
||||
this.#context.resolve(context);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
this.#taskManager.rerunAll();
|
||||
}
|
||||
|
||||
hasContext(): boolean {
|
||||
|
|
@ -245,9 +223,10 @@ export class IsolatedWorld {
|
|||
async $eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[ElementHandle<NodeFor<Selector>>, ...Params]
|
||||
> = EvaluateFunc<[ElementHandle<NodeFor<Selector>>, ...Params]>
|
||||
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
|
||||
NodeFor<Selector>,
|
||||
Params
|
||||
>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
|
|
@ -260,9 +239,10 @@ export class IsolatedWorld {
|
|||
async $$eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[Array<NodeFor<Selector>>, ...Params]
|
||||
> = EvaluateFunc<[Array<NodeFor<Selector>>, ...Params]>
|
||||
Func extends EvaluateFuncWith<
|
||||
Array<NodeFor<Selector>>,
|
||||
Params
|
||||
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
|
|
@ -371,71 +351,50 @@ export class IsolatedWorld {
|
|||
|
||||
// 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.
|
||||
#settingUpBinding: Promise<void> | null = null;
|
||||
|
||||
#mutex = new Mutex();
|
||||
async _addBindingToContext(
|
||||
context: ExecutionContext,
|
||||
name: string
|
||||
): Promise<void> {
|
||||
// Previous operation added the binding so we are done.
|
||||
if (
|
||||
this.#ctxBindings.has(
|
||||
IsolatedWorld.#bindingIdentifier(name, context._contextId)
|
||||
)
|
||||
) {
|
||||
if (this.#contextBindings.has(name)) {
|
||||
return;
|
||||
}
|
||||
// Wait for other operation to finish
|
||||
if (this.#settingUpBinding) {
|
||||
await this.#settingUpBinding;
|
||||
return this._addBindingToContext(context, name);
|
||||
}
|
||||
|
||||
const bind = async (name: string) => {
|
||||
const expression = pageBindingInitString('internal', name);
|
||||
try {
|
||||
// TODO: In theory, it would be enough to call this just once
|
||||
await context._client.send('Runtime.addBinding', {
|
||||
name,
|
||||
executionContextName: context._contextName,
|
||||
});
|
||||
await context.evaluate(expression);
|
||||
} catch (error) {
|
||||
// We could have tried to evaluate in a context which was already
|
||||
// destroyed. This happens, for example, if the page is navigated while
|
||||
// we are trying to add the binding
|
||||
if (error instanceof Error) {
|
||||
// Destroyed context.
|
||||
if (error.message.includes('Execution context was destroyed')) {
|
||||
return;
|
||||
}
|
||||
// Missing context.
|
||||
if (error.message.includes('Cannot find context with specified id')) {
|
||||
return;
|
||||
}
|
||||
await this.#mutex.acquire();
|
||||
try {
|
||||
await context._client.send('Runtime.addBinding', {
|
||||
name,
|
||||
executionContextName: context._contextName,
|
||||
});
|
||||
|
||||
await context.evaluate(addPageBinding, 'internal', name);
|
||||
|
||||
this.#contextBindings.add(name);
|
||||
} catch (error) {
|
||||
// We could have tried to evaluate in a context which was already
|
||||
// destroyed. This happens, for example, if the page is navigated while
|
||||
// we are trying to add the binding
|
||||
if (error instanceof Error) {
|
||||
// Destroyed context.
|
||||
if (error.message.includes('Execution context was destroyed')) {
|
||||
return;
|
||||
}
|
||||
// Missing context.
|
||||
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);
|
||||
await this.#settingUpBinding;
|
||||
this.#settingUpBinding = null;
|
||||
debugError(error);
|
||||
} finally {
|
||||
this.#mutex.release();
|
||||
}
|
||||
}
|
||||
|
||||
#onBindingCalled = async (
|
||||
event: Protocol.Runtime.BindingCalledEvent
|
||||
): Promise<void> => {
|
||||
let payload: {type: string; name: string; seq: number; args: unknown[]};
|
||||
if (!this.hasContext()) {
|
||||
return;
|
||||
}
|
||||
const context = await this.executionContext();
|
||||
let payload: BindingPayload;
|
||||
try {
|
||||
payload = JSON.parse(event.payload);
|
||||
} catch {
|
||||
|
|
@ -443,108 +402,23 @@ export class IsolatedWorld {
|
|||
// called before our wrapper was initialized.
|
||||
return;
|
||||
}
|
||||
const {type, name, seq, args} = payload;
|
||||
if (
|
||||
type !== 'internal' ||
|
||||
!this.#ctxBindings.has(
|
||||
IsolatedWorld.#bindingIdentifier(name, context._contextId)
|
||||
)
|
||||
) {
|
||||
const {type, name, seq, args, isTrivial} = payload;
|
||||
if (type !== 'internal') {
|
||||
return;
|
||||
}
|
||||
if (context._contextId !== event.executionContextId) {
|
||||
if (!this.#contextBindings.has(name)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const fn = this._boundFunctions.get(name);
|
||||
if (!fn) {
|
||||
throw new Error(`Bound function $name is not found`);
|
||||
}
|
||||
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 context = await this.#context;
|
||||
if (event.executionContextId !== context._contextId) {
|
||||
return;
|
||||
}
|
||||
|
||||
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<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<InnerLazyParams<Params>> = EvaluateFunc<
|
||||
|
|
@ -556,14 +430,12 @@ export class IsolatedWorld {
|
|||
polling?: 'raf' | 'mutation' | number;
|
||||
timeout?: number;
|
||||
root?: ElementHandle<Node>;
|
||||
bindings?: Map<string, (...args: never[]) => unknown>;
|
||||
} = {},
|
||||
...args: Params
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
const {
|
||||
polling = 'raf',
|
||||
timeout = this.#timeoutSettings.timeout(),
|
||||
bindings,
|
||||
root,
|
||||
} = options;
|
||||
if (typeof polling === 'number' && polling < 0) {
|
||||
|
|
@ -572,7 +444,6 @@ export class IsolatedWorld {
|
|||
const waitTask = new WaitTask(
|
||||
this,
|
||||
{
|
||||
bindings,
|
||||
polling,
|
||||
root,
|
||||
timeout,
|
||||
|
|
@ -603,20 +474,57 @@ export class IsolatedWorld {
|
|||
}
|
||||
|
||||
async adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
|
||||
const executionContext = await this.executionContext();
|
||||
const context = await this.executionContext();
|
||||
assert(
|
||||
handle.executionContext() !== executionContext,
|
||||
handle.executionContext() !== context,
|
||||
'Cannot adopt handle that already belongs to this execution context'
|
||||
);
|
||||
const nodeInfo = await this.#client.send('DOM.describeNode', {
|
||||
objectId: handle.remoteObject().objectId,
|
||||
objectId: handle.id,
|
||||
});
|
||||
return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as 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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,64 +15,22 @@
|
|||
*/
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {JSHandle} from '../api/JSHandle.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import type {ElementHandle} from './ElementHandle.js';
|
||||
import type {CDPElementHandle} from './ElementHandle.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {MouseButton} from './Input.js';
|
||||
import {EvaluateFunc, HandleFor, HandleOr} from './types.js';
|
||||
import {EvaluateFuncWith, HandleFor, HandleOr} from './types.js';
|
||||
import {createJSHandle, releaseObject, valueFromRemoteObject} from './util.js';
|
||||
|
||||
declare const __JSHandleSymbol: unique symbol;
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @internal
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
export class CDPJSHandle<T = unknown> extends JSHandle<T> {
|
||||
/**
|
||||
* Used for nominally typing {@link JSHandle}.
|
||||
*/
|
||||
|
|
@ -82,73 +40,50 @@ export class JSHandle<T = unknown> {
|
|||
#context: ExecutionContext;
|
||||
#remoteObject: Protocol.Runtime.RemoteObject;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
get client(): CDPSession {
|
||||
return this.#context._client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
get disposed(): boolean {
|
||||
override get disposed(): boolean {
|
||||
return this.#disposed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor(
|
||||
context: ExecutionContext,
|
||||
remoteObject: Protocol.Runtime.RemoteObject
|
||||
) {
|
||||
super();
|
||||
this.#context = context;
|
||||
this.#remoteObject = remoteObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
executionContext(): ExecutionContext {
|
||||
override executionContext(): ExecutionContext {
|
||||
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.
|
||||
*/
|
||||
async evaluate<
|
||||
override async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[this, ...Params]> = EvaluateFunc<
|
||||
[this, ...Params]
|
||||
>
|
||||
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): // @ts-expect-error Circularity here is okay because we only need the return
|
||||
// type which doesn't use `this`.
|
||||
Promise<Awaited<ReturnType<Func>>> {
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
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.
|
||||
*/
|
||||
async evaluateHandle<
|
||||
override async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[this, ...Params]> = EvaluateFunc<
|
||||
[this, ...Params]
|
||||
>
|
||||
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): // @ts-expect-error Circularity here is okay because we only need the return
|
||||
// type which doesn't use `this`.
|
||||
Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
return await this.executionContext().evaluateHandle(
|
||||
pageFunction,
|
||||
this,
|
||||
|
|
@ -156,14 +91,11 @@ export class JSHandle<T = unknown> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a single property from the referenced object.
|
||||
*/
|
||||
async getProperty<K extends keyof T>(
|
||||
override 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>(
|
||||
override async getProperty(propertyName: string): Promise<JSHandle<unknown>>;
|
||||
override async getProperty<K extends keyof T>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<T[K]>> {
|
||||
return this.evaluateHandle((object, propertyName) => {
|
||||
|
|
@ -171,25 +103,7 @@ export class JSHandle<T = unknown> {
|
|||
}, propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>> {
|
||||
override async getProperties(): Promise<Map<string, JSHandle>> {
|
||||
assert(this.#remoteObject.objectId);
|
||||
// We use Runtime.getProperties rather than iterative building because the
|
||||
// iterative approach might create a distorted snapshot.
|
||||
|
|
@ -207,15 +121,7 @@ export class JSHandle<T = unknown> {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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> {
|
||||
override async jsonValue(): Promise<T> {
|
||||
if (!this.#remoteObject.objectId) {
|
||||
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
|
||||
* instance of {@link ElementHandle}.
|
||||
*/
|
||||
asElement(): ElementHandle<Node> | null {
|
||||
override asElement(): CDPElementHandle<Node> | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the object referenced by the handle for garbage collection.
|
||||
*/
|
||||
async dispose(): Promise<void> {
|
||||
override async dispose(): Promise<void> {
|
||||
if (this.#disposed) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -247,13 +150,7 @@ export class JSHandle<T = unknown> {
|
|||
await releaseObject(this.client, this.#remoteObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the JSHandle.
|
||||
*
|
||||
* @remarks
|
||||
* Useful during debugging.
|
||||
*/
|
||||
toString(): string {
|
||||
override toString(): string {
|
||||
if (!this.#remoteObject.objectId) {
|
||||
return 'JSHandle:' + valueFromRemoteObject(this.#remoteObject);
|
||||
}
|
||||
|
|
@ -261,72 +158,11 @@ export class JSHandle<T = unknown> {
|
|||
return 'JSHandle@' + type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the
|
||||
* [Protocol.Runtime.RemoteObject](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObject)
|
||||
* backing this handle.
|
||||
*/
|
||||
remoteObject(): Protocol.Runtime.RemoteObject {
|
||||
override get id(): string | undefined {
|
||||
return this.#remoteObject.objectId;
|
||||
}
|
||||
|
||||
override remoteObject(): Protocol.Runtime.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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,16 +14,26 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class LazyArg<T> {
|
||||
#get: () => Promise<T>;
|
||||
constructor(get: () => Promise<T>) {
|
||||
static create = <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;
|
||||
}
|
||||
|
||||
get(): Promise<T> {
|
||||
return this.#get();
|
||||
async get(context: ExecutionContext): Promise<T> {
|
||||
return this.#get(context);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,22 +15,23 @@
|
|||
*/
|
||||
|
||||
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 {
|
||||
addEventListener,
|
||||
PuppeteerEventListener,
|
||||
removeEventListeners,
|
||||
} 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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {HTTPRequest} from './HTTPRequest.js';
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,16 +15,18 @@
|
|||
*/
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
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 {Frame} from './Frame.js';
|
||||
import {HTTPRequest} from './HTTPRequest.js';
|
||||
import {HTTPResponse} from './HTTPResponse.js';
|
||||
import {FetchRequestId, NetworkEventManager} from './NetworkEventManager.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
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
import NodeWebSocket from 'ws';
|
||||
|
||||
import {ConnectionTransport} from '../common/ConnectionTransport.js';
|
||||
import {packageVersion} from '../generated/version.js';
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
@ -14,10 +14,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
import type {Readable} from 'stream';
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import type {Browser} from '../api/Browser.js';
|
||||
import type {BrowserContext} from '../api/BrowserContext.js';
|
||||
import {ElementHandle} from '../api/ElementHandle.js';
|
||||
import {JSHandle} from '../api/JSHandle.js';
|
||||
import {
|
||||
GeolocationOptions,
|
||||
MediaFeature,
|
||||
|
|
@ -35,7 +39,9 @@ import {
|
|||
DeferredPromise,
|
||||
} from '../util/DeferredPromise.js';
|
||||
import {isErrorLike} from '../util/ErrorLike.js';
|
||||
|
||||
import {Accessibility} from './Accessibility.js';
|
||||
import {Binding} from './Binding.js';
|
||||
import {
|
||||
CDPSession,
|
||||
CDPSessionEmittedEvents,
|
||||
|
|
@ -44,7 +50,6 @@ import {
|
|||
import {ConsoleMessage, ConsoleMessageType} from './ConsoleMessage.js';
|
||||
import {Coverage} from './Coverage.js';
|
||||
import {Dialog} from './Dialog.js';
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
import {EmulationManager} from './EmulationManager.js';
|
||||
import {FileChooser} from './FileChooser.js';
|
||||
import {
|
||||
|
|
@ -59,7 +64,6 @@ import {HTTPResponse} from './HTTPResponse.js';
|
|||
import {Keyboard, Mouse, MouseButton, Touchscreen} from './Input.js';
|
||||
import {WaitForSelectorOptions} from './IsolatedWorld.js';
|
||||
import {MAIN_WORLD} from './IsolatedWorlds.js';
|
||||
import {JSHandle} from './JSHandle.js';
|
||||
import {
|
||||
Credentials,
|
||||
NetworkConditions,
|
||||
|
|
@ -72,7 +76,13 @@ import {TargetManagerEmittedEvents} from './TargetManager.js';
|
|||
import {TaskQueue} from './TaskQueue.js';
|
||||
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||
import {Tracing} from './Tracing.js';
|
||||
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
|
||||
import {
|
||||
BindingPayload,
|
||||
EvaluateFunc,
|
||||
EvaluateFuncWith,
|
||||
HandleFor,
|
||||
NodeFor,
|
||||
} from './types.js';
|
||||
import {
|
||||
createJSHandle,
|
||||
debugError,
|
||||
|
|
@ -83,9 +93,6 @@ import {
|
|||
importFS,
|
||||
isNumber,
|
||||
isString,
|
||||
pageBindingDeliverErrorString,
|
||||
pageBindingDeliverErrorValueString,
|
||||
pageBindingDeliverResultString,
|
||||
pageBindingInitString,
|
||||
releaseObject,
|
||||
valueFromRemoteObject,
|
||||
|
|
@ -140,7 +147,7 @@ export class CDPPage extends Page {
|
|||
#frameManager: FrameManager;
|
||||
#emulationManager: EmulationManager;
|
||||
#tracing: Tracing;
|
||||
#pageBindings = new Map<string, Function>();
|
||||
#bindings = new Map<string, Binding>();
|
||||
#coverage: Coverage;
|
||||
#javascriptEnabled = true;
|
||||
#viewport: Viewport | null;
|
||||
|
|
@ -521,13 +528,12 @@ export class CDPPage extends Page {
|
|||
): Promise<JSHandle<Prototype[]>> {
|
||||
const context = await this.mainFrame().executionContext();
|
||||
assert(!prototypeHandle.disposed, 'Prototype JSHandle is disposed!');
|
||||
const remoteObject = prototypeHandle.remoteObject();
|
||||
assert(
|
||||
remoteObject.objectId,
|
||||
prototypeHandle.id,
|
||||
'Prototype JSHandle must not be referencing primitive value'
|
||||
);
|
||||
const response = await context._client.send('Runtime.queryObjects', {
|
||||
prototypeObjectId: remoteObject.objectId,
|
||||
prototypeObjectId: prototypeHandle.id,
|
||||
});
|
||||
return createJSHandle(context, response.objects) as HandleFor<Prototype[]>;
|
||||
}
|
||||
|
|
@ -535,9 +541,10 @@ export class CDPPage extends Page {
|
|||
override async $eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[ElementHandle<NodeFor<Selector>>, ...Params]
|
||||
> = EvaluateFunc<[ElementHandle<NodeFor<Selector>>, ...Params]>
|
||||
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
|
||||
NodeFor<Selector>,
|
||||
Params
|
||||
>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
|
|
@ -549,9 +556,10 @@ export class CDPPage extends Page {
|
|||
override async $$eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[Array<NodeFor<Selector>>, ...Params]
|
||||
> = EvaluateFunc<[Array<NodeFor<Selector>>, ...Params]>
|
||||
Func extends EvaluateFuncWith<
|
||||
Array<NodeFor<Selector>>,
|
||||
Params
|
||||
> = EvaluateFuncWith<Array<NodeFor<Selector>>, Params>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
|
|
@ -646,23 +654,29 @@ export class CDPPage extends Page {
|
|||
name: string,
|
||||
pptrFunction: Function | {default: Function}
|
||||
): Promise<void> {
|
||||
if (this.#pageBindings.has(name)) {
|
||||
if (this.#bindings.has(name)) {
|
||||
throw new Error(
|
||||
`Failed to add page binding with name ${name}: window['${name}'] already exists!`
|
||||
);
|
||||
}
|
||||
|
||||
let exposedFunction: Function;
|
||||
let binding: Binding;
|
||||
switch (typeof pptrFunction) {
|
||||
case 'function':
|
||||
exposedFunction = pptrFunction;
|
||||
binding = new Binding(
|
||||
name,
|
||||
pptrFunction as (...args: unknown[]) => unknown
|
||||
);
|
||||
break;
|
||||
default:
|
||||
exposedFunction = pptrFunction.default;
|
||||
binding = new Binding(
|
||||
name,
|
||||
pptrFunction.default as (...args: unknown[]) => unknown
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
this.#pageBindings.set(name, exposedFunction);
|
||||
this.#bindings.set(name, binding);
|
||||
|
||||
const expression = pageBindingInitString('exposedFun', 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
|
||||
return;
|
||||
}
|
||||
const context = this.#frameManager.executionContextById(
|
||||
const context = this.#frameManager.getExecutionContextById(
|
||||
event.executionContextId,
|
||||
this.#client
|
||||
);
|
||||
if (!context) {
|
||||
debugError(
|
||||
new Error(
|
||||
`ExecutionContext not found for a console message: ${JSON.stringify(
|
||||
event
|
||||
)}`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
const values = event.args.map(arg => {
|
||||
return createJSHandle(context, arg);
|
||||
});
|
||||
|
|
@ -760,7 +784,7 @@ export class CDPPage extends Page {
|
|||
async #onBindingCalled(
|
||||
event: Protocol.Runtime.BindingCalledEvent
|
||||
): Promise<void> {
|
||||
let payload: {type: string; name: string; seq: number; args: unknown[]};
|
||||
let payload: BindingPayload;
|
||||
try {
|
||||
payload = JSON.parse(event.payload);
|
||||
} catch {
|
||||
|
|
@ -768,34 +792,21 @@ export class CDPPage extends Page {
|
|||
// called before our wrapper was initialized.
|
||||
return;
|
||||
}
|
||||
const {type, name, seq, args} = payload;
|
||||
if (type !== 'exposedFun' || !this.#pageBindings.has(name)) {
|
||||
const {type, name, seq, args, isTrivial} = payload;
|
||||
if (type !== 'exposedFun') {
|
||||
return;
|
||||
}
|
||||
let expression = null;
|
||||
try {
|
||||
const pageBinding = this.#pageBindings.get(name);
|
||||
assert(pageBinding);
|
||||
const result = await pageBinding(...args);
|
||||
expression = pageBindingDeliverResultString(name, seq, result);
|
||||
} catch (error) {
|
||||
if (isErrorLike(error)) {
|
||||
expression = pageBindingDeliverErrorString(
|
||||
name,
|
||||
seq,
|
||||
error.message,
|
||||
error.stack
|
||||
);
|
||||
} else {
|
||||
expression = pageBindingDeliverErrorValueString(name, seq, error);
|
||||
}
|
||||
|
||||
const context = this.#frameManager.executionContextById(
|
||||
event.executionContextId,
|
||||
this.#client
|
||||
);
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
this.#client
|
||||
.send('Runtime.evaluate', {
|
||||
expression,
|
||||
contextId: event.executionContextId,
|
||||
})
|
||||
.catch(debugError);
|
||||
|
||||
const binding = this.#bindings.get(name);
|
||||
await binding?.run(context, seq, args, isTrivial);
|
||||
}
|
||||
|
||||
#addConsoleMessage(
|
||||
|
|
@ -811,7 +822,7 @@ export class CDPPage extends Page {
|
|||
}
|
||||
const textTokens = [];
|
||||
for (const arg of args) {
|
||||
const remoteObject = arg.remoteObject();
|
||||
const remoteObject = arg.remoteObject() as Protocol.Runtime.RemoteObject;
|
||||
if (remoteObject.objectId) {
|
||||
textTokens.push(arg.toString());
|
||||
} else {
|
||||
|
|
@ -1621,11 +1632,7 @@ export class CDPPage extends Page {
|
|||
|
||||
override waitForXPath(
|
||||
xpath: string,
|
||||
options: {
|
||||
visible?: boolean;
|
||||
hidden?: boolean;
|
||||
timeout?: number;
|
||||
} = {}
|
||||
options: WaitForSelectorOptions = {}
|
||||
): Promise<ElementHandle<Node> | null> {
|
||||
return this.mainFrame().waitForXPath(xpath, options);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
@ -13,19 +13,15 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Browser} from '../api/Browser.js';
|
||||
|
||||
import {
|
||||
BrowserConnectOptions,
|
||||
_connectToCDPBrowser,
|
||||
} from './BrowserConnector.js';
|
||||
import {ConnectionTransport} from './ConnectionTransport.js';
|
||||
import {
|
||||
clearCustomQueryHandlers,
|
||||
CustomQueryHandler,
|
||||
customQueryHandlerNames,
|
||||
registerCustomQueryHandler,
|
||||
unregisterCustomQueryHandler,
|
||||
} from './QueryHandler.js';
|
||||
import {CustomQueryHandler, customQueryHandlers} from './CustomQueryHandler.js';
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
* That class extends `Puppeteer`, so has all the methods documented below as
|
||||
* well as all that are defined on {@link PuppeteerNode}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class Puppeteer {
|
||||
/**
|
||||
* Operations for {@link CustomQueryHandler | custom query handlers}. See
|
||||
* {@link CustomQueryHandlerRegistry}.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static customQueryHandlers = customQueryHandlers;
|
||||
|
||||
/**
|
||||
* Registers a {@link CustomQueryHandler | custom query handler}.
|
||||
*
|
||||
|
|
@ -86,28 +91,28 @@ export class Puppeteer {
|
|||
name: string,
|
||||
queryHandler: CustomQueryHandler
|
||||
): void {
|
||||
return registerCustomQueryHandler(name, queryHandler);
|
||||
return this.customQueryHandlers.register(name, queryHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a custom query handler for a given name.
|
||||
*/
|
||||
static unregisterCustomQueryHandler(name: string): void {
|
||||
return unregisterCustomQueryHandler(name);
|
||||
return this.customQueryHandlers.unregister(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the names of all custom query handlers.
|
||||
*/
|
||||
static customQueryHandlerNames(): string[] {
|
||||
return customQueryHandlerNames();
|
||||
return this.customQueryHandlers.names();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters all custom query handlers.
|
||||
*/
|
||||
static clearCustomQueryHandlers(): void {
|
||||
return clearCustomQueryHandlers();
|
||||
return this.customQueryHandlers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -14,305 +14,202 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import PuppeteerUtil from '../injected/injected.js';
|
||||
import {ariaHandler} from './AriaQueryHandler.js';
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {WaitForSelectorOptions} from './IsolatedWorld.js';
|
||||
import {ElementHandle} from '../api/ElementHandle.js';
|
||||
import type PuppeteerUtil from '../injected/injected.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {isErrorLike} from '../util/ErrorLike.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';
|
||||
|
||||
/**
|
||||
* @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[];
|
||||
}
|
||||
import {LazyArg} from './LazyArg.js';
|
||||
import type {Awaitable, AwaitableIterable} from './types.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface InternalQueryHandler {
|
||||
/**
|
||||
* @returns A {@link Node} matching the given `selector` from {@link node}.
|
||||
*/
|
||||
queryOne?: (
|
||||
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[];
|
||||
}
|
||||
export type QuerySelectorAll = (
|
||||
node: Node,
|
||||
selector: string,
|
||||
PuppeteerUtil: PuppeteerUtil
|
||||
) => AwaitableIterable<Node>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface PuppeteerQueryHandler {
|
||||
/**
|
||||
* Queries for a single node given a selector and {@link ElementHandle}.
|
||||
*
|
||||
* Akin to {@link Window.prototype.querySelector}.
|
||||
*/
|
||||
queryOne?: (
|
||||
element: ElementHandle<Node>,
|
||||
selector: string
|
||||
) => Promise<ElementHandle<Node> | null>;
|
||||
export type QuerySelector = (
|
||||
node: Node,
|
||||
selector: string,
|
||||
PuppeteerUtil: PuppeteerUtil
|
||||
) => Awaitable<Node | null>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
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}.
|
||||
*
|
||||
* Akin to {@link Window.prototype.querySelectorAll}.
|
||||
* Akin to {@link Document.prototype.querySelectorAll}.
|
||||
*/
|
||||
queryAll?: (
|
||||
static async *queryAll(
|
||||
element: ElementHandle<Node>,
|
||||
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
|
||||
* {@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,
|
||||
selector: string,
|
||||
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(
|
||||
handler: InternalQueryHandler
|
||||
): PuppeteerQueryHandler {
|
||||
const internalHandler: PuppeteerQueryHandler = {};
|
||||
const {visible = false, hidden = false, timeout} = options;
|
||||
|
||||
if (handler.queryOne) {
|
||||
const queryOne = handler.queryOne;
|
||||
internalHandler.queryOne = async (element, selector) => {
|
||||
const jsHandle = await element.evaluateHandle(
|
||||
queryOne,
|
||||
try {
|
||||
const handle = await frame.worlds[PUPPETEER_WORLD].waitForFunction(
|
||||
async (PuppeteerUtil, query, selector, root, visible) => {
|
||||
const querySelector = PuppeteerUtil.createFunction(
|
||||
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,
|
||||
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,
|
||||
selector,
|
||||
options
|
||||
visible ? true : hidden ? false : undefined
|
||||
);
|
||||
|
||||
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) {
|
||||
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};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -14,16 +14,18 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Page, PageEmittedEvents} from '../api/Page.js';
|
||||
import {WebWorker} from './WebWorker.js';
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import type {Browser, IsPageTargetCallback} from '../api/Browser.js';
|
||||
import type {BrowserContext} from '../api/BrowserContext.js';
|
||||
import {Viewport} from './PuppeteerViewport.js';
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
import {TaskQueue} from './TaskQueue.js';
|
||||
import {TargetManager} from './TargetManager.js';
|
||||
import {Page, PageEmittedEvents} from '../api/Page.js';
|
||||
|
||||
import {CDPSession} from './Connection.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
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
import {Target} from './Target.js';
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
@ -14,9 +14,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
import {assert} from '../util/assert.js';
|
||||
import {getReadableAsBuffer, getReadableFromProtocolStream} from './util.js';
|
||||
import {isErrorLike} from '../util/ErrorLike.js';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {getReadableAsBuffer, getReadableFromProtocolStream} from './util.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
|
|
|||
|
|
@ -14,19 +14,21 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {ElementHandle} from '../api/ElementHandle.js';
|
||||
import {JSHandle} from '../api/JSHandle.js';
|
||||
import type {Poller} from '../injected/Poller.js';
|
||||
import {createDeferredPromise} from '../util/DeferredPromise.js';
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
import {stringifyFunction} from '../util/Function.js';
|
||||
|
||||
import {TimeoutError} from './Errors.js';
|
||||
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||
import {JSHandle} from './JSHandle.js';
|
||||
import {LazyArg} from './LazyArg.js';
|
||||
import {HandleFor} from './types.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface WaitTaskOptions {
|
||||
bindings?: Map<string, (...args: never[]) => unknown>;
|
||||
polling: 'raf' | 'mutation' | number;
|
||||
root?: ElementHandle<Node>;
|
||||
timeout: number;
|
||||
|
|
@ -37,7 +39,6 @@ export interface WaitTaskOptions {
|
|||
*/
|
||||
export class WaitTask<T = unknown> {
|
||||
#world: IsolatedWorld;
|
||||
#bindings: Map<string, (...args: never[]) => unknown>;
|
||||
#polling: 'raf' | 'mutation' | number;
|
||||
#root?: ElementHandle<Node>;
|
||||
|
||||
|
|
@ -57,7 +58,6 @@ export class WaitTask<T = unknown> {
|
|||
...args: unknown[]
|
||||
) {
|
||||
this.#world = world;
|
||||
this.#bindings = options.bindings ?? new Map();
|
||||
this.#polling = options.polling;
|
||||
this.#root = options.root;
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ export class WaitTask<T = unknown> {
|
|||
this.#fn = `() => {return (${fn});}`;
|
||||
break;
|
||||
default:
|
||||
this.#fn = fn.toString();
|
||||
this.#fn = stringifyFunction(fn);
|
||||
break;
|
||||
}
|
||||
this.#args = args;
|
||||
|
|
@ -81,12 +81,6 @@ export class WaitTask<T = unknown> {
|
|||
}, options.timeout);
|
||||
}
|
||||
|
||||
if (this.#bindings.size !== 0) {
|
||||
for (const [name, fn] of this.#bindings) {
|
||||
this.#world._boundFunctions.set(name, fn);
|
||||
}
|
||||
}
|
||||
|
||||
this.rerun();
|
||||
}
|
||||
|
||||
|
|
@ -96,15 +90,6 @@ export class WaitTask<T = unknown> {
|
|||
|
||||
async rerun(): Promise<void> {
|
||||
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) {
|
||||
case 'raf':
|
||||
this.#poller = await this.#world.evaluateHandle(
|
||||
|
|
@ -114,7 +99,9 @@ export class WaitTask<T = unknown> {
|
|||
return fun(...args) as Promise<T>;
|
||||
});
|
||||
},
|
||||
await this.#world.puppeteerUtil,
|
||||
LazyArg.create(context => {
|
||||
return context.puppeteerUtil;
|
||||
}),
|
||||
this.#fn,
|
||||
...this.#args
|
||||
);
|
||||
|
|
@ -127,7 +114,9 @@ export class WaitTask<T = unknown> {
|
|||
return fun(...args) as Promise<T>;
|
||||
}, root || document);
|
||||
},
|
||||
await this.#world.puppeteerUtil,
|
||||
LazyArg.create(context => {
|
||||
return context.puppeteerUtil;
|
||||
}),
|
||||
this.#root,
|
||||
this.#fn,
|
||||
...this.#args
|
||||
|
|
@ -141,7 +130,9 @@ export class WaitTask<T = unknown> {
|
|||
return fun(...args) as Promise<T>;
|
||||
}, ms);
|
||||
},
|
||||
await this.#world.puppeteerUtil,
|
||||
LazyArg.create(context => {
|
||||
return context.puppeteerUtil;
|
||||
}),
|
||||
this.#polling,
|
||||
this.#fn,
|
||||
...this.#args
|
||||
|
|
|
|||
|
|
@ -14,21 +14,23 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {createDeferredPromise} from '../util/DeferredPromise.js';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {ConsoleMessageType} from './ConsoleMessage.js';
|
||||
import {EvaluateFunc, HandleFor} from './types.js';
|
||||
import {EventEmitter} from './EventEmitter.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 {createDeferredPromise} from '../util/DeferredPromise.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type ConsoleAPICalledCallback = (
|
||||
eventType: ConsoleMessageType,
|
||||
handles: JSHandle[],
|
||||
handles: CDPJSHandle[],
|
||||
trace: Protocol.Runtime.StackTrace
|
||||
) => void;
|
||||
|
||||
|
|
@ -93,7 +95,7 @@ export class WebWorker extends EventEmitter {
|
|||
return consoleAPICalled(
|
||||
event.type,
|
||||
event.args.map((object: Protocol.Runtime.RemoteObject) => {
|
||||
return new JSHandle(context, object);
|
||||
return new CDPJSHandle(context, object);
|
||||
}),
|
||||
event.stackTrace
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
import {CDPSession, Connection as CDPPPtrConnection} from '../Connection.js';
|
||||
import {Connection as BidiPPtrConnection} from './Connection.js';
|
||||
import {Bidi, BidiMapper} from '../../../third_party/chromium-bidi/index.js';
|
||||
import * as BidiMapper from 'chromium-bidi/lib/cjs/bidiMapper/bidiMapper.js';
|
||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.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 {Connection as BidiPPtrConnection} from './Connection.js';
|
||||
|
||||
type CdpEvents = {
|
||||
[Property in keyof ProtocolMapping.Events]: ProtocolMapping.Events[Property][0];
|
||||
};
|
||||
|
|
@ -99,9 +102,9 @@ class CDPClientAdapter<
|
|||
this.#client.on('*', this.#forwardMessage as Handler<any>);
|
||||
}
|
||||
|
||||
#forwardMessage = (
|
||||
method: keyof ProtocolMapping.Events,
|
||||
event: ProtocolMapping.Events[keyof ProtocolMapping.Events]
|
||||
#forwardMessage = <T extends keyof CdpEvents>(
|
||||
method: T,
|
||||
event: CdpEvents[T]
|
||||
) => {
|
||||
this.emit(method, event);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,15 +14,17 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {ChildProcess} from 'child_process';
|
||||
|
||||
import {
|
||||
Browser as BrowserBase,
|
||||
BrowserCloseCallback,
|
||||
BrowserContextOptions,
|
||||
} from '../../api/Browser.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 {Connection} from './Connection.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
|
@ -34,7 +36,7 @@ export class Browser extends BrowserBase {
|
|||
static async create(opts: Options): Promise<Browser> {
|
||||
// TODO: await until the connection is established.
|
||||
try {
|
||||
(await opts.connection.send('session.new', {})) as {sessionId: string};
|
||||
await opts.connection.send('session.new', {});
|
||||
} catch {}
|
||||
return new Browser(opts);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js';
|
||||
import {Page as PageBase} from '../../api/Page.js';
|
||||
|
||||
import {Connection} from './Connection.js';
|
||||
import {Page} from './Page.js';
|
||||
|
||||
|
|
@ -31,10 +32,10 @@ export class BrowserContext extends BrowserContextBase {
|
|||
}
|
||||
|
||||
override async newPage(): Promise<PageBase> {
|
||||
const result = (await this.#connection.send('browsingContext.create', {
|
||||
const response = await this.#connection.send('browsingContext.create', {
|
||||
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> {}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue