forked from mirrors/gecko-dev
Bug 1791532 - [puppeteer] Sync puppeteer v17.1.2 r=webdriver-reviewers,jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D160331
This commit is contained in:
parent
51aa9fcd1f
commit
428f90d6b8
99 changed files with 4253 additions and 2404 deletions
15
.hgignore
15
.hgignore
|
|
@ -108,20 +108,23 @@ _OPT\.OBJ/
|
||||||
^devtools/.*/node_modules/
|
^devtools/.*/node_modules/
|
||||||
|
|
||||||
# Ignore node_module directories and npm artifacts
|
# Ignore node_module directories and npm artifacts
|
||||||
^remote/test/puppeteer/.github
|
^remote/test/puppeteer/.*\.tsbuildinfo
|
||||||
^remote/test/puppeteer/.husky
|
^remote/test/puppeteer/\.github
|
||||||
^remote/test/puppeteer/.local-chromium/
|
^remote/test/puppeteer/\.husky
|
||||||
^remote/test/puppeteer/.local-firefox/
|
^remote/test/puppeteer/\.local-chromium/
|
||||||
|
^remote/test/puppeteer/\.local-firefox/
|
||||||
^remote/test/puppeteer/coverage/
|
^remote/test/puppeteer/coverage/
|
||||||
^remote/test/puppeteer/docker/
|
^remote/test/puppeteer/docker/
|
||||||
|
^remote/test/puppeteer/docs/puppeteer\.api\.json
|
||||||
^remote/test/puppeteer/experimental/
|
^remote/test/puppeteer/experimental/
|
||||||
^remote/test/puppeteer/lib/
|
^remote/test/puppeteer/lib/
|
||||||
^remote/test/puppeteer/node_modules/
|
^remote/test/puppeteer/node_modules/
|
||||||
^remote/test/puppeteer/package-lock.json
|
^remote/test/puppeteer/package-lock\.json
|
||||||
|
^remote/test/puppeteer/puppeteer.*\.tgz
|
||||||
|
^remote/test/puppeteer/src/generated
|
||||||
^remote/test/puppeteer/test/build
|
^remote/test/puppeteer/test/build
|
||||||
^remote/test/puppeteer/test/output-firefox
|
^remote/test/puppeteer/test/output-firefox
|
||||||
^remote/test/puppeteer/test/output-chromium
|
^remote/test/puppeteer/test/output-chromium
|
||||||
^remote/test/puppeteer/utils/testserver/tsconfig.tsbuildinfo
|
|
||||||
^remote/test/puppeteer/website
|
^remote/test/puppeteer/website
|
||||||
|
|
||||||
# git checkout of libstagefright
|
# git checkout of libstagefright
|
||||||
|
|
|
||||||
5
remote/.gitignore
vendored
5
remote/.gitignore
vendored
|
|
@ -1,15 +1,18 @@
|
||||||
|
test/puppeteer/**/*.tsbuildinfo
|
||||||
test/puppeteer/.github
|
test/puppeteer/.github
|
||||||
test/puppeteer/.husky
|
test/puppeteer/.husky
|
||||||
test/puppeteer/.local-chromium/
|
test/puppeteer/.local-chromium/
|
||||||
test/puppeteer/.local-firefox/
|
test/puppeteer/.local-firefox/
|
||||||
test/puppeteer/coverage/
|
test/puppeteer/coverage/
|
||||||
test/puppeteer/docker/
|
test/puppeteer/docker/
|
||||||
|
test/puppeteer/docs/puppeteer.api.json
|
||||||
test/puppeteer/experimental/
|
test/puppeteer/experimental/
|
||||||
test/puppeteer/lib/
|
test/puppeteer/lib/
|
||||||
test/puppeteer/node_modules/
|
test/puppeteer/node_modules/
|
||||||
test/puppeteer/package-lock.json
|
test/puppeteer/package-lock.json
|
||||||
|
test/puppeteer/puppeteer*.tgz
|
||||||
|
test/puppeteer/src/generated
|
||||||
test/puppeteer/test/build
|
test/puppeteer/test/build
|
||||||
test/puppeteer/test/output-firefox
|
test/puppeteer/test/output-firefox
|
||||||
test/puppeteer/test/output-chromium
|
test/puppeteer/test/output-chromium
|
||||||
test/puppeteer/utils/testserver/tsconfig.tsbuildinfo
|
|
||||||
test/puppeteer/website
|
test/puppeteer/website
|
||||||
|
|
|
||||||
|
|
@ -710,6 +710,12 @@ def install_puppeteer(command_context, product, ci):
|
||||||
|
|
||||||
command = "ci" if ci else "install"
|
command = "ci" if ci else "install"
|
||||||
npm(command, cwd=os.path.join(command_context.topsrcdir, puppeteer_dir), env=env)
|
npm(command, cwd=os.path.join(command_context.topsrcdir, puppeteer_dir), env=env)
|
||||||
|
npm(
|
||||||
|
"run",
|
||||||
|
"build:dev",
|
||||||
|
cwd=os.path.join(command_context.topsrcdir, puppeteer_dir),
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def exit(code, error=None):
|
def exit(code, error=None):
|
||||||
|
|
|
||||||
|
|
@ -698,6 +698,9 @@
|
||||||
"ignoreHTTPSErrors Response.securityDetails Network redirects should report SecurityDetails (ignorehttpserrors.spec.js)": [
|
"ignoreHTTPSErrors Response.securityDetails Network redirects should report SecurityDetails (ignorehttpserrors.spec.js)": [
|
||||||
"PASS", "FAIL"
|
"PASS", "FAIL"
|
||||||
],
|
],
|
||||||
|
"InjectedUtil tests should work (injected.spec.js)": [
|
||||||
|
"PASS"
|
||||||
|
],
|
||||||
"input tests input should upload the file (input.spec.js)": [
|
"input tests input should upload the file (input.spec.js)": [
|
||||||
"SKIP"
|
"SKIP"
|
||||||
],
|
],
|
||||||
|
|
@ -924,7 +927,7 @@
|
||||||
"PASS"
|
"PASS"
|
||||||
],
|
],
|
||||||
"Launcher specs Puppeteer Puppeteer.launch userDataDir option should restore cookies (launcher.spec.js)": [
|
"Launcher specs Puppeteer Puppeteer.launch userDataDir option should restore cookies (launcher.spec.js)": [
|
||||||
"SKIP"
|
"PASS"
|
||||||
],
|
],
|
||||||
"Launcher specs Puppeteer Puppeteer.launch should return the default arguments (launcher.spec.js)": [
|
"Launcher specs Puppeteer Puppeteer.launch should return the default arguments (launcher.spec.js)": [
|
||||||
"PASS"
|
"PASS"
|
||||||
|
|
@ -1097,9 +1100,6 @@
|
||||||
"navigation Page.goto should fail when navigating to bad SSL after redirects (navigation.spec.js)": [
|
"navigation Page.goto should fail when navigating to bad SSL after redirects (navigation.spec.js)": [
|
||||||
"PASS"
|
"PASS"
|
||||||
],
|
],
|
||||||
"navigation Page.goto should throw if networkidle is passed as an option (navigation.spec.js)": [
|
|
||||||
"PASS"
|
|
||||||
],
|
|
||||||
"navigation Page.goto should fail when main resources failed to load (navigation.spec.js)": [
|
"navigation Page.goto should fail when main resources failed to load (navigation.spec.js)": [
|
||||||
"PASS"
|
"PASS"
|
||||||
],
|
],
|
||||||
|
|
@ -2176,6 +2176,9 @@
|
||||||
"Screenshots Page.screenshot should clip rect (screenshot.spec.js)": [
|
"Screenshots Page.screenshot should clip rect (screenshot.spec.js)": [
|
||||||
"FAIL"
|
"FAIL"
|
||||||
],
|
],
|
||||||
|
"Screenshots Page.screenshot should use scale for clip (screenshot.spec.js)": [
|
||||||
|
"FAIL"
|
||||||
|
],
|
||||||
"Screenshots Page.screenshot should get screenshot bigger than the viewport (screenshot.spec.js)": [
|
"Screenshots Page.screenshot should get screenshot bigger than the viewport (screenshot.spec.js)": [
|
||||||
"PASS"
|
"PASS"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ build/
|
||||||
lib/
|
lib/
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
tsconfig.tsbuildinfo
|
**/*.tsbuildinfo
|
||||||
puppeteer.api.json
|
puppeteer.api.json
|
||||||
puppeteer*.tgz
|
puppeteer*.tgz
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|
@ -18,9 +18,11 @@ yarn.lock
|
||||||
test/output-*/
|
test/output-*/
|
||||||
.dev_profile*
|
.dev_profile*
|
||||||
coverage/
|
coverage/
|
||||||
|
src/generated
|
||||||
|
|
||||||
# IDE Artifacts
|
# IDE Artifacts
|
||||||
.vscode
|
.vscode
|
||||||
|
.devcontainer
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
@ -32,6 +34,7 @@ coverage/
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
## [END] Keep in sync with .gitignore
|
## [END] Keep in sync with .gitignore
|
||||||
|
|
||||||
# ESLint ignores.
|
# ESLint ignores.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ build/
|
||||||
lib/
|
lib/
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
tsconfig.tsbuildinfo
|
**/*.tsbuildinfo
|
||||||
puppeteer.api.json
|
puppeteer.api.json
|
||||||
puppeteer*.tgz
|
puppeteer*.tgz
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|
@ -18,9 +18,11 @@ yarn.lock
|
||||||
test/output-*/
|
test/output-*/
|
||||||
.dev_profile*
|
.dev_profile*
|
||||||
coverage/
|
coverage/
|
||||||
|
src/generated
|
||||||
|
|
||||||
# IDE Artifacts
|
# IDE Artifacts
|
||||||
.vscode
|
.vscode
|
||||||
|
.devcontainer
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
".": "16.1.1"
|
".": "17.1.2"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,65 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
## [17.1.2](https://github.com/puppeteer/puppeteer/compare/v17.1.1...v17.1.2) (2022-09-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add missing code coverage ranges that span only a single character ([#8911](https://github.com/puppeteer/puppeteer/issues/8911)) ([0c577b9](https://github.com/puppeteer/puppeteer/commit/0c577b9bf8855dc0ccb6098cd43a25c528f6d7f5))
|
||||||
|
* add Page.getDefaultTimeout getter ([#8903](https://github.com/puppeteer/puppeteer/issues/8903)) ([3240095](https://github.com/puppeteer/puppeteer/commit/32400954c50cbddc48468ad118c3f8a47653b9d3)), closes [#8901](https://github.com/puppeteer/puppeteer/issues/8901)
|
||||||
|
* don't detect project root for puppeteer-core ([#8907](https://github.com/puppeteer/puppeteer/issues/8907)) ([b4f5ea1](https://github.com/puppeteer/puppeteer/commit/b4f5ea1167a60c870194c70d22f5372ada5b7c4c)), closes [#8896](https://github.com/puppeteer/puppeteer/issues/8896)
|
||||||
|
* support scale for screenshot clips ([#8908](https://github.com/puppeteer/puppeteer/issues/8908)) ([260e428](https://github.com/puppeteer/puppeteer/commit/260e4282275ab1d05c86e5643e2a02c01f269a9c)), closes [#5329](https://github.com/puppeteer/puppeteer/issues/5329)
|
||||||
|
* work around a race in waitForFileChooser ([#8905](https://github.com/puppeteer/puppeteer/issues/8905)) ([053d960](https://github.com/puppeteer/puppeteer/commit/053d960fb593e514e7914d7da9af436afc39a12f)), closes [#6040](https://github.com/puppeteer/puppeteer/issues/6040)
|
||||||
|
|
||||||
|
## [17.1.1](https://github.com/puppeteer/puppeteer/compare/v17.1.0...v17.1.1) (2022-09-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* restore deferred promise debugging ([#8895](https://github.com/puppeteer/puppeteer/issues/8895)) ([7b42250](https://github.com/puppeteer/puppeteer/commit/7b42250c7bb91ac873307acda493726ffc4c54a8))
|
||||||
|
|
||||||
|
## [17.1.0](https://github.com/puppeteer/puppeteer/compare/v17.0.0...v17.1.0) (2022-09-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **chromium:** roll to Chromium 106.0.5249.0 (r1036745) ([#8869](https://github.com/puppeteer/puppeteer/issues/8869)) ([6e9a47a](https://github.com/puppeteer/puppeteer/commit/6e9a47a6faa06d241dec0bcf7bcdf49370517008))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* allow getting a frame from an elementhandle ([#8875](https://github.com/puppeteer/puppeteer/issues/8875)) ([3732757](https://github.com/puppeteer/puppeteer/commit/3732757450b4363041ccbacc3b236289a156abb0))
|
||||||
|
* typos in documentation ([#8858](https://github.com/puppeteer/puppeteer/issues/8858)) ([8d95a9b](https://github.com/puppeteer/puppeteer/commit/8d95a9bc920b98820aa655ad4eb2d8fd9b2b893a))
|
||||||
|
* use the timeout setting in waitForFileChooser ([#8856](https://github.com/puppeteer/puppeteer/issues/8856)) ([f477b46](https://github.com/puppeteer/puppeteer/commit/f477b46f212da9206102da695697760eea539f05))
|
||||||
|
|
||||||
|
## [17.0.0](https://github.com/puppeteer/puppeteer/compare/v16.2.0...v17.0.0) (2022-08-26)
|
||||||
|
|
||||||
|
|
||||||
|
### ⚠ BREAKING CHANGES
|
||||||
|
|
||||||
|
* remove `root` from `WaitForSelectorOptions` (#8848)
|
||||||
|
* internalize execution context (#8844)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* allow multiple navigations to happen in LifecycleWatcher ([#8826](https://github.com/puppeteer/puppeteer/issues/8826)) ([341b669](https://github.com/puppeteer/puppeteer/commit/341b669a5e45ecbb9ffb0f28c45b520660f27ad2)), closes [#8811](https://github.com/puppeteer/puppeteer/issues/8811)
|
||||||
|
* internalize execution context ([#8844](https://github.com/puppeteer/puppeteer/issues/8844)) ([2f33237](https://github.com/puppeteer/puppeteer/commit/2f33237d0443de77d58dca4454b0c9a1d2b57d03))
|
||||||
|
* remove `root` from `WaitForSelectorOptions` ([#8848](https://github.com/puppeteer/puppeteer/issues/8848)) ([1155c8e](https://github.com/puppeteer/puppeteer/commit/1155c8eac85b176c3334cc3d98adfe7d943dfbe6))
|
||||||
|
* remove deferred promise timeouts ([#8835](https://github.com/puppeteer/puppeteer/issues/8835)) ([202ffce](https://github.com/puppeteer/puppeteer/commit/202ffce0aa4f34dba35fbb8e7d740af16efee35f)), closes [#8832](https://github.com/puppeteer/puppeteer/issues/8832)
|
||||||
|
|
||||||
|
## [16.2.0](https://github.com/puppeteer/puppeteer/compare/v16.1.1...v16.2.0) (2022-08-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add Khmer (Cambodian) language support ([#8809](https://github.com/puppeteer/puppeteer/issues/8809)) ([34f8737](https://github.com/puppeteer/puppeteer/commit/34f873721804d57a5faf3eab8ef50340c69ed180))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* handle service workers in extensions ([#8807](https://github.com/puppeteer/puppeteer/issues/8807)) ([2a0eefb](https://github.com/puppeteer/puppeteer/commit/2a0eefb99f0ae00dacc9e768a253308c0d18a4c3)), closes [#8800](https://github.com/puppeteer/puppeteer/issues/8800)
|
||||||
|
|
||||||
## [16.1.1](https://github.com/puppeteer/puppeteer/compare/v16.1.0...v16.1.1) (2022-08-16)
|
## [16.1.1](https://github.com/puppeteer/puppeteer/compare/v16.1.0...v16.1.1) (2022-08-16)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,6 @@ origin:
|
||||||
description: Headless Chrome Node API
|
description: Headless Chrome Node API
|
||||||
license: Apache-2.0
|
license: Apache-2.0
|
||||||
name: puppeteer
|
name: puppeteer
|
||||||
release: a4938d7edc53fbb1d217914981155ce3bbcc149f
|
release: 0d2d99efeca73fba255fb10b28b5d3f50c2e20e4
|
||||||
url: /Users/alexandraborovova/Projects/puppeteer
|
url: /Users/alexandraborovova/Projects/puppeteer
|
||||||
schema: 1
|
schema: 1
|
||||||
|
|
|
||||||
1576
remote/test/puppeteer/package-lock.json
generated
1576
remote/test/puppeteer/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "puppeteer",
|
"name": "puppeteer",
|
||||||
"version": "16.1.1",
|
"version": "17.1.2",
|
||||||
"description": "A high-level API to control headless Chrome over the DevTools Protocol",
|
"description": "A high-level API to control headless Chrome over the DevTools Protocol",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"puppeteer",
|
"puppeteer",
|
||||||
|
|
@ -30,35 +30,35 @@
|
||||||
"test": "c8 --check-coverage --lines 93 run-s test:chrome:* test:firefox",
|
"test": "c8 --check-coverage --lines 93 run-s test:chrome:* test:firefox",
|
||||||
"test:types": "tsd",
|
"test:types": "tsd",
|
||||||
"test:install": "scripts/test-install.sh",
|
"test:install": "scripts/test-install.sh",
|
||||||
"test:firefox": "cross-env PUPPETEER_PRODUCT=firefox mocha",
|
"test:firefox": "cross-env PUPPETEER_PRODUCT=firefox MOZ_WEBRENDER=0 PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT=20000 mocha",
|
||||||
"test:chrome": "run-s test:chrome:*",
|
"test:chrome": "run-s test:chrome:*",
|
||||||
"test:chrome:headless": "cross-env HEADLESS=true mocha",
|
"test:chrome:headless": "cross-env HEADLESS=true PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT=20000 mocha",
|
||||||
"test:chrome:headless-chrome": "cross-env HEADLESS=chrome mocha",
|
"test:chrome:headless-chrome": "cross-env HEADLESS=chrome PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT=20000 mocha",
|
||||||
"test:chrome:headful": "cross-env HEADLESS=false mocha",
|
"test:chrome:headful": "cross-env HEADLESS=false PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT=20000 mocha",
|
||||||
"prepublishOnly": "npm run build",
|
"prepublishOnly": "npm run build",
|
||||||
"prepare": "node typescript-if-required.js && husky install",
|
"prepare": "node typescript-if-required.js && husky install",
|
||||||
"lint": "run-s lint:prettier lint:eslint",
|
"lint": "run-s lint:prettier lint:eslint",
|
||||||
"lint:prettier": "prettier --check .",
|
"lint:prettier": "prettier --check .",
|
||||||
"lint:eslint": "([ \"$CI\" = true ] && eslint --ext js --ext ts --quiet -f codeframe . || eslint --ext js --ext ts .)",
|
"lint:eslint": "([ \"$CI\" = true ] && eslint --ext js --ext ts --quiet -f codeframe . || eslint --ext js --ext ts .)",
|
||||||
"install": "node install.js",
|
"install": "node install.js",
|
||||||
"generate:types": "node utils/export_all.js && api-extractor run --local --verbose && eslint --ext ts --no-ignore --no-eslintrc -c .eslintrc.types.cjs --fix lib/types.d.ts",
|
"generate:sources": "tsx utils/generate_sources.ts",
|
||||||
"generate:markdown": "ts-node -O '{\"module\":\"commonjs\"}' utils/generate_docs.ts && prettier --ignore-path none --write docs",
|
"generate:artifacts": "tsx utils/generate_artifacts.ts",
|
||||||
"generate:esm-package-json": "echo '{\"type\": \"module\"}' > lib/esm/package.json",
|
"generate:markdown": "tsx utils/generate_docs.ts",
|
||||||
"format": "run-s format:*",
|
"format": "run-s format:*",
|
||||||
"format:prettier": "prettier --write .",
|
"format:prettier": "prettier --write .",
|
||||||
"format:eslint": "eslint --ext js --ext ts --fix .",
|
"format:eslint": "eslint --ext js --ext ts --fix .",
|
||||||
"docs": "run-s build generate:markdown",
|
"docs": "run-s build generate:markdown",
|
||||||
"debug": "npm run build && mocha --inspect-brk",
|
"debug": "npm run build:dev && mocha --inspect-brk",
|
||||||
"commitlint": "commitlint --from=HEAD~1",
|
"commitlint": "commitlint --from=HEAD~1",
|
||||||
"clean": "rimraf lib && rimraf test/build",
|
"clean": "rimraf lib && rimraf test/build",
|
||||||
"check": "run-p check:*",
|
"check": "run-p check:*",
|
||||||
"check:protocol-revision": "ts-node -s scripts/ensure-correct-devtools-protocol-package",
|
"check:protocol-revision": "tsx scripts/ensure-correct-devtools-protocol-package",
|
||||||
"check:pinned-deps": "ts-node -s scripts/ensure-pinned-deps",
|
"check:pinned-deps": "tsx scripts/ensure-pinned-deps",
|
||||||
"build": "run-s build:tsc generate:types generate:esm-package-json",
|
"build": "npm run build:prod",
|
||||||
"build:tsc": "tsc --version && run-p build:tsc:*",
|
"build:dev": "run-s generate:sources build:tsc:dev generate:artifacts",
|
||||||
"build:tsc:esm": "tsc -b src/tsconfig.esm.json",
|
"build:prod": "run-s generate:sources build:tsc:prod generate:artifacts",
|
||||||
"build:tsc:cjs": "tsc -b src/tsconfig.cjs.json",
|
"build:tsc:dev": "tsc -b test",
|
||||||
"build:tsc:test": "tsc -b test"
|
"build:tsc:prod": "tsc -b tsconfig.lib.json"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib",
|
"lib",
|
||||||
|
|
@ -71,10 +71,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-fetch": "3.1.5",
|
"cross-fetch": "3.1.5",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
"devtools-protocol": "0.0.1019158",
|
"devtools-protocol": "0.0.1036444",
|
||||||
"extract-zip": "2.0.1",
|
"extract-zip": "2.0.1",
|
||||||
"https-proxy-agent": "5.0.1",
|
"https-proxy-agent": "5.0.1",
|
||||||
"pkg-dir": "4.2.0",
|
|
||||||
"progress": "2.0.3",
|
"progress": "2.0.3",
|
||||||
"proxy-from-env": "1.1.0",
|
"proxy-from-env": "1.1.0",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
|
|
@ -90,6 +89,7 @@
|
||||||
"@microsoft/api-extractor-model": "7.23.0",
|
"@microsoft/api-extractor-model": "7.23.0",
|
||||||
"@types/debug": "4.1.7",
|
"@types/debug": "4.1.7",
|
||||||
"@types/diff": "5.0.2",
|
"@types/diff": "5.0.2",
|
||||||
|
"@types/glob": "7.2.0",
|
||||||
"@types/mime": "3.0.1",
|
"@types/mime": "3.0.1",
|
||||||
"@types/mocha": "9.1.1",
|
"@types/mocha": "9.1.1",
|
||||||
"@types/node": "18.7.1",
|
"@types/node": "18.7.1",
|
||||||
|
|
@ -109,6 +109,7 @@
|
||||||
"commonmark": "0.30.0",
|
"commonmark": "0.30.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"diff": "5.1.0",
|
"diff": "5.1.0",
|
||||||
|
"esbuild": "0.15.5",
|
||||||
"eslint": "8.21.0",
|
"eslint": "8.21.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
"eslint-formatter-codeframe": "7.32.1",
|
"eslint-formatter-codeframe": "7.32.1",
|
||||||
|
|
@ -120,6 +121,7 @@
|
||||||
"eslint-plugin-unused-imports": "2.0.0",
|
"eslint-plugin-unused-imports": "2.0.0",
|
||||||
"esprima": "4.0.1",
|
"esprima": "4.0.1",
|
||||||
"expect": "25.2.7",
|
"expect": "25.2.7",
|
||||||
|
"glob": "8.0.3",
|
||||||
"gts": "4.0.0",
|
"gts": "4.0.0",
|
||||||
"husky": "8.0.1",
|
"husky": "8.0.1",
|
||||||
"jpeg-js": "0.4.4",
|
"jpeg-js": "0.4.4",
|
||||||
|
|
@ -136,6 +138,7 @@
|
||||||
"source-map-support": "0.5.21",
|
"source-map-support": "0.5.21",
|
||||||
"text-diff": "1.0.1",
|
"text-diff": "1.0.1",
|
||||||
"tsd": "0.22.0",
|
"tsd": "0.22.0",
|
||||||
|
"tsx": "3.8.2",
|
||||||
"typescript": "4.7.4"
|
"typescript": "4.7.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {CDPSession} from './Connection.js';
|
import {CDPSession} from './Connection.js';
|
||||||
import {
|
|
||||||
IsolatedWorld,
|
|
||||||
PageBinding,
|
|
||||||
WaitForSelectorOptions,
|
|
||||||
} from './IsolatedWorld.js';
|
|
||||||
import {ElementHandle} from './ElementHandle.js';
|
import {ElementHandle} from './ElementHandle.js';
|
||||||
import {JSHandle} from './JSHandle.js';
|
import {Frame} from './Frame.js';
|
||||||
|
import {MAIN_WORLD, PageBinding, PUPPETEER_WORLD} from './IsolatedWorld.js';
|
||||||
import {InternalQueryHandler} from './QueryHandler.js';
|
import {InternalQueryHandler} from './QueryHandler.js';
|
||||||
|
|
||||||
async function queryAXTree(
|
async function queryAXTree(
|
||||||
|
|
@ -90,52 +86,86 @@ function parseAriaSelector(selector: string): ARIAQueryOption {
|
||||||
return queryOptions;
|
return queryOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryOne = async (
|
const queryOneId = async (element: ElementHandle<Node>, selector: string) => {
|
||||||
element: ElementHandle<Node>,
|
|
||||||
selector: string
|
|
||||||
): Promise<ElementHandle<Node> | null> => {
|
|
||||||
const exeCtx = element.executionContext();
|
|
||||||
const {name, role} = parseAriaSelector(selector);
|
const {name, role} = parseAriaSelector(selector);
|
||||||
const res = await queryAXTree(exeCtx._client, element, name, role);
|
const res = await queryAXTree(element.client, element, name, role);
|
||||||
if (!res[0] || !res[0].backendDOMNodeId) {
|
if (!res[0] || !res[0].backendDOMNodeId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (await exeCtx._world!.adoptBackendNode(
|
return res[0].backendDOMNodeId;
|
||||||
res[0].backendDOMNodeId
|
};
|
||||||
|
|
||||||
|
const queryOne: InternalQueryHandler['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>;
|
)) as ElementHandle<Node>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const waitFor = async (
|
const waitFor: InternalQueryHandler['waitFor'] = async (
|
||||||
isolatedWorld: IsolatedWorld,
|
elementOrFrame,
|
||||||
selector: string,
|
selector,
|
||||||
options: WaitForSelectorOptions
|
options
|
||||||
): Promise<ElementHandle<Element> | null> => {
|
) => {
|
||||||
|
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 binding: PageBinding = {
|
const binding: PageBinding = {
|
||||||
name: 'ariaQuerySelector',
|
name: 'ariaQuerySelector',
|
||||||
pptrFunction: async (selector: string) => {
|
pptrFunction: async (selector: string) => {
|
||||||
const root = options.root || (await isolatedWorld.document());
|
const id = await queryOneId(
|
||||||
const element = await queryOne(root, selector);
|
element || (await frame.worlds[PUPPETEER_WORLD].document()),
|
||||||
return element;
|
selector
|
||||||
|
);
|
||||||
|
if (!id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (await frame.worlds[PUPPETEER_WORLD].adoptBackendNode(
|
||||||
|
id
|
||||||
|
)) as ElementHandle<Node>;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return (await isolatedWorld._waitForSelectorInPage(
|
const result = await frame.worlds[PUPPETEER_WORLD]._waitForSelectorInPage(
|
||||||
(_: Element, selector: string) => {
|
(_: Element, selector: string) => {
|
||||||
return (
|
return (
|
||||||
globalThis as unknown as {
|
globalThis as unknown as {
|
||||||
ariaQuerySelector(selector: string): void;
|
ariaQuerySelector(selector: string): Node | null;
|
||||||
}
|
}
|
||||||
).ariaQuerySelector(selector);
|
).ariaQuerySelector(selector);
|
||||||
},
|
},
|
||||||
|
element,
|
||||||
selector,
|
selector,
|
||||||
options,
|
options,
|
||||||
binding
|
binding
|
||||||
)) as ElementHandle<Element> | null;
|
);
|
||||||
|
if (element) {
|
||||||
|
await element.dispose();
|
||||||
|
}
|
||||||
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!(result instanceof ElementHandle)) {
|
||||||
|
await result.dispose();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return result.frame.worlds[MAIN_WORLD].transferHandle(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryAll = async (
|
const queryAll: InternalQueryHandler['queryAll'] = async (
|
||||||
element: ElementHandle<Node>,
|
element,
|
||||||
selector: string
|
selector
|
||||||
): Promise<Array<ElementHandle<Node>>> => {
|
) => {
|
||||||
const exeCtx = element.executionContext();
|
const exeCtx = element.executionContext();
|
||||||
const {name, role} = parseAriaSelector(selector);
|
const {name, role} = parseAriaSelector(selector);
|
||||||
const res = await queryAXTree(exeCtx._client, element, name, role);
|
const res = await queryAXTree(exeCtx._client, element, name, role);
|
||||||
|
|
@ -149,18 +179,6 @@ const queryAll = async (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryAllArray = async (
|
|
||||||
element: ElementHandle<Node>,
|
|
||||||
selector: string
|
|
||||||
): Promise<JSHandle<Node[]>> => {
|
|
||||||
const elementHandles = await queryAll(element, selector);
|
|
||||||
const exeCtx = element.executionContext();
|
|
||||||
const jsHandle = exeCtx.evaluateHandle((...elements) => {
|
|
||||||
return elements;
|
|
||||||
}, ...elementHandles);
|
|
||||||
return jsHandle;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
|
@ -168,5 +186,4 @@ export const ariaHandler: InternalQueryHandler = {
|
||||||
queryOne,
|
queryOne,
|
||||||
waitFor,
|
waitFor,
|
||||||
queryAll,
|
queryAll,
|
||||||
queryAllArray,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import {ChildProcess} from 'child_process';
|
import {ChildProcess} from 'child_process';
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js';
|
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js';
|
||||||
import {EventEmitter} from './EventEmitter.js';
|
import {EventEmitter} from './EventEmitter.js';
|
||||||
import {waitWithTimeout} from './util.js';
|
import {waitWithTimeout} from './util.js';
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {debugError, isErrorLike} from './util.js';
|
import {debugError} from './util.js';
|
||||||
|
import {isErrorLike} from '../util/ErrorLike.js';
|
||||||
import {isNode} from '../environment.js';
|
import {isNode} from '../environment.js';
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {
|
import {
|
||||||
Browser,
|
Browser,
|
||||||
IsPageTargetCallback,
|
IsPageTargetCallback,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Protocol from 'devtools-protocol';
|
import Protocol from 'devtools-protocol';
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {CDPSession, Connection} from './Connection.js';
|
import {CDPSession, Connection} from './Connection.js';
|
||||||
import {EventEmitter} from './EventEmitter.js';
|
import {EventEmitter} from './EventEmitter.js';
|
||||||
import {Target} from './Target.js';
|
import {Target} from './Target.js';
|
||||||
|
|
@ -317,11 +317,12 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
|
||||||
) {
|
) {
|
||||||
this.#finishInitializationIfReady(targetInfo.targetId);
|
this.#finishInitializationIfReady(targetInfo.targetId);
|
||||||
await silentDetach();
|
await silentDetach();
|
||||||
if (parentSession instanceof CDPSession) {
|
if (this.#attachedTargetsByTargetId.has(targetInfo.targetId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const target = this.#targetFactory(targetInfo);
|
const target = this.#targetFactory(targetInfo);
|
||||||
this.#attachedTargetsByTargetId.set(targetInfo.targetId, target);
|
this.#attachedTargetsByTargetId.set(targetInfo.targetId, target);
|
||||||
this.emit(TargetManagerEmittedEvents.TargetAvailable, target);
|
this.emit(TargetManagerEmittedEvents.TargetAvailable, target);
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {debug} from './Debug.js';
|
import {debug} from './Debug.js';
|
||||||
const debugProtocolSend = debug('puppeteer:protocol:SEND ►');
|
const debugProtocolSend = debug('puppeteer:protocol:SEND ►');
|
||||||
const debugProtocolReceive = debug('puppeteer:protocol:RECV ◀');
|
const debugProtocolReceive = debug('puppeteer:protocol:RECV ◀');
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {addEventListener, debugError, PuppeteerEventListener} from './util.js';
|
import {addEventListener, debugError, PuppeteerEventListener} from './util.js';
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
import {CDPSession} from './Connection.js';
|
import {CDPSession} from './Connection.js';
|
||||||
|
|
@ -484,6 +484,6 @@ function convertToDisjointRanges(
|
||||||
}
|
}
|
||||||
// Filter out empty ranges.
|
// Filter out empty ranges.
|
||||||
return results.filter(range => {
|
return results.filter(range => {
|
||||||
return range.end - range.start > 1;
|
return range.end - range.start > 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {CDPSession} from './Connection.js';
|
import {CDPSession} from './Connection.js';
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {CDPSession} from './Connection.js';
|
|
||||||
import {ExecutionContext} from './ExecutionContext.js';
|
import {ExecutionContext} from './ExecutionContext.js';
|
||||||
import {Frame, FrameManager} from './FrameManager.js';
|
import {Frame} from './Frame.js';
|
||||||
import {
|
import {FrameManager} from './FrameManager.js';
|
||||||
MAIN_WORLD,
|
import {WaitForSelectorOptions} from './IsolatedWorld.js';
|
||||||
PUPPETEER_WORLD,
|
|
||||||
WaitForSelectorOptions,
|
|
||||||
} from './IsolatedWorld.js';
|
|
||||||
import {
|
import {
|
||||||
BoundingBox,
|
BoundingBox,
|
||||||
BoxModel,
|
BoxModel,
|
||||||
|
|
@ -19,7 +15,7 @@ import {
|
||||||
} from './JSHandle.js';
|
} from './JSHandle.js';
|
||||||
import {Page, ScreenshotOptions} from './Page.js';
|
import {Page, ScreenshotOptions} from './Page.js';
|
||||||
import {getQueryHandlerAndSelector} from './QueryHandler.js';
|
import {getQueryHandlerAndSelector} from './QueryHandler.js';
|
||||||
import {EvaluateFunc, NodeFor} from './types.js';
|
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
|
||||||
import {KeyInput} from './USKeyboardLayout.js';
|
import {KeyInput} from './USKeyboardLayout.js';
|
||||||
import {debugError, isString} from './util.js';
|
import {debugError, isString} from './util.js';
|
||||||
|
|
||||||
|
|
@ -71,24 +67,29 @@ export class ElementHandle<
|
||||||
ElementType extends Node = Element
|
ElementType extends Node = Element
|
||||||
> extends JSHandle<ElementType> {
|
> extends JSHandle<ElementType> {
|
||||||
#frame: Frame;
|
#frame: Frame;
|
||||||
#page: Page;
|
|
||||||
#frameManager: FrameManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
client: CDPSession,
|
|
||||||
remoteObject: Protocol.Runtime.RemoteObject,
|
remoteObject: Protocol.Runtime.RemoteObject,
|
||||||
frame: Frame,
|
frame: Frame
|
||||||
page: Page,
|
|
||||||
frameManager: FrameManager
|
|
||||||
) {
|
) {
|
||||||
super(context, client, remoteObject);
|
super(context, remoteObject);
|
||||||
this.#frame = frame;
|
this.#frame = frame;
|
||||||
this.#page = page;
|
}
|
||||||
this.#frameManager = frameManager;
|
|
||||||
|
get #frameManager(): FrameManager {
|
||||||
|
return this.#frame._frameManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
get #page(): Page {
|
||||||
|
return this.#frame.page();
|
||||||
|
}
|
||||||
|
|
||||||
|
get frame(): Frame {
|
||||||
|
return this.#frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -228,13 +229,24 @@ export class ElementHandle<
|
||||||
): Promise<Awaited<ReturnType<Func>>> {
|
): Promise<Awaited<ReturnType<Func>>> {
|
||||||
const {updatedSelector, queryHandler} =
|
const {updatedSelector, queryHandler} =
|
||||||
getQueryHandlerAndSelector(selector);
|
getQueryHandlerAndSelector(selector);
|
||||||
assert(queryHandler.queryAllArray);
|
assert(
|
||||||
const arrayHandle = (await queryHandler.queryAllArray(
|
queryHandler.queryAll,
|
||||||
|
'Cannot handle queries for a multiple element with the given selector'
|
||||||
|
);
|
||||||
|
const handles = (await queryHandler.queryAll(
|
||||||
this,
|
this,
|
||||||
updatedSelector
|
updatedSelector
|
||||||
)) as JSHandle<Array<NodeFor<Selector>>>;
|
)) as Array<HandleFor<NodeFor<Selector>>>;
|
||||||
const result = await arrayHandle.evaluate(pageFunction, ...args);
|
const elements = await this.evaluateHandle((_, ...elements) => {
|
||||||
await arrayHandle.dispose();
|
return elements;
|
||||||
|
}, ...handles);
|
||||||
|
const [result] = await Promise.all([
|
||||||
|
elements.evaluate(pageFunction, ...args),
|
||||||
|
...handles.map(handle => {
|
||||||
|
return handle.dispose();
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
await elements.dispose();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,27 +303,16 @@ export class ElementHandle<
|
||||||
*/
|
*/
|
||||||
async waitForSelector<Selector extends string>(
|
async waitForSelector<Selector extends string>(
|
||||||
selector: Selector,
|
selector: Selector,
|
||||||
options: Exclude<WaitForSelectorOptions, 'root'> = {}
|
options: WaitForSelectorOptions = {}
|
||||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||||
const frame = this.executionContext().frame();
|
const {updatedSelector, queryHandler} =
|
||||||
assert(frame);
|
getQueryHandlerAndSelector(selector);
|
||||||
const adoptedRoot = await frame.worlds[PUPPETEER_WORLD].adoptHandle(this);
|
assert(queryHandler.waitFor, 'Query handler does not support waiting');
|
||||||
const handle = await frame.worlds[PUPPETEER_WORLD].waitForSelector(
|
return (await queryHandler.waitFor(
|
||||||
selector,
|
this,
|
||||||
{
|
updatedSelector,
|
||||||
...options,
|
options
|
||||||
root: adoptedRoot,
|
)) as ElementHandle<NodeFor<Selector>> | null;
|
||||||
}
|
|
||||||
);
|
|
||||||
await adoptedRoot.dispose();
|
|
||||||
if (!handle) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const result = (await frame.worlds[MAIN_WORLD].adoptHandle(
|
|
||||||
handle
|
|
||||||
)) as ElementHandle<NodeFor<Selector>>;
|
|
||||||
await handle.dispose();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
import {assert} from './assert.js';
|
|
||||||
import {CDPSession} from './Connection.js';
|
import {CDPSession} from './Connection.js';
|
||||||
import {Frame} from './FrameManager.js';
|
|
||||||
import {IsolatedWorld} from './IsolatedWorld.js';
|
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||||
import {JSHandle} from './JSHandle.js';
|
import {JSHandle} from './JSHandle.js';
|
||||||
import {EvaluateFunc, HandleFor} from './types.js';
|
import {EvaluateFunc, HandleFor} from './types.js';
|
||||||
|
|
@ -35,8 +33,6 @@ export const EVALUATION_SCRIPT_URL = 'pptr://__puppeteer_evaluation_script__';
|
||||||
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
|
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Do not use directly.
|
|
||||||
*
|
|
||||||
* Represents a context for JavaScript execution.
|
* Represents a context for JavaScript execution.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -55,6 +51,8 @@ const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
|
||||||
* @remarks
|
* @remarks
|
||||||
* Besides pages, execution contexts can be found in
|
* Besides pages, execution contexts can be found in
|
||||||
* {@link WebWorker | workers}.
|
* {@link WebWorker | workers}.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class ExecutionContext {
|
export class ExecutionContext {
|
||||||
/**
|
/**
|
||||||
|
|
@ -88,18 +86,6 @@ export class ExecutionContext {
|
||||||
this._contextName = contextPayload.name;
|
this._contextName = contextPayload.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns The frame associated with this execution context.
|
|
||||||
*
|
|
||||||
* @remarks
|
|
||||||
* Not every execution context is associated with a frame. For example,
|
|
||||||
* {@link WebWorker | workers} have execution contexts that are not associated
|
|
||||||
* with frames.
|
|
||||||
*/
|
|
||||||
frame(): Frame | null {
|
|
||||||
return this._world ? this._world.frame() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates the given function.
|
* Evaluates the given function.
|
||||||
*
|
*
|
||||||
|
|
@ -355,8 +341,10 @@ export class ExecutionContext {
|
||||||
}
|
}
|
||||||
return {value: arg};
|
return {value: arg};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function rewriteError(error: Error): Protocol.Runtime.EvaluateResponse {
|
const rewriteError = (error: Error): Protocol.Runtime.EvaluateResponse => {
|
||||||
if (error.message.includes('Object reference chain is too long')) {
|
if (error.message.includes('Object reference chain is too long')) {
|
||||||
return {result: {type: 'undefined'}};
|
return {result: {type: 'undefined'}};
|
||||||
}
|
}
|
||||||
|
|
@ -373,43 +361,4 @@ export class ExecutionContext {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterates through the JavaScript heap and finds all the objects with the
|
|
||||||
* given prototype.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* // Create a Map object
|
|
||||||
* await page.evaluate(() => (window.map = new Map()));
|
|
||||||
* // Get a handle to the Map object prototype
|
|
||||||
* const mapPrototype = await page.evaluateHandle(() => Map.prototype);
|
|
||||||
* // Query all map instances into an array
|
|
||||||
* const mapInstances = await page.queryObjects(mapPrototype);
|
|
||||||
* // Count amount of map objects in heap
|
|
||||||
* const count = await page.evaluate(maps => maps.length, mapInstances);
|
|
||||||
* await mapInstances.dispose();
|
|
||||||
* await mapPrototype.dispose();
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param prototypeHandle - a handle to the object prototype
|
|
||||||
* @returns A handle to an array of objects with the given prototype.
|
|
||||||
*/
|
|
||||||
async queryObjects<Prototype>(
|
|
||||||
prototypeHandle: JSHandle<Prototype>
|
|
||||||
): Promise<HandleFor<Prototype[]>> {
|
|
||||||
assert(!prototypeHandle.disposed, 'Prototype JSHandle is disposed!');
|
|
||||||
const remoteObject = prototypeHandle.remoteObject();
|
|
||||||
assert(
|
|
||||||
remoteObject.objectId,
|
|
||||||
'Prototype JSHandle must not be referencing primitive value'
|
|
||||||
);
|
|
||||||
const response = await this._client.send('Runtime.queryObjects', {
|
|
||||||
prototypeObjectId: remoteObject.objectId,
|
|
||||||
});
|
|
||||||
return createJSHandle(this, response.objects) as HandleFor<Prototype[]>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {ElementHandle} from './ElementHandle.js';
|
import {ElementHandle} from './ElementHandle.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Protocol from 'devtools-protocol';
|
import Protocol from 'devtools-protocol';
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {CDPSession, Connection} from './Connection.js';
|
import {CDPSession, Connection} from './Connection.js';
|
||||||
import {Target} from './Target.js';
|
import {Target} from './Target.js';
|
||||||
import {TargetFilterCallback} from './Browser.js';
|
import {TargetFilterCallback} from './Browser.js';
|
||||||
|
|
|
||||||
1097
remote/test/puppeteer/src/common/Frame.ts
Normal file
1097
remote/test/puppeteer/src/common/Frame.ts
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -15,10 +15,10 @@
|
||||||
*/
|
*/
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
|
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {ProtocolError} from './Errors.js';
|
import {ProtocolError} from './Errors.js';
|
||||||
import {EventEmitter} from './EventEmitter.js';
|
import {EventEmitter} from './EventEmitter.js';
|
||||||
import {Frame} from './FrameManager.js';
|
import {Frame} from './Frame.js';
|
||||||
import {debugError, isString} from './util.js';
|
import {debugError, isString} from './util.js';
|
||||||
import {HTTPResponse} from './HTTPResponse.js';
|
import {HTTPResponse} from './HTTPResponse.js';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
|
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
|
||||||
|
|
||||||
import {EventEmitter} from './EventEmitter.js';
|
import {EventEmitter} from './EventEmitter.js';
|
||||||
import {Frame} from './FrameManager.js';
|
import {Frame} from './Frame.js';
|
||||||
import {HTTPRequest} from './HTTPRequest.js';
|
import {HTTPRequest} from './HTTPRequest.js';
|
||||||
import {SecurityDetails} from './SecurityDetails.js';
|
import {SecurityDetails} from './SecurityDetails.js';
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {CDPSession} from './Connection.js';
|
import {CDPSession} from './Connection.js';
|
||||||
import {_keyDefinitions, KeyDefinition, KeyInput} from './USKeyboardLayout.js';
|
import {_keyDefinitions, KeyDefinition, KeyInput} from './USKeyboardLayout.js';
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
|
|
|
||||||
|
|
@ -15,24 +15,23 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
import {assert} from './assert.js';
|
import {source as injectedSource} from '../generated/injected.js';
|
||||||
|
import {assert} from '../util/assert.js';
|
||||||
|
import {createDeferredPromise} from '../util/DeferredPromise.js';
|
||||||
import {CDPSession} from './Connection.js';
|
import {CDPSession} from './Connection.js';
|
||||||
import {ElementHandle} from './ElementHandle.js';
|
import {ElementHandle} from './ElementHandle.js';
|
||||||
import {TimeoutError} from './Errors.js';
|
import {TimeoutError} from './Errors.js';
|
||||||
import {ExecutionContext} from './ExecutionContext.js';
|
import {ExecutionContext} from './ExecutionContext.js';
|
||||||
import {Frame, FrameManager} from './FrameManager.js';
|
import {Frame} from './Frame.js';
|
||||||
|
import {FrameManager} from './FrameManager.js';
|
||||||
import {MouseButton} from './Input.js';
|
import {MouseButton} from './Input.js';
|
||||||
import {JSHandle} from './JSHandle.js';
|
import {JSHandle} from './JSHandle.js';
|
||||||
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||||
import {getQueryHandlerAndSelector} from './QueryHandler.js';
|
|
||||||
import {TimeoutSettings} from './TimeoutSettings.js';
|
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||||
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
|
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
|
||||||
import {
|
import {
|
||||||
createDeferredPromise,
|
|
||||||
createJSHandle,
|
createJSHandle,
|
||||||
debugError,
|
debugError,
|
||||||
DeferredPromise,
|
|
||||||
importFS,
|
|
||||||
isNumber,
|
isNumber,
|
||||||
isString,
|
isString,
|
||||||
makePredicateString,
|
makePredicateString,
|
||||||
|
|
@ -77,10 +76,6 @@ export interface WaitForSelectorOptions {
|
||||||
* @defaultValue `30000` (30 seconds)
|
* @defaultValue `30000` (30 seconds)
|
||||||
*/
|
*/
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
/**
|
|
||||||
* @deprecated Do not use. Use the {@link ElementHandle.waitForSelector}
|
|
||||||
*/
|
|
||||||
root?: ElementHandle<Node>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -118,12 +113,10 @@ export interface IsolatedWorldChart {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class IsolatedWorld {
|
export class IsolatedWorld {
|
||||||
#frameManager: FrameManager;
|
|
||||||
#client: CDPSession;
|
|
||||||
#frame: Frame;
|
#frame: Frame;
|
||||||
#timeoutSettings: TimeoutSettings;
|
#injected: boolean;
|
||||||
#documentPromise: Promise<ElementHandle<Document>> | null = null;
|
#document?: ElementHandle<Document>;
|
||||||
#contextPromise: DeferredPromise<ExecutionContext> = createDeferredPromise();
|
#context = createDeferredPromise<ExecutionContext>();
|
||||||
#detached = false;
|
#detached = false;
|
||||||
|
|
||||||
// Set of bindings that have been registered in the current context.
|
// Set of bindings that have been registered in the current context.
|
||||||
|
|
@ -145,44 +138,48 @@ export class IsolatedWorld {
|
||||||
return `${name}_${contextId}`;
|
return `${name}_${contextId}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(frame: Frame, injected = false) {
|
||||||
client: CDPSession,
|
|
||||||
frameManager: FrameManager,
|
|
||||||
frame: Frame,
|
|
||||||
timeoutSettings: TimeoutSettings
|
|
||||||
) {
|
|
||||||
// Keep own reference to client because it might differ from the FrameManager's
|
// Keep own reference to client because it might differ from the FrameManager's
|
||||||
// client for OOP iframes.
|
// client for OOP iframes.
|
||||||
this.#client = client;
|
|
||||||
this.#frameManager = frameManager;
|
|
||||||
this.#frame = frame;
|
this.#frame = frame;
|
||||||
this.#timeoutSettings = timeoutSettings;
|
this.#injected = injected;
|
||||||
this.#client.on('Runtime.bindingCalled', this.#onBindingCalled);
|
this.#client.on('Runtime.bindingCalled', this.#onBindingCalled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get #client(): CDPSession {
|
||||||
|
return this.#frame._client();
|
||||||
|
}
|
||||||
|
|
||||||
|
get #frameManager(): FrameManager {
|
||||||
|
return this.#frame._frameManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
get #timeoutSettings(): TimeoutSettings {
|
||||||
|
return this.#frameManager.timeoutSettings;
|
||||||
|
}
|
||||||
|
|
||||||
frame(): Frame {
|
frame(): Frame {
|
||||||
return this.#frame;
|
return this.#frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearContext(): void {
|
clearContext(): void {
|
||||||
this.#documentPromise = null;
|
this.#document = undefined;
|
||||||
this.#contextPromise = createDeferredPromise();
|
this.#context = createDeferredPromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
setContext(context: ExecutionContext): void {
|
setContext(context: ExecutionContext): void {
|
||||||
assert(
|
if (this.#injected) {
|
||||||
this.#contextPromise,
|
context.evaluate(injectedSource).catch(debugError);
|
||||||
`ExecutionContext ${context._contextId} has already been set.`
|
}
|
||||||
);
|
|
||||||
this.#ctxBindings.clear();
|
this.#ctxBindings.clear();
|
||||||
this.#contextPromise.resolve(context);
|
this.#context.resolve(context);
|
||||||
for (const waitTask of this._waitTasks) {
|
for (const waitTask of this._waitTasks) {
|
||||||
waitTask.rerun();
|
waitTask.rerun();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasContext(): boolean {
|
hasContext(): boolean {
|
||||||
return this.#contextPromise.resolved();
|
return this.#context.resolved();
|
||||||
}
|
}
|
||||||
|
|
||||||
_detach(): void {
|
_detach(): void {
|
||||||
|
|
@ -201,10 +198,10 @@ export class IsolatedWorld {
|
||||||
`Execution context is not available in detached frame "${this.#frame.url()}" (are you trying to evaluate?)`
|
`Execution context is not available in detached frame "${this.#frame.url()}" (are you trying to evaluate?)`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.#contextPromise === null) {
|
if (this.#context === null) {
|
||||||
throw new Error(`Execution content promise is missing`);
|
throw new Error(`Execution content promise is missing`);
|
||||||
}
|
}
|
||||||
return this.#contextPromise;
|
return this.#context;
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateHandle<
|
async evaluateHandle<
|
||||||
|
|
@ -244,15 +241,14 @@ export class IsolatedWorld {
|
||||||
}
|
}
|
||||||
|
|
||||||
async document(): Promise<ElementHandle<Document>> {
|
async document(): Promise<ElementHandle<Document>> {
|
||||||
if (this.#documentPromise) {
|
if (this.#document) {
|
||||||
return this.#documentPromise;
|
return this.#document;
|
||||||
}
|
}
|
||||||
this.#documentPromise = this.executionContext().then(async context => {
|
const context = await this.executionContext();
|
||||||
return await context.evaluateHandle(() => {
|
this.#document = await context.evaluateHandle(() => {
|
||||||
return document;
|
return document;
|
||||||
});
|
});
|
||||||
});
|
return this.#document;
|
||||||
return this.#documentPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
|
async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
|
||||||
|
|
@ -290,20 +286,6 @@ export class IsolatedWorld {
|
||||||
return document.$$eval(selector, pageFunction, ...args);
|
return document.$$eval(selector, pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSelector<Selector extends string>(
|
|
||||||
selector: Selector,
|
|
||||||
options: WaitForSelectorOptions
|
|
||||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
|
||||||
const {updatedSelector, queryHandler} =
|
|
||||||
getQueryHandlerAndSelector(selector);
|
|
||||||
assert(queryHandler.waitFor, 'Query handler does not support waiting');
|
|
||||||
return (await queryHandler.waitFor(
|
|
||||||
this,
|
|
||||||
updatedSelector,
|
|
||||||
options
|
|
||||||
)) as ElementHandle<NodeFor<Selector>> | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async content(): Promise<string> {
|
async content(): Promise<string> {
|
||||||
return await this.evaluate(() => {
|
return await this.evaluate(() => {
|
||||||
let retVal = '';
|
let retVal = '';
|
||||||
|
|
@ -351,191 +333,6 @@ export class IsolatedWorld {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a script tag into the current context.
|
|
||||||
*
|
|
||||||
* @remarks
|
|
||||||
* You can pass a URL, filepath or string of contents. Note that when running Puppeteer
|
|
||||||
* in a browser environment you cannot pass a filepath and should use either
|
|
||||||
* `url` or `content`.
|
|
||||||
*/
|
|
||||||
async addScriptTag(options: {
|
|
||||||
url?: string;
|
|
||||||
path?: string;
|
|
||||||
content?: string;
|
|
||||||
id?: string;
|
|
||||||
type?: string;
|
|
||||||
}): Promise<ElementHandle<HTMLScriptElement>> {
|
|
||||||
const {
|
|
||||||
url = null,
|
|
||||||
path = null,
|
|
||||||
content = null,
|
|
||||||
id = '',
|
|
||||||
type = '',
|
|
||||||
} = options;
|
|
||||||
if (url !== null) {
|
|
||||||
try {
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return await context.evaluateHandle(addScriptUrl, url, id, type);
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Loading script from ${url} failed`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path !== null) {
|
|
||||||
let fs;
|
|
||||||
try {
|
|
||||||
fs = (await import('fs')).promises;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof TypeError) {
|
|
||||||
throw new Error(
|
|
||||||
'Can only pass a filepath to addScriptTag in a Node-like environment.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
let contents = await fs.readFile(path, 'utf8');
|
|
||||||
contents += '//# sourceURL=' + path.replace(/\n/g, '');
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return await context.evaluateHandle(addScriptContent, contents, id, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content !== null) {
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return await context.evaluateHandle(addScriptContent, content, id, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
'Provide an object with a `url`, `path` or `content` property'
|
|
||||||
);
|
|
||||||
|
|
||||||
async function addScriptUrl(url: string, id: string, type: string) {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.src = url;
|
|
||||||
if (id) {
|
|
||||||
script.id = id;
|
|
||||||
}
|
|
||||||
if (type) {
|
|
||||||
script.type = type;
|
|
||||||
}
|
|
||||||
const promise = new Promise((res, rej) => {
|
|
||||||
script.onload = res;
|
|
||||||
script.onerror = rej;
|
|
||||||
});
|
|
||||||
document.head.appendChild(script);
|
|
||||||
await promise;
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addScriptContent(
|
|
||||||
content: string,
|
|
||||||
id: string,
|
|
||||||
type = 'text/javascript'
|
|
||||||
) {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.type = type;
|
|
||||||
script.text = content;
|
|
||||||
if (id) {
|
|
||||||
script.id = id;
|
|
||||||
}
|
|
||||||
let error = null;
|
|
||||||
script.onerror = e => {
|
|
||||||
return (error = e);
|
|
||||||
};
|
|
||||||
document.head.appendChild(script);
|
|
||||||
if (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a style tag into the current context.
|
|
||||||
*
|
|
||||||
* @remarks
|
|
||||||
* You can pass a URL, filepath or string of contents. Note that when running Puppeteer
|
|
||||||
* in a browser environment you cannot pass a filepath and should use either
|
|
||||||
* `url` or `content`.
|
|
||||||
*/
|
|
||||||
async addStyleTag(options: {
|
|
||||||
url?: string;
|
|
||||||
path?: string;
|
|
||||||
content?: string;
|
|
||||||
}): Promise<ElementHandle<HTMLStyleElement | HTMLLinkElement>> {
|
|
||||||
const {url = null, path = null, content = null} = options;
|
|
||||||
if (url !== null) {
|
|
||||||
try {
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return (await context.evaluateHandle(
|
|
||||||
addStyleUrl,
|
|
||||||
url
|
|
||||||
)) as ElementHandle<HTMLLinkElement>;
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Loading style from ${url} failed`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path !== null) {
|
|
||||||
let fs: typeof import('fs').promises;
|
|
||||||
try {
|
|
||||||
fs = (await importFS()).promises;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof TypeError) {
|
|
||||||
throw new Error(
|
|
||||||
'Cannot pass a filepath to addStyleTag in the browser environment.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
let contents = await fs.readFile(path, 'utf8');
|
|
||||||
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return (await context.evaluateHandle(
|
|
||||||
addStyleContent,
|
|
||||||
contents
|
|
||||||
)) as ElementHandle<HTMLStyleElement>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content !== null) {
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return (await context.evaluateHandle(
|
|
||||||
addStyleContent,
|
|
||||||
content
|
|
||||||
)) as ElementHandle<HTMLStyleElement>;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
'Provide an object with a `url`, `path` or `content` property'
|
|
||||||
);
|
|
||||||
|
|
||||||
async function addStyleUrl(url: string): Promise<HTMLElement> {
|
|
||||||
const link = document.createElement('link');
|
|
||||||
link.rel = 'stylesheet';
|
|
||||||
link.href = url;
|
|
||||||
const promise = new Promise((res, rej) => {
|
|
||||||
link.onload = res;
|
|
||||||
link.onerror = rej;
|
|
||||||
});
|
|
||||||
document.head.appendChild(link);
|
|
||||||
await promise;
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addStyleContent(content: string): Promise<HTMLElement> {
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.appendChild(document.createTextNode(content));
|
|
||||||
const promise = new Promise((res, rej) => {
|
|
||||||
style.onload = res;
|
|
||||||
style.onerror = rej;
|
|
||||||
});
|
|
||||||
document.head.appendChild(style);
|
|
||||||
await promise;
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async click(
|
async click(
|
||||||
selector: string,
|
selector: string,
|
||||||
options: {delay?: number; button?: MouseButton; clickCount?: number}
|
options: {delay?: number; button?: MouseButton; clickCount?: number}
|
||||||
|
|
@ -703,10 +500,11 @@ export class IsolatedWorld {
|
||||||
|
|
||||||
async _waitForSelectorInPage(
|
async _waitForSelectorInPage(
|
||||||
queryOne: Function,
|
queryOne: Function,
|
||||||
|
root: ElementHandle<Node> | undefined,
|
||||||
selector: string,
|
selector: string,
|
||||||
options: WaitForSelectorOptions,
|
options: WaitForSelectorOptions,
|
||||||
binding?: PageBinding
|
binding?: PageBinding
|
||||||
): Promise<ElementHandle<Node> | null> {
|
): Promise<JSHandle<unknown> | null> {
|
||||||
const {
|
const {
|
||||||
visible: waitForVisible = false,
|
visible: waitForVisible = false,
|
||||||
hidden: waitForHidden = false,
|
hidden: waitForHidden = false,
|
||||||
|
|
@ -722,9 +520,7 @@ export class IsolatedWorld {
|
||||||
waitForVisible: boolean,
|
waitForVisible: boolean,
|
||||||
waitForHidden: boolean
|
waitForHidden: boolean
|
||||||
): Promise<Node | null | boolean> {
|
): Promise<Node | null | boolean> {
|
||||||
const node = predicateQueryHandler
|
const node = (await predicateQueryHandler(root, selector)) as Element;
|
||||||
? ((await predicateQueryHandler(root, selector)) as Element)
|
|
||||||
: root.querySelector(selector);
|
|
||||||
return checkWaitForOptions(node, waitForVisible, waitForHidden);
|
return checkWaitForOptions(node, waitForVisible, waitForHidden);
|
||||||
}
|
}
|
||||||
const waitTaskOptions: WaitTaskOptions = {
|
const waitTaskOptions: WaitTaskOptions = {
|
||||||
|
|
@ -736,16 +532,10 @@ export class IsolatedWorld {
|
||||||
timeout,
|
timeout,
|
||||||
args: [selector, waitForVisible, waitForHidden],
|
args: [selector, waitForVisible, waitForHidden],
|
||||||
binding,
|
binding,
|
||||||
root: options.root,
|
root,
|
||||||
};
|
};
|
||||||
const waitTask = new WaitTask(waitTaskOptions);
|
const waitTask = new WaitTask(waitTaskOptions);
|
||||||
const jsHandle = await waitTask.promise;
|
return waitTask.promise;
|
||||||
const elementHandle = jsHandle.asElement();
|
|
||||||
if (!elementHandle) {
|
|
||||||
await jsHandle.dispose();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return elementHandle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForFunction(
|
waitForFunction(
|
||||||
|
|
@ -796,6 +586,12 @@ export class IsolatedWorld {
|
||||||
});
|
});
|
||||||
return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T;
|
return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async transferHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
|
||||||
|
const result = await this.adoptHandle(handle);
|
||||||
|
await handle.dispose();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {CDPSession} from './Connection.js';
|
import {CDPSession} from './Connection.js';
|
||||||
import type {ElementHandle} from './ElementHandle.js';
|
import type {ElementHandle} from './ElementHandle.js';
|
||||||
import {ExecutionContext} from './ExecutionContext.js';
|
import {ExecutionContext} from './ExecutionContext.js';
|
||||||
|
|
@ -78,7 +78,6 @@ export class JSHandle<T = unknown> {
|
||||||
*/
|
*/
|
||||||
[__JSHandleSymbol]?: T;
|
[__JSHandleSymbol]?: T;
|
||||||
|
|
||||||
#client: CDPSession;
|
|
||||||
#disposed = false;
|
#disposed = false;
|
||||||
#context: ExecutionContext;
|
#context: ExecutionContext;
|
||||||
#remoteObject: Protocol.Runtime.RemoteObject;
|
#remoteObject: Protocol.Runtime.RemoteObject;
|
||||||
|
|
@ -87,7 +86,7 @@ export class JSHandle<T = unknown> {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
get client(): CDPSession {
|
get client(): CDPSession {
|
||||||
return this.#client;
|
return this.#context._client;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -102,16 +101,14 @@ export class JSHandle<T = unknown> {
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
client: CDPSession,
|
|
||||||
remoteObject: Protocol.Runtime.RemoteObject
|
remoteObject: Protocol.Runtime.RemoteObject
|
||||||
) {
|
) {
|
||||||
this.#context = context;
|
this.#context = context;
|
||||||
this.#client = client;
|
|
||||||
this.#remoteObject = remoteObject;
|
this.#remoteObject = remoteObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns The execution context the handle belongs to.
|
* @internal
|
||||||
*/
|
*/
|
||||||
executionContext(): ExecutionContext {
|
executionContext(): ExecutionContext {
|
||||||
return this.#context;
|
return this.#context;
|
||||||
|
|
@ -196,7 +193,7 @@ export class JSHandle<T = unknown> {
|
||||||
assert(this.#remoteObject.objectId);
|
assert(this.#remoteObject.objectId);
|
||||||
// We use Runtime.getProperties rather than iterative building because the
|
// We use Runtime.getProperties rather than iterative building because the
|
||||||
// iterative approach might create a distorted snapshot.
|
// iterative approach might create a distorted snapshot.
|
||||||
const response = await this.#client.send('Runtime.getProperties', {
|
const response = await this.client.send('Runtime.getProperties', {
|
||||||
objectId: this.#remoteObject.objectId,
|
objectId: this.#remoteObject.objectId,
|
||||||
ownProperties: true,
|
ownProperties: true,
|
||||||
});
|
});
|
||||||
|
|
@ -247,7 +244,7 @@ export class JSHandle<T = unknown> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#disposed = true;
|
this.#disposed = true;
|
||||||
await releaseObject(this.#client, this.#remoteObject);
|
await releaseObject(this.client, this.#remoteObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -279,11 +276,11 @@ export class JSHandle<T = unknown> {
|
||||||
*/
|
*/
|
||||||
export interface Offset {
|
export interface Offset {
|
||||||
/**
|
/**
|
||||||
* x-offset for the clickable point relative to the top-left corder of the border box.
|
* x-offset for the clickable point relative to the top-left corner of the border box.
|
||||||
*/
|
*/
|
||||||
x: number;
|
x: number;
|
||||||
/**
|
/**
|
||||||
* y-offset for the clickable point relative to the top-left corder of the border box.
|
* y-offset for the clickable point relative to the top-left corner of the border box.
|
||||||
*/
|
*/
|
||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
@ -307,7 +304,7 @@ export interface ClickOptions {
|
||||||
*/
|
*/
|
||||||
clickCount?: number;
|
clickCount?: number;
|
||||||
/**
|
/**
|
||||||
* Offset for the clickable point relative to the top-left corder of the border box.
|
* Offset for the clickable point relative to the top-left corner of the border box.
|
||||||
*/
|
*/
|
||||||
offset?: Offset;
|
offset?: Offset;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,20 +14,19 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {
|
import {
|
||||||
addEventListener,
|
addEventListener,
|
||||||
PuppeteerEventListener,
|
PuppeteerEventListener,
|
||||||
removeEventListeners,
|
removeEventListeners,
|
||||||
|
} from './util.js';
|
||||||
|
import {
|
||||||
DeferredPromise,
|
DeferredPromise,
|
||||||
createDeferredPromise,
|
createDeferredPromise,
|
||||||
} from './util.js';
|
} from '../util/DeferredPromise.js';
|
||||||
import {TimeoutError} from './Errors.js';
|
import {TimeoutError} from './Errors.js';
|
||||||
import {
|
import {FrameManager, FrameManagerEmittedEvents} from './FrameManager.js';
|
||||||
FrameManager,
|
import {Frame} from './Frame.js';
|
||||||
Frame,
|
|
||||||
FrameManagerEmittedEvents,
|
|
||||||
} from './FrameManager.js';
|
|
||||||
import {HTTPRequest} from './HTTPRequest.js';
|
import {HTTPRequest} from './HTTPRequest.js';
|
||||||
import {HTTPResponse} from './HTTPResponse.js';
|
import {HTTPResponse} from './HTTPResponse.js';
|
||||||
import {NetworkManagerEmittedEvents} from './NetworkManager.js';
|
import {NetworkManagerEmittedEvents} from './NetworkManager.js';
|
||||||
|
|
@ -180,9 +179,10 @@ export class LifecycleWatcher {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#navigationRequest = request;
|
this.#navigationRequest = request;
|
||||||
this.#navigationResponseReceived?.reject(
|
// Resolve previous navigation response in case there are multiple
|
||||||
new Error('New navigation request was received')
|
// navigation requests reported by the backend. This generally should not
|
||||||
);
|
// happen by it looks like it's possible.
|
||||||
|
this.#navigationResponseReceived?.resolve();
|
||||||
this.#navigationResponseReceived = createDeferredPromise();
|
this.#navigationResponseReceived = createDeferredPromise();
|
||||||
if (request.response() !== null) {
|
if (request.response() !== null) {
|
||||||
this.#navigationResponseReceived?.resolve();
|
this.#navigationResponseReceived?.resolve();
|
||||||
|
|
|
||||||
|
|
@ -16,18 +16,15 @@
|
||||||
|
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
|
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {EventEmitter} from './EventEmitter.js';
|
import {EventEmitter} from './EventEmitter.js';
|
||||||
import {Frame} from './FrameManager.js';
|
import {Frame} from './Frame.js';
|
||||||
import {HTTPRequest} from './HTTPRequest.js';
|
import {HTTPRequest} from './HTTPRequest.js';
|
||||||
import {HTTPResponse} from './HTTPResponse.js';
|
import {HTTPResponse} from './HTTPResponse.js';
|
||||||
import {FetchRequestId, NetworkEventManager} from './NetworkEventManager.js';
|
import {FetchRequestId, NetworkEventManager} from './NetworkEventManager.js';
|
||||||
import {
|
import {debugError, isString} from './util.js';
|
||||||
debugError,
|
import {DeferredPromise} from '../util/DeferredPromise.js';
|
||||||
isString,
|
import {createDebuggableDeferredPromise} from '../util/DebuggableDeferredPromise.js';
|
||||||
createDeferredPromiseWithTimer,
|
|
||||||
DeferredPromise,
|
|
||||||
} from './util.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
|
|
@ -145,9 +142,8 @@ export class NetworkManager extends EventEmitter {
|
||||||
if (this.#deferredInitPromise) {
|
if (this.#deferredInitPromise) {
|
||||||
return this.#deferredInitPromise;
|
return this.#deferredInitPromise;
|
||||||
}
|
}
|
||||||
this.#deferredInitPromise = createDeferredPromiseWithTimer<void>(
|
this.#deferredInitPromise = createDebuggableDeferredPromise(
|
||||||
'NetworkManager initialization timed out',
|
'NetworkManager initialization timed out'
|
||||||
30000
|
|
||||||
);
|
);
|
||||||
const init = Promise.all([
|
const init = Promise.all([
|
||||||
this.#ignoreHTTPSErrors
|
this.#ignoreHTTPSErrors
|
||||||
|
|
|
||||||
|
|
@ -16,26 +16,32 @@
|
||||||
|
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
import type {Readable} from 'stream';
|
import type {Readable} from 'stream';
|
||||||
|
import {assert} from '../util/assert.js';
|
||||||
|
import {
|
||||||
|
createDeferredPromise,
|
||||||
|
DeferredPromise,
|
||||||
|
} from '../util/DeferredPromise.js';
|
||||||
|
import {isErrorLike} from '../util/ErrorLike.js';
|
||||||
import {Accessibility} from './Accessibility.js';
|
import {Accessibility} from './Accessibility.js';
|
||||||
import {assert} from './assert.js';
|
|
||||||
import {Browser, BrowserContext} from './Browser.js';
|
import {Browser, BrowserContext} from './Browser.js';
|
||||||
import {CDPSession, CDPSessionEmittedEvents} from './Connection.js';
|
import {CDPSession, CDPSessionEmittedEvents} from './Connection.js';
|
||||||
import {ConsoleMessage, ConsoleMessageType} from './ConsoleMessage.js';
|
import {ConsoleMessage, ConsoleMessageType} from './ConsoleMessage.js';
|
||||||
import {Coverage} from './Coverage.js';
|
import {Coverage} from './Coverage.js';
|
||||||
import {Dialog} from './Dialog.js';
|
import {Dialog} from './Dialog.js';
|
||||||
import {MAIN_WORLD, WaitForSelectorOptions} from './IsolatedWorld.js';
|
|
||||||
import {ElementHandle} from './ElementHandle.js';
|
import {ElementHandle} from './ElementHandle.js';
|
||||||
import {EmulationManager} from './EmulationManager.js';
|
import {EmulationManager} from './EmulationManager.js';
|
||||||
import {EventEmitter, Handler} from './EventEmitter.js';
|
import {EventEmitter, Handler} from './EventEmitter.js';
|
||||||
import {FileChooser} from './FileChooser.js';
|
import {FileChooser} from './FileChooser.js';
|
||||||
import {
|
import {
|
||||||
Frame,
|
Frame,
|
||||||
FrameManager,
|
FrameAddScriptTagOptions,
|
||||||
FrameManagerEmittedEvents,
|
FrameAddStyleTagOptions,
|
||||||
} from './FrameManager.js';
|
} from './Frame.js';
|
||||||
|
import {FrameManager, FrameManagerEmittedEvents} from './FrameManager.js';
|
||||||
import {HTTPRequest} from './HTTPRequest.js';
|
import {HTTPRequest} from './HTTPRequest.js';
|
||||||
import {HTTPResponse} from './HTTPResponse.js';
|
import {HTTPResponse} from './HTTPResponse.js';
|
||||||
import {Keyboard, Mouse, MouseButton, Touchscreen} from './Input.js';
|
import {Keyboard, Mouse, MouseButton, Touchscreen} from './Input.js';
|
||||||
|
import {MAIN_WORLD, WaitForSelectorOptions} from './IsolatedWorld.js';
|
||||||
import {JSHandle} from './JSHandle.js';
|
import {JSHandle} from './JSHandle.js';
|
||||||
import {PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
import {PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||||
import {
|
import {
|
||||||
|
|
@ -56,10 +62,9 @@ import {
|
||||||
debugError,
|
debugError,
|
||||||
evaluationString,
|
evaluationString,
|
||||||
getExceptionMessage,
|
getExceptionMessage,
|
||||||
importFS,
|
|
||||||
getReadableAsBuffer,
|
getReadableAsBuffer,
|
||||||
getReadableFromProtocolStream,
|
getReadableFromProtocolStream,
|
||||||
isErrorLike,
|
importFS,
|
||||||
isNumber,
|
isNumber,
|
||||||
isString,
|
isString,
|
||||||
pageBindingDeliverErrorString,
|
pageBindingDeliverErrorString,
|
||||||
|
|
@ -70,8 +75,6 @@ import {
|
||||||
valueFromRemoteObject,
|
valueFromRemoteObject,
|
||||||
waitForEvent,
|
waitForEvent,
|
||||||
waitWithTimeout,
|
waitWithTimeout,
|
||||||
createDeferredPromiseWithTimer,
|
|
||||||
DeferredPromise,
|
|
||||||
} from './util.js';
|
} from './util.js';
|
||||||
import {WebWorker} from './WebWorker.js';
|
import {WebWorker} from './WebWorker.js';
|
||||||
|
|
||||||
|
|
@ -160,6 +163,10 @@ export interface ScreenshotClip {
|
||||||
y: number;
|
y: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
/**
|
||||||
|
* @defaultValue 1
|
||||||
|
*/
|
||||||
|
scale?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -760,21 +767,25 @@ export class Page extends EventEmitter {
|
||||||
* await fileChooser.accept(['/tmp/myfile.pdf']);
|
* await fileChooser.accept(['/tmp/myfile.pdf']);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
async waitForFileChooser(
|
waitForFileChooser(options: WaitTimeoutOptions = {}): Promise<FileChooser> {
|
||||||
options: WaitTimeoutOptions = {}
|
const needsEnable = this.#fileChooserPromises.size === 0;
|
||||||
): Promise<FileChooser> {
|
const {timeout = this.#timeoutSettings.timeout()} = options;
|
||||||
if (!this.#fileChooserPromises.size) {
|
const promise = createDeferredPromise<FileChooser>({
|
||||||
await this.#client.send('Page.setInterceptFileChooserDialog', {
|
message: `Waiting for \`FileChooser\` failed: ${timeout}ms exceeded`,
|
||||||
|
timeout,
|
||||||
|
});
|
||||||
|
this.#fileChooserPromises.add(promise);
|
||||||
|
let enablePromise: Promise<void> | undefined;
|
||||||
|
if (needsEnable) {
|
||||||
|
enablePromise = this.#client.send('Page.setInterceptFileChooserDialog', {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return Promise.all([promise, enablePromise])
|
||||||
const {timeout = this.#timeoutSettings.timeout()} = options;
|
.then(([result]) => {
|
||||||
const promise = createDeferredPromiseWithTimer<FileChooser>(
|
return result;
|
||||||
`Waiting for \`FileChooser\` failed: ${timeout}ms exceeded`
|
})
|
||||||
);
|
.catch(error => {
|
||||||
this.#fileChooserPromises.add(promise);
|
|
||||||
return promise.catch(error => {
|
|
||||||
this.#fileChooserPromises.delete(promise);
|
this.#fileChooserPromises.delete(promise);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
|
|
@ -1038,6 +1049,13 @@ export class Page extends EventEmitter {
|
||||||
this.#timeoutSettings.setDefaultTimeout(timeout);
|
this.#timeoutSettings.setDefaultTimeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns Maximum time in milliseconds.
|
||||||
|
*/
|
||||||
|
getDefaultTimeout(): number {
|
||||||
|
return this.#timeoutSettings.timeout();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs `document.querySelector` within the page. If no element matches the
|
* Runs `document.querySelector` within the page. If no element matches the
|
||||||
* selector, the return value resolves to `null`.
|
* selector, the return value resolves to `null`.
|
||||||
|
|
@ -1137,11 +1155,6 @@ export class Page extends EventEmitter {
|
||||||
* This method iterates the JavaScript heap and finds all objects with the
|
* This method iterates the JavaScript heap and finds all objects with the
|
||||||
* given prototype.
|
* given prototype.
|
||||||
*
|
*
|
||||||
* @remarks
|
|
||||||
* Shortcut for
|
|
||||||
* {@link ExecutionContext.queryObjects |
|
|
||||||
* page.mainFrame().executionContext().queryObjects(prototypeHandle)}.
|
|
||||||
*
|
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
|
|
@ -1165,7 +1178,16 @@ export class Page extends EventEmitter {
|
||||||
prototypeHandle: JSHandle<Prototype>
|
prototypeHandle: JSHandle<Prototype>
|
||||||
): Promise<JSHandle<Prototype[]>> {
|
): Promise<JSHandle<Prototype[]>> {
|
||||||
const context = await this.mainFrame().executionContext();
|
const context = await this.mainFrame().executionContext();
|
||||||
return context.queryObjects(prototypeHandle);
|
assert(!prototypeHandle.disposed, 'Prototype JSHandle is disposed!');
|
||||||
|
const remoteObject = prototypeHandle.remoteObject();
|
||||||
|
assert(
|
||||||
|
remoteObject.objectId,
|
||||||
|
'Prototype JSHandle must not be referencing primitive value'
|
||||||
|
);
|
||||||
|
const response = await context._client.send('Runtime.queryObjects', {
|
||||||
|
prototypeObjectId: remoteObject.objectId,
|
||||||
|
});
|
||||||
|
return createJSHandle(context, response.objects) as HandleFor<Prototype[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1408,30 +1430,35 @@ export class Page extends EventEmitter {
|
||||||
* Shortcut for
|
* Shortcut for
|
||||||
* {@link Frame.addScriptTag | page.mainFrame().addScriptTag(options)}.
|
* {@link Frame.addScriptTag | page.mainFrame().addScriptTag(options)}.
|
||||||
*
|
*
|
||||||
* @returns Promise which resolves to the added tag when the script's onload
|
* @param options - Options for the script.
|
||||||
* fires or when the script content was injected into frame.
|
* @returns An {@link ElementHandle | element handle} to the injected
|
||||||
|
* `<script>` element.
|
||||||
*/
|
*/
|
||||||
async addScriptTag(options: {
|
async addScriptTag(
|
||||||
url?: string;
|
options: FrameAddScriptTagOptions
|
||||||
path?: string;
|
): Promise<ElementHandle<HTMLScriptElement>> {
|
||||||
content?: string;
|
|
||||||
type?: string;
|
|
||||||
id?: string;
|
|
||||||
}): Promise<ElementHandle<HTMLScriptElement>> {
|
|
||||||
return this.mainFrame().addScriptTag(options);
|
return this.mainFrame().addScriptTag(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a `<link rel="stylesheet">` tag into the page with the desired URL or a
|
* Adds a `<link rel="stylesheet">` tag into the page with the desired URL or
|
||||||
* `<style type="text/css">` tag with the content.
|
* a `<style type="text/css">` tag with the content.
|
||||||
* @returns Promise which resolves to the added tag when the stylesheet's
|
*
|
||||||
* onload fires or when the CSS content was injected into frame.
|
* Shortcut for
|
||||||
|
* {@link Frame.addStyleTag | page.mainFrame().addStyleTag(options)}.
|
||||||
|
*
|
||||||
|
* @returns An {@link ElementHandle | element handle} to the injected `<link>`
|
||||||
|
* or `<style>` element.
|
||||||
*/
|
*/
|
||||||
async addStyleTag(options: {
|
async addStyleTag(
|
||||||
url?: string;
|
options: Omit<FrameAddStyleTagOptions, 'url'>
|
||||||
path?: string;
|
): Promise<ElementHandle<HTMLStyleElement>>;
|
||||||
content?: string;
|
async addStyleTag(
|
||||||
}): Promise<ElementHandle<Node>> {
|
options: FrameAddStyleTagOptions
|
||||||
|
): Promise<ElementHandle<HTMLLinkElement>>;
|
||||||
|
async addStyleTag(
|
||||||
|
options: FrameAddStyleTagOptions
|
||||||
|
): Promise<ElementHandle<HTMLStyleElement | HTMLLinkElement>> {
|
||||||
return this.mainFrame().addStyleTag(options);
|
return this.mainFrame().addStyleTag(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2770,7 +2797,7 @@ export class Page extends EventEmitter {
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* // In your puppeteer script, assuming the preload.js file is
|
* // In your puppeteer script, assuming the preload.js file is
|
||||||
* in same folder of our script
|
* // in same folder of our script.
|
||||||
* const preloadFile = fs.readFileSync('./preload.js', 'utf8');
|
* const preloadFile = fs.readFileSync('./preload.js', 'utf8');
|
||||||
* await page.evaluateOnNewDocument(preloadFile);
|
* await page.evaluateOnNewDocument(preloadFile);
|
||||||
* ```
|
* ```
|
||||||
|
|
@ -2989,7 +3016,12 @@ export class Page extends EventEmitter {
|
||||||
const result = await this.#client.send('Page.captureScreenshot', {
|
const result = await this.#client.send('Page.captureScreenshot', {
|
||||||
format,
|
format,
|
||||||
quality: options.quality,
|
quality: options.quality,
|
||||||
clip,
|
clip: clip
|
||||||
|
? {
|
||||||
|
...clip,
|
||||||
|
scale: clip.scale === undefined ? 1 : clip.scale,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
captureBeyondViewport,
|
captureBeyondViewport,
|
||||||
fromSurface,
|
fromSurface,
|
||||||
});
|
});
|
||||||
|
|
@ -3021,14 +3053,12 @@ export class Page extends EventEmitter {
|
||||||
}
|
}
|
||||||
return buffer;
|
return buffer;
|
||||||
|
|
||||||
function processClip(
|
function processClip(clip: ScreenshotClip): ScreenshotClip {
|
||||||
clip: ScreenshotClip
|
|
||||||
): ScreenshotClip & {scale: number} {
|
|
||||||
const x = Math.round(clip.x);
|
const x = Math.round(clip.x);
|
||||||
const y = Math.round(clip.y);
|
const y = Math.round(clip.y);
|
||||||
const width = Math.round(clip.width + clip.x - x);
|
const width = Math.round(clip.width + clip.x - x);
|
||||||
const height = Math.round(clip.height + clip.y - y);
|
const height = Math.round(clip.height + clip.y - y);
|
||||||
return {x, y, width, height, scale: 1};
|
return {x, y, width, height, scale: clip.scale};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3393,7 +3423,7 @@ export class Page extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
async waitForSelector<Selector extends string>(
|
async waitForSelector<Selector extends string>(
|
||||||
selector: Selector,
|
selector: Selector,
|
||||||
options: Exclude<WaitForSelectorOptions, 'root'> = {}
|
options: WaitForSelectorOptions = {}
|
||||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||||
return await this.mainFrame().waitForSelector(selector, options);
|
return await this.mainFrame().waitForSelector(selector, options);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ariaHandler} from './AriaQueryHandler.js';
|
import {ariaHandler} from './AriaQueryHandler.js';
|
||||||
import {IsolatedWorld, WaitForSelectorOptions} from './IsolatedWorld.js';
|
|
||||||
import {ElementHandle} from './ElementHandle.js';
|
import {ElementHandle} from './ElementHandle.js';
|
||||||
import {JSHandle} from './JSHandle.js';
|
import {Frame} from './Frame.js';
|
||||||
|
import {
|
||||||
|
MAIN_WORLD,
|
||||||
|
PUPPETEER_WORLD,
|
||||||
|
WaitForSelectorOptions,
|
||||||
|
} from './IsolatedWorld.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
|
|
@ -55,25 +59,13 @@ export interface InternalQueryHandler {
|
||||||
element: ElementHandle<Node>,
|
element: ElementHandle<Node>,
|
||||||
selector: string
|
selector: string
|
||||||
) => Promise<Array<ElementHandle<Node>>>;
|
) => Promise<Array<ElementHandle<Node>>>;
|
||||||
/**
|
|
||||||
* Queries for multiple nodes given a selector and {@link ElementHandle}.
|
|
||||||
* Unlike {@link queryAll}, this returns a handle to a node array.
|
|
||||||
*
|
|
||||||
* Akin to {@link Window.prototype.querySelectorAll}.
|
|
||||||
*/
|
|
||||||
queryAllArray?: (
|
|
||||||
element: ElementHandle<Node>,
|
|
||||||
selector: string
|
|
||||||
) => Promise<JSHandle<Node[]>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits until a single node appears for a given selector and
|
* Waits until a single node appears for a given selector and
|
||||||
* {@link ElementHandle}.
|
* {@link ElementHandle}.
|
||||||
*
|
|
||||||
* Akin to {@link Window.prototype.querySelectorAll}.
|
|
||||||
*/
|
*/
|
||||||
waitFor?: (
|
waitFor?: (
|
||||||
isolatedWorld: IsolatedWorld,
|
elementOrFrame: ElementHandle<Node> | Frame,
|
||||||
selector: string,
|
selector: string,
|
||||||
options: WaitForSelectorOptions
|
options: WaitForSelectorOptions
|
||||||
) => Promise<ElementHandle<Node> | null>;
|
) => Promise<ElementHandle<Node> | null>;
|
||||||
|
|
@ -95,12 +87,34 @@ function internalizeCustomQueryHandler(
|
||||||
await jsHandle.dispose();
|
await jsHandle.dispose();
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
internalHandler.waitFor = (
|
internalHandler.waitFor = async (elementOrFrame, selector, options) => {
|
||||||
domWorld: IsolatedWorld,
|
let frame: Frame;
|
||||||
selector: string,
|
let element: ElementHandle<Node> | undefined;
|
||||||
options: WaitForSelectorOptions
|
if (elementOrFrame instanceof Frame) {
|
||||||
) => {
|
frame = elementOrFrame;
|
||||||
return domWorld._waitForSelectorInPage(queryOne, selector, options);
|
} else {
|
||||||
|
frame = elementOrFrame.frame;
|
||||||
|
element = await frame.worlds[PUPPETEER_WORLD].adoptHandle(
|
||||||
|
elementOrFrame
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result = await frame.worlds[PUPPETEER_WORLD]._waitForSelectorInPage(
|
||||||
|
queryOne,
|
||||||
|
element,
|
||||||
|
selector,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,16 +133,6 @@ function internalizeCustomQueryHandler(
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
internalHandler.queryAllArray = async (element, selector) => {
|
|
||||||
const resultHandle = (await element.evaluateHandle(
|
|
||||||
queryAll,
|
|
||||||
selector
|
|
||||||
)) as JSHandle<Element[] | NodeListOf<Element>>;
|
|
||||||
const arrayHandle = await resultHandle.evaluateHandle(res => {
|
|
||||||
return Array.from(res);
|
|
||||||
});
|
|
||||||
return arrayHandle;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return internalHandler;
|
return internalHandler;
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,9 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {
|
import {getReadableAsBuffer, getReadableFromProtocolStream} from './util.js';
|
||||||
getReadableAsBuffer,
|
import {isErrorLike} from '../util/ErrorLike.js';
|
||||||
getReadableFromProtocolStream,
|
|
||||||
isErrorLike,
|
|
||||||
} from './util.js';
|
|
||||||
import {CDPSession} from './Connection.js';
|
import {CDPSession} from './Connection.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {EventEmitter} from './EventEmitter.js';
|
||||||
import {ExecutionContext} from './ExecutionContext.js';
|
import {ExecutionContext} from './ExecutionContext.js';
|
||||||
import {JSHandle} from './JSHandle.js';
|
import {JSHandle} from './JSHandle.js';
|
||||||
import {debugError} from './util.js';
|
import {debugError} from './util.js';
|
||||||
|
import {createDeferredPromise} from '../util/DeferredPromise.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
|
@ -38,8 +39,6 @@ export type ExceptionThrownCallback = (
|
||||||
details: Protocol.Runtime.ExceptionDetails
|
details: Protocol.Runtime.ExceptionDetails
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
type JSHandleFactory = (obj: Protocol.Runtime.RemoteObject) => JSHandle;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a
|
* This class represents a
|
||||||
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | WebWorker}.
|
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | WebWorker}.
|
||||||
|
|
@ -67,10 +66,10 @@ type JSHandleFactory = (obj: Protocol.Runtime.RemoteObject) => JSHandle;
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export class WebWorker extends EventEmitter {
|
export class WebWorker extends EventEmitter {
|
||||||
|
#executionContext = createDeferredPromise<ExecutionContext>();
|
||||||
|
|
||||||
#client: CDPSession;
|
#client: CDPSession;
|
||||||
#url: string;
|
#url: string;
|
||||||
#executionContextPromise: Promise<ExecutionContext>;
|
|
||||||
#executionContextCallback!: (value: ExecutionContext) => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
|
@ -84,32 +83,34 @@ export class WebWorker extends EventEmitter {
|
||||||
super();
|
super();
|
||||||
this.#client = client;
|
this.#client = client;
|
||||||
this.#url = url;
|
this.#url = url;
|
||||||
this.#executionContextPromise = new Promise<ExecutionContext>(x => {
|
|
||||||
return (this.#executionContextCallback = x);
|
|
||||||
});
|
|
||||||
|
|
||||||
let jsHandleFactory: JSHandleFactory;
|
|
||||||
this.#client.once('Runtime.executionContextCreated', async event => {
|
this.#client.once('Runtime.executionContextCreated', async event => {
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
const context = new ExecutionContext(client, event.context);
|
||||||
jsHandleFactory = remoteObject => {
|
this.#executionContext.resolve(context);
|
||||||
return new JSHandle(executionContext, client, remoteObject);
|
|
||||||
};
|
|
||||||
const executionContext = new ExecutionContext(client, event.context);
|
|
||||||
this.#executionContextCallback(executionContext);
|
|
||||||
});
|
});
|
||||||
|
this.#client.on('Runtime.consoleAPICalled', async event => {
|
||||||
// This might fail if the target is closed before we receive all execution contexts.
|
const context = await this.#executionContext;
|
||||||
this.#client.send('Runtime.enable').catch(debugError);
|
|
||||||
this.#client.on('Runtime.consoleAPICalled', event => {
|
|
||||||
return consoleAPICalled(
|
return consoleAPICalled(
|
||||||
event.type,
|
event.type,
|
||||||
event.args.map(jsHandleFactory),
|
event.args.map((object: Protocol.Runtime.RemoteObject) => {
|
||||||
|
return new JSHandle(context, object);
|
||||||
|
}),
|
||||||
event.stackTrace
|
event.stackTrace
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.#client.on('Runtime.exceptionThrown', exception => {
|
this.#client.on('Runtime.exceptionThrown', exception => {
|
||||||
return exceptionThrown(exception.exceptionDetails);
|
return exceptionThrown(exception.exceptionDetails);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This might fail if the target is closed before we receive all execution contexts.
|
||||||
|
this.#client.send('Runtime.enable').catch(debugError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
async executionContext(): Promise<ExecutionContext> {
|
||||||
|
return this.#executionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -119,14 +120,6 @@ export class WebWorker extends EventEmitter {
|
||||||
return this.#url;
|
return this.#url;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the ExecutionContext the WebWorker runs in
|
|
||||||
* @returns The ExecutionContext the web worker runs in.
|
|
||||||
*/
|
|
||||||
async executionContext(): Promise<ExecutionContext> {
|
|
||||||
return this.#executionContextPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the function passed to the `worker.evaluate` returns a Promise, then
|
* If the function passed to the `worker.evaluate` returns a Promise, then
|
||||||
* `worker.evaluate` would wait for the promise to resolve and return its
|
* `worker.evaluate` would wait for the promise to resolve and return its
|
||||||
|
|
@ -148,10 +141,8 @@ export class WebWorker extends EventEmitter {
|
||||||
pageFunction: Func | string,
|
pageFunction: Func | string,
|
||||||
...args: Params
|
...args: Params
|
||||||
): Promise<Awaited<ReturnType<Func>>> {
|
): Promise<Awaited<ReturnType<Func>>> {
|
||||||
return (await this.#executionContextPromise).evaluate(
|
const context = await this.#executionContext;
|
||||||
pageFunction,
|
return context.evaluate(pageFunction, ...args);
|
||||||
...args
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -173,9 +164,7 @@ export class WebWorker extends EventEmitter {
|
||||||
pageFunction: Func | string,
|
pageFunction: Func | string,
|
||||||
...args: Params
|
...args: Params
|
||||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||||
return (await this.#executionContextPromise).evaluateHandle(
|
const context = await this.#executionContext;
|
||||||
pageFunction,
|
return context.evaluateHandle(pageFunction, ...args);
|
||||||
...args
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@
|
||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
import type {Readable} from 'stream';
|
import type {Readable} from 'stream';
|
||||||
import {isNode} from '../environment.js';
|
import {isNode} from '../environment.js';
|
||||||
import {assert} from './assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
|
import {isErrorLike} from '../util/ErrorLike.js';
|
||||||
import {CDPSession} from './Connection.js';
|
import {CDPSession} from './Connection.js';
|
||||||
import {debug} from './Debug.js';
|
import {debug} from './Debug.js';
|
||||||
import {ElementHandle} from './ElementHandle.js';
|
import {ElementHandle} from './ElementHandle.js';
|
||||||
|
|
@ -216,19 +217,10 @@ export function createJSHandle(
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
remoteObject: Protocol.Runtime.RemoteObject
|
remoteObject: Protocol.Runtime.RemoteObject
|
||||||
): JSHandle | ElementHandle<Node> {
|
): JSHandle | ElementHandle<Node> {
|
||||||
const frame = context.frame();
|
if (remoteObject.subtype === 'node' && context._world) {
|
||||||
if (remoteObject.subtype === 'node' && frame) {
|
return new ElementHandle(context, remoteObject, context._world.frame());
|
||||||
const frameManager = frame._frameManager;
|
|
||||||
return new ElementHandle(
|
|
||||||
context,
|
|
||||||
context._client,
|
|
||||||
remoteObject,
|
|
||||||
frame,
|
|
||||||
frameManager.page(),
|
|
||||||
frameManager
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return new JSHandle(context, context._client, remoteObject);
|
return new JSHandle(context, remoteObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -341,7 +333,7 @@ export function pageBindingDeliverErrorValueString(
|
||||||
*/
|
*/
|
||||||
export function makePredicateString(
|
export function makePredicateString(
|
||||||
predicate: Function,
|
predicate: Function,
|
||||||
predicateQueryHandler?: Function
|
predicateQueryHandler: Function
|
||||||
): string {
|
): string {
|
||||||
function checkWaitForOptions(
|
function checkWaitForOptions(
|
||||||
node: Node | null,
|
node: Node | null,
|
||||||
|
|
@ -371,12 +363,10 @@ export function makePredicateString(
|
||||||
return !!(rect.top || rect.bottom || rect.width || rect.height);
|
return !!(rect.top || rect.bottom || rect.width || rect.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const predicateQueryHandlerDef = predicateQueryHandler
|
|
||||||
? `const predicateQueryHandler = ${predicateQueryHandler};`
|
|
||||||
: '';
|
|
||||||
return `
|
return `
|
||||||
(() => {
|
(() => {
|
||||||
${predicateQueryHandlerDef}
|
const predicateQueryHandler = ${predicateQueryHandler};
|
||||||
const checkWaitForOptions = ${checkWaitForOptions};
|
const checkWaitForOptions = ${checkWaitForOptions};
|
||||||
return (${predicate})(...args)
|
return (${predicate})(...args)
|
||||||
})() `;
|
})() `;
|
||||||
|
|
@ -496,104 +486,3 @@ export async function getReadableFromProtocolStream(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export interface DeferredPromise<T> extends Promise<T> {
|
|
||||||
resolved: () => boolean;
|
|
||||||
resolve: (_: T) => void;
|
|
||||||
reject: (_: Error) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an returns a promise along with the resolve/reject functions.
|
|
||||||
*
|
|
||||||
* If the promise has not been resolved/rejected withing the `timeout` period,
|
|
||||||
* the promise gets rejected with a timeout error.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export function createDeferredPromiseWithTimer<T>(
|
|
||||||
timeoutMessage: string,
|
|
||||||
timeout = 5000
|
|
||||||
): DeferredPromise<T> {
|
|
||||||
let isResolved = false;
|
|
||||||
let resolver = (_: T): void => {};
|
|
||||||
let rejector = (_: Error) => {};
|
|
||||||
const taskPromise = new Promise<T>((resolve, reject) => {
|
|
||||||
resolver = resolve;
|
|
||||||
rejector = reject;
|
|
||||||
});
|
|
||||||
const timeoutId = setTimeout(() => {
|
|
||||||
rejector(new TimeoutError(timeoutMessage));
|
|
||||||
}, timeout);
|
|
||||||
return Object.assign(taskPromise, {
|
|
||||||
resolved: () => {
|
|
||||||
return isResolved;
|
|
||||||
},
|
|
||||||
resolve: (value: T) => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
isResolved = true;
|
|
||||||
resolver(value);
|
|
||||||
},
|
|
||||||
reject: (err: Error) => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
rejector(err);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an returns a promise along with the resolve/reject functions.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export function createDeferredPromise<T>(): DeferredPromise<T> {
|
|
||||||
let isResolved = false;
|
|
||||||
let resolver = (_: T): void => {};
|
|
||||||
let rejector = (_: Error) => {};
|
|
||||||
const taskPromise = new Promise<T>((resolve, reject) => {
|
|
||||||
resolver = resolve;
|
|
||||||
rejector = reject;
|
|
||||||
});
|
|
||||||
return Object.assign(taskPromise, {
|
|
||||||
resolved: () => {
|
|
||||||
return isResolved;
|
|
||||||
},
|
|
||||||
resolve: (value: T) => {
|
|
||||||
isResolved = true;
|
|
||||||
resolver(value);
|
|
||||||
},
|
|
||||||
reject: (err: Error) => {
|
|
||||||
rejector(err);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,12 @@
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export const isNode = !!(typeof process !== 'undefined' && process.version);
|
export const isNode = !!(typeof process !== 'undefined' && process.version);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export const DEFERRED_PROMISE_DEBUG_TIMEOUT =
|
||||||
|
typeof process !== 'undefined' &&
|
||||||
|
typeof process.env['PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT'] !== 'undefined'
|
||||||
|
? Number(process.env['PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT'])
|
||||||
|
: -1;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# Generated Artifacts
|
|
||||||
|
|
||||||
**Do not edit manually edit any TypeScript files in this folder** All TS files are generated from their respectively named template file (ext. `tmpl`) in the `templates` directory. Edit them there is needed.
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export const packageVersion = '16.1.1';
|
export const packageVersion = '17.1.2';
|
||||||
|
|
|
||||||
|
|
@ -14,18 +14,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {sync} from 'pkg-dir';
|
|
||||||
import {Product} from './common/Product.js';
|
import {Product} from './common/Product.js';
|
||||||
import {rootDirname} from './constants.js';
|
import {rootDirname} from './constants.js';
|
||||||
import {PuppeteerNode} from './node/Puppeteer.js';
|
import {PuppeteerNode} from './node/Puppeteer.js';
|
||||||
import {PUPPETEER_REVISIONS} from './revisions.js';
|
import {PUPPETEER_REVISIONS} from './revisions.js';
|
||||||
|
import {getPackageDirectory} from './util/getPackageDirectory.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export const initializePuppeteer = (packageName: string): PuppeteerNode => {
|
export const initializePuppeteer = (packageName: string): PuppeteerNode => {
|
||||||
const isPuppeteerCore = packageName === 'puppeteer-core';
|
const isPuppeteerCore = packageName === 'puppeteer-core';
|
||||||
const puppeteerRootDirectory = sync(rootDirname);
|
|
||||||
let preferredRevision = PUPPETEER_REVISIONS.chromium;
|
let preferredRevision = PUPPETEER_REVISIONS.chromium;
|
||||||
// puppeteer-core ignores environment variables
|
// puppeteer-core ignores environment variables
|
||||||
const productName = !isPuppeteerCore
|
const productName = !isPuppeteerCore
|
||||||
|
|
@ -39,7 +38,7 @@ export const initializePuppeteer = (packageName: string): PuppeteerNode => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PuppeteerNode({
|
return new PuppeteerNode({
|
||||||
projectRoot: puppeteerRootDirectory,
|
projectRoot: isPuppeteerCore ? undefined : getPackageDirectory(rootDirname),
|
||||||
preferredRevision,
|
preferredRevision,
|
||||||
isPuppeteerCore,
|
isPuppeteerCore,
|
||||||
productName,
|
productName,
|
||||||
|
|
|
||||||
156
remote/test/puppeteer/src/injected/Poller.ts
Normal file
156
remote/test/puppeteer/src/injected/Poller.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
import {
|
||||||
|
createDeferredPromise,
|
||||||
|
DeferredPromise,
|
||||||
|
} from '../util/DeferredPromise.js';
|
||||||
|
import {assert} from '../util/assert.js';
|
||||||
|
|
||||||
|
interface Poller<T> {
|
||||||
|
start(): Promise<T>;
|
||||||
|
stop(): Promise<void>;
|
||||||
|
result(): Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MutationPoller<T> implements Poller<T> {
|
||||||
|
#fn: () => Promise<T>;
|
||||||
|
|
||||||
|
#root: Node;
|
||||||
|
|
||||||
|
#observer?: MutationObserver;
|
||||||
|
#promise?: DeferredPromise<T>;
|
||||||
|
constructor(fn: () => Promise<T>, root: Node) {
|
||||||
|
this.#fn = fn;
|
||||||
|
this.#root = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(): Promise<T> {
|
||||||
|
const promise = (this.#promise = createDeferredPromise<T>());
|
||||||
|
const result = await this.#fn();
|
||||||
|
if (result) {
|
||||||
|
promise.resolve(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#observer = new MutationObserver(async () => {
|
||||||
|
const result = await this.#fn();
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise.resolve(result);
|
||||||
|
await this.stop();
|
||||||
|
});
|
||||||
|
this.#observer.observe(this.#root, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.#promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop(): Promise<void> {
|
||||||
|
assert(this.#promise, 'Polling never started.');
|
||||||
|
if (!this.#promise.finished()) {
|
||||||
|
this.#promise.reject(new Error('Polling stopped'));
|
||||||
|
}
|
||||||
|
if (this.#observer) {
|
||||||
|
this.#observer.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result(): Promise<T> {
|
||||||
|
assert(this.#promise, 'Polling never started.');
|
||||||
|
return this.#promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RAFPoller<T> implements Poller<T> {
|
||||||
|
#fn: () => Promise<T>;
|
||||||
|
#promise?: DeferredPromise<T>;
|
||||||
|
constructor(fn: () => Promise<T>) {
|
||||||
|
this.#fn = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(): Promise<T> {
|
||||||
|
const promise = (this.#promise = createDeferredPromise<T>());
|
||||||
|
const result = await this.#fn();
|
||||||
|
if (result) {
|
||||||
|
promise.resolve(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const poll = async () => {
|
||||||
|
if (promise.finished()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await this.#fn();
|
||||||
|
if (!result) {
|
||||||
|
window.requestAnimationFrame(poll);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise.resolve(result);
|
||||||
|
await this.stop();
|
||||||
|
};
|
||||||
|
window.requestAnimationFrame(poll);
|
||||||
|
|
||||||
|
return this.#promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop(): Promise<void> {
|
||||||
|
assert(this.#promise, 'Polling never started.');
|
||||||
|
if (!this.#promise.finished()) {
|
||||||
|
this.#promise.reject(new Error('Polling stopped'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result(): Promise<T> {
|
||||||
|
assert(this.#promise, 'Polling never started.');
|
||||||
|
return this.#promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IntervalPoller<T> implements Poller<T> {
|
||||||
|
#fn: () => Promise<T>;
|
||||||
|
#ms: number;
|
||||||
|
|
||||||
|
#interval?: NodeJS.Timer;
|
||||||
|
#promise?: DeferredPromise<T>;
|
||||||
|
constructor(fn: () => Promise<T>, ms: number) {
|
||||||
|
this.#fn = fn;
|
||||||
|
this.#ms = ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(): Promise<T> {
|
||||||
|
const promise = (this.#promise = createDeferredPromise<T>());
|
||||||
|
const result = await this.#fn();
|
||||||
|
if (result) {
|
||||||
|
promise.resolve(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#interval = setInterval(async () => {
|
||||||
|
const result = await this.#fn();
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise.resolve(result);
|
||||||
|
await this.stop();
|
||||||
|
}, this.#ms);
|
||||||
|
|
||||||
|
return this.#promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop(): Promise<void> {
|
||||||
|
assert(this.#promise, 'Polling never started.');
|
||||||
|
if (!this.#promise.finished()) {
|
||||||
|
this.#promise.reject(new Error('Polling stopped'));
|
||||||
|
}
|
||||||
|
if (this.#interval) {
|
||||||
|
clearInterval(this.#interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result(): Promise<T> {
|
||||||
|
assert(this.#promise, 'Polling never started.');
|
||||||
|
return this.#promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
remote/test/puppeteer/src/injected/README.md
Normal file
5
remote/test/puppeteer/src/injected/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Injected
|
||||||
|
|
||||||
|
This folder contains code that is injected into every Puppeteer execution context. Each file is transpiled using esbuild into a script in `src/generated` which is then imported into server code.
|
||||||
|
|
||||||
|
See `utils/generate_injected.ts` for more information.
|
||||||
14
remote/test/puppeteer/src/injected/injected.ts
Normal file
14
remote/test/puppeteer/src/injected/injected.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import {createDeferredPromise} from '../util/DeferredPromise.js';
|
||||||
|
import * as Poller from './Poller.js';
|
||||||
|
import * as util from './util.js';
|
||||||
|
|
||||||
|
Object.assign(
|
||||||
|
self,
|
||||||
|
Object.freeze({
|
||||||
|
InjectedUtil: {
|
||||||
|
...Poller,
|
||||||
|
...util,
|
||||||
|
createDeferredPromise,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
18
remote/test/puppeteer/src/injected/util.ts
Normal file
18
remote/test/puppeteer/src/injected/util.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
const createdFunctions = new Map<string, (...args: unknown[]) => unknown>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a function from a string.
|
||||||
|
*/
|
||||||
|
export const createFunction = (
|
||||||
|
functionValue: string
|
||||||
|
): ((...args: unknown[]) => unknown) => {
|
||||||
|
let fn = createdFunctions.get(functionValue);
|
||||||
|
if (fn) {
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
fn = new Function(`return ${functionValue}`)() as (
|
||||||
|
...args: unknown[]
|
||||||
|
) => unknown;
|
||||||
|
createdFunctions.set(functionValue, fn);
|
||||||
|
return fn;
|
||||||
|
};
|
||||||
|
|
@ -33,7 +33,7 @@ import createHttpsProxyAgent, {
|
||||||
HttpsProxyAgentOptions,
|
HttpsProxyAgentOptions,
|
||||||
} from 'https-proxy-agent';
|
} from 'https-proxy-agent';
|
||||||
import {getProxyForUrl} from 'proxy-from-env';
|
import {getProxyForUrl} from 'proxy-from-env';
|
||||||
import {assert} from '../common/assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
|
|
||||||
import tar from 'tar-fs';
|
import tar from 'tar-fs';
|
||||||
import bzip from 'unbzip2-stream';
|
import bzip from 'unbzip2-stream';
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,17 @@ import * as path from 'path';
|
||||||
import * as readline from 'readline';
|
import * as readline from 'readline';
|
||||||
import removeFolder from 'rimraf';
|
import removeFolder from 'rimraf';
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
import {assert} from '../common/assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {Connection} from '../common/Connection.js';
|
import {Connection} from '../common/Connection.js';
|
||||||
import {debug} from '../common/Debug.js';
|
import {debug} from '../common/Debug.js';
|
||||||
import {TimeoutError} from '../common/Errors.js';
|
import {TimeoutError} from '../common/Errors.js';
|
||||||
import {
|
import {
|
||||||
debugError,
|
debugError,
|
||||||
addEventListener,
|
addEventListener,
|
||||||
isErrnoException,
|
|
||||||
isErrorLike,
|
|
||||||
PuppeteerEventListener,
|
PuppeteerEventListener,
|
||||||
removeEventListeners,
|
removeEventListeners,
|
||||||
} from '../common/util.js';
|
} from '../common/util.js';
|
||||||
|
import {isErrnoException, isErrorLike} from '../util/ErrorLike.js';
|
||||||
import {Product} from '../common/Product.js';
|
import {Product} from '../common/Product.js';
|
||||||
import {NodeWebSocketTransport as WebSocketTransport} from '../node/NodeWebSocketTransport.js';
|
import {NodeWebSocketTransport as WebSocketTransport} from '../node/NodeWebSocketTransport.js';
|
||||||
import {LaunchOptions} from './LaunchOptions.js';
|
import {LaunchOptions} from './LaunchOptions.js';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {assert} from '../common/assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {Browser} from '../common/Browser.js';
|
import {Browser} from '../common/Browser.js';
|
||||||
import {Product} from '../common/Product.js';
|
import {Product} from '../common/Product.js';
|
||||||
import {BrowserRunner} from './BrowserRunner.js';
|
import {BrowserRunner} from './BrowserRunner.js';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {assert} from '../common/assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {Browser} from '../common/Browser.js';
|
import {Browser} from '../common/Browser.js';
|
||||||
import {Product} from '../common/Product.js';
|
import {Product} from '../common/Product.js';
|
||||||
import {BrowserFetcher} from './BrowserFetcher.js';
|
import {BrowserFetcher} from './BrowserFetcher.js';
|
||||||
|
|
|
||||||
|
|
@ -16,27 +16,12 @@
|
||||||
import NodeWebSocket from 'ws';
|
import NodeWebSocket from 'ws';
|
||||||
import {ConnectionTransport} from '../common/ConnectionTransport.js';
|
import {ConnectionTransport} from '../common/ConnectionTransport.js';
|
||||||
import {packageVersion} from '../generated/version.js';
|
import {packageVersion} from '../generated/version.js';
|
||||||
import {promises as dns} from 'dns';
|
|
||||||
import {URL} from 'url';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class NodeWebSocketTransport implements ConnectionTransport {
|
export class NodeWebSocketTransport implements ConnectionTransport {
|
||||||
static async create(urlString: string): Promise<NodeWebSocketTransport> {
|
static create(url: string): Promise<NodeWebSocketTransport> {
|
||||||
// TODO(jrandolf): Starting in Node 17, IPv6 is favoured over IPv4 due to a change
|
|
||||||
// in a default option:
|
|
||||||
// - https://github.com/nodejs/node/issues/40537,
|
|
||||||
// Due to this, for Firefox, we must parse and resolve the `localhost` hostname
|
|
||||||
// manually with the previous behavior according to:
|
|
||||||
// - https://nodejs.org/api/dns.html#dnslookuphostname-options-callback
|
|
||||||
// because of https://bugzilla.mozilla.org/show_bug.cgi?id=1769994.
|
|
||||||
const url = new URL(urlString);
|
|
||||||
if (url.hostname === 'localhost') {
|
|
||||||
const {address} = await dns.lookup(url.hostname, {verbatim: false});
|
|
||||||
url.hostname = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const ws = new NodeWebSocket(url, [], {
|
const ws = new NodeWebSocket(url, [], {
|
||||||
followRedirects: true,
|
followRedirects: true,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import {assert} from '../common/assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {ConnectionTransport} from '../common/ConnectionTransport.js';
|
import {ConnectionTransport} from '../common/ConnectionTransport.js';
|
||||||
import {
|
import {
|
||||||
addEventListener,
|
addEventListener,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,6 @@
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export const PUPPETEER_REVISIONS = Object.freeze({
|
export const PUPPETEER_REVISIONS = Object.freeze({
|
||||||
chromium: '1022525',
|
chromium: '1036745',
|
||||||
firefox: 'latest',
|
firefox: 'latest',
|
||||||
});
|
});
|
||||||
|
|
|
||||||
3
remote/test/puppeteer/src/templates/README.md
Normal file
3
remote/test/puppeteer/src/templates/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Templated Artifacts
|
||||||
|
|
||||||
|
These files are generated as TypeScript files in the `src/generated` folder.
|
||||||
10
remote/test/puppeteer/src/templates/injected.ts.tmpl
Normal file
10
remote/test/puppeteer/src/templates/injected.ts.tmpl
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import {createDeferredPromise} from '../util/DeferredPromise.js';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
const InjectedUtil: {
|
||||||
|
createDeferredPromise: typeof createDeferredPromise;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export const source = SOURCE_CODE;
|
||||||
|
|
@ -8,5 +8,6 @@
|
||||||
"references": [
|
"references": [
|
||||||
{"path": "../vendor/tsconfig.cjs.json"},
|
{"path": "../vendor/tsconfig.cjs.json"},
|
||||||
{"path": "../compat/cjs/tsconfig.json"}
|
{"path": "../compat/cjs/tsconfig.json"}
|
||||||
]
|
],
|
||||||
|
"exclude": ["injected/injected.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,6 @@
|
||||||
"references": [
|
"references": [
|
||||||
{"path": "../vendor/tsconfig.esm.json"},
|
{"path": "../vendor/tsconfig.esm.json"},
|
||||||
{"path": "../compat/esm/tsconfig.json"}
|
{"path": "../compat/esm/tsconfig.json"}
|
||||||
]
|
],
|
||||||
|
"exclude": ["injected/injected.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,5 @@
|
||||||
// AUTOGENERATED - Use `utils/export_all.js` to regenerate.
|
// AUTOGENERATED - Use `npm run generate:sources` to regenerate.
|
||||||
|
|
||||||
export * from './compat.d.js';
|
|
||||||
export * from './constants.js';
|
|
||||||
export * from './environment.js';
|
|
||||||
export * from './initializePuppeteer.js';
|
|
||||||
export * from './puppeteer.js';
|
|
||||||
export * from './revisions.js';
|
|
||||||
|
|
||||||
// Exports from `common`
|
|
||||||
export * from './common/Accessibility.js';
|
export * from './common/Accessibility.js';
|
||||||
export * from './common/AriaQueryHandler.js';
|
export * from './common/AriaQueryHandler.js';
|
||||||
export * from './common/Browser.js';
|
export * from './common/Browser.js';
|
||||||
|
|
@ -26,8 +18,10 @@ export * from './common/EmulationManager.js';
|
||||||
export * from './common/Errors.js';
|
export * from './common/Errors.js';
|
||||||
export * from './common/EventEmitter.js';
|
export * from './common/EventEmitter.js';
|
||||||
export * from './common/ExecutionContext.js';
|
export * from './common/ExecutionContext.js';
|
||||||
|
export * from './common/fetch.js';
|
||||||
export * from './common/FileChooser.js';
|
export * from './common/FileChooser.js';
|
||||||
export * from './common/FirefoxTargetManager.js';
|
export * from './common/FirefoxTargetManager.js';
|
||||||
|
export * from './common/Frame.js';
|
||||||
export * from './common/FrameManager.js';
|
export * from './common/FrameManager.js';
|
||||||
export * from './common/HTTPRequest.js';
|
export * from './common/HTTPRequest.js';
|
||||||
export * from './common/HTTPResponse.js';
|
export * from './common/HTTPResponse.js';
|
||||||
|
|
@ -38,8 +32,8 @@ export * from './common/LifecycleWatcher.js';
|
||||||
export * from './common/NetworkConditions.js';
|
export * from './common/NetworkConditions.js';
|
||||||
export * from './common/NetworkEventManager.js';
|
export * from './common/NetworkEventManager.js';
|
||||||
export * from './common/NetworkManager.js';
|
export * from './common/NetworkManager.js';
|
||||||
export * from './common/PDFOptions.js';
|
|
||||||
export * from './common/Page.js';
|
export * from './common/Page.js';
|
||||||
|
export * from './common/PDFOptions.js';
|
||||||
export * from './common/Product.js';
|
export * from './common/Product.js';
|
||||||
export * from './common/Puppeteer.js';
|
export * from './common/Puppeteer.js';
|
||||||
export * from './common/PuppeteerViewport.js';
|
export * from './common/PuppeteerViewport.js';
|
||||||
|
|
@ -50,25 +44,31 @@ export * from './common/TargetManager.js';
|
||||||
export * from './common/TaskQueue.js';
|
export * from './common/TaskQueue.js';
|
||||||
export * from './common/TimeoutSettings.js';
|
export * from './common/TimeoutSettings.js';
|
||||||
export * from './common/Tracing.js';
|
export * from './common/Tracing.js';
|
||||||
export * from './common/USKeyboardLayout.js';
|
|
||||||
export * from './common/WebWorker.js';
|
|
||||||
export * from './common/assert.js';
|
|
||||||
export * from './common/fetch.js';
|
|
||||||
export * from './common/types.js';
|
export * from './common/types.js';
|
||||||
|
export * from './common/USKeyboardLayout.js';
|
||||||
export * from './common/util.js';
|
export * from './common/util.js';
|
||||||
|
export * from './common/WebWorker.js';
|
||||||
// Exports from `node`
|
export * from './compat.d.js';
|
||||||
|
export * from './constants.js';
|
||||||
|
export * from './environment.js';
|
||||||
|
export * from './generated/injected.js';
|
||||||
|
export * from './generated/version.js';
|
||||||
|
export * from './initializePuppeteer.js';
|
||||||
export * from './node/BrowserFetcher.js';
|
export * from './node/BrowserFetcher.js';
|
||||||
export * from './node/BrowserRunner.js';
|
export * from './node/BrowserRunner.js';
|
||||||
export * from './node/ChromeLauncher.js';
|
export * from './node/ChromeLauncher.js';
|
||||||
export * from './node/FirefoxLauncher.js';
|
export * from './node/FirefoxLauncher.js';
|
||||||
|
export * from './node/install.js';
|
||||||
export * from './node/LaunchOptions.js';
|
export * from './node/LaunchOptions.js';
|
||||||
export * from './node/NodeWebSocketTransport.js';
|
export * from './node/NodeWebSocketTransport.js';
|
||||||
export * from './node/PipeTransport.js';
|
export * from './node/PipeTransport.js';
|
||||||
export * from './node/ProductLauncher.js';
|
export * from './node/ProductLauncher.js';
|
||||||
export * from './node/Puppeteer.js';
|
export * from './node/Puppeteer.js';
|
||||||
export * from './node/install.js';
|
|
||||||
export * from './node/util.js';
|
export * from './node/util.js';
|
||||||
|
export * from './puppeteer.js';
|
||||||
// Exports from `generated`
|
export * from './revisions.js';
|
||||||
export * from './generated/version.js';
|
export * from './util/assert.js';
|
||||||
|
export * from './util/DebuggableDeferredPromise.js';
|
||||||
|
export * from './util/DeferredPromise.js';
|
||||||
|
export * from './util/ErrorLike.js';
|
||||||
|
export * from './util/getPackageDirectory.js';
|
||||||
|
|
|
||||||
20
remote/test/puppeteer/src/util/DebuggableDeferredPromise.ts
Normal file
20
remote/test/puppeteer/src/util/DebuggableDeferredPromise.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import {DEFERRED_PROMISE_DEBUG_TIMEOUT} from '../environment.js';
|
||||||
|
import {DeferredPromise, createDeferredPromise} from './DeferredPromise.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a deferred promise using DEFERRED_PROMISE_DEBUG_TIMEOUT
|
||||||
|
* if it's specified or a normal deferred promise otherwise.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function createDebuggableDeferredPromise<T>(
|
||||||
|
message: string
|
||||||
|
): DeferredPromise<T> {
|
||||||
|
if (DEFERRED_PROMISE_DEBUG_TIMEOUT > 0) {
|
||||||
|
return createDeferredPromise({
|
||||||
|
message,
|
||||||
|
timeout: DEFERRED_PROMISE_DEBUG_TIMEOUT,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return createDeferredPromise();
|
||||||
|
}
|
||||||
68
remote/test/puppeteer/src/util/DeferredPromise.ts
Normal file
68
remote/test/puppeteer/src/util/DeferredPromise.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import {TimeoutError} from '../common/Errors.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export interface DeferredPromise<T> extends Promise<T> {
|
||||||
|
finished: () => boolean;
|
||||||
|
resolved: () => boolean;
|
||||||
|
resolve: (_: T) => void;
|
||||||
|
reject: (_: Error) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export interface DeferredPromiseOptions {
|
||||||
|
message: string;
|
||||||
|
timeout: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a promise along with the resolve/reject functions.
|
||||||
|
*
|
||||||
|
* If the promise has not been resolved/rejected within the `timeout` period,
|
||||||
|
* the promise gets rejected with a timeout error. `timeout` has to be greater than 0 or
|
||||||
|
* it is ignored.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function createDeferredPromise<T>(
|
||||||
|
opts?: DeferredPromiseOptions
|
||||||
|
): DeferredPromise<T> {
|
||||||
|
let isResolved = false;
|
||||||
|
let isRejected = false;
|
||||||
|
let resolver = (_: T): void => {};
|
||||||
|
let rejector = (_: Error) => {};
|
||||||
|
const taskPromise = new Promise<T>((resolve, reject) => {
|
||||||
|
resolver = resolve;
|
||||||
|
rejector = reject;
|
||||||
|
});
|
||||||
|
const timeoutId =
|
||||||
|
opts && opts.timeout > 0
|
||||||
|
? setTimeout(() => {
|
||||||
|
isRejected = true;
|
||||||
|
rejector(new TimeoutError(opts.message));
|
||||||
|
}, opts.timeout)
|
||||||
|
: undefined;
|
||||||
|
return Object.assign(taskPromise, {
|
||||||
|
resolved: () => {
|
||||||
|
return isResolved;
|
||||||
|
},
|
||||||
|
finished: () => {
|
||||||
|
return isResolved || isRejected;
|
||||||
|
},
|
||||||
|
resolve: (value: T) => {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
isResolved = true;
|
||||||
|
resolver(value);
|
||||||
|
},
|
||||||
|
reject: (err: Error) => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
isRejected = true;
|
||||||
|
rejector(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
27
remote/test/puppeteer/src/util/ErrorLike.ts
Normal file
27
remote/test/puppeteer/src/util/ErrorLike.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
|
);
|
||||||
|
}
|
||||||
18
remote/test/puppeteer/src/util/getPackageDirectory.ts
Normal file
18
remote/test/puppeteer/src/util/getPackageDirectory.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import {existsSync} from 'fs';
|
||||||
|
import {dirname, join, parse} from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export const getPackageDirectory = (from: string): string => {
|
||||||
|
let found = existsSync(join(from, 'package.json'));
|
||||||
|
const root = parse(from).root;
|
||||||
|
while (!found) {
|
||||||
|
if (from === root) {
|
||||||
|
throw new Error('Cannot find package directory');
|
||||||
|
}
|
||||||
|
from = dirname(from);
|
||||||
|
found = existsSync(join(from, 'package.json'));
|
||||||
|
}
|
||||||
|
return from;
|
||||||
|
};
|
||||||
|
|
@ -6,6 +6,7 @@ function foo() {
|
||||||
console.log(2);
|
console.log(2);
|
||||||
let x = 1 > 2 ? 'foo' : 'bar';
|
let x = 1 > 2 ? 'foo' : 'bar';
|
||||||
let y = 1 < 2 ? 'foo' : 'bar';
|
let y = 1 < 2 ? 'foo' : 'bar';
|
||||||
|
let p = {a:1 > 2?function(){console.log('unused');}:function(){console.log('unused');}};
|
||||||
let z = () => {};
|
let z = () => {};
|
||||||
let q = () => {};
|
let q = () => {};
|
||||||
q();
|
q();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
// empty
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "Simple extension",
|
||||||
|
"version": "0.1",
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js"
|
||||||
|
},
|
||||||
|
"permissions": ["background", "activeTab"],
|
||||||
|
"manifest_version": 3
|
||||||
|
}
|
||||||
|
|
@ -16,13 +16,21 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 148,
|
"start": 148,
|
||||||
"end": 160
|
"end": 168
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 168,
|
"start": 203,
|
||||||
"end": 207
|
"end": 204
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": 238,
|
||||||
|
"end": 251
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": 259,
|
||||||
|
"end": 298
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"text": "\nfunction foo() {\n if (1 > 2)\n console.log(1);\n if (1 < 2)\n console.log(2);\n let x = 1 > 2 ? 'foo' : 'bar';\n let y = 1 < 2 ? 'foo' : 'bar';\n let z = () => {};\n let q = () => {};\n q();\n}\n\nfoo();\n"
|
"text": "\nfunction foo() {\n if (1 > 2)\n console.log(1);\n if (1 < 2)\n console.log(2);\n let x = 1 > 2 ? 'foo' : 'bar';\n let y = 1 < 2 ? 'foo' : 'bar';\n let p = {a:1 > 2?function(){console.log('unused');}:function(){console.log('unused');}};\n let z = () => {};\n let q = () => {};\n q();\n}\n\nfoo();\n"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
|
|
@ -22,7 +22,7 @@ import {
|
||||||
setupTestPageAndContextHooks,
|
setupTestPageAndContextHooks,
|
||||||
describeChromeOnly,
|
describeChromeOnly,
|
||||||
} from './mocha-utils.js';
|
} from './mocha-utils.js';
|
||||||
import {isErrorLike} from '../../lib/cjs/puppeteer/common/util.js';
|
import {isErrorLike} from '../../lib/cjs/puppeteer/util/ErrorLike.js';
|
||||||
|
|
||||||
describeChromeOnly('Target.createCDPSession', function () {
|
describeChromeOnly('Target.createCDPSession', function () {
|
||||||
setupTestBrowserHooks();
|
setupTestBrowserHooks();
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import {
|
||||||
} from '../../lib/cjs/puppeteer/common/NetworkManager.js';
|
} from '../../lib/cjs/puppeteer/common/NetworkManager.js';
|
||||||
import {HTTPRequest} from '../../lib/cjs/puppeteer/common/HTTPRequest.js';
|
import {HTTPRequest} from '../../lib/cjs/puppeteer/common/HTTPRequest.js';
|
||||||
import {EventEmitter} from '../../lib/cjs/puppeteer/common/EventEmitter.js';
|
import {EventEmitter} from '../../lib/cjs/puppeteer/common/EventEmitter.js';
|
||||||
import {Frame} from '../../lib/cjs/puppeteer/common/FrameManager.js';
|
import {Frame} from '../../lib/cjs/puppeteer/common/Frame.js';
|
||||||
import {HTTPResponse} from '../../lib/cjs/puppeteer/common/HTTPResponse.js';
|
import {HTTPResponse} from '../../lib/cjs/puppeteer/common/HTTPResponse.js';
|
||||||
|
|
||||||
class MockCDPSession extends EventEmitter {
|
class MockCDPSession extends EventEmitter {
|
||||||
|
|
|
||||||
|
|
@ -334,7 +334,7 @@ describeChromeOnly('AriaQueryHandler', () => {
|
||||||
await otherFrame!.evaluate(addElement, 'button');
|
await otherFrame!.evaluate(addElement, 'button');
|
||||||
await page.evaluate(addElement, 'button');
|
await page.evaluate(addElement, 'button');
|
||||||
const elementHandle = await watchdog;
|
const elementHandle = await watchdog;
|
||||||
expect(elementHandle!.executionContext().frame()).toBe(page.mainFrame());
|
expect(elementHandle!.frame).toBe(page.mainFrame());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run in specified frame', async () => {
|
it('should run in specified frame', async () => {
|
||||||
|
|
@ -350,7 +350,7 @@ describeChromeOnly('AriaQueryHandler', () => {
|
||||||
await frame1!.evaluate(addElement, 'button');
|
await frame1!.evaluate(addElement, 'button');
|
||||||
await frame2!.evaluate(addElement, 'button');
|
await frame2!.evaluate(addElement, 'button');
|
||||||
const elementHandle = await waitForSelectorPromise;
|
const elementHandle = await waitForSelectorPromise;
|
||||||
expect(elementHandle!.executionContext().frame()).toBe(frame2);
|
expect(elementHandle!.frame).toBe(frame2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when frame is detached', async () => {
|
it('should throw when frame is detached', async () => {
|
||||||
|
|
@ -687,7 +687,7 @@ describeChromeOnly('AriaQueryHandler', () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
const found = await page.$$('aria/title');
|
const found = await page.$$('aria/title');
|
||||||
const ids = await getIds(found);
|
const ids = await getIds(found);
|
||||||
expect(ids).toEqual(['shown', 'hidden']);
|
expect(ids).toEqual(['shown']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -104,9 +104,11 @@ describe('Coverage specs', function () {
|
||||||
const coverage = await page.coverage.stopJSCoverage();
|
const coverage = await page.coverage.stopJSCoverage();
|
||||||
expect(coverage.length).toBe(1);
|
expect(coverage.length).toBe(1);
|
||||||
const entry = coverage[0]!;
|
const entry = coverage[0]!;
|
||||||
expect(entry.ranges.length).toBe(1);
|
expect(entry.ranges.length).toBe(2);
|
||||||
const range = entry.ranges[0]!;
|
const range1 = entry.ranges[0]!;
|
||||||
expect(entry.text.substring(range.start, range.end)).toBe(
|
expect(entry.text.substring(range1.start, range1.end)).toBe('\n');
|
||||||
|
const range2 = entry.ranges[1]!;
|
||||||
|
expect(entry.text.substring(range2.start, range2.end)).toBe(
|
||||||
`console.log('used!');`
|
`console.log('used!');`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
import {CDPSession} from '../../lib/cjs/puppeteer/common/Connection.js';
|
import {CDPSession} from '../../lib/cjs/puppeteer/common/Connection.js';
|
||||||
import {Frame} from '../../lib/cjs/puppeteer/common/FrameManager.js';
|
import {Frame} from '../../lib/cjs/puppeteer/common/Frame.js';
|
||||||
import {
|
import {
|
||||||
getTestState,
|
getTestState,
|
||||||
setupTestBrowserHooks,
|
setupTestBrowserHooks,
|
||||||
|
|
@ -41,8 +41,8 @@ describe('Frame specs', function () {
|
||||||
expect(context1).toBeTruthy();
|
expect(context1).toBeTruthy();
|
||||||
expect(context2).toBeTruthy();
|
expect(context2).toBeTruthy();
|
||||||
expect(context1 !== context2).toBeTruthy();
|
expect(context1 !== context2).toBeTruthy();
|
||||||
expect(context1.frame()).toBe(frame1);
|
expect(context1._world?.frame()).toBe(frame1);
|
||||||
expect(context2.frame()).toBe(frame2);
|
expect(context2._world?.frame()).toBe(frame2);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
context1.evaluate(() => {
|
context1.evaluate(() => {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,13 @@ const mkdtempAsync = promisify(fs.mkdtemp);
|
||||||
const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-');
|
const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-');
|
||||||
|
|
||||||
const extensionPath = path.join(__dirname, '../assets', 'simple-extension');
|
const extensionPath = path.join(__dirname, '../assets', 'simple-extension');
|
||||||
|
const serviceWorkerExtensionPath = path.join(
|
||||||
|
__dirname,
|
||||||
|
'..',
|
||||||
|
'assets',
|
||||||
|
'serviceworkers',
|
||||||
|
'extension'
|
||||||
|
);
|
||||||
|
|
||||||
describeChromeOnly('headful tests', function () {
|
describeChromeOnly('headful tests', function () {
|
||||||
/* These tests fire up an actual browser so let's
|
/* These tests fire up an actual browser so let's
|
||||||
|
|
@ -120,7 +127,7 @@ describeChromeOnly('headful tests', function () {
|
||||||
);
|
);
|
||||||
const page = await browserWithExtension.newPage();
|
const page = await browserWithExtension.newPage();
|
||||||
const backgroundPageTarget = await browserWithExtension.waitForTarget(
|
const backgroundPageTarget = await browserWithExtension.waitForTarget(
|
||||||
(target: {type: () => string}) => {
|
target => {
|
||||||
return target.type() === 'background_page';
|
return target.type() === 'background_page';
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -128,6 +135,26 @@ describeChromeOnly('headful tests', function () {
|
||||||
await browserWithExtension.close();
|
await browserWithExtension.close();
|
||||||
expect(backgroundPageTarget).toBeTruthy();
|
expect(backgroundPageTarget).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
it('service_worker target type should be available', async () => {
|
||||||
|
const {puppeteer, defaultBrowserOptions} = getTestState();
|
||||||
|
const browserWithExtension = await launchBrowser(puppeteer, {
|
||||||
|
...defaultBrowserOptions,
|
||||||
|
headless: false,
|
||||||
|
args: [
|
||||||
|
`--disable-extensions-except=${serviceWorkerExtensionPath}`,
|
||||||
|
`--load-extension=${serviceWorkerExtensionPath}`,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const page = await browserWithExtension.newPage();
|
||||||
|
const serviceWorkerTarget = await browserWithExtension.waitForTarget(
|
||||||
|
target => {
|
||||||
|
return target.type() === 'service_worker';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await page.close();
|
||||||
|
await browserWithExtension.close();
|
||||||
|
expect(serviceWorkerTarget).toBeTruthy();
|
||||||
|
});
|
||||||
it('target.page() should return a background_page', async function () {
|
it('target.page() should return a background_page', async function () {
|
||||||
const {puppeteer} = getTestState();
|
const {puppeteer} = getTestState();
|
||||||
const browserWithExtension = await launchBrowser(
|
const browserWithExtension = await launchBrowser(
|
||||||
|
|
@ -135,7 +162,7 @@ describeChromeOnly('headful tests', function () {
|
||||||
extensionOptions
|
extensionOptions
|
||||||
);
|
);
|
||||||
const backgroundPageTarget = await browserWithExtension.waitForTarget(
|
const backgroundPageTarget = await browserWithExtension.waitForTarget(
|
||||||
(target: {type: () => string}) => {
|
target => {
|
||||||
return target.type() === 'background_page';
|
return target.type() === 'background_page';
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
40
remote/test/puppeteer/test/src/injected.spec.ts
Normal file
40
remote/test/puppeteer/test/src/injected.spec.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2022 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 expect from 'expect';
|
||||||
|
import '../../lib/cjs/puppeteer/generated/injected.js';
|
||||||
|
import {PUPPETEER_WORLD} from '../../lib/cjs/puppeteer/common/IsolatedWorld.js';
|
||||||
|
import {
|
||||||
|
getTestState,
|
||||||
|
setupTestBrowserHooks,
|
||||||
|
setupTestPageAndContextHooks,
|
||||||
|
} from './mocha-utils.js';
|
||||||
|
|
||||||
|
describe('InjectedUtil tests', function () {
|
||||||
|
setupTestBrowserHooks();
|
||||||
|
setupTestPageAndContextHooks();
|
||||||
|
|
||||||
|
it('should work', async () => {
|
||||||
|
const {page} = getTestState();
|
||||||
|
|
||||||
|
const handle = await page
|
||||||
|
.mainFrame()
|
||||||
|
.worlds[PUPPETEER_WORLD].evaluate(() => {
|
||||||
|
return typeof InjectedUtil === 'object';
|
||||||
|
});
|
||||||
|
expect(handle).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -375,9 +375,7 @@ describe('Launcher specs', function () {
|
||||||
// This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
|
// This might throw. See https://github.com/puppeteer/puppeteer/issues/2778
|
||||||
await rmAsync(userDataDir).catch(() => {});
|
await rmAsync(userDataDir).catch(() => {});
|
||||||
});
|
});
|
||||||
// This mysteriously fails on Windows on AppVeyor. See
|
it('userDataDir option should restore cookies', async () => {
|
||||||
// https://github.com/puppeteer/puppeteer/issues/4111
|
|
||||||
xit('userDataDir option should restore cookies', async () => {
|
|
||||||
const {server, puppeteer, defaultBrowserOptions} = getTestState();
|
const {server, puppeteer, defaultBrowserOptions} = getTestState();
|
||||||
|
|
||||||
const userDataDir = await mkdtempAsync(TMP_FOLDER);
|
const userDataDir = await mkdtempAsync(TMP_FOLDER);
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import {
|
||||||
BrowserContext,
|
BrowserContext,
|
||||||
} from '../../lib/cjs/puppeteer/common/Browser.js';
|
} from '../../lib/cjs/puppeteer/common/Browser.js';
|
||||||
import {Page} from '../../lib/cjs/puppeteer/common/Page.js';
|
import {Page} from '../../lib/cjs/puppeteer/common/Page.js';
|
||||||
import {isErrorLike} from '../../lib/cjs/puppeteer/common/util.js';
|
import {isErrorLike} from '../../lib/cjs/puppeteer/util/ErrorLike.js';
|
||||||
import {
|
import {
|
||||||
PuppeteerLaunchOptions,
|
PuppeteerLaunchOptions,
|
||||||
PuppeteerNode,
|
PuppeteerNode,
|
||||||
|
|
|
||||||
|
|
@ -22,21 +22,21 @@ import {
|
||||||
setupTestPageAndContextHooks,
|
setupTestPageAndContextHooks,
|
||||||
} from './mocha-utils.js';
|
} from './mocha-utils.js';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { ServerResponse } from 'http';
|
import {ServerResponse} from 'http';
|
||||||
import { HTTPRequest } from '../../lib/cjs/puppeteer/common/HTTPRequest.js';
|
import {HTTPRequest} from '../../lib/cjs/puppeteer/common/HTTPRequest.js';
|
||||||
|
|
||||||
describe('navigation', function () {
|
describe('navigation', function () {
|
||||||
setupTestBrowserHooks();
|
setupTestBrowserHooks();
|
||||||
setupTestPageAndContextHooks();
|
setupTestPageAndContextHooks();
|
||||||
describe('Page.goto', function () {
|
describe('Page.goto', function () {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
expect(page.url()).toBe(server.EMPTY_PAGE);
|
expect(page.url()).toBe(server.EMPTY_PAGE);
|
||||||
});
|
});
|
||||||
it('should work with anchor navigation', async () => {
|
it('should work with anchor navigation', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
expect(page.url()).toBe(server.EMPTY_PAGE);
|
expect(page.url()).toBe(server.EMPTY_PAGE);
|
||||||
|
|
@ -46,7 +46,7 @@ describe('navigation', function () {
|
||||||
expect(page.url()).toBe(server.EMPTY_PAGE + '#bar');
|
expect(page.url()).toBe(server.EMPTY_PAGE + '#bar');
|
||||||
});
|
});
|
||||||
it('should work with redirects', async () => {
|
it('should work with redirects', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
server.setRedirect('/redirect/1.html', '/redirect/2.html');
|
server.setRedirect('/redirect/1.html', '/redirect/2.html');
|
||||||
server.setRedirect('/redirect/2.html', '/empty.html');
|
server.setRedirect('/redirect/2.html', '/empty.html');
|
||||||
|
|
@ -54,19 +54,19 @@ describe('navigation', function () {
|
||||||
expect(page.url()).toBe(server.EMPTY_PAGE);
|
expect(page.url()).toBe(server.EMPTY_PAGE);
|
||||||
});
|
});
|
||||||
it('should navigate to about:blank', async () => {
|
it('should navigate to about:blank', async () => {
|
||||||
const { page } = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
const response = await page.goto('about:blank');
|
const response = await page.goto('about:blank');
|
||||||
expect(response).toBe(null);
|
expect(response).toBe(null);
|
||||||
});
|
});
|
||||||
it('should return response when page changes its URL after load', async () => {
|
it('should return response when page changes its URL after load', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
const response = (await page.goto(server.PREFIX + '/historyapi.html'))!;
|
const response = (await page.goto(server.PREFIX + '/historyapi.html'))!;
|
||||||
expect(response.status()).toBe(200);
|
expect(response.status()).toBe(200);
|
||||||
});
|
});
|
||||||
it('should work with subframes return 204', async () => {
|
it('should work with subframes return 204', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
server.setRoute('/frames/frame.html', (_req, res) => {
|
server.setRoute('/frames/frame.html', (_req, res) => {
|
||||||
res.statusCode = 204;
|
res.statusCode = 204;
|
||||||
|
|
@ -75,20 +75,20 @@ describe('navigation', function () {
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
await page
|
await page
|
||||||
.goto(server.PREFIX + '/frames/one-frame.html')
|
.goto(server.PREFIX + '/frames/one-frame.html')
|
||||||
.catch((error_) => {
|
.catch(error_ => {
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
expect(error).toBeUndefined();
|
expect(error).toBeUndefined();
|
||||||
});
|
});
|
||||||
it('should fail when server returns 204', async () => {
|
it('should fail when server returns 204', async () => {
|
||||||
const { page, server, isChrome } = getTestState();
|
const {page, server, isChrome} = getTestState();
|
||||||
|
|
||||||
server.setRoute('/empty.html', (_req, res) => {
|
server.setRoute('/empty.html', (_req, res) => {
|
||||||
res.statusCode = 204;
|
res.statusCode = 204;
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
await page.goto(server.EMPTY_PAGE).catch((error_) => {
|
await page.goto(server.EMPTY_PAGE).catch(error_ => {
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
expect(error).not.toBe(null);
|
expect(error).not.toBe(null);
|
||||||
|
|
@ -99,7 +99,7 @@ describe('navigation', function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should navigate to empty page with domcontentloaded', async () => {
|
it('should navigate to empty page with domcontentloaded', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
const response = await page.goto(server.EMPTY_PAGE, {
|
const response = await page.goto(server.EMPTY_PAGE, {
|
||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
|
|
@ -107,7 +107,7 @@ describe('navigation', function () {
|
||||||
expect(response!.status()).toBe(200);
|
expect(response!.status()).toBe(200);
|
||||||
});
|
});
|
||||||
it('should work when page calls history API in beforeunload', async () => {
|
it('should work when page calls history API in beforeunload', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
|
|
@ -122,27 +122,33 @@ describe('navigation', function () {
|
||||||
const response = await page.goto(server.PREFIX + '/grid.html');
|
const response = await page.goto(server.PREFIX + '/grid.html');
|
||||||
expect(response!.status()).toBe(200);
|
expect(response!.status()).toBe(200);
|
||||||
});
|
});
|
||||||
it('should navigate to empty page with networkidle0', async () => {
|
it(
|
||||||
const { page, server } = getTestState();
|
'should navigate to empty page with networkidle0',
|
||||||
|
async () => {
|
||||||
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
const response = await page.goto(server.EMPTY_PAGE, {
|
const response = await page.goto(server.EMPTY_PAGE, {
|
||||||
waitUntil: 'networkidle0',
|
waitUntil: 'networkidle0',
|
||||||
});
|
});
|
||||||
expect(response!.status()).toBe(200);
|
expect(response!.status()).toBe(200);
|
||||||
});
|
}
|
||||||
it('should navigate to empty page with networkidle2', async () => {
|
);
|
||||||
const { page, server } = getTestState();
|
it(
|
||||||
|
'should navigate to empty page with networkidle2',
|
||||||
|
async () => {
|
||||||
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
const response = await page.goto(server.EMPTY_PAGE, {
|
const response = await page.goto(server.EMPTY_PAGE, {
|
||||||
waitUntil: 'networkidle2',
|
waitUntil: 'networkidle2',
|
||||||
});
|
});
|
||||||
expect(response!.status()).toBe(200);
|
expect(response!.status()).toBe(200);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
it('should fail when navigating to bad url', async () => {
|
it('should fail when navigating to bad url', async () => {
|
||||||
const { page, isChrome } = getTestState();
|
const {page, isChrome} = getTestState();
|
||||||
|
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
await page.goto('asdfasdf').catch((error_) => {
|
await page.goto('asdfasdf').catch(error_ => {
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
if (isChrome) {
|
if (isChrome) {
|
||||||
|
|
@ -153,7 +159,7 @@ describe('navigation', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
function getExpectedSSLCertMessage(): string {
|
function getExpectedSSLCertMessage(): string {
|
||||||
const { headless } = getTestState();
|
const {headless} = getTestState();
|
||||||
/**
|
/**
|
||||||
* If you are running this on pre-Catalina versions of macOS this will fail
|
* If you are running this on pre-Catalina versions of macOS this will fail
|
||||||
* locally. Mac OSX Catalina outputs a different message than other
|
* locally. Mac OSX Catalina outputs a different message than other
|
||||||
|
|
@ -168,7 +174,7 @@ describe('navigation', function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should fail when navigating to bad SSL', async () => {
|
it('should fail when navigating to bad SSL', async () => {
|
||||||
const { page, httpsServer, isChrome } = getTestState();
|
const {page, httpsServer, isChrome} = getTestState();
|
||||||
|
|
||||||
// Make sure that network events do not emit 'undefined'.
|
// Make sure that network events do not emit 'undefined'.
|
||||||
// @see https://crbug.com/750469
|
// @see https://crbug.com/750469
|
||||||
|
|
@ -184,7 +190,7 @@ describe('navigation', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
await page.goto(httpsServer.EMPTY_PAGE).catch((error_) => {
|
await page.goto(httpsServer.EMPTY_PAGE).catch(error_ => {
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
if (isChrome) {
|
if (isChrome) {
|
||||||
|
|
@ -198,14 +204,12 @@ describe('navigation', function () {
|
||||||
expect(requests[1]!).toBe('requestfailed');
|
expect(requests[1]!).toBe('requestfailed');
|
||||||
});
|
});
|
||||||
it('should fail when navigating to bad SSL after redirects', async () => {
|
it('should fail when navigating to bad SSL after redirects', async () => {
|
||||||
const { page, server, httpsServer, isChrome } = getTestState();
|
const {page, server, httpsServer, isChrome} = getTestState();
|
||||||
|
|
||||||
server.setRedirect('/redirect/1.html', '/redirect/2.html');
|
server.setRedirect('/redirect/1.html', '/redirect/2.html');
|
||||||
server.setRedirect('/redirect/2.html', '/empty.html');
|
server.setRedirect('/redirect/2.html', '/empty.html');
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
await page
|
await page.goto(httpsServer.PREFIX + '/redirect/1.html').catch(error_ => {
|
||||||
.goto(httpsServer.PREFIX + '/redirect/1.html')
|
|
||||||
.catch((error_) => {
|
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
if (isChrome) {
|
if (isChrome) {
|
||||||
|
|
@ -214,27 +218,13 @@ describe('navigation', function () {
|
||||||
expect(error.message).toContain('SSL_ERROR_UNKNOWN');
|
expect(error.message).toContain('SSL_ERROR_UNKNOWN');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should throw if networkidle is passed as an option', async () => {
|
|
||||||
const { page, server } = getTestState();
|
|
||||||
|
|
||||||
let error!: Error;
|
|
||||||
await page
|
|
||||||
// @ts-expect-error purposefully passing an old option
|
|
||||||
.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' })
|
|
||||||
.catch((error_) => {
|
|
||||||
return (error = error_);
|
|
||||||
});
|
|
||||||
expect(error.message).toContain(
|
|
||||||
'"networkidle" option is no longer supported'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it('should fail when main resources failed to load', async () => {
|
it('should fail when main resources failed to load', async () => {
|
||||||
const { page, isChrome } = getTestState();
|
const {page, isChrome} = getTestState();
|
||||||
|
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
await page
|
await page
|
||||||
.goto('http://localhost:44123/non-existing-url')
|
.goto('http://localhost:44123/non-existing-url')
|
||||||
.catch((error_) => {
|
.catch(error_ => {
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
if (isChrome) {
|
if (isChrome) {
|
||||||
|
|
@ -244,61 +234,61 @@ describe('navigation', function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should fail when exceeding maximum navigation timeout', async () => {
|
it('should fail when exceeding maximum navigation timeout', async () => {
|
||||||
const { page, server, puppeteer } = getTestState();
|
const {page, server, puppeteer} = getTestState();
|
||||||
|
|
||||||
// Hang for request to the empty.html
|
// Hang for request to the empty.html
|
||||||
server.setRoute('/empty.html', () => {});
|
server.setRoute('/empty.html', () => {});
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
await page
|
await page
|
||||||
.goto(server.PREFIX + '/empty.html', { timeout: 1 })
|
.goto(server.PREFIX + '/empty.html', {timeout: 1})
|
||||||
.catch((error_) => {
|
.catch(error_ => {
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
|
expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
|
||||||
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
|
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
|
||||||
});
|
});
|
||||||
it('should fail when exceeding default maximum navigation timeout', async () => {
|
it('should fail when exceeding default maximum navigation timeout', async () => {
|
||||||
const { page, server, puppeteer } = getTestState();
|
const {page, server, puppeteer} = getTestState();
|
||||||
|
|
||||||
// Hang for request to the empty.html
|
// Hang for request to the empty.html
|
||||||
server.setRoute('/empty.html', () => {});
|
server.setRoute('/empty.html', () => {});
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
page.setDefaultNavigationTimeout(1);
|
page.setDefaultNavigationTimeout(1);
|
||||||
await page.goto(server.PREFIX + '/empty.html').catch((error_) => {
|
await page.goto(server.PREFIX + '/empty.html').catch(error_ => {
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
|
expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
|
||||||
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
|
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
|
||||||
});
|
});
|
||||||
it('should fail when exceeding default maximum timeout', async () => {
|
it('should fail when exceeding default maximum timeout', async () => {
|
||||||
const { page, server, puppeteer } = getTestState();
|
const {page, server, puppeteer} = getTestState();
|
||||||
|
|
||||||
// Hang for request to the empty.html
|
// Hang for request to the empty.html
|
||||||
server.setRoute('/empty.html', () => {});
|
server.setRoute('/empty.html', () => {});
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
page.setDefaultTimeout(1);
|
page.setDefaultTimeout(1);
|
||||||
await page.goto(server.PREFIX + '/empty.html').catch((error_) => {
|
await page.goto(server.PREFIX + '/empty.html').catch(error_ => {
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
|
expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
|
||||||
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
|
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
|
||||||
});
|
});
|
||||||
it('should prioritize default navigation timeout over default timeout', async () => {
|
it('should prioritize default navigation timeout over default timeout', async () => {
|
||||||
const { page, server, puppeteer } = getTestState();
|
const {page, server, puppeteer} = getTestState();
|
||||||
|
|
||||||
// Hang for request to the empty.html
|
// Hang for request to the empty.html
|
||||||
server.setRoute('/empty.html', () => {});
|
server.setRoute('/empty.html', () => {});
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
page.setDefaultTimeout(0);
|
page.setDefaultTimeout(0);
|
||||||
page.setDefaultNavigationTimeout(1);
|
page.setDefaultNavigationTimeout(1);
|
||||||
await page.goto(server.PREFIX + '/empty.html').catch((error_) => {
|
await page.goto(server.PREFIX + '/empty.html').catch(error_ => {
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
|
expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
|
||||||
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
|
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
|
||||||
});
|
});
|
||||||
it('should disable timeout when its set to 0', async () => {
|
it('should disable timeout when its set to 0', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
|
@ -306,34 +296,34 @@ describe('navigation', function () {
|
||||||
return (loaded = true);
|
return (loaded = true);
|
||||||
});
|
});
|
||||||
await page
|
await page
|
||||||
.goto(server.PREFIX + '/grid.html', { timeout: 0, waitUntil: ['load'] })
|
.goto(server.PREFIX + '/grid.html', {timeout: 0, waitUntil: ['load']})
|
||||||
.catch((error_) => {
|
.catch(error_ => {
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
expect(error).toBeUndefined();
|
expect(error).toBeUndefined();
|
||||||
expect(loaded).toBe(true);
|
expect(loaded).toBe(true);
|
||||||
});
|
});
|
||||||
it('should work when navigating to valid url', async () => {
|
it('should work when navigating to valid url', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
const response = (await page.goto(server.EMPTY_PAGE))!;
|
const response = (await page.goto(server.EMPTY_PAGE))!;
|
||||||
expect(response.ok()).toBe(true);
|
expect(response.ok()).toBe(true);
|
||||||
});
|
});
|
||||||
it('should work when navigating to data url', async () => {
|
it('should work when navigating to data url', async () => {
|
||||||
const { page } = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
const response = (await page.goto('data:text/html,hello'))!;
|
const response = (await page.goto('data:text/html,hello'))!;
|
||||||
expect(response.ok()).toBe(true);
|
expect(response.ok()).toBe(true);
|
||||||
});
|
});
|
||||||
it('should work when navigating to 404', async () => {
|
it('should work when navigating to 404', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
const response = (await page.goto(server.PREFIX + '/not-found'))!;
|
const response = (await page.goto(server.PREFIX + '/not-found'))!;
|
||||||
expect(response.ok()).toBe(false);
|
expect(response.ok()).toBe(false);
|
||||||
expect(response.status()).toBe(404);
|
expect(response.status()).toBe(404);
|
||||||
});
|
});
|
||||||
it('should return last response in redirect chain', async () => {
|
it('should return last response in redirect chain', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
server.setRedirect('/redirect/1.html', '/redirect/2.html');
|
server.setRedirect('/redirect/1.html', '/redirect/2.html');
|
||||||
server.setRedirect('/redirect/2.html', '/redirect/3.html');
|
server.setRedirect('/redirect/2.html', '/redirect/3.html');
|
||||||
|
|
@ -342,8 +332,10 @@ describe('navigation', function () {
|
||||||
expect(response.ok()).toBe(true);
|
expect(response.ok()).toBe(true);
|
||||||
expect(response.url()).toBe(server.EMPTY_PAGE);
|
expect(response.url()).toBe(server.EMPTY_PAGE);
|
||||||
});
|
});
|
||||||
it('should wait for network idle to succeed navigation', async () => {
|
it(
|
||||||
const { page, server } = getTestState();
|
'should wait for network idle to succeed navigation',
|
||||||
|
async () => {
|
||||||
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
let responses: ServerResponse[] = [];
|
let responses: ServerResponse[] = [];
|
||||||
// Hold on to a bunch of requests without answering.
|
// Hold on to a bunch of requests without answering.
|
||||||
|
|
@ -370,9 +362,12 @@ describe('navigation', function () {
|
||||||
|
|
||||||
// Navigate to a page which loads immediately and then does a bunch of
|
// Navigate to a page which loads immediately and then does a bunch of
|
||||||
// requests via javascript's fetch method.
|
// requests via javascript's fetch method.
|
||||||
const navigationPromise = page.goto(server.PREFIX + '/networkidle.html', {
|
const navigationPromise = page.goto(
|
||||||
|
server.PREFIX + '/networkidle.html',
|
||||||
|
{
|
||||||
waitUntil: 'networkidle0',
|
waitUntil: 'networkidle0',
|
||||||
});
|
}
|
||||||
|
);
|
||||||
// Track when the navigation gets completed.
|
// Track when the navigation gets completed.
|
||||||
let navigationFinished = false;
|
let navigationFinished = false;
|
||||||
navigationPromise.then(() => {
|
navigationPromise.then(() => {
|
||||||
|
|
@ -380,7 +375,7 @@ describe('navigation', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait for the page's 'load' event.
|
// Wait for the page's 'load' event.
|
||||||
await new Promise((fulfill) => {
|
await new Promise(fulfill => {
|
||||||
return page.once('load', fulfill);
|
return page.once('load', fulfill);
|
||||||
});
|
});
|
||||||
expect(navigationFinished).toBe(false);
|
expect(navigationFinished).toBe(false);
|
||||||
|
|
@ -414,12 +409,13 @@ describe('navigation', function () {
|
||||||
const response = (await navigationPromise)!;
|
const response = (await navigationPromise)!;
|
||||||
// Expect navigation to succeed.
|
// Expect navigation to succeed.
|
||||||
expect(response.ok()).toBe(true);
|
expect(response.ok()).toBe(true);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
it('should not leak listeners during navigation', async () => {
|
it('should not leak listeners during navigation', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
let warning = null;
|
let warning = null;
|
||||||
const warningHandler: NodeJS.WarningListener = (w) => {
|
const warningHandler: NodeJS.WarningListener = w => {
|
||||||
return (warning = w);
|
return (warning = w);
|
||||||
};
|
};
|
||||||
process.on('warning', warningHandler);
|
process.on('warning', warningHandler);
|
||||||
|
|
@ -430,10 +426,10 @@ describe('navigation', function () {
|
||||||
expect(warning).toBe(null);
|
expect(warning).toBe(null);
|
||||||
});
|
});
|
||||||
it('should not leak listeners during bad navigation', async () => {
|
it('should not leak listeners during bad navigation', async () => {
|
||||||
const { page } = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
let warning = null;
|
let warning = null;
|
||||||
const warningHandler: NodeJS.WarningListener = (w) => {
|
const warningHandler: NodeJS.WarningListener = w => {
|
||||||
return (warning = w);
|
return (warning = w);
|
||||||
};
|
};
|
||||||
process.on('warning', warningHandler);
|
process.on('warning', warningHandler);
|
||||||
|
|
@ -446,10 +442,10 @@ describe('navigation', function () {
|
||||||
expect(warning).toBe(null);
|
expect(warning).toBe(null);
|
||||||
});
|
});
|
||||||
it('should not leak listeners during navigation of 11 pages', async () => {
|
it('should not leak listeners during navigation of 11 pages', async () => {
|
||||||
const { context, server } = getTestState();
|
const {context, server} = getTestState();
|
||||||
|
|
||||||
let warning = null;
|
let warning = null;
|
||||||
const warningHandler: NodeJS.WarningListener = (w) => {
|
const warningHandler: NodeJS.WarningListener = w => {
|
||||||
return (warning = w);
|
return (warning = w);
|
||||||
};
|
};
|
||||||
process.on('warning', warningHandler);
|
process.on('warning', warningHandler);
|
||||||
|
|
@ -463,11 +459,13 @@ describe('navigation', function () {
|
||||||
process.removeListener('warning', warningHandler);
|
process.removeListener('warning', warningHandler);
|
||||||
expect(warning).toBe(null);
|
expect(warning).toBe(null);
|
||||||
});
|
});
|
||||||
it('should navigate to dataURL and fire dataURL requests', async () => {
|
it(
|
||||||
const { page } = getTestState();
|
'should navigate to dataURL and fire dataURL requests',
|
||||||
|
async () => {
|
||||||
|
const {page} = getTestState();
|
||||||
|
|
||||||
const requests: HTTPRequest[] = [];
|
const requests: HTTPRequest[] = [];
|
||||||
page.on('request', (request) => {
|
page.on('request', request => {
|
||||||
return !utils.isFavicon(request) && requests.push(request);
|
return !utils.isFavicon(request) && requests.push(request);
|
||||||
});
|
});
|
||||||
const dataURL = 'data:text/html,<div>yo</div>';
|
const dataURL = 'data:text/html,<div>yo</div>';
|
||||||
|
|
@ -475,12 +473,15 @@ describe('navigation', function () {
|
||||||
expect(response.status()).toBe(200);
|
expect(response.status()).toBe(200);
|
||||||
expect(requests.length).toBe(1);
|
expect(requests.length).toBe(1);
|
||||||
expect(requests[0]!.url()).toBe(dataURL);
|
expect(requests[0]!.url()).toBe(dataURL);
|
||||||
});
|
}
|
||||||
it('should navigate to URL with hash and fire requests without hash', async () => {
|
);
|
||||||
const { page, server } = getTestState();
|
it(
|
||||||
|
'should navigate to URL with hash and fire requests without hash',
|
||||||
|
async () => {
|
||||||
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
const requests: HTTPRequest[] = [];
|
const requests: HTTPRequest[] = [];
|
||||||
page.on('request', (request) => {
|
page.on('request', request => {
|
||||||
return !utils.isFavicon(request) && requests.push(request);
|
return !utils.isFavicon(request) && requests.push(request);
|
||||||
});
|
});
|
||||||
const response = (await page.goto(server.EMPTY_PAGE + '#hash'))!;
|
const response = (await page.goto(server.EMPTY_PAGE + '#hash'))!;
|
||||||
|
|
@ -488,16 +489,17 @@ describe('navigation', function () {
|
||||||
expect(response.url()).toBe(server.EMPTY_PAGE);
|
expect(response.url()).toBe(server.EMPTY_PAGE);
|
||||||
expect(requests.length).toBe(1);
|
expect(requests.length).toBe(1);
|
||||||
expect(requests[0]!.url()).toBe(server.EMPTY_PAGE);
|
expect(requests[0]!.url()).toBe(server.EMPTY_PAGE);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
it('should work with self requesting page', async () => {
|
it('should work with self requesting page', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
const response = (await page.goto(server.PREFIX + '/self-request.html'))!;
|
const response = (await page.goto(server.PREFIX + '/self-request.html'))!;
|
||||||
expect(response.status()).toBe(200);
|
expect(response.status()).toBe(200);
|
||||||
expect(response.url()).toContain('self-request.html');
|
expect(response.url()).toContain('self-request.html');
|
||||||
});
|
});
|
||||||
it('should fail when navigating and show the url at the error message', async () => {
|
it('should fail when navigating and show the url at the error message', async () => {
|
||||||
const { page, httpsServer } = getTestState();
|
const {page, httpsServer} = getTestState();
|
||||||
|
|
||||||
const url = httpsServer.PREFIX + '/redirect/1.html';
|
const url = httpsServer.PREFIX + '/redirect/1.html';
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
|
|
@ -509,7 +511,7 @@ describe('navigation', function () {
|
||||||
expect(error.message).toContain(url);
|
expect(error.message).toContain(url);
|
||||||
});
|
});
|
||||||
it('should send referer', async () => {
|
it('should send referer', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
const [request1, request2] = await Promise.all([
|
const [request1, request2] = await Promise.all([
|
||||||
server.waitForRequest('/grid.html'),
|
server.waitForRequest('/grid.html'),
|
||||||
|
|
@ -526,7 +528,7 @@ describe('navigation', function () {
|
||||||
|
|
||||||
describe('Page.waitForNavigation', function () {
|
describe('Page.waitForNavigation', function () {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
const [response] = await Promise.all([
|
const [response] = await Promise.all([
|
||||||
|
|
@ -539,7 +541,7 @@ describe('navigation', function () {
|
||||||
expect(response!.url()).toContain('grid.html');
|
expect(response!.url()).toContain('grid.html');
|
||||||
});
|
});
|
||||||
it('should work with both domcontentloaded and load', async () => {
|
it('should work with both domcontentloaded and load', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
let response!: ServerResponse;
|
let response!: ServerResponse;
|
||||||
server.setRoute('/one-style.css', (_req, res) => {
|
server.setRoute('/one-style.css', (_req, res) => {
|
||||||
|
|
@ -567,7 +569,7 @@ describe('navigation', function () {
|
||||||
await navigationPromise;
|
await navigationPromise;
|
||||||
});
|
});
|
||||||
it('should work with clicking on anchor links', async () => {
|
it('should work with clicking on anchor links', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent(`<a href='#foobar'>foobar</a>`);
|
await page.setContent(`<a href='#foobar'>foobar</a>`);
|
||||||
|
|
@ -579,7 +581,7 @@ describe('navigation', function () {
|
||||||
expect(page.url()).toBe(server.EMPTY_PAGE + '#foobar');
|
expect(page.url()).toBe(server.EMPTY_PAGE + '#foobar');
|
||||||
});
|
});
|
||||||
it('should work with history.pushState()', async () => {
|
it('should work with history.pushState()', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent(`
|
await page.setContent(`
|
||||||
|
|
@ -596,7 +598,7 @@ describe('navigation', function () {
|
||||||
expect(page.url()).toBe(server.PREFIX + '/wow.html');
|
expect(page.url()).toBe(server.PREFIX + '/wow.html');
|
||||||
});
|
});
|
||||||
it('should work with history.replaceState()', async () => {
|
it('should work with history.replaceState()', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent(`
|
await page.setContent(`
|
||||||
|
|
@ -612,8 +614,10 @@ describe('navigation', function () {
|
||||||
expect(response).toBe(null);
|
expect(response).toBe(null);
|
||||||
expect(page.url()).toBe(server.PREFIX + '/replaced.html');
|
expect(page.url()).toBe(server.PREFIX + '/replaced.html');
|
||||||
});
|
});
|
||||||
it('should work with DOM history.back()/history.forward()', async () => {
|
it(
|
||||||
const { page, server } = getTestState();
|
'should work with DOM history.back()/history.forward()',
|
||||||
|
async () => {
|
||||||
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent(`
|
await page.setContent(`
|
||||||
|
|
@ -639,17 +643,20 @@ describe('navigation', function () {
|
||||||
]);
|
]);
|
||||||
expect(forwardResponse).toBe(null);
|
expect(forwardResponse).toBe(null);
|
||||||
expect(page.url()).toBe(server.PREFIX + '/second.html');
|
expect(page.url()).toBe(server.PREFIX + '/second.html');
|
||||||
});
|
}
|
||||||
it('should work when subframe issues window.stop()', async () => {
|
);
|
||||||
const { page, server } = getTestState();
|
it(
|
||||||
|
'should work when subframe issues window.stop()',
|
||||||
|
async () => {
|
||||||
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
server.setRoute('/frames/style.css', () => {});
|
server.setRoute('/frames/style.css', () => {});
|
||||||
const navigationPromise = page.goto(
|
const navigationPromise = page.goto(
|
||||||
server.PREFIX + '/frames/one-frame.html'
|
server.PREFIX + '/frames/one-frame.html'
|
||||||
);
|
);
|
||||||
const frame = await utils.waitEvent(page, 'frameattached');
|
const frame = await utils.waitEvent(page, 'frameattached');
|
||||||
await new Promise<void>((fulfill) => {
|
await new Promise<void>(fulfill => {
|
||||||
page.on('framenavigated', (f) => {
|
page.on('framenavigated', f => {
|
||||||
if (f === frame) {
|
if (f === frame) {
|
||||||
fulfill();
|
fulfill();
|
||||||
}
|
}
|
||||||
|
|
@ -661,12 +668,13 @@ describe('navigation', function () {
|
||||||
}),
|
}),
|
||||||
navigationPromise,
|
navigationPromise,
|
||||||
]);
|
]);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Page.goBack', function () {
|
describe('Page.goBack', function () {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.goto(server.PREFIX + '/grid.html');
|
await page.goto(server.PREFIX + '/grid.html');
|
||||||
|
|
@ -683,7 +691,7 @@ describe('navigation', function () {
|
||||||
expect(response).toBe(null);
|
expect(response).toBe(null);
|
||||||
});
|
});
|
||||||
it('should work with HistoryAPI', async () => {
|
it('should work with HistoryAPI', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
|
|
@ -703,7 +711,7 @@ describe('navigation', function () {
|
||||||
|
|
||||||
describe('Frame.goto', function () {
|
describe('Frame.goto', function () {
|
||||||
it('should navigate subframes', async () => {
|
it('should navigate subframes', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.PREFIX + '/frames/one-frame.html');
|
await page.goto(server.PREFIX + '/frames/one-frame.html');
|
||||||
expect(page.frames()[0]!.url()).toContain('/frames/one-frame.html');
|
expect(page.frames()[0]!.url()).toContain('/frames/one-frame.html');
|
||||||
|
|
@ -714,7 +722,7 @@ describe('navigation', function () {
|
||||||
expect(response.frame()).toBe(page.frames()[1]!);
|
expect(response.frame()).toBe(page.frames()[1]!);
|
||||||
});
|
});
|
||||||
it('should reject when frame detaches', async () => {
|
it('should reject when frame detaches', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.PREFIX + '/frames/one-frame.html');
|
await page.goto(server.PREFIX + '/frames/one-frame.html');
|
||||||
|
|
||||||
|
|
@ -722,19 +730,19 @@ describe('navigation', function () {
|
||||||
const navigationPromise = page
|
const navigationPromise = page
|
||||||
.frames()[1]!
|
.frames()[1]!
|
||||||
.goto(server.EMPTY_PAGE)
|
.goto(server.EMPTY_PAGE)
|
||||||
.catch((error_) => {
|
.catch(error_ => {
|
||||||
return error_;
|
return error_;
|
||||||
});
|
});
|
||||||
await server.waitForRequest('/empty.html');
|
await server.waitForRequest('/empty.html');
|
||||||
|
|
||||||
await page.$eval('iframe', (frame) => {
|
await page.$eval('iframe', frame => {
|
||||||
return frame.remove();
|
return frame.remove();
|
||||||
});
|
});
|
||||||
const error = await navigationPromise;
|
const error = await navigationPromise;
|
||||||
expect(error.message).toBe('Navigating frame was detached');
|
expect(error.message).toBe('Navigating frame was detached');
|
||||||
});
|
});
|
||||||
it('should return matching responses', async () => {
|
it('should return matching responses', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
// Disable cache: otherwise, chromium will cache similar requests.
|
// Disable cache: otherwise, chromium will cache similar requests.
|
||||||
await page.setCacheEnabled(false);
|
await page.setCacheEnabled(false);
|
||||||
|
|
@ -768,7 +776,7 @@ describe('navigation', function () {
|
||||||
|
|
||||||
describe('Frame.waitForNavigation', function () {
|
describe('Frame.waitForNavigation', function () {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.PREFIX + '/frames/one-frame.html');
|
await page.goto(server.PREFIX + '/frames/one-frame.html');
|
||||||
const frame = page.frames()[1]!;
|
const frame = page.frames()[1]!;
|
||||||
|
|
@ -784,14 +792,14 @@ describe('navigation', function () {
|
||||||
expect(page.url()).toContain('/frames/one-frame.html');
|
expect(page.url()).toContain('/frames/one-frame.html');
|
||||||
});
|
});
|
||||||
it('should fail when frame detaches', async () => {
|
it('should fail when frame detaches', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.PREFIX + '/frames/one-frame.html');
|
await page.goto(server.PREFIX + '/frames/one-frame.html');
|
||||||
const frame = page.frames()[1]!;
|
const frame = page.frames()[1]!;
|
||||||
|
|
||||||
server.setRoute('/empty.html', () => {});
|
server.setRoute('/empty.html', () => {});
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
const navigationPromise = frame.waitForNavigation().catch((error_) => {
|
const navigationPromise = frame.waitForNavigation().catch(error_ => {
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
|
@ -800,7 +808,7 @@ describe('navigation', function () {
|
||||||
return ((window as any).location = '/empty.html');
|
return ((window as any).location = '/empty.html');
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
await page.$eval('iframe', (frame) => {
|
await page.$eval('iframe', frame => {
|
||||||
return frame.remove();
|
return frame.remove();
|
||||||
});
|
});
|
||||||
await navigationPromise;
|
await navigationPromise;
|
||||||
|
|
@ -810,7 +818,7 @@ describe('navigation', function () {
|
||||||
|
|
||||||
describe('Page.reload', function () {
|
describe('Page.reload', function () {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const { page, server } = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
|
|
|
||||||
|
|
@ -469,7 +469,6 @@ describe('network', function () {
|
||||||
});
|
});
|
||||||
(await page.goto(server.EMPTY_PAGE))!;
|
(await page.goto(server.EMPTY_PAGE))!;
|
||||||
expect(responses.length).toBe(1);
|
expect(responses.length).toBe(1);
|
||||||
console.log('timing',responses[0]!.timing())
|
|
||||||
expect(responses[0]!.timing()!.receiveHeadersEnd).toBeGreaterThan(0);
|
expect(responses[0]!.timing()!.receiveHeadersEnd).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -854,10 +853,13 @@ describe('network', function () {
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
await page.goto(httpsServer.PREFIX + '/setcookie.html');
|
await page.goto(httpsServer.PREFIX + '/setcookie.html');
|
||||||
|
|
||||||
const response = await new Promise<HTTPResponse>(resolve => {
|
|
||||||
page.on('response', resolve);
|
|
||||||
const url = httpsServer.CROSS_PROCESS_PREFIX + '/setcookie.html';
|
const url = httpsServer.CROSS_PROCESS_PREFIX + '/setcookie.html';
|
||||||
|
const response = await new Promise<HTTPResponse>(resolve => {
|
||||||
|
page.on('response', response => {
|
||||||
|
if (response.url() === url) {
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
page.evaluate(src => {
|
page.evaluate(src => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', src);
|
xhr.open('GET', src);
|
||||||
|
|
|
||||||
|
|
@ -433,9 +433,7 @@ describe('Page', function () {
|
||||||
})
|
})
|
||||||
).toEqual(['prompt', 'denied', 'granted', 'prompt']);
|
).toEqual(['prompt', 'denied', 'granted', 'prompt']);
|
||||||
});
|
});
|
||||||
it(
|
it('should isolate permissions between browser contexts', async () => {
|
||||||
'should isolate permissions between browser contexts',
|
|
||||||
async () => {
|
|
||||||
const {page, server, context, browser} = getTestState();
|
const {page, server, context, browser} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
|
@ -457,8 +455,7 @@ describe('Page', function () {
|
||||||
expect(await getPermission(otherPage, 'geolocation')).toBe('granted');
|
expect(await getPermission(otherPage, 'geolocation')).toBe('granted');
|
||||||
|
|
||||||
await otherContext.close();
|
await otherContext.close();
|
||||||
}
|
});
|
||||||
);
|
|
||||||
it('should grant persistent-storage', async () => {
|
it('should grant persistent-storage', async () => {
|
||||||
const {page, server, context} = getTestState();
|
const {page, server, context} = getTestState();
|
||||||
|
|
||||||
|
|
@ -1630,7 +1627,7 @@ describe('Page', function () {
|
||||||
error = error_ as Error;
|
error = error_ as Error;
|
||||||
}
|
}
|
||||||
expect(error.message).toBe(
|
expect(error.message).toBe(
|
||||||
'Provide an object with a `url`, `path` or `content` property'
|
'Exactly one of `url`, `path`, or `content` must be specified.'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1692,7 +1689,7 @@ describe('Page', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if loading from url fail', async () => {
|
it('should throw an error if loading from url fail', async () => {
|
||||||
const {page, server} = getTestState();
|
const {page, server, isFirefox} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
|
|
@ -1701,7 +1698,11 @@ describe('Page', function () {
|
||||||
} catch (error_) {
|
} catch (error_) {
|
||||||
error = error_ as Error;
|
error = error_ as Error;
|
||||||
}
|
}
|
||||||
expect(error.message).toBe('Loading script from /nonexistfile.js failed');
|
if (isFirefox) {
|
||||||
|
expect(error.message).toBeTruthy();
|
||||||
|
} else {
|
||||||
|
expect(error.message).toContain('Could not load script');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with a path', async () => {
|
it('should work with a path', async () => {
|
||||||
|
|
@ -1796,7 +1797,7 @@ describe('Page', function () {
|
||||||
error = error_ as Error;
|
error = error_ as Error;
|
||||||
}
|
}
|
||||||
expect(error.message).toBe(
|
expect(error.message).toBe(
|
||||||
'Provide an object with a `url`, `path` or `content` property'
|
'Exactly one of `url`, `path`, or `content` must be specified.'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1814,7 +1815,7 @@ describe('Page', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if loading from url fail', async () => {
|
it('should throw an error if loading from url fail', async () => {
|
||||||
const {page, server} = getTestState();
|
const {page, server, isFirefox} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
|
|
@ -1823,7 +1824,11 @@ describe('Page', function () {
|
||||||
} catch (error_) {
|
} catch (error_) {
|
||||||
error = error_ as Error;
|
error = error_ as Error;
|
||||||
}
|
}
|
||||||
expect(error.message).toBe('Loading style from /nonexistfile.js failed');
|
if (isFirefox) {
|
||||||
|
expect(error.message).toBeTruthy();
|
||||||
|
} else {
|
||||||
|
expect(error.message).toContain('Could not load style');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with a path', async () => {
|
it('should work with a path', async () => {
|
||||||
|
|
@ -1849,7 +1854,7 @@ describe('Page', function () {
|
||||||
path: path.join(__dirname, '../assets/injectedstyle.css'),
|
path: path.join(__dirname, '../assets/injectedstyle.css'),
|
||||||
});
|
});
|
||||||
const styleHandle = (await page.$('style'))!;
|
const styleHandle = (await page.$('style'))!;
|
||||||
const styleContent = await page.evaluate((style: HTMLStyleElement) => {
|
const styleContent = await page.evaluate(style => {
|
||||||
return style.innerHTML;
|
return style.innerHTML;
|
||||||
}, styleHandle);
|
}, styleHandle);
|
||||||
expect(styleContent).toContain(path.join('assets', 'injectedstyle.css'));
|
expect(styleContent).toContain(path.join('assets', 'injectedstyle.css'));
|
||||||
|
|
@ -1870,9 +1875,7 @@ describe('Page', function () {
|
||||||
).toBe('rgb(0, 128, 0)');
|
).toBe('rgb(0, 128, 0)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it(
|
it('should throw when added with content to the CSP page', async () => {
|
||||||
'should throw when added with content to the CSP page',
|
|
||||||
async () => {
|
|
||||||
const {page, server} = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.PREFIX + '/csp.html');
|
await page.goto(server.PREFIX + '/csp.html');
|
||||||
|
|
@ -1883,8 +1886,7 @@ describe('Page', function () {
|
||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
expect(error).toBeTruthy();
|
expect(error).toBeTruthy();
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
it('should throw when added with URL to the CSP page', async () => {
|
it('should throw when added with URL to the CSP page', async () => {
|
||||||
const {page, server} = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
@ -1953,9 +1955,7 @@ describe('Page', function () {
|
||||||
]);
|
]);
|
||||||
expect(nonCachedRequest.headers['if-modified-since']).toBe(undefined);
|
expect(nonCachedRequest.headers['if-modified-since']).toBe(undefined);
|
||||||
});
|
});
|
||||||
it(
|
it('should stay disabled when toggling request interception on/off', async () => {
|
||||||
'should stay disabled when toggling request interception on/off',
|
|
||||||
async () => {
|
|
||||||
const {page, server} = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.setCacheEnabled(false);
|
await page.setCacheEnabled(false);
|
||||||
|
|
@ -1968,8 +1968,7 @@ describe('Page', function () {
|
||||||
page.reload(),
|
page.reload(),
|
||||||
]);
|
]);
|
||||||
expect(nonCachedRequest.headers['if-modified-since']).toBe(undefined);
|
expect(nonCachedRequest.headers['if-modified-since']).toBe(undefined);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('printing to PDF', function () {
|
describe('printing to PDF', function () {
|
||||||
|
|
@ -2214,9 +2213,7 @@ describe('Page', function () {
|
||||||
expect(error.message).toContain('Values must be strings');
|
expect(error.message).toContain('Values must be strings');
|
||||||
});
|
});
|
||||||
// @see https://github.com/puppeteer/puppeteer/issues/3327
|
// @see https://github.com/puppeteer/puppeteer/issues/3327
|
||||||
it(
|
it('should work when re-defining top-level Event class', async () => {
|
||||||
'should work when re-defining top-level Event class',
|
|
||||||
async () => {
|
|
||||||
const {page, server} = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
await page.goto(server.PREFIX + '/input/select.html');
|
await page.goto(server.PREFIX + '/input/select.html');
|
||||||
|
|
@ -2235,8 +2232,7 @@ describe('Page', function () {
|
||||||
return (globalThis as any).result.onChange;
|
return (globalThis as any).result.onChange;
|
||||||
})
|
})
|
||||||
).toEqual(['blue']);
|
).toEqual(['blue']);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Page.Events.Close', function () {
|
describe('Page.Events.Close', function () {
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ import expect from 'expect';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import {
|
import {
|
||||||
describeFailsFirefox,
|
|
||||||
getTestState,
|
getTestState,
|
||||||
|
describeFailsFirefox,
|
||||||
itFailsWindows,
|
itFailsWindows,
|
||||||
} from './mocha-utils.js';
|
} from './mocha-utils.js';
|
||||||
import type {Server, IncomingMessage, ServerResponse} from 'http';
|
import type {Server, IncomingMessage, ServerResponse} from 'http';
|
||||||
|
|
@ -125,7 +125,7 @@ describeFailsFirefox('request proxy', () => {
|
||||||
const response = (await page.goto(emptyPageUrl))!;
|
const response = (await page.goto(emptyPageUrl))!;
|
||||||
|
|
||||||
expect(response.ok()).toBe(true);
|
expect(response.ok()).toBe(true);
|
||||||
console.log('test',{proxiedRequestUrls, emptyPageUrl})
|
|
||||||
expect(proxiedRequestUrls).toEqual([emptyPageUrl]);
|
expect(proxiedRequestUrls).toEqual([emptyPageUrl]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -614,10 +614,11 @@ describe('request interception', function () {
|
||||||
return request.continue();
|
return request.continue();
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.goto(server.PREFIX + '/cached/one-style-font.html');
|
const responsePromise = page.waitForResponse(r => {
|
||||||
await page.waitForResponse(r => {
|
|
||||||
return r.url().endsWith('/one-style.woff');
|
return r.url().endsWith('/one-style.woff');
|
||||||
});
|
});
|
||||||
|
await page.goto(server.PREFIX + '/cached/one-style-font.html');
|
||||||
|
await responsePromise;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,22 @@ describe('Screenshots', function () {
|
||||||
});
|
});
|
||||||
expect(screenshot).toBeGolden('screenshot-clip-rect.png');
|
expect(screenshot).toBeGolden('screenshot-clip-rect.png');
|
||||||
});
|
});
|
||||||
|
it('should use scale for clip', async () => {
|
||||||
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
|
await page.setViewport({width: 500, height: 500});
|
||||||
|
await page.goto(server.PREFIX + '/grid.html');
|
||||||
|
const screenshot = await page.screenshot({
|
||||||
|
clip: {
|
||||||
|
x: 50,
|
||||||
|
y: 100,
|
||||||
|
width: 150,
|
||||||
|
height: 100,
|
||||||
|
scale: 2,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(screenshot).toBeGolden('screenshot-clip-rect-scale2.png');
|
||||||
|
});
|
||||||
it(
|
it(
|
||||||
'should get screenshot bigger than the viewport',
|
'should get screenshot bigger than the viewport',
|
||||||
async () => {
|
async () => {
|
||||||
|
|
|
||||||
|
|
@ -99,13 +99,10 @@ describe('Target', function () {
|
||||||
return window.open(url);
|
return window.open(url);
|
||||||
}, server.CROSS_PROCESS_PREFIX + '/empty.html'),
|
}, server.CROSS_PROCESS_PREFIX + '/empty.html'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(otherPage!.url()).toEqual(
|
expect(otherPage!.url()).toEqual(
|
||||||
server.CROSS_PROCESS_PREFIX + '/empty.html'
|
server.CROSS_PROCESS_PREFIX + '/empty.html'
|
||||||
);
|
);
|
||||||
expect(page).not.toEqual(otherPage);
|
expect(page).not.toEqual(otherPage);
|
||||||
|
|
||||||
await otherPage!.close();
|
|
||||||
});
|
});
|
||||||
it(
|
it(
|
||||||
'should report when a new page is created and closed',
|
'should report when a new page is created and closed',
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {Frame} from '../../lib/cjs/puppeteer/common/FrameManager.js';
|
import {Frame} from '../../lib/cjs/puppeteer/common/Frame.js';
|
||||||
import {Page} from '../../lib/cjs/puppeteer/common/Page.js';
|
import {Page} from '../../lib/cjs/puppeteer/common/Page.js';
|
||||||
import {EventEmitter} from '../../lib/cjs/puppeteer/common/EventEmitter.js';
|
import {EventEmitter} from '../../lib/cjs/puppeteer/common/EventEmitter.js';
|
||||||
import {compare} from './golden-utils.js';
|
import {compare} from './golden-utils.js';
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
import {isErrorLike} from '../../lib/cjs/puppeteer/common/util.js';
|
import {isErrorLike} from '../../lib/cjs/puppeteer/util/ErrorLike.js';
|
||||||
import {
|
import {
|
||||||
getTestState,
|
getTestState,
|
||||||
setupTestBrowserHooks,
|
setupTestBrowserHooks,
|
||||||
|
|
@ -476,7 +476,7 @@ describe('waittask specs', function () {
|
||||||
await otherFrame.evaluate(addElement, 'div');
|
await otherFrame.evaluate(addElement, 'div');
|
||||||
await page.evaluate(addElement, 'div');
|
await page.evaluate(addElement, 'div');
|
||||||
const eHandle = await watchdog;
|
const eHandle = await watchdog;
|
||||||
expect(eHandle?.executionContext().frame()).toBe(page.mainFrame());
|
expect(eHandle?.frame).toBe(page.mainFrame());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -491,7 +491,7 @@ describe('waittask specs', function () {
|
||||||
await frame1.evaluate(addElement, 'div');
|
await frame1.evaluate(addElement, 'div');
|
||||||
await frame2.evaluate(addElement, 'div');
|
await frame2.evaluate(addElement, 'div');
|
||||||
const eHandle = await waitForSelectorPromise;
|
const eHandle = await waitForSelectorPromise;
|
||||||
expect(eHandle?.executionContext().frame()).toBe(frame2);
|
expect(eHandle?.frame).toBe(frame2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when frame is detached', async () => {
|
it('should throw when frame is detached', async () => {
|
||||||
|
|
@ -748,7 +748,7 @@ describe('waittask specs', function () {
|
||||||
await frame1.evaluate(addElement, 'div');
|
await frame1.evaluate(addElement, 'div');
|
||||||
await frame2.evaluate(addElement, 'div');
|
await frame2.evaluate(addElement, 'div');
|
||||||
const eHandle = await waitForXPathPromise;
|
const eHandle = await waitForXPathPromise;
|
||||||
expect(eHandle?.executionContext().frame()).toBe(frame2);
|
expect(eHandle?.frame).toBe(frame2);
|
||||||
});
|
});
|
||||||
it('should throw when frame is detached', async () => {
|
it('should throw when frame is detached', async () => {
|
||||||
const {page, server} = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"references": [
|
"references": [
|
||||||
{"path": "../src/tsconfig.cjs.json"},
|
{"path": "../tsconfig.lib.json"},
|
||||||
{"path": "../utils/testserver/tsconfig.json"}
|
{"path": "../utils/testserver/tsconfig.json"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
remote/test/puppeteer/tsconfig.lib.json
Normal file
16
remote/test/puppeteer/tsconfig.lib.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
/**
|
||||||
|
* This configuration only exists for the API Extractor tool and for VSCode to use. It is NOT the tsconfig used for compilation.
|
||||||
|
* For CJS builds, `tsconfig.cjs.json` is used, and for ESM, it's `tsconfig.esm.json`.
|
||||||
|
* See the details in CONTRIBUTING.md that describes our TypeScript setup.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{"path": "src/tsconfig.esm.json"},
|
||||||
|
{"path": "src/tsconfig.cjs.json"}
|
||||||
|
],
|
||||||
|
"exclude": ["**/*"]
|
||||||
|
}
|
||||||
|
|
@ -45,7 +45,7 @@ const fileExists = async filePath => {
|
||||||
* place.
|
* place.
|
||||||
*/
|
*/
|
||||||
async function compileTypeScript() {
|
async function compileTypeScript() {
|
||||||
return exec('npm run build:tsc').catch(error => {
|
return exec('npm run build').catch(error => {
|
||||||
console.error('Error running TypeScript', error);
|
console.error('Error running TypeScript', error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
const {readdirSync, writeFileSync} = require('fs');
|
|
||||||
const {join, basename} = require('path');
|
|
||||||
|
|
||||||
const EXCLUDE_FILES = ['puppeteer-core.ts'];
|
|
||||||
|
|
||||||
let typesTs = '// AUTOGENERATED - Use `utils/export_all.js` to regenerate.\n';
|
|
||||||
|
|
||||||
typesTs += `\n`;
|
|
||||||
for (const file of readdirSync(join(__dirname, `../src`)).filter(filename => {
|
|
||||||
return (
|
|
||||||
filename.endsWith('ts') &&
|
|
||||||
!filename.startsWith('types') &&
|
|
||||||
!EXCLUDE_FILES.includes(filename)
|
|
||||||
);
|
|
||||||
})) {
|
|
||||||
typesTs += `export * from './${basename(file, '.ts')}.js';\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const folder of ['common', 'node', 'generated']) {
|
|
||||||
typesTs += `\n// Exports from \`${folder}\`\n`;
|
|
||||||
for (const file of readdirSync(join(__dirname, `../src/${folder}`)).filter(
|
|
||||||
filename => {
|
|
||||||
return filename.endsWith('ts') && !EXCLUDE_FILES.includes(filename);
|
|
||||||
}
|
|
||||||
)) {
|
|
||||||
typesTs += `export * from './${folder}/${basename(file, '.ts')}.js';\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFileSync(join(__dirname, '../src/types.ts'), typesTs);
|
|
||||||
28
remote/test/puppeteer/utils/generate_artifacts.ts
Normal file
28
remote/test/puppeteer/utils/generate_artifacts.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
import {writeFile} from 'fs/promises';
|
||||||
|
import {job} from './internal/job.js';
|
||||||
|
import {spawnAndLog} from './internal/util.js';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
job('', async ({outputs}) => {
|
||||||
|
await writeFile(outputs[0]!, '{"type": "module"}');
|
||||||
|
})
|
||||||
|
.outputs(['lib/esm/package.json'])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
job('', async ({outputs}) => {
|
||||||
|
spawnAndLog('api-extractor', 'run', '--local');
|
||||||
|
spawnAndLog(
|
||||||
|
'eslint',
|
||||||
|
'--ext=ts',
|
||||||
|
'--no-ignore',
|
||||||
|
'--no-eslintrc',
|
||||||
|
'-c=.eslintrc.types.cjs',
|
||||||
|
'--fix',
|
||||||
|
outputs[0]!
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.inputs(['lib/esm/puppeteer/types.d.ts'])
|
||||||
|
.outputs(['lib/types.d.ts', 'docs/puppeteer.api.json'])
|
||||||
|
.build();
|
||||||
|
})();
|
||||||
|
|
@ -14,15 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {readFileSync, writeFileSync} from 'fs';
|
import {readFile, rm, writeFile} from 'fs/promises';
|
||||||
import {join} from 'path';
|
|
||||||
import {chdir} from 'process';
|
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
import {versionsPerRelease} from '../versions.js';
|
import {generateDocs} from './internal/custom_markdown_action.js';
|
||||||
import versionsArchived from '../website/versionsArchived.json';
|
import {job} from './internal/job.js';
|
||||||
|
import {spawnAndLog} from './internal/util.js';
|
||||||
// eslint-disable-next-line import/extensions
|
|
||||||
import {generateDocs} from './internal/custom_markdown_action';
|
|
||||||
|
|
||||||
function getOffsetAndLimit(
|
function getOffsetAndLimit(
|
||||||
sectionName: string,
|
sectionName: string,
|
||||||
|
|
@ -49,25 +45,25 @@ function spliceIntoSection(
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change to root directory
|
(async () => {
|
||||||
chdir(join(__dirname, '..'));
|
const job1 = job('', async ({inputs, outputs}) => {
|
||||||
|
const content = await readFile(inputs[0]!, 'utf-8');
|
||||||
// README
|
|
||||||
{
|
|
||||||
const content = readFileSync('README.md', 'utf-8');
|
|
||||||
const sectionContent = `
|
const sectionContent = `
|
||||||
---
|
---
|
||||||
sidebar_position: 1
|
sidebar_position: 1
|
||||||
---
|
---
|
||||||
|
`;
|
||||||
|
await writeFile(outputs[0]!, sectionContent + content);
|
||||||
|
})
|
||||||
|
.inputs(['README.md'])
|
||||||
|
.outputs(['docs/index.md'])
|
||||||
|
.build();
|
||||||
|
|
||||||
`;
|
// Chrome Versions
|
||||||
writeFileSync('docs/index.md', sectionContent + content);
|
const job2 = job('', async ({inputs, outputs}) => {
|
||||||
}
|
let content = await readFile(inputs[2]!, {encoding: 'utf8'});
|
||||||
|
const {versionsPerRelease} = await import(inputs[0]!);
|
||||||
// Chrome Versions
|
const versionsArchived = JSON.parse(await readFile(inputs[1]!, 'utf8'));
|
||||||
{
|
|
||||||
const filename = 'docs/chromium-support.md';
|
|
||||||
let content = readFileSync(filename, {encoding: 'utf8'});
|
|
||||||
|
|
||||||
// Generate versions
|
// Generate versions
|
||||||
const buffer: string[] = [];
|
const buffer: string[] = [];
|
||||||
|
|
@ -97,8 +93,25 @@ chdir(join(__dirname, '..'));
|
||||||
}
|
}
|
||||||
content = spliceIntoSection('version', content, buffer.join('\n'));
|
content = spliceIntoSection('version', content, buffer.join('\n'));
|
||||||
|
|
||||||
writeFileSync(filename, content);
|
await writeFile(outputs[0]!, content);
|
||||||
}
|
})
|
||||||
|
.inputs([
|
||||||
|
'versions.js',
|
||||||
|
'website/versionsArchived.json',
|
||||||
|
'docs/chromium-support.md',
|
||||||
|
])
|
||||||
|
.outputs(['docs/chromium-support.md'])
|
||||||
|
.build();
|
||||||
|
|
||||||
// Generate documentation
|
await Promise.all([job1, job2]);
|
||||||
generateDocs('docs/puppeteer.api.json', 'docs/api');
|
|
||||||
|
// Generate documentation
|
||||||
|
job('', async ({inputs, outputs}) => {
|
||||||
|
await rm(outputs[0]!, {recursive: true, force: true});
|
||||||
|
generateDocs(inputs[0]!, outputs[0]!);
|
||||||
|
spawnAndLog('prettier', '--ignore-path', 'none', '--write', 'docs');
|
||||||
|
})
|
||||||
|
.inputs(['docs/puppeteer.api.json'])
|
||||||
|
.outputs(['docs/api'])
|
||||||
|
.build();
|
||||||
|
})();
|
||||||
|
|
|
||||||
106
remote/test/puppeteer/utils/generate_sources.ts
Normal file
106
remote/test/puppeteer/utils/generate_sources.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
import {createHash} from 'crypto';
|
||||||
|
import esbuild from 'esbuild';
|
||||||
|
import {mkdir, mkdtemp, readFile, rm, writeFile} from 'fs/promises';
|
||||||
|
import {sync as glob} from 'glob';
|
||||||
|
import path from 'path';
|
||||||
|
import {job} from './internal/job.js';
|
||||||
|
|
||||||
|
const INCLUDED_FOLDERS = ['common', 'node', 'generated', 'util'];
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await job('', async ({outputs}) => {
|
||||||
|
await Promise.all(
|
||||||
|
outputs.map(outputs => {
|
||||||
|
return mkdir(outputs, {recursive: true});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.outputs(['src/generated'])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
await job('', async ({name, inputs, outputs}) => {
|
||||||
|
const input = inputs.find(input => {
|
||||||
|
return input.endsWith('injected.ts');
|
||||||
|
})!;
|
||||||
|
const template = await readFile(
|
||||||
|
inputs.find(input => {
|
||||||
|
return input.includes('injected.ts.tmpl');
|
||||||
|
})!,
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
const tmp = await mkdtemp(name);
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: [input],
|
||||||
|
bundle: true,
|
||||||
|
outdir: tmp,
|
||||||
|
format: 'cjs',
|
||||||
|
platform: 'browser',
|
||||||
|
target: 'ES2019',
|
||||||
|
});
|
||||||
|
const baseName = path.basename(input);
|
||||||
|
const content = await readFile(
|
||||||
|
path.join(tmp, baseName.replace('.ts', '.js')),
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
const scriptContent = template.replace(
|
||||||
|
'SOURCE_CODE',
|
||||||
|
JSON.stringify(content)
|
||||||
|
);
|
||||||
|
await writeFile(outputs[0]!, scriptContent);
|
||||||
|
await rm(tmp, {recursive: true, force: true});
|
||||||
|
})
|
||||||
|
.inputs(['src/templates/injected.ts.tmpl', 'src/injected/**/*.ts'])
|
||||||
|
.outputs(['src/generated/injected.ts'])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const sources = glob(
|
||||||
|
`src/{@(${INCLUDED_FOLDERS.join('|')})/*.ts,!(types|puppeteer-core).ts}`
|
||||||
|
);
|
||||||
|
await job('', async ({outputs}) => {
|
||||||
|
let types =
|
||||||
|
'// AUTOGENERATED - Use `npm run generate:sources` to regenerate.\n\n';
|
||||||
|
for (const input of sources.map(source => {
|
||||||
|
return `.${source.slice(3)}`;
|
||||||
|
})) {
|
||||||
|
types += `export * from '${input.replace('.ts', '.js')}';\n`;
|
||||||
|
}
|
||||||
|
await writeFile(outputs[0]!, types);
|
||||||
|
})
|
||||||
|
.value(
|
||||||
|
sources
|
||||||
|
.reduce((hmac, value) => {
|
||||||
|
return hmac.update(value);
|
||||||
|
}, createHash('sha256'))
|
||||||
|
.digest('hex')
|
||||||
|
)
|
||||||
|
.outputs(['src/types.ts'])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
if (process.env['PUBLISH']) {
|
||||||
|
job('', async ({inputs}) => {
|
||||||
|
const version = JSON.parse(await readFile(inputs[0]!, 'utf8')).version;
|
||||||
|
await writeFile(
|
||||||
|
inputs[1]!,
|
||||||
|
(
|
||||||
|
await readFile(inputs[1]!, {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
})
|
||||||
|
).replace("'NEXT'", `'v${version}'`)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.inputs(['package.json', 'versions.js'])
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
job('', async ({inputs, outputs}) => {
|
||||||
|
const version = JSON.parse(await readFile(inputs[0]!, 'utf8')).version;
|
||||||
|
await writeFile(
|
||||||
|
outputs[0]!,
|
||||||
|
(await readFile(inputs[1]!, 'utf8')).replace('PACKAGE_VERSION', version)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.inputs(['package.json', 'src/templates/version.ts.tmpl'])
|
||||||
|
.outputs(['src/generated/version.ts'])
|
||||||
|
.build();
|
||||||
|
})();
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
const {writeFileSync, readFileSync} = require('fs');
|
|
||||||
const {join} = require('path');
|
|
||||||
|
|
||||||
const version = require('../package.json').version;
|
|
||||||
|
|
||||||
writeFileSync(
|
|
||||||
join(__dirname, '../src/generated/version.ts'),
|
|
||||||
readFileSync(join(__dirname, '../src/templates/version.ts.tmpl'), {
|
|
||||||
encoding: 'utf-8',
|
|
||||||
}).replace('PACKAGE_VERSION', version)
|
|
||||||
);
|
|
||||||
|
|
||||||
writeFileSync(
|
|
||||||
join(__dirname, '../versions.js'),
|
|
||||||
readFileSync(join(__dirname, '../versions.js'), {
|
|
||||||
encoding: 'utf-8',
|
|
||||||
}).replace('NEXT', `v${version}`)
|
|
||||||
);
|
|
||||||
|
|
@ -15,9 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ApiModel} from '@microsoft/api-extractor-model';
|
import {ApiModel} from '@microsoft/api-extractor-model';
|
||||||
|
import {MarkdownDocumenter} from './custom_markdown_documenter.js';
|
||||||
// eslint-disable-next-line import/extensions
|
|
||||||
import {MarkdownDocumenter} from './custom_markdown_documenter';
|
|
||||||
|
|
||||||
export const generateDocs = (jsonPath: string, outputDir: string): void => {
|
export const generateDocs = (jsonPath: string, outputDir: string): void => {
|
||||||
const apiModel = new ApiModel();
|
const apiModel = new ApiModel();
|
||||||
|
|
|
||||||
161
remote/test/puppeteer/utils/internal/job.ts
Normal file
161
remote/test/puppeteer/utils/internal/job.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
import {createHash} from 'crypto';
|
||||||
|
import {existsSync, Stats} from 'fs';
|
||||||
|
import {mkdir, readFile, stat, writeFile} from 'fs/promises';
|
||||||
|
import {glob} from 'glob';
|
||||||
|
import {tmpdir} from 'os';
|
||||||
|
import {dirname, join, resolve} from 'path';
|
||||||
|
import {chdir} from 'process';
|
||||||
|
|
||||||
|
const packageRoot = resolve(join(__dirname, '..', '..'));
|
||||||
|
chdir(packageRoot);
|
||||||
|
|
||||||
|
interface JobContext {
|
||||||
|
name: string;
|
||||||
|
inputs: string[];
|
||||||
|
outputs: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class JobBuilder {
|
||||||
|
#inputs: string[] = [];
|
||||||
|
#outputs: string[] = [];
|
||||||
|
#callback: (ctx: JobContext) => Promise<void>;
|
||||||
|
#name: string;
|
||||||
|
#value = '';
|
||||||
|
#force = false;
|
||||||
|
|
||||||
|
constructor(name: string, callback: (ctx: JobContext) => Promise<void>) {
|
||||||
|
this.#name = name;
|
||||||
|
this.#callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
get jobHash(): string {
|
||||||
|
return createHash('sha256').update(this.#name).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
force() {
|
||||||
|
this.#force = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
value(value: string) {
|
||||||
|
this.#value = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs(inputs: string[]): JobBuilder {
|
||||||
|
this.#inputs = inputs.flatMap(value => {
|
||||||
|
if (glob.hasMagic(value)) {
|
||||||
|
return glob.sync(value).map(value => {
|
||||||
|
// Glob doesn't support `\` on Windows, so we join here.
|
||||||
|
return join(packageRoot, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return join(packageRoot, value);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs(outputs: string[]): JobBuilder {
|
||||||
|
if (!this.#name) {
|
||||||
|
this.#name = outputs.join(' and ');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#outputs = outputs.map(value => {
|
||||||
|
return join(packageRoot, value);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async build(): Promise<void> {
|
||||||
|
console.log(`Running job ${this.#name}...`);
|
||||||
|
// For debugging.
|
||||||
|
if (this.#force) {
|
||||||
|
return this.#run();
|
||||||
|
}
|
||||||
|
// In case we deleted an output file on purpose.
|
||||||
|
if (!this.getOutputStats()) {
|
||||||
|
return this.#run();
|
||||||
|
}
|
||||||
|
// Run if the job has a value, but it changes.
|
||||||
|
if (this.#value) {
|
||||||
|
if (!(await this.isValueDifferent())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this.#run();
|
||||||
|
}
|
||||||
|
// Always run when there is no output.
|
||||||
|
if (!this.#outputs.length) {
|
||||||
|
return this.#run();
|
||||||
|
}
|
||||||
|
// Make-like comparator.
|
||||||
|
if (!(await this.areInputsNewer())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this.#run();
|
||||||
|
}
|
||||||
|
|
||||||
|
async isValueDifferent(): Promise<boolean> {
|
||||||
|
const file = join(tmpdir(), `puppeteer/${this.jobHash}.txt`);
|
||||||
|
await mkdir(dirname(file), {recursive: true});
|
||||||
|
if (!existsSync(file)) {
|
||||||
|
await writeFile(file, this.#value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.#value !== (await readFile(file, 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#outputStats?: Stats[];
|
||||||
|
async getOutputStats(): Promise<Stats[] | undefined> {
|
||||||
|
if (this.#outputStats) {
|
||||||
|
return this.#outputStats;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.#outputStats = await Promise.all(
|
||||||
|
this.#outputs.map(output => {
|
||||||
|
return stat(output);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch {}
|
||||||
|
return this.#outputStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
async areInputsNewer(): Promise<boolean> {
|
||||||
|
const inputStats = await Promise.all(
|
||||||
|
this.#inputs.map(input => {
|
||||||
|
return stat(input);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const outputStats = await this.getOutputStats();
|
||||||
|
if (
|
||||||
|
outputStats &&
|
||||||
|
outputStats.reduce(reduceMinTime, Infinity) >
|
||||||
|
inputStats.reduce(reduceMaxTime, 0)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#run(): Promise<void> {
|
||||||
|
return this.#callback({
|
||||||
|
name: this.#name,
|
||||||
|
inputs: this.#inputs,
|
||||||
|
outputs: this.#outputs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const job = (
|
||||||
|
name: string,
|
||||||
|
callback: (ctx: JobContext) => Promise<void>
|
||||||
|
): JobBuilder => {
|
||||||
|
return new JobBuilder(name, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
const reduceMaxTime = (time: number, stat: Stats) => {
|
||||||
|
return time < stat.mtimeMs ? stat.mtimeMs : time;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reduceMinTime = (time: number, stat: Stats) => {
|
||||||
|
return time > stat.mtimeMs ? stat.mtimeMs : time;
|
||||||
|
};
|
||||||
14
remote/test/puppeteer/utils/internal/util.ts
Normal file
14
remote/test/puppeteer/utils/internal/util.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import {spawnSync} from 'child_process';
|
||||||
|
|
||||||
|
export const spawnAndLog = (...args: string[]): void => {
|
||||||
|
const {stdout, stderr} = spawnSync(args[0]!, args.slice(1), {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
shell: true,
|
||||||
|
});
|
||||||
|
if (stdout) {
|
||||||
|
console.log(stdout);
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
console.error(stderr);
|
||||||
|
}
|
||||||
|
};
|
||||||
12
remote/test/puppeteer/utils/tsconfig.json
Normal file
12
remote/test/puppeteer/utils/tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* This configuration only exists for the API Extractor tool and for VSCode to use. It is NOT the tsconfig used for compilation.
|
||||||
|
* For CJS builds, `tsconfig.cjs.json` is used, and for ESM, it's `tsconfig.esm.json`.
|
||||||
|
* See the details in CONTRIBUTING.md that describes our TypeScript setup.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": true,
|
||||||
|
"module": "CommonJS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,8 @@
|
||||||
|
|
||||||
const versionsPerRelease = new Map([
|
const versionsPerRelease = new Map([
|
||||||
// This is a mapping from Chromium version => Puppeteer version.
|
// This is a mapping from Chromium version => Puppeteer version.
|
||||||
// In Chromium roll patches, use 'v16.1.1' for the Puppeteer version.
|
// In Chromium roll patches, use `NEXT` for the Puppeteer version.
|
||||||
|
['106.0.5249.0', 'v17.1.0'],
|
||||||
['105.0.5173.0', 'v15.5.0'],
|
['105.0.5173.0', 'v15.5.0'],
|
||||||
['104.0.5109.0', 'v15.1.0'],
|
['104.0.5109.0', 'v15.1.0'],
|
||||||
['103.0.5059.0', 'v14.2.0'],
|
['103.0.5059.0', 'v14.2.0'],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue