diff options
Diffstat (limited to 'remote/test/puppeteer')
110 files changed, 3611 insertions, 2076 deletions
diff --git a/remote/test/puppeteer/.release-please-manifest.json b/remote/test/puppeteer/.release-please-manifest.json index a3ccb1a4ae..85dc75087e 100644 --- a/remote/test/puppeteer/.release-please-manifest.json +++ b/remote/test/puppeteer/.release-please-manifest.json @@ -1,7 +1,7 @@ { - "packages/puppeteer": "22.4.0", - "packages/puppeteer-core": "22.4.0", + "packages/puppeteer": "22.6.5", + "packages/puppeteer-core": "22.6.5", "packages/testserver": "0.6.0", "packages/ng-schematics": "0.6.0", - "packages/browsers": "2.1.0" + "packages/browsers": "2.2.2" } diff --git a/remote/test/puppeteer/.vscode/launch.template.json b/remote/test/puppeteer/.vscode/launch.template.json new file mode 100644 index 0000000000..51870281bd --- /dev/null +++ b/remote/test/puppeteer/.vscode/launch.template.json @@ -0,0 +1,46 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "inputs": [ + { + "type": "pickString", + "id": "suit", + "description": "Which test suit to run?", + "options": [ + "chrome-headless", + "chrome-headful", + "chrome-headless-shell", + "firefox-headless", + "firefox-headful", + "firefox-bidi", + "chrome-bidi" + ], + "default": "chrome-headless" + } + ], + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Tests", + "skipFiles": ["<node_internals>/**"], + "runtimeExecutable": "npm", + "cwd": "${workspaceFolder}", + "runtimeArgs": [ + "run-script", + "test", + "--", + "--test-suite", + "${input:suit}", + "--no-coverage", + "--no-suggestions" + ], + "outFiles": ["${workspaceFolder}/**/*.js"], + "env": { + "DEBUGGER_ATTACHED": true + } + } + ] +} diff --git a/remote/test/puppeteer/Herebyfile.mjs b/remote/test/puppeteer/Herebyfile.mjs index 30f9c75262..7b1e27958f 100644 --- a/remote/test/puppeteer/Herebyfile.mjs +++ b/remote/test/puppeteer/Herebyfile.mjs @@ -6,20 +6,20 @@ /* eslint-disable import/order */ -import {copyFile, readFile, writeFile} from 'fs/promises'; +import {readFile, writeFile} from 'fs/promises'; import {docgen, spliceIntoSection} from '@puppeteer/docgen'; import {execa} from 'execa'; import {task} from 'hereby'; import semver from 'semver'; -export const docsNgSchematicsTask = task({ - name: 'docs:ng-schematics', - run: async () => { - const readme = await readFile('packages/ng-schematics/README.md', 'utf-8'); - await writeFile('docs/integrations/ng-schematics.md', readme); - }, -}); +function addNoTocHeader(markdown) { + return `--- +hide_table_of_contents: true +--- + +${markdown}`; +} /** * This logic should match the one in `website/docusaurus.config.js`. @@ -34,10 +34,18 @@ function getApiUrl(version) { } } +export const docsNgSchematicsTask = task({ + name: 'docs:ng-schematics', + run: async () => { + const readme = await readFile('packages/ng-schematics/README.md', 'utf-8'); + await writeFile('docs/guides/ng-schematics.md', readme); + }, +}); + export const docsChromiumSupportTask = task({ - name: 'docs:chromium-support', + name: 'docs:supported-browsers', run: async () => { - const content = await readFile('docs/chromium-support.md', { + const content = await readFile('docs/supported-browsers.md', { encoding: 'utf8', }); const {versionsPerRelease} = await import('./versions.js'); @@ -61,7 +69,7 @@ export const docsChromiumSupportTask = task({ } } await writeFile( - 'docs/chromium-support.md', + 'docs/supported-browsers.md', spliceIntoSection('version', content, buffer.join('\n')) ); }, @@ -72,7 +80,8 @@ export const docsTask = task({ dependencies: [docsNgSchematicsTask, docsChromiumSupportTask], run: async () => { // Copy main page. - await copyFile('README.md', 'docs/index.md'); + const mainPage = await readFile('README.md', 'utf-8'); + await writeFile('docs/index.md', addNoTocHeader(mainPage)); // Generate documentation for (const [name, folder] of [ diff --git a/remote/test/puppeteer/README.md b/remote/test/puppeteer/README.md index 288a5b623c..6078b23c2c 100644 --- a/remote/test/puppeteer/README.md +++ b/remote/test/puppeteer/README.md @@ -1,145 +1,21 @@ # Puppeteer -[![Build status](https://github.com/puppeteer/puppeteer/workflows/CI/badge.svg)](https://github.com/puppeteer/puppeteer/actions?query=workflow%3ACI) +[![build](https://github.com/puppeteer/puppeteer/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/puppeteer/puppeteer/actions/workflows/ci.yml) [![npm puppeteer package](https://img.shields.io/npm/v/puppeteer.svg)](https://npmjs.org/package/puppeteer) <img src="https://user-images.githubusercontent.com/10379601/29446482-04f7036a-841f-11e7-9872-91d1fc2ea683.png" height="200" align="right"/> -#### [Guides](https://pptr.dev/category/guides) | [API](https://pptr.dev/api) | [FAQ](https://pptr.dev/faq) | [Contributing](https://pptr.dev/contributing) | [Troubleshooting](https://pptr.dev/troubleshooting) - > Puppeteer is a Node.js library which provides a high-level API to control > Chrome/Chromium over the > [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). > Puppeteer runs in -> [headless](https://developer.chrome.com/articles/new-headless/) +> [headless](https://developer.chrome.com/docs/chromium/new-headless/) > mode by default, but can be configured to run in full ("headful") > Chrome/Chromium. -#### What can I do? - -Most things that you can do manually in the browser can be done using Puppeteer! -Here are a few examples to get you started: - -- Generate screenshots and PDFs of pages. -- Crawl a SPA (Single-Page Application) and generate pre-rendered content (i.e. - "SSR" (Server-Side Rendering)). -- Automate form submission, UI testing, keyboard input, etc. -- Create an automated testing environment using the latest JavaScript and - browser features. -- Capture a - [timeline trace](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference) - of your site to help diagnose performance issues. -- [Test Chrome Extensions](https://pptr.dev/guides/chrome-extensions). - -## Getting Started - -### Installation - -To use Puppeteer in your project, run: - -```bash -npm i puppeteer -# or using yarn -yarn add puppeteer -# or using pnpm -pnpm i puppeteer -``` - -When you install Puppeteer, it automatically downloads a recent version of -[Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (~170MB macOS, ~282MB Linux, ~280MB Windows) and a `chrome-headless-shell` binary (starting with Puppeteer v21.6.0) that is [guaranteed to -work](https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) -with Puppeteer. The browser is downloaded to the `$HOME/.cache/puppeteer` folder -by default (starting with Puppeteer v19.0.0). See [configuration](https://pptr.dev/api/puppeteer.configuration) for configuration options and environmental variables to control the download behavor. - -If you deploy a project using Puppeteer to a hosting provider, such as Render or -Heroku, you might need to reconfigure the location of the cache to be within -your project folder (see an example below) because not all hosting providers -include `$HOME/.cache` into the project's deployment. - -For a version of Puppeteer without the browser installation, see -[`puppeteer-core`](#puppeteer-core). - -If used with TypeScript, the minimum supported TypeScript version is `4.7.4`. - -#### Configuration - -Puppeteer uses several defaults that can be customized through configuration -files. - -For example, to change the default cache directory Puppeteer uses to install -browsers, you can add a `.puppeteerrc.cjs` (or `puppeteer.config.cjs`) at the -root of your application with the contents - -```js -const {join} = require('path'); - -/** - * @type {import("puppeteer").Configuration} - */ -module.exports = { - // Changes the cache location for Puppeteer. - cacheDirectory: join(__dirname, '.cache', 'puppeteer'), -}; -``` - -After adding the configuration file, you will need to remove and reinstall -`puppeteer` for it to take effect. - -See the [configuration guide](https://pptr.dev/guides/configuration) for more -information. - -#### `puppeteer-core` - -For every release since v1.7.0 we publish two packages: - -- [`puppeteer`](https://www.npmjs.com/package/puppeteer) -- [`puppeteer-core`](https://www.npmjs.com/package/puppeteer-core) - -`puppeteer` is a _product_ for browser automation. When installed, it downloads -a version of Chrome, which it then drives using `puppeteer-core`. Being an -end-user product, `puppeteer` automates several workflows using reasonable -defaults [that can be customized](https://pptr.dev/guides/configuration). - -`puppeteer-core` is a _library_ to help drive anything that supports DevTools -protocol. Being a library, `puppeteer-core` is fully driven through its -programmatic interface implying no defaults are assumed and `puppeteer-core` -will not download Chrome when installed. - -You should use `puppeteer-core` if you are -[connecting to a remote browser](https://pptr.dev/api/puppeteer.puppeteer.connect) -or [managing browsers yourself](https://pptr.dev/browsers-api/). -If you are managing browsers yourself, you will need to call -[`puppeteer.launch`](https://pptr.dev/api/puppeteer.puppeteernode.launch) with -an explicit -[`executablePath`](https://pptr.dev/api/puppeteer.launchoptions) -(or [`channel`](https://pptr.dev/api/puppeteer.launchoptions) if it's -installed in a standard location). - -When using `puppeteer-core`, remember to change the import: - -```ts -import puppeteer from 'puppeteer-core'; -``` - -### Usage - -Puppeteer follows the latest -[maintenance LTS](https://github.com/nodejs/Release#release-schedule) version of -Node. - -Puppeteer will be familiar to people using other browser testing frameworks. You -[launch](https://pptr.dev/api/puppeteer.puppeteernode.launch)/[connect](https://pptr.dev/api/puppeteer.puppeteernode.connect) -a [browser](https://pptr.dev/api/puppeteer.browser), -[create](https://pptr.dev/api/puppeteer.browser.newpage) some -[pages](https://pptr.dev/api/puppeteer.page), and then manipulate them with -[Puppeteer's API](https://pptr.dev/api). - -For more in-depth usage, check our [guides](https://pptr.dev/category/guides) -and [examples](https://github.com/puppeteer/puppeteer/tree/main/examples). +## [Get started](https://pptr.dev/docs) | [API](https://pptr.dev/api) | [FAQ](https://pptr.dev/faq) | [Contributing](https://pptr.dev/contributing) | [Troubleshooting](https://pptr.dev/troubleshooting) -#### Example - -The following example searches [developer.chrome.com](https://developer.chrome.com/) for blog posts with text "automate beyond recorder", click on the first result and print the full title of the blog post. +## Example ```ts import puppeteer from 'puppeteer'; @@ -175,87 +51,3 @@ import puppeteer from 'puppeteer'; await browser.close(); })(); ``` - -### Default runtime settings - -**1. Uses Headless mode** - -By default Puppeteer launches Chrome in -[the Headless mode](https://developer.chrome.com/articles/new-headless/). - -```ts -const browser = await puppeteer.launch(); -// Equivalent to -const browser = await puppeteer.launch({headless: true}); -``` - -Before v22, Puppeteer launched the [old Headless mode](https://developer.chrome.com/articles/new-headless/) by default. -The old headless mode is now known as -[`chrome-headless-shell`](https://developer.chrome.com/blog/chrome-headless-shell) -and ships as a separate binary. `chrome-headless-shell` does not match the -behavior of the regular Chrome completely but it is currently more performant -for automation tasks where the complete Chrome feature set is not needed. If the performance -is more important for your use case, switch to `chrome-headless-shell` as following: - -```ts -const browser = await puppeteer.launch({headless: 'shell'}); -``` - -To launch a "headful" version of Chrome, set the -[`headless`](https://pptr.dev/api/puppeteer.browserlaunchargumentoptions) to `false` -option when launching a browser: - -```ts -const browser = await puppeteer.launch({headless: false}); -``` - -**2. Runs a bundled version of Chrome** - -By default, Puppeteer downloads and uses a specific version of Chrome so its -API is guaranteed to work out of the box. To use Puppeteer with a different -version of Chrome or Chromium, pass in the executable's path when creating a -`Browser` instance: - -```ts -const browser = await puppeteer.launch({executablePath: '/path/to/Chrome'}); -``` - -You can also use Puppeteer with Firefox. See -[status of cross-browser support](https://pptr.dev/faq/#q-what-is-the-status-of-cross-browser-support) for -more information. - -See -[`this article`](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) -for a description of the differences between Chromium and Chrome. -[`This article`](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/chromium_browser_vs_google_chrome.md) -describes some differences for Linux users. - -**3. Creates a fresh user profile** - -Puppeteer creates its own browser user profile which it **cleans up on every -run**. - -#### Using Docker - -See our [Docker guide](https://pptr.dev/guides/docker). - -#### Using Chrome Extensions - -See our [Chrome extensions guide](https://pptr.dev/guides/chrome-extensions). - -## Resources - -- [API Documentation](https://pptr.dev/api) -- [Guides](https://pptr.dev/category/guides) -- [Examples](https://github.com/puppeteer/puppeteer/tree/main/examples) -- [Community list of Puppeteer resources](https://github.com/transitive-bullshit/awesome-puppeteer) - -## Contributing - -Check out our [contributing guide](https://pptr.dev/contributing) to get an -overview of Puppeteer development. - -## FAQ - -Our [FAQ](https://pptr.dev/faq) has migrated to -[our site](https://pptr.dev/faq). diff --git a/remote/test/puppeteer/examples/README.md b/remote/test/puppeteer/examples/README.md index 92e83eb39c..b87324b7e0 100644 --- a/remote/test/puppeteer/examples/README.md +++ b/remote/test/puppeteer/examples/README.md @@ -1,6 +1,18 @@ # Running the examples -Assuming you have a checkout of the Puppeteer repo and have run `npm i` (or `yarn`) to install the dependencies, and `npm run build` (or `yarn run build`) to build the project, the examples can be run from the root folder like so: +Assuming you have a checkout of the Puppeteer repo and install the dependencies: + +```bash npm2yarn +npm install +``` + +Build the project: + +```bash npm2yarn +npm run build +``` + +The examples can be run from the root folder like so: ```bash NODE_PATH=../ node examples/search.js diff --git a/remote/test/puppeteer/moz.yaml b/remote/test/puppeteer/moz.yaml index 35363b67dc..9c6b0e80c3 100644 --- a/remote/test/puppeteer/moz.yaml +++ b/remote/test/puppeteer/moz.yaml @@ -5,6 +5,6 @@ origin: description: Headless Chrome Node API license: Apache-2.0 name: puppeteer - release: puppeteer-v22.4.0 - url: ../puppeteer + release: puppeteer-v22.6.5 + url: /Users/juliandescottes/Development/git/puppeteer schema: 1 diff --git a/remote/test/puppeteer/package-lock.json b/remote/test/puppeteer/package-lock.json index b1fb3b0189..d99df978d1 100644 --- a/remote/test/puppeteer/package-lock.json +++ b/remote/test/puppeteer/package-lock.json @@ -21,32 +21,32 @@ "@types/node": "20.8.4", "@types/semver": "7.5.8", "@types/sinon": "17.0.3", - "@typescript-eslint/eslint-plugin": "7.1.0", - "@typescript-eslint/parser": "7.1.0", - "esbuild": "0.20.1", + "@typescript-eslint/eslint-plugin": "7.6.0", + "@typescript-eslint/parser": "7.6.0", + "esbuild": "0.20.2", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.29.1", - "eslint-plugin-mocha": "10.3.0", + "eslint-plugin-mocha": "10.4.2", "eslint-plugin-prettier": "5.1.3", "eslint-plugin-rulesdir": "0.2.2", "eslint-plugin-tsdoc": "0.2.17", "eslint-plugin-unused-imports": "3.1.0", "execa": "8.0.1", "expect": "29.7.0", - "gts": "5.2.0", + "gts": "5.3.0", "hereby": "1.8.9", "license-checker": "25.0.1", - "mocha": "10.3.0", + "mocha": "10.4.0", "npm-run-all2": "6.1.2", "prettier": "3.2.5", "semver": "7.6.0", "sinon": "17.0.1", "source-map-support": "0.5.21", "spdx-satisfies": "5.0.1", - "tsd": "0.30.7", - "tsx": "4.7.1", + "tsd": "0.31.0", + "tsx": "4.7.2", "typescript": "5.3.3", "wireit": "0.14.4" } @@ -80,6 +80,18 @@ "undici": "^5.25.4" } }, + "node_modules/@actions/http-client/node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dev": true, + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", @@ -544,16 +556,16 @@ } }, "node_modules/@microsoft/api-documenter": { - "version": "7.23.35", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.23.35.tgz", - "integrity": "sha512-zSHTX0abumOsfA3GsWJADFtiqxgwcoSCGSO+84e4s/SWotAqlUwXMYVJk6/huJFKSL+LG46gvcn7r8bsOd3K2Q==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.24.2.tgz", + "integrity": "sha512-q03DXLBj7nzAzLyLRAVklBynqKgSFI/JBmrhF/mEEIpg8orNo4qKXWO1RSkD2IYrqvZV63b13mcUPYgcFdifQA==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.28.13", + "@microsoft/api-extractor-model": "7.28.14", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "4.0.2", - "@rushstack/terminal": "0.10.0", - "@rushstack/ts-command-line": "4.19.0", + "@rushstack/node-core-library": "4.1.0", + "@rushstack/terminal": "0.10.1", + "@rushstack/ts-command-line": "4.19.2", "js-yaml": "~3.13.1", "resolve": "~1.22.1" }, @@ -562,12 +574,12 @@ } }, "node_modules/@microsoft/api-documenter/node_modules/@rushstack/terminal": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.0.tgz", - "integrity": "sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.1.tgz", + "integrity": "sha512-C6Vi/m/84IYJTkfzmXr1+W8Wi3MmBjVF/q3za91Gb3VYjKbpALHVxY6FgH625AnDe5Z0Kh4MHKWA3Z7bqgAezA==", "dev": true, "dependencies": { - "@rushstack/node-core-library": "4.0.2", + "@rushstack/node-core-library": "4.1.0", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -595,12 +607,12 @@ } }, "node_modules/@microsoft/api-documenter/node_modules/@rushstack/ts-command-line": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.0.tgz", - "integrity": "sha512-0sIHWOFGLFb6tC1zk2R0aM79ic3CF0XGzVBvhf6ytMyjDwt03DVb1qe5/5NQ0FGcvB5YyQ2WVfGsnxG6SANvHA==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.2.tgz", + "integrity": "sha512-cqmXXmBEBlzo9WtyUrHtF9e6kl0LvBY7aTSVX4jfnBfXWZQWnPq9JTFPlQZ+L/ZwjZ4HrNwQsOVvhe9oOucZkw==", "dev": true, "dependencies": { - "@rushstack/terminal": "0.10.0", + "@rushstack/terminal": "0.10.1", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" @@ -635,47 +647,47 @@ "dev": true }, "node_modules/@microsoft/api-extractor": { - "version": "7.42.2", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.42.2.tgz", - "integrity": "sha512-HYiOQDO4WR+Pj4XQZZE5qK5R6e3MF6Ut5s+Hi2IkeI6MiCXkdmRugQH6ppc9YzTUiydRqZ+jshZD7UWNGSA8bg==", + "version": "7.43.1", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.43.1.tgz", + "integrity": "sha512-ohg40SsvFFgzHFAtYq5wKJc8ZDyY46bphjtnSvhSSlXpPTG7GHwyyXkn48UZiUCBwr2WC7TRC1Jfwz7nreuiyQ==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.28.13", + "@microsoft/api-extractor-model": "7.28.14", "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "4.0.2", + "@rushstack/node-core-library": "4.1.0", "@rushstack/rig-package": "0.5.2", - "@rushstack/terminal": "0.10.0", - "@rushstack/ts-command-line": "4.19.0", + "@rushstack/terminal": "0.10.1", + "@rushstack/ts-command-line": "4.19.2", "lodash": "~4.17.15", "minimatch": "~3.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", - "typescript": "5.3.3" + "typescript": "5.4.2" }, "bin": { "api-extractor": "bin/api-extractor" } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.28.13", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.13.tgz", - "integrity": "sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==", + "version": "7.28.14", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.14.tgz", + "integrity": "sha512-Bery/c8A8SsKPSvA82cTTuy/+OcxZbLRmKhPkk91/AJOQzxZsShcrmHFAGeiEqSIrv1nPZ3tKq9kfMLdCHmsqg==", "dev": true, "dependencies": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "4.0.2" + "@rushstack/node-core-library": "4.1.0" } }, "node_modules/@microsoft/api-extractor/node_modules/@rushstack/terminal": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.0.tgz", - "integrity": "sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.1.tgz", + "integrity": "sha512-C6Vi/m/84IYJTkfzmXr1+W8Wi3MmBjVF/q3za91Gb3VYjKbpALHVxY6FgH625AnDe5Z0Kh4MHKWA3Z7bqgAezA==", "dev": true, "dependencies": { - "@rushstack/node-core-library": "4.0.2", + "@rushstack/node-core-library": "4.1.0", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -703,12 +715,12 @@ } }, "node_modules/@microsoft/api-extractor/node_modules/@rushstack/ts-command-line": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.0.tgz", - "integrity": "sha512-0sIHWOFGLFb6tC1zk2R0aM79ic3CF0XGzVBvhf6ytMyjDwt03DVb1qe5/5NQ0FGcvB5YyQ2WVfGsnxG6SANvHA==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.19.2.tgz", + "integrity": "sha512-cqmXXmBEBlzo9WtyUrHtF9e6kl0LvBY7aTSVX4jfnBfXWZQWnPq9JTFPlQZ+L/ZwjZ4HrNwQsOVvhe9oOucZkw==", "dev": true, "dependencies": { - "@rushstack/terminal": "0.10.0", + "@rushstack/terminal": "0.10.1", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" @@ -905,9 +917,9 @@ "link": true }, "node_modules/@rushstack/node-core-library": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.0.2.tgz", - "integrity": "sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.1.0.tgz", + "integrity": "sha512-qz4JFBZJCf1YN5cAXa1dP6Mki/HrsQxc/oYGAGx29dF2cwF2YMxHoly0FBhMw3IEnxo5fMj0boVfoHVBkpkx/w==", "dev": true, "dependencies": { "fs-extra": "~7.0.1", @@ -1002,9 +1014,9 @@ "dev": true }, "node_modules/@swc/core": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.2.tgz", - "integrity": "sha512-vWgY07R/eqj1/a0vsRKLI9o9klGZfpLNOVEnrv4nrccxBgYPjcf22IWwAoaBJ+wpA7Q4fVjCUM8lP0m01dpxcg==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.13.tgz", + "integrity": "sha512-rOtusBE+2gaeRkAJn5E4zp5yzZekZOypzSOz5ZG6P1hFbd+Cc26fWEdK6sUSnrkkvTd0Oj33KXLB/4UkbK/UHA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -1019,16 +1031,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.4.2", - "@swc/core-darwin-x64": "1.4.2", - "@swc/core-linux-arm-gnueabihf": "1.4.2", - "@swc/core-linux-arm64-gnu": "1.4.2", - "@swc/core-linux-arm64-musl": "1.4.2", - "@swc/core-linux-x64-gnu": "1.4.2", - "@swc/core-linux-x64-musl": "1.4.2", - "@swc/core-win32-arm64-msvc": "1.4.2", - "@swc/core-win32-ia32-msvc": "1.4.2", - "@swc/core-win32-x64-msvc": "1.4.2" + "@swc/core-darwin-arm64": "1.4.13", + "@swc/core-darwin-x64": "1.4.13", + "@swc/core-linux-arm-gnueabihf": "1.4.13", + "@swc/core-linux-arm64-gnu": "1.4.13", + "@swc/core-linux-arm64-musl": "1.4.13", + "@swc/core-linux-x64-gnu": "1.4.13", + "@swc/core-linux-x64-musl": "1.4.13", + "@swc/core-win32-arm64-msvc": "1.4.13", + "@swc/core-win32-ia32-msvc": "1.4.13", + "@swc/core-win32-x64-msvc": "1.4.13" }, "peerDependencies": { "@swc/helpers": "^0.5.0" @@ -1040,9 +1052,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-darwin-arm64": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.2.tgz", - "integrity": "sha512-1uSdAn1MRK5C1m/TvLZ2RDvr0zLvochgrZ2xL+lRzugLlCTlSA+Q4TWtrZaOz+vnnFVliCpw7c7qu0JouhgQIw==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.13.tgz", + "integrity": "sha512-36P72FLpm5iq85IvoEjBvi22DiqkkEIanJ1M0E8bkxcFHUbjBrYfPY9T6cpPyK5oQqkaTBvNAc3j1BlVD6IH6w==", "cpu": [ "arm64" ], @@ -1056,9 +1068,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-darwin-x64": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.2.tgz", - "integrity": "sha512-TYD28+dCQKeuxxcy7gLJUCFLqrwDZnHtC2z7cdeGfZpbI2mbfppfTf2wUPzqZk3gEC96zHd4Yr37V3Tvzar+lQ==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.13.tgz", + "integrity": "sha512-ye7OgKpDdyA8AMIVVdmD1ICDaFXgoEXORnVO8bBHyul0WN71yUBZMX+YxEx2lpWtiftA2vY/1MAuOR80vHkBCw==", "cpu": [ "x64" ], @@ -1072,9 +1084,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.2.tgz", - "integrity": "sha512-Eyqipf7ZPGj0vplKHo8JUOoU1un2sg5PjJMpEesX0k+6HKE2T8pdyeyXODN0YTFqzndSa/J43EEPXm+rHAsLFQ==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.13.tgz", + "integrity": "sha512-+x593Jlmu4c3lJtZUKRejWpV2MAij1Js5nmQLLdjo6ChR2D4B2rzj3iMiKn5gITew7fraF9t3fvXALdWh7HmUg==", "cpu": [ "arm" ], @@ -1088,9 +1100,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.2.tgz", - "integrity": "sha512-wZn02DH8VYPv3FC0ub4my52Rttsus/rFw+UUfzdb3tHMHXB66LqN+rR0ssIOZrH6K+VLN6qpTw9VizjyoH0BxA==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.13.tgz", + "integrity": "sha512-0x8OVw4dfyNerrs/9eZX9wNnmvwbwXSMCi+LbE6Xt1pXOIwvoLtFIXcV3NsrlkFboO3sr5UAQIwDxKqbIZA9pQ==", "cpu": [ "arm64" ], @@ -1104,9 +1116,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-linux-arm64-musl": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.2.tgz", - "integrity": "sha512-3G0D5z9hUj9bXNcwmA1eGiFTwe5rWkuL3DsoviTj73TKLpk7u64ND0XjEfO0huVv4vVu9H1jodrKb7nvln/dlw==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.13.tgz", + "integrity": "sha512-Z9c4JiequtZvngPcxbCuAOkmWBxi2vInZbjjhD5I+Q9oiJdXUz1t2USGwsGPS41Xvk1BOA3ecK2Sn1ilY3titg==", "cpu": [ "arm64" ], @@ -1120,9 +1132,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-linux-x64-gnu": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.2.tgz", - "integrity": "sha512-LFxn9U8cjmYHw3jrdPNqPAkBGglKE3tCZ8rA7hYyp0BFxuo7L2ZcEnPm4RFpmSCCsExFH+LEJWuMGgWERoktvg==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.13.tgz", + "integrity": "sha512-ChatHtk+vX0Ke5QG+jO+rIapw/KwZsi9MedCBHFXHH6iWF4z8d51cJeN68ykcn+vAXzjNeFNdlNy5Vbkd1zAqg==", "cpu": [ "x64" ], @@ -1136,9 +1148,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-linux-x64-musl": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.2.tgz", - "integrity": "sha512-dp0fAmreeVVYTUcb4u9njTPrYzKnbIH0EhH2qvC9GOYNNREUu2GezSIDgonjOXkHiTCvopG4xU7y56XtXj4VrQ==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.13.tgz", + "integrity": "sha512-0Pz39YR530mXpsztwQkmEKdkkZy4fY4Smdh4pkm6Ly8Nndyo0te/l4bcAGqN24Jp7aVwF/QSy14SAtw4HRjU9g==", "cpu": [ "x64" ], @@ -1152,9 +1164,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.2.tgz", - "integrity": "sha512-HlVIiLMQkzthAdqMslQhDkoXJ5+AOLUSTV6fm6shFKZKqc/9cJvr4S8UveNERL9zUficA36yM3bbfo36McwnvQ==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.13.tgz", + "integrity": "sha512-LVZfhlD+jHcAbz5NN+gAJ1BEasB0WpcvUzcsJt0nQSRsojgzPzFjJ+fzEBnvT7SMtqKkrnVJ0OmDYeh88bDRpw==", "cpu": [ "arm64" ], @@ -1168,9 +1180,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.2.tgz", - "integrity": "sha512-WCF8faPGjCl4oIgugkp+kL9nl3nUATlzKXCEGFowMEmVVCFM0GsqlmGdPp1pjZoWc9tpYanoXQDnp5IvlDSLhA==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.13.tgz", + "integrity": "sha512-78hxHWUvUZtWsnhcf8DKwhBcNFJw+j4y4fN2B9ioXmBWX2tIyw+BqUHOrismOtjPihaZmwe/Ok2e4qmkawE2fw==", "cpu": [ "ia32" ], @@ -1184,9 +1196,9 @@ } }, "node_modules/@swc/core/node_modules/@swc/core-win32-x64-msvc": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.2.tgz", - "integrity": "sha512-oV71rwiSpA5xre2C5570BhCsg1HF97SNLsZ/12xv7zayGzqr3yvFALFJN8tHKpqUdCB4FGPjoP3JFdV3i+1wUw==", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.13.tgz", + "integrity": "sha512-WSfy1u2Xde6jU7UpHIInCUMW98Zw9iZglddKUAvmr1obkZji5U6EX0Oca3asEJdZPFb+2lMLjt0Mh5a1YisROg==", "cpu": [ "x64" ], @@ -1216,15 +1228,6 @@ "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" }, - "node_modules/@tsd/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-CQlfzol0ldaU+ftWuG52vH29uRoKboLinLy84wS8TQOu+m+tWoaUfk4svL4ij2V8M5284KymJBlHUusKj6k34w==", - "dev": true, - "engines": { - "node": ">=14.17" - } - }, "node_modules/@types/argparse": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", @@ -1479,25 +1482,25 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.0.tgz", - "integrity": "sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz", + "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/type-utils": "7.1.0", - "@typescript-eslint/utils": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/type-utils": "7.6.0", + "@typescript-eslint/utils": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1514,16 +1517,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz", - "integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0" + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1531,12 +1534,12 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1544,18 +1547,18 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.0.tgz", - "integrity": "sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz", + "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.1.0", - "@typescript-eslint/utils": "7.1.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/utils": "7.6.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1571,22 +1574,22 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz", - "integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1599,29 +1602,44 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", - "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1629,32 +1647,44 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/@typescript-eslint/parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.0.tgz", - "integrity": "sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz", + "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/typescript-estree": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1670,16 +1700,16 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz", - "integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0" + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1687,12 +1717,12 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1700,22 +1730,22 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz", - "integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1727,17 +1757,44 @@ } } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", - "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1745,21 +1802,21 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.0.tgz", - "integrity": "sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz", + "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.1.0", - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/typescript-estree": "7.1.0", - "semver": "^7.5.4" + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.6.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/typescript-estree": "7.6.0", + "semver": "^7.6.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1770,16 +1827,16 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz", - "integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", + "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0" + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1787,16 +1844,16 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", - "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1804,12 +1861,12 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz", - "integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", + "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1817,22 +1874,22 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz", - "integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", + "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "@typescript-eslint/visitor-keys": "7.1.0", + "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/visitor-keys": "7.6.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1845,22 +1902,49 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz", - "integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", + "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.6.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -2755,14 +2839,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2926,11 +3002,6 @@ "node": ">= 14" } }, - "node_modules/devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==" - }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", @@ -2996,6 +3067,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -3005,6 +3077,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -3173,9 +3246,9 @@ } }, "node_modules/esbuild": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", - "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, "bin": { @@ -3185,35 +3258,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.1", - "@esbuild/android-arm": "0.20.1", - "@esbuild/android-arm64": "0.20.1", - "@esbuild/android-x64": "0.20.1", - "@esbuild/darwin-arm64": "0.20.1", - "@esbuild/darwin-x64": "0.20.1", - "@esbuild/freebsd-arm64": "0.20.1", - "@esbuild/freebsd-x64": "0.20.1", - "@esbuild/linux-arm": "0.20.1", - "@esbuild/linux-arm64": "0.20.1", - "@esbuild/linux-ia32": "0.20.1", - "@esbuild/linux-loong64": "0.20.1", - "@esbuild/linux-mips64el": "0.20.1", - "@esbuild/linux-ppc64": "0.20.1", - "@esbuild/linux-riscv64": "0.20.1", - "@esbuild/linux-s390x": "0.20.1", - "@esbuild/linux-x64": "0.20.1", - "@esbuild/netbsd-x64": "0.20.1", - "@esbuild/openbsd-x64": "0.20.1", - "@esbuild/sunos-x64": "0.20.1", - "@esbuild/win32-arm64": "0.20.1", - "@esbuild/win32-ia32": "0.20.1", - "@esbuild/win32-x64": "0.20.1" + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", - "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", "cpu": [ "ppc64" ], @@ -3227,9 +3300,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/android-arm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", - "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", "cpu": [ "arm" ], @@ -3243,9 +3316,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/android-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", - "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", "cpu": [ "arm64" ], @@ -3259,9 +3332,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/android-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", - "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", "cpu": [ "x64" ], @@ -3275,9 +3348,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", - "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], @@ -3291,9 +3364,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", - "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", "cpu": [ "x64" ], @@ -3307,9 +3380,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", - "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", "cpu": [ "arm64" ], @@ -3323,9 +3396,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", - "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", "cpu": [ "x64" ], @@ -3339,9 +3412,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-arm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", - "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", "cpu": [ "arm" ], @@ -3355,9 +3428,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", - "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", "cpu": [ "arm64" ], @@ -3371,9 +3444,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", - "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", "cpu": [ "ia32" ], @@ -3387,9 +3460,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", - "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", "cpu": [ "loong64" ], @@ -3403,9 +3476,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", - "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", "cpu": [ "mips64el" ], @@ -3419,9 +3492,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", - "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", "cpu": [ "ppc64" ], @@ -3435,9 +3508,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", - "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", "cpu": [ "riscv64" ], @@ -3451,9 +3524,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", - "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", "cpu": [ "s390x" ], @@ -3467,9 +3540,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/linux-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", - "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", "cpu": [ "x64" ], @@ -3483,9 +3556,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", - "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", "cpu": [ "x64" ], @@ -3499,9 +3572,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", - "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", "cpu": [ "x64" ], @@ -3515,9 +3588,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", - "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", "cpu": [ "x64" ], @@ -3531,9 +3604,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", - "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", "cpu": [ "arm64" ], @@ -3547,9 +3620,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", - "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", "cpu": [ "ia32" ], @@ -3563,9 +3636,9 @@ } }, "node_modules/esbuild/node_modules/@esbuild/win32-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", - "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", "cpu": [ "x64" ], @@ -3914,12 +3987,13 @@ } }, "node_modules/eslint-plugin-mocha": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.3.0.tgz", - "integrity": "sha512-IWzbg2K6B1Q7h37Ih4zMyW+nhmw1JvUlHlbCUUUu6PfOOAUGCB0gxmvv7/U+TQQ6e8yHUv+q7KMdIIum4bx+PA==", + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.4.2.tgz", + "integrity": "sha512-cur4dVYnSEWTBwdqIBQFxa/9siAhesu0TX+lbJ4ClE9j0eNMNe6BSx3vkFFNz6tGoveyMyELFXa30f3fvuAVDg==", "dev": true, "dependencies": { "eslint-utils": "^3.0.0", + "globals": "^13.24.0", "rambda": "^7.4.0" }, "engines": { @@ -4720,6 +4794,28 @@ "node": ">= 10.0.0" } }, + "node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4732,6 +4828,31 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -4806,24 +4927,24 @@ "dev": true }, "node_modules/gts": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/gts/-/gts-5.2.0.tgz", - "integrity": "sha512-25qOnePUUX7upFc4ycqWersDBq+o1X6hXUTW56JOWCxPYKJXQ1RWzqT9q+2SU3LfPKJf+4sz4Dw3VT0p96Kv6g==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gts/-/gts-5.3.0.tgz", + "integrity": "sha512-/V0nbaWLBv8g0v2kol5M7Vf+kHXk19Ew5sa3wQJXeUaccesXg7AFo7eEInoIpR+aIJ3QDjoYt4zHYeFr8w8rng==", "dev": true, "dependencies": { "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "5.62.0", "chalk": "^4.1.2", - "eslint": "8.50.0", - "eslint-config-prettier": "9.0.0", + "eslint": "8.53.0", + "eslint-config-prettier": "9.1.0", "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "5.0.0", + "eslint-plugin-prettier": "5.1.3", "execa": "^5.0.0", "inquirer": "^7.3.3", "json5": "^2.1.3", "meow": "^9.0.0", "ncp": "^2.0.0", - "prettier": "3.0.3", + "prettier": "3.1.1", "rimraf": "3.0.2", "write-file-atomic": "^4.0.0" }, @@ -4837,15 +4958,6 @@ "typescript": ">=3" } }, - "node_modules/gts/node_modules/@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/gts/node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", @@ -5058,18 +5170,19 @@ } }, "node_modules/gts/node_modules/eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -5111,45 +5224,13 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/gts/node_modules/eslint-config-prettier": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", - "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "node_modules/gts/node_modules/eslint/node_modules/@eslint/js": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/gts/node_modules/eslint-plugin-prettier": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", - "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" - }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/gts/node_modules/estraverse": { @@ -5266,9 +5347,9 @@ } }, "node_modules/gts/node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -6616,9 +6697,9 @@ } }, "node_modules/mocha": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", - "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", + "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", "dependencies": { "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", @@ -6750,25 +6831,6 @@ "path-to-regexp": "^6.2.1" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/nopt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", @@ -7239,6 +7301,7 @@ "version": "1.10.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, "dependencies": { "lru-cache": "^9.1.1 || ^10.0.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -7254,6 +7317,7 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, "engines": { "node": "14 || >=16.14" } @@ -8183,7 +8247,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true + "dev": true }, "node_modules/semver": { "version": "7.6.0", @@ -8853,11 +8917,6 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/treeify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", @@ -8876,18 +8935,6 @@ "node": ">=8" } }, - "node_modules/ts-api-utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", - "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -8913,12 +8960,12 @@ } }, "node_modules/tsd": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.30.7.tgz", - "integrity": "sha512-oTiJ28D6B/KXoU3ww/Eji+xqHJojiuPVMwA12g4KYX1O72N93Nb6P3P3h2OAhhf92Xl8NIhb/xFmBZd5zw/xUw==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.31.0.tgz", + "integrity": "sha512-yjBiQ5n8OMv/IZOuhDjBy0ZLCoJ7rky/RxRh5W4sJ0oNNCU/kf6s3puPAkGNi59PptDdkcpUm+RsKSdjR2YbNg==", "dev": true, "dependencies": { - "@tsd/typescript": "~5.3.3", + "@tsd/typescript": "~5.4.3", "eslint-formatter-pretty": "^4.1.0", "globby": "^11.0.1", "jest-diff": "^29.0.3", @@ -8933,6 +8980,15 @@ "node": ">=14.16" } }, + "node_modules/tsd/node_modules/@tsd/typescript": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-sqE6Rz9UNHBuCtuREo/PwsuUaegP1KDSfxMd+9K1qPpt7XO8BmkIImUp7zAqyvwXWUHs+sj6osEkkpyE0tFgfA==", + "dev": true, + "engines": { + "node": ">=14.17" + } + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -8955,9 +9011,9 @@ } }, "node_modules/tsx": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.1.tgz", - "integrity": "sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.2.tgz", + "integrity": "sha512-BCNd4kz6fz12fyrgCTEdZHGJ9fWTGeUzXmQysh0RVocDY3h4frk05ZNCXSy4kIenF7y/QnrdiVpTsyNRn6vlAw==", "dev": true, "dependencies": { "esbuild": "~0.19.10", @@ -9540,18 +9596,6 @@ "through": "^2.3.8" } }, - "node_modules/undici": { - "version": "5.28.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", - "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", - "dev": true, - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, "node_modules/undici-types": { "version": "5.25.3", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", @@ -9641,20 +9685,6 @@ "defaults": "^1.0.3" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9944,14 +9974,13 @@ "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", - "dev": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "packages/browsers": { "name": "@puppeteer/browsers", - "version": "2.1.0", + "version": "2.2.2", "license": "Apache-2.0", "dependencies": { "debug": "4.3.4", @@ -11996,9 +12025,9 @@ } }, "packages/ng-schematics/node_modules/@angular/cli/node_modules/pacote/node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, "dependencies": { "chownr": "^2.0.0", @@ -12206,13 +12235,14 @@ } }, "packages/puppeteer": { - "version": "22.4.0", + "version": "22.6.5", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.1.0", + "@puppeteer/browsers": "2.2.2", "cosmiconfig": "9.0.0", - "puppeteer-core": "22.4.0" + "devtools-protocol": "0.0.1262051", + "puppeteer-core": "22.6.5" }, "bin": { "puppeteer": "lib/esm/puppeteer/node/cli.js" @@ -12225,14 +12255,13 @@ } }, "packages/puppeteer-core": { - "version": "22.4.0", + "version": "22.6.5", "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.1.0", - "chromium-bidi": "0.5.12", - "cross-fetch": "4.0.0", + "@puppeteer/browsers": "2.2.2", + "chromium-bidi": "0.5.17", "debug": "4.3.4", - "devtools-protocol": "0.0.1249869", + "devtools-protocol": "0.0.1262051", "ws": "8.16.0" }, "devDependencies": { @@ -12253,17 +12282,23 @@ "license": "MIT" }, "packages/puppeteer-core/node_modules/chromium-bidi": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.12.tgz", - "integrity": "sha512-sZMgEBWKbupD0Q7lyFu8AWkrE+rs5ycE12jFkGwIgD/VS8lDPtelPlXM7LYaq4zrkZ/O2L3f4afHUHL0ICdKog==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.17.tgz", + "integrity": "sha512-BqOuIWUgTPj8ayuBFJUYCCuwIcwjBsb3/614P7tt1bEPJ4i1M0kCdIl0Wi9xhtswBXnfO2bTpTMkHD71H8rJMg==", "dependencies": { "mitt": "3.0.1", - "urlpattern-polyfill": "10.0.0" + "urlpattern-polyfill": "10.0.0", + "zod": "3.22.4" }, "peerDependencies": { "devtools-protocol": "*" } }, + "packages/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1262051", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1262051.tgz", + "integrity": "sha512-YJe4CT5SA8on3Spa+UDtNhEqtuV6Epwz3OZ4HQVLhlRccpZ9/PAYk0/cy/oKxFKRrZPBUPyxympQci4yWNWZ9g==" + }, "packages/puppeteer-core/node_modules/rxjs": { "version": "7.8.1", "dev": true, @@ -12306,6 +12341,11 @@ } } }, + "packages/puppeteer/node_modules/devtools-protocol": { + "version": "0.0.1262051", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1262051.tgz", + "integrity": "sha512-YJe4CT5SA8on3Spa+UDtNhEqtuV6Epwz3OZ4HQVLhlRccpZ9/PAYk0/cy/oKxFKRrZPBUPyxympQci4yWNWZ9g==" + }, "packages/puppeteer/node_modules/parse-json": { "version": "5.2.0", "license": "MIT", @@ -12353,26 +12393,20 @@ "name": "@puppeteer-test/installation", "version": "latest", "dependencies": { - "glob": "10.3.10", - "mocha": "10.3.0" + "glob": "10.3.12", + "mocha": "10.4.0" } }, - "test/node_modules/diff": { - "version": "5.2.0", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "test/node_modules/glob": { - "version": "10.3.10", - "license": "ISC", + "test/installation/node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -12384,16 +12418,46 @@ "url": "https://github.com/sponsors/isaacs" } }, + "test/installation/node_modules/glob/node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "test/installation/node_modules/glob/node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "engines": { + "node": "14 || >=16.14" + } + }, + "test/node_modules/diff": { + "version": "5.2.0", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "tools/docgen": { "name": "@puppeteer/docgen", "version": "0.1.0", "license": "Apache-2.0", "devDependencies": { - "@microsoft/api-documenter": "7.23.35", - "@microsoft/api-extractor": "7.42.2", - "@microsoft/api-extractor-model": "7.28.13", + "@microsoft/api-documenter": "7.24.2", + "@microsoft/api-extractor": "7.43.1", + "@microsoft/api-extractor-model": "7.28.14", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "4.0.2" + "@rushstack/node-core-library": "4.1.0" } }, "tools/doctest": { @@ -12404,13 +12468,13 @@ "doctest": "bin/doctest.js" }, "devDependencies": { - "@swc/core": "1.4.2", + "@swc/core": "1.4.13", "@types/doctrine": "0.0.9", "@types/source-map-support": "0.5.10", "@types/yargs": "17.0.32", "acorn": "8.11.3", "doctrine": "3.0.0", - "glob": "10.3.10", + "glob": "10.3.12", "pkg-dir": "8.0.0", "source-map": "0.7.4", "source-map-support": "0.5.21", @@ -12430,27 +12494,6 @@ "node": ">=12" } }, - "tools/doctest/node_modules/glob": { - "version": "10.3.10", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "tools/doctest/node_modules/source-map": { "version": "0.7.4", "dev": true, @@ -12490,7 +12533,7 @@ "license": "Apache-2.0", "devDependencies": { "@prettier/sync": "0.5.1", - "@typescript-eslint/utils": "7.1.0" + "@typescript-eslint/utils": "7.6.0" } }, "tools/mocha-runner": { @@ -12503,7 +12546,7 @@ "devDependencies": { "@types/yargs": "17.0.32", "c8": "9.1.0", - "glob": "10.3.10", + "glob": "10.3.12", "yargs": "17.7.2", "zod": "3.22.4" } @@ -12521,27 +12564,6 @@ "node": ">=12" } }, - "tools/mocha-runner/node_modules/glob": { - "version": "10.3.10", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "tools/mocha-runner/node_modules/yargs": { "version": "17.7.2", "dev": true, diff --git a/remote/test/puppeteer/package.json b/remote/test/puppeteer/package.json index 0ffdceedf7..a871a1d459 100644 --- a/remote/test/puppeteer/package.json +++ b/remote/test/puppeteer/package.json @@ -142,13 +142,13 @@ "@types/node": "20.8.4", "@types/semver": "7.5.8", "@types/sinon": "17.0.3", - "@typescript-eslint/eslint-plugin": "7.1.0", - "@typescript-eslint/parser": "7.1.0", - "esbuild": "0.20.1", + "@typescript-eslint/eslint-plugin": "7.6.0", + "@typescript-eslint/parser": "7.6.0", + "esbuild": "0.20.2", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.29.1", - "eslint-plugin-mocha": "10.3.0", + "eslint-plugin-mocha": "10.4.2", "eslint-plugin-prettier": "5.1.3", "eslint-plugin-rulesdir": "0.2.2", "eslint-plugin-tsdoc": "0.2.17", @@ -156,18 +156,18 @@ "eslint": "8.57.0", "execa": "8.0.1", "expect": "29.7.0", - "gts": "5.2.0", + "gts": "5.3.0", "hereby": "1.8.9", "license-checker": "25.0.1", - "mocha": "10.3.0", + "mocha": "10.4.0", "npm-run-all2": "6.1.2", "prettier": "3.2.5", "semver": "7.6.0", "sinon": "17.0.1", "source-map-support": "0.5.21", "spdx-satisfies": "5.0.1", - "tsd": "0.30.7", - "tsx": "4.7.1", + "tsd": "0.31.0", + "tsx": "4.7.2", "typescript": "5.3.3", "wireit": "0.14.4" }, diff --git a/remote/test/puppeteer/packages/browsers/CHANGELOG.md b/remote/test/puppeteer/packages/browsers/CHANGELOG.md index a2798068e9..75fa1c370b 100644 --- a/remote/test/puppeteer/packages/browsers/CHANGELOG.md +++ b/remote/test/puppeteer/packages/browsers/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## [2.2.2](https://github.com/puppeteer/puppeteer/compare/browsers-v2.2.1...browsers-v2.2.2) (2024-04-15) + + +### Bug Fixes + +* remove NetworkServiceInProcess2 set by default ([#12261](https://github.com/puppeteer/puppeteer/issues/12261)) ([ff4f70f](https://github.com/puppeteer/puppeteer/commit/ff4f70f4ae7ca8deb0becbec2e49b35322dba336)), closes [#12257](https://github.com/puppeteer/puppeteer/issues/12257) + +## [2.2.1](https://github.com/puppeteer/puppeteer/compare/browsers-v2.2.0...browsers-v2.2.1) (2024-04-05) + + +### Bug Fixes + +* do not use fallback download URLs if custom baseUrl is provided ([#12206](https://github.com/puppeteer/puppeteer/issues/12206)) ([ab560bc](https://github.com/puppeteer/puppeteer/commit/ab560bcf6fee57cabde94d9d261d28ffc2112948)) +* only set up a single process event listener in launch ([#12200](https://github.com/puppeteer/puppeteer/issues/12200)) ([7bc5e0f](https://github.com/puppeteer/puppeteer/commit/7bc5e0fb2dc443765e2512e4dc15fb2bcc1cb4be)) + +## [2.2.0](https://github.com/puppeteer/puppeteer/compare/browsers-v2.1.0...browsers-v2.2.0) (2024-03-15) + + +### Features + +* allow downloading Firefox channels other than nightly ([#12051](https://github.com/puppeteer/puppeteer/issues/12051)) ([e4cc2f9](https://github.com/puppeteer/puppeteer/commit/e4cc2f9ee944f2507a03cf8f5af99759c97ee2ec)) + + +### Bug Fixes + +* don't keep connection alive ([#12096](https://github.com/puppeteer/puppeteer/issues/12096)) ([0a142bf](https://github.com/puppeteer/puppeteer/commit/0a142bf0aa8a6666d1ca230d05a1ece0e03ad7d4)) + ## [2.1.0](https://github.com/puppeteer/puppeteer/compare/browsers-v2.0.1...browsers-v2.1.0) (2024-02-21) diff --git a/remote/test/puppeteer/packages/browsers/README.md b/remote/test/puppeteer/packages/browsers/README.md index f5342126c6..a05a4d38b8 100644 --- a/remote/test/puppeteer/packages/browsers/README.md +++ b/remote/test/puppeteer/packages/browsers/README.md @@ -10,7 +10,7 @@ Use `npx` to run the CLI: npx @puppeteer/browsers --help ``` -CLI help will provide all documentation you need to use the CLI. +Built-in per-command `help` will provide all documentation you need to use the CLI. ```bash npx @puppeteer/browsers --help # help for all commands @@ -18,10 +18,28 @@ npx @puppeteer/browsers install --help # help for the install command npx @puppeteer/browsers launch --help # help for the launch command ``` +Some example to give an idea of what the CLI looks like (use the `--help` command for more examples): + +```sh +# Download the latest available Chrome for Testing binary corresponding to the Stable channel. +npx @puppeteer/browsers install chrome@stable + +# Download a specific Chrome for Testing version. +npx @puppeteer/browsers install chrome@116.0.5793.0 + +# Download the latest Chrome for Testing version for the given milestone. +npx @puppeteer/browsers install chrome@117 + +# Download the latest available ChromeDriver version corresponding to the Canary channel. +npx @puppeteer/browsers install chromedriver@canary + +# Download a specific ChromeDriver version. +npx @puppeteer/browsers install chromedriver@116.0.5793.0 +``` + ## Known limitations -1. We support installing and running Firefox, Chrome and Chromium. The `latest`, `beta`, `dev`, `canary`, `stable` keywords are only supported for the install command. For the `launch` command you need to specify an exact build ID. The build ID is provided by the `install` command (see `npx @puppeteer/browsers install --help` for the format). -2. Launching the system browsers is only possible for Chrome/Chromium. +1. Launching the system browsers is only possible for Chrome/Chromium. ## API diff --git a/remote/test/puppeteer/packages/browsers/package.json b/remote/test/puppeteer/packages/browsers/package.json index 0f2afa74de..f9f2b1c5ea 100644 --- a/remote/test/puppeteer/packages/browsers/package.json +++ b/remote/test/puppeteer/packages/browsers/package.json @@ -1,6 +1,6 @@ { "name": "@puppeteer/browsers", - "version": "2.1.0", + "version": "2.2.2", "description": "Download and launch browsers", "scripts": { "build:docs": "wireit", diff --git a/remote/test/puppeteer/packages/browsers/src/CLI.ts b/remote/test/puppeteer/packages/browsers/src/CLI.ts index 281f22c9f5..3bae584e62 100644 --- a/remote/test/puppeteer/packages/browsers/src/CLI.ts +++ b/remote/test/puppeteer/packages/browsers/src/CLI.ts @@ -222,7 +222,31 @@ export class CLI { ); yargs.example( '$0 install firefox', - 'Install the latest available build of the Firefox browser.' + 'Install the latest nightly available build of the Firefox browser.' + ); + yargs.example( + '$0 install firefox@stable', + 'Install the latest stable build of the Firefox browser.' + ); + yargs.example( + '$0 install firefox@beta', + 'Install the latest beta build of the Firefox browser.' + ); + yargs.example( + '$0 install firefox@devedition', + 'Install the latest devedition build of the Firefox browser.' + ); + yargs.example( + '$0 install firefox@esr', + 'Install the latest ESR build of the Firefox browser.' + ); + yargs.example( + '$0 install firefox@nightly', + 'Install the latest nightly build of the Firefox browser.' + ); + yargs.example( + '$0 install firefox@stable_111.0.1', + 'Install a specific version of the Firefox browser.' ); yargs.example( '$0 install firefox --platform mac', @@ -395,7 +419,7 @@ export function makeProgressCallback( return (downloadedBytes: number, totalBytes: number) => { if (!progressBar) { progressBar = new ProgressBar( - `Downloading ${browser} r${buildId} - ${toMegabytes( + `Downloading ${browser} ${buildId} - ${toMegabytes( totalBytes )} [:bar] :percent :etas `, { diff --git a/remote/test/puppeteer/packages/browsers/src/browser-data/browser-data.ts b/remote/test/puppeteer/packages/browsers/src/browser-data/browser-data.ts index 3e78030aa7..fa7ec9be14 100644 --- a/remote/test/puppeteer/packages/browsers/src/browser-data/browser-data.ts +++ b/remote/test/puppeteer/packages/browsers/src/browser-data/browser-data.ts @@ -54,28 +54,36 @@ export const versionComparators = { export {Browser, BrowserPlatform, ChromeReleaseChannel}; /** - * @public + * @internal */ -export async function resolveBuildId( +async function resolveBuildIdForBrowserTag( browser: Browser, platform: BrowserPlatform, - tag: string + tag: BrowserTag ): Promise<string> { switch (browser) { case Browser.FIREFOX: - switch (tag as BrowserTag) { + switch (tag) { case BrowserTag.LATEST: - return await firefox.resolveBuildId('FIREFOX_NIGHTLY'); + return await firefox.resolveBuildId(firefox.FirefoxChannel.NIGHTLY); case BrowserTag.BETA: + return await firefox.resolveBuildId(firefox.FirefoxChannel.BETA); + case BrowserTag.NIGHTLY: + return await firefox.resolveBuildId(firefox.FirefoxChannel.NIGHTLY); + case BrowserTag.DEVEDITION: + return await firefox.resolveBuildId( + firefox.FirefoxChannel.DEVEDITION + ); + case BrowserTag.STABLE: + return await firefox.resolveBuildId(firefox.FirefoxChannel.STABLE); + case BrowserTag.ESR: + return await firefox.resolveBuildId(firefox.FirefoxChannel.ESR); case BrowserTag.CANARY: case BrowserTag.DEV: - case BrowserTag.STABLE: - throw new Error( - `${tag} is not supported for ${browser}. Use 'latest' instead.` - ); + throw new Error(`${tag.toUpperCase()} is not available for Firefox`); } case Browser.CHROME: { - switch (tag as BrowserTag) { + switch (tag) { case BrowserTag.LATEST: return await chrome.resolveBuildId(ChromeReleaseChannel.CANARY); case BrowserTag.BETA: @@ -86,13 +94,11 @@ export async function resolveBuildId( return await chrome.resolveBuildId(ChromeReleaseChannel.DEV); case BrowserTag.STABLE: return await chrome.resolveBuildId(ChromeReleaseChannel.STABLE); - default: - const result = await chrome.resolveBuildId(tag); - if (result) { - return result; - } + case BrowserTag.NIGHTLY: + case BrowserTag.DEVEDITION: + case BrowserTag.ESR: + throw new Error(`${tag.toUpperCase()} is not available for Chrome`); } - return tag; } case Browser.CHROMEDRIVER: { switch (tag) { @@ -105,13 +111,13 @@ export async function resolveBuildId( return await chromedriver.resolveBuildId(ChromeReleaseChannel.DEV); case BrowserTag.STABLE: return await chromedriver.resolveBuildId(ChromeReleaseChannel.STABLE); - default: - const result = await chromedriver.resolveBuildId(tag); - if (result) { - return result; - } + case BrowserTag.NIGHTLY: + case BrowserTag.DEVEDITION: + case BrowserTag.ESR: + throw new Error( + `${tag.toUpperCase()} is not available for ChromeDriver` + ); } - return tag; } case Browser.CHROMEHEADLESSSHELL: { switch (tag) { @@ -132,29 +138,68 @@ export async function resolveBuildId( return await chromeHeadlessShell.resolveBuildId( ChromeReleaseChannel.STABLE ); - default: - const result = await chromeHeadlessShell.resolveBuildId(tag); - if (result) { - return result; - } + case BrowserTag.NIGHTLY: + case BrowserTag.DEVEDITION: + case BrowserTag.ESR: + throw new Error(`${tag} is not available for chrome-headless-shell`); } - return tag; } case Browser.CHROMIUM: - switch (tag as BrowserTag) { + switch (tag) { case BrowserTag.LATEST: return await chromium.resolveBuildId(platform); - case BrowserTag.BETA: + case BrowserTag.NIGHTLY: case BrowserTag.CANARY: case BrowserTag.DEV: + case BrowserTag.DEVEDITION: + case BrowserTag.BETA: case BrowserTag.STABLE: + case BrowserTag.ESR: throw new Error( - `${tag} is not supported for ${browser}. Use 'latest' instead.` + `${tag} is not supported for Chromium. Use 'latest' instead.` ); } } - // We assume the tag is the buildId if it didn't match any keywords. - return tag; +} + +/** + * @public + */ +export async function resolveBuildId( + browser: Browser, + platform: BrowserPlatform, + tag: string +): Promise<string> { + const browserTag = tag as BrowserTag; + if (Object.values(BrowserTag).includes(browserTag)) { + return await resolveBuildIdForBrowserTag(browser, platform, browserTag); + } + + switch (browser) { + case Browser.FIREFOX: + return tag; + case Browser.CHROME: + const chromeResult = await chrome.resolveBuildId(tag); + if (chromeResult) { + return chromeResult; + } + return tag; + case Browser.CHROMEDRIVER: + const chromeDriverResult = await chromedriver.resolveBuildId(tag); + if (chromeDriverResult) { + return chromeDriverResult; + } + return tag; + case Browser.CHROMEHEADLESSSHELL: + const chromeHeadlessShellResult = + await chromeHeadlessShell.resolveBuildId(tag); + if (chromeHeadlessShellResult) { + return chromeHeadlessShellResult; + } + return tag; + case Browser.CHROMIUM: + return tag; + } } /** diff --git a/remote/test/puppeteer/packages/browsers/src/browser-data/firefox.ts b/remote/test/puppeteer/packages/browsers/src/browser-data/firefox.ts index 87e3804c8f..95e03c54c0 100644 --- a/remote/test/puppeteer/packages/browsers/src/browser-data/firefox.ts +++ b/remote/test/puppeteer/packages/browsers/src/browser-data/firefox.ts @@ -11,7 +11,7 @@ import {getJSON} from '../httpUtil.js'; import {BrowserPlatform, type ProfileOptions} from './types.js'; -function archive(platform: BrowserPlatform, buildId: string): string { +function archiveNightly(platform: BrowserPlatform, buildId: string): string { switch (platform) { case BrowserPlatform.LINUX: return `firefox-${buildId}.en-US.${platform}-x86_64.tar.bz2`; @@ -24,48 +24,146 @@ function archive(platform: BrowserPlatform, buildId: string): string { } } +function archive(platform: BrowserPlatform, buildId: string): string { + switch (platform) { + case BrowserPlatform.LINUX: + return `firefox-${buildId}.tar.bz2`; + case BrowserPlatform.MAC_ARM: + case BrowserPlatform.MAC: + return `Firefox ${buildId}.dmg`; + case BrowserPlatform.WIN32: + case BrowserPlatform.WIN64: + return `Firefox Setup ${buildId}.exe`; + } +} + +function platformName(platform: BrowserPlatform): string { + switch (platform) { + case BrowserPlatform.LINUX: + return `linux-x86_64`; + case BrowserPlatform.MAC_ARM: + case BrowserPlatform.MAC: + return `mac`; + case BrowserPlatform.WIN32: + case BrowserPlatform.WIN64: + return platform; + } +} + +function parseBuildId(buildId: string): [FirefoxChannel, string] { + for (const value of Object.values(FirefoxChannel)) { + if (buildId.startsWith(value + '_')) { + buildId = buildId.substring(value.length + 1); + return [value, buildId]; + } + } + // Older versions do not have channel as the prefix.« + return [FirefoxChannel.NIGHTLY, buildId]; +} + export function resolveDownloadUrl( platform: BrowserPlatform, buildId: string, - baseUrl = 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central' + baseUrl?: string ): string { - return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`; + const [channel, resolvedBuildId] = parseBuildId(buildId); + switch (channel) { + case FirefoxChannel.NIGHTLY: + baseUrl ??= + 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central'; + break; + case FirefoxChannel.DEVEDITION: + baseUrl ??= 'https://archive.mozilla.org/pub/devedition/releases'; + break; + case FirefoxChannel.BETA: + case FirefoxChannel.STABLE: + case FirefoxChannel.ESR: + baseUrl ??= 'https://archive.mozilla.org/pub/firefox/releases'; + break; + } + switch (channel) { + case FirefoxChannel.NIGHTLY: + return `${baseUrl}/${resolveDownloadPath(platform, resolvedBuildId).join('/')}`; + case FirefoxChannel.DEVEDITION: + case FirefoxChannel.BETA: + case FirefoxChannel.STABLE: + case FirefoxChannel.ESR: + return `${baseUrl}/${resolvedBuildId}/${platformName(platform)}/en-US/${archive(platform, resolvedBuildId)}`; + } } export function resolveDownloadPath( platform: BrowserPlatform, buildId: string ): string[] { - return [archive(platform, buildId)]; + return [archiveNightly(platform, buildId)]; } export function relativeExecutablePath( platform: BrowserPlatform, - _buildId: string + buildId: string ): string { - switch (platform) { - case BrowserPlatform.MAC_ARM: - case BrowserPlatform.MAC: - return path.join('Firefox Nightly.app', 'Contents', 'MacOS', 'firefox'); - case BrowserPlatform.LINUX: - return path.join('firefox', 'firefox'); - case BrowserPlatform.WIN32: - case BrowserPlatform.WIN64: - return path.join('firefox', 'firefox.exe'); + const [channel] = parseBuildId(buildId); + switch (channel) { + case FirefoxChannel.NIGHTLY: + switch (platform) { + case BrowserPlatform.MAC_ARM: + case BrowserPlatform.MAC: + return path.join( + 'Firefox Nightly.app', + 'Contents', + 'MacOS', + 'firefox' + ); + case BrowserPlatform.LINUX: + return path.join('firefox', 'firefox'); + case BrowserPlatform.WIN32: + case BrowserPlatform.WIN64: + return path.join('firefox', 'firefox.exe'); + } + case FirefoxChannel.BETA: + case FirefoxChannel.DEVEDITION: + case FirefoxChannel.ESR: + case FirefoxChannel.STABLE: + switch (platform) { + case BrowserPlatform.MAC_ARM: + case BrowserPlatform.MAC: + return path.join('Firefox.app', 'Contents', 'MacOS', 'firefox'); + case BrowserPlatform.LINUX: + return path.join('firefox', 'firefox'); + case BrowserPlatform.WIN32: + case BrowserPlatform.WIN64: + return path.join('core', 'firefox.exe'); + } } } +export enum FirefoxChannel { + STABLE = 'stable', + ESR = 'esr', + DEVEDITION = 'devedition', + BETA = 'beta', + NIGHTLY = 'nightly', +} + export async function resolveBuildId( - channel: 'FIREFOX_NIGHTLY' = 'FIREFOX_NIGHTLY' + channel: FirefoxChannel = FirefoxChannel.NIGHTLY ): Promise<string> { + const channelToVersionKey = { + [FirefoxChannel.ESR]: 'FIREFOX_ESR', + [FirefoxChannel.STABLE]: 'LATEST_FIREFOX_VERSION', + [FirefoxChannel.DEVEDITION]: 'FIREFOX_DEVEDITION', + [FirefoxChannel.BETA]: 'FIREFOX_DEVEDITION', + [FirefoxChannel.NIGHTLY]: 'FIREFOX_NIGHTLY', + }; const versions = (await getJSON( new URL('https://product-details.mozilla.org/1.0/firefox_versions.json') )) as Record<string, string>; - const version = versions[channel]; + const version = versions[channelToVersionKey[channel]]; if (!version) { throw new Error(`Channel ${channel} is not found.`); } - return version; + return channel + '_' + version; } export async function createProfile(options: ProfileOptions): Promise<void> { diff --git a/remote/test/puppeteer/packages/browsers/src/browser-data/types.ts b/remote/test/puppeteer/packages/browsers/src/browser-data/types.ts index ac72661a2d..4990350659 100644 --- a/remote/test/puppeteer/packages/browsers/src/browser-data/types.ts +++ b/remote/test/puppeteer/packages/browsers/src/browser-data/types.ts @@ -36,9 +36,12 @@ export enum BrowserPlatform { */ export enum BrowserTag { CANARY = 'canary', + NIGHTLY = 'nightly', BETA = 'beta', DEV = 'dev', + DEVEDITION = 'devedition', STABLE = 'stable', + ESR = 'esr', LATEST = 'latest', } diff --git a/remote/test/puppeteer/packages/browsers/src/fileUtil.ts b/remote/test/puppeteer/packages/browsers/src/fileUtil.ts index 50a6897853..28168afad1 100644 --- a/remote/test/puppeteer/packages/browsers/src/fileUtil.ts +++ b/remote/test/puppeteer/packages/browsers/src/fileUtil.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {exec as execChildProcess} from 'child_process'; +import {exec as execChildProcess, spawnSync} from 'child_process'; import {createReadStream} from 'fs'; import {mkdir, readdir} from 'fs/promises'; import * as path from 'path'; @@ -30,6 +30,18 @@ export async function unpackArchive( } else if (archivePath.endsWith('.dmg')) { await mkdir(folderPath); await installDMG(archivePath, folderPath); + } else if (archivePath.endsWith('.exe')) { + // Firefox on Windows. + const result = spawnSync(archivePath, [`/ExtractDir=${folderPath}`], { + env: { + __compat_layer: 'RunAsInvoker', + }, + }); + if (result.status !== 0) { + throw new Error( + `Failed to extract ${archivePath} to ${folderPath}: ${result.output}` + ); + } } else { throw new Error(`Unsupported archive format: ${archivePath}`); } diff --git a/remote/test/puppeteer/packages/browsers/src/httpUtil.ts b/remote/test/puppeteer/packages/browsers/src/httpUtil.ts index 96f7fc9f36..084bae599c 100644 --- a/remote/test/puppeteer/packages/browsers/src/httpUtil.ts +++ b/remote/test/puppeteer/packages/browsers/src/httpUtil.ts @@ -54,6 +54,9 @@ export function httpRequest( res.headers.location ) { httpRequest(new URL(res.headers.location), method, response); + // consume response data to free up memory + // And prevents the connection from being kept alive + res.resume(); } else { response(res); } diff --git a/remote/test/puppeteer/packages/browsers/src/install.ts b/remote/test/puppeteer/packages/browsers/src/install.ts index e78b2c3461..f585021df2 100644 --- a/remote/test/puppeteer/packages/browsers/src/install.ts +++ b/remote/test/puppeteer/packages/browsers/src/install.ts @@ -92,6 +92,11 @@ export interface InstallOptions { * @defaultValue `true` */ unpack?: boolean; + /** + * @internal + * @defaultValue `false` + */ + forceFallbackForTesting?: boolean; } /** @@ -125,6 +130,10 @@ export async function install( try { return await installUrl(url, options); } catch (err) { + // If custom baseUrl is provided, do not fall back to CfT dashboard. + if (options.baseUrl && !options.forceFallbackForTesting) { + throw err; + } debugInstall(`Error downloading from ${url}.`); switch (options.browser) { case Browser.CHROME: diff --git a/remote/test/puppeteer/packages/browsers/src/launch.ts b/remote/test/puppeteer/packages/browsers/src/launch.ts index dfb0fbf633..434f34c0f6 100644 --- a/remote/test/puppeteer/packages/browsers/src/launch.ts +++ b/remote/test/puppeteer/packages/browsers/src/launch.ts @@ -135,6 +135,59 @@ export const CDP_WEBSOCKET_ENDPOINT_REGEX = export const WEBDRIVER_BIDI_WEBSOCKET_ENDPOINT_REGEX = /^WebDriver BiDi listening on (ws:\/\/.*)$/; +type EventHandler = (...args: any[]) => void; +const processListeners = new Map<string, EventHandler[]>(); +const dispatchers = { + exit: (...args: any[]) => { + processListeners.get('exit')?.forEach(handler => { + return handler(...args); + }); + }, + SIGINT: (...args: any[]) => { + processListeners.get('SIGINT')?.forEach(handler => { + return handler(...args); + }); + }, + SIGHUP: (...args: any[]) => { + processListeners.get('SIGHUP')?.forEach(handler => { + return handler(...args); + }); + }, + SIGTERM: (...args: any[]) => { + processListeners.get('SIGTERM')?.forEach(handler => { + return handler(...args); + }); + }, +}; + +function subscribeToProcessEvent( + event: 'exit' | 'SIGINT' | 'SIGHUP' | 'SIGTERM', + handler: EventHandler +): void { + const listeners = processListeners.get(event) || []; + if (listeners.length === 0) { + process.on(event, dispatchers[event]); + } + listeners.push(handler); + processListeners.set(event, listeners); +} + +function unsubscribeFromProcessEvent( + event: 'exit' | 'SIGINT' | 'SIGHUP' | 'SIGTERM', + handler: EventHandler +): void { + const listeners = processListeners.get(event) || []; + const existingListenerIdx = listeners.indexOf(handler); + if (existingListenerIdx === -1) { + return; + } + listeners.splice(existingListenerIdx, 1); + processListeners.set(event, listeners); + if (listeners.length === 0) { + process.off(event, dispatchers[event]); + } +} + /** * @public */ @@ -201,15 +254,15 @@ export class Process { this.#browserProcess.stderr?.pipe(process.stderr); this.#browserProcess.stdout?.pipe(process.stdout); } - process.on('exit', this.#onDriverProcessExit); + subscribeToProcessEvent('exit', this.#onDriverProcessExit); if (opts.handleSIGINT) { - process.on('SIGINT', this.#onDriverProcessSignal); + subscribeToProcessEvent('SIGINT', this.#onDriverProcessSignal); } if (opts.handleSIGTERM) { - process.on('SIGTERM', this.#onDriverProcessSignal); + subscribeToProcessEvent('SIGTERM', this.#onDriverProcessSignal); } if (opts.handleSIGHUP) { - process.on('SIGHUP', this.#onDriverProcessSignal); + subscribeToProcessEvent('SIGHUP', this.#onDriverProcessSignal); } if (opts.onExit) { this.#onExitHook = opts.onExit; @@ -262,10 +315,10 @@ export class Process { } #clearListeners(): void { - process.off('exit', this.#onDriverProcessExit); - process.off('SIGINT', this.#onDriverProcessSignal); - process.off('SIGTERM', this.#onDriverProcessSignal); - process.off('SIGHUP', this.#onDriverProcessSignal); + unsubscribeFromProcessEvent('exit', this.#onDriverProcessExit); + unsubscribeFromProcessEvent('SIGINT', this.#onDriverProcessSignal); + unsubscribeFromProcessEvent('SIGTERM', this.#onDriverProcessSignal); + unsubscribeFromProcessEvent('SIGHUP', this.#onDriverProcessSignal); } #onDriverProcessExit = (_code: number) => { diff --git a/remote/test/puppeteer/packages/browsers/test/src/chrome/install.spec.ts b/remote/test/puppeteer/packages/browsers/test/src/chrome/install.spec.ts index f669d1c57c..d7c9dfbf4a 100644 --- a/remote/test/puppeteer/packages/browsers/test/src/chrome/install.spec.ts +++ b/remote/test/puppeteer/packages/browsers/test/src/chrome/install.spec.ts @@ -138,6 +138,7 @@ describe('Chrome install', () => { }); it('falls back to the chrome-for-testing dashboard URLs if URL is not available', async function () { + this.timeout(60000); const expectedOutputPath = path.join( tmpDir, 'chrome', @@ -150,6 +151,7 @@ describe('Chrome install', () => { platform: BrowserPlatform.LINUX, buildId: testChromeBuildId, baseUrl: 'https://127.0.0.1', + forceFallbackForTesting: true, }); assert.strictEqual(fs.existsSync(expectedOutputPath), true); }); diff --git a/remote/test/puppeteer/packages/browsers/test/src/chrome/launch.spec.ts b/remote/test/puppeteer/packages/browsers/test/src/chrome/launch.spec.ts index c420d9e0b6..dc3d118d67 100644 --- a/remote/test/puppeteer/packages/browsers/test/src/chrome/launch.spec.ts +++ b/remote/test/puppeteer/packages/browsers/test/src/chrome/launch.spec.ts @@ -77,7 +77,6 @@ describe('Chrome', () => { '--disable-renderer-backgrounding', '--disable-sync', '--enable-automation', - '--enable-features=NetworkServiceInProcess2', '--export-tagged-pdf', '--force-color-profile=srgb', '--headless=new', diff --git a/remote/test/puppeteer/packages/browsers/test/src/chromium/launch.spec.ts b/remote/test/puppeteer/packages/browsers/test/src/chromium/launch.spec.ts index 8cf7c8255b..e382761981 100644 --- a/remote/test/puppeteer/packages/browsers/test/src/chromium/launch.spec.ts +++ b/remote/test/puppeteer/packages/browsers/test/src/chromium/launch.spec.ts @@ -77,7 +77,6 @@ describe('Chromium', () => { '--disable-renderer-backgrounding', '--disable-sync', '--enable-automation', - '--enable-features=NetworkServiceInProcess2', '--export-tagged-pdf', '--force-color-profile=srgb', '--headless=new', diff --git a/remote/test/puppeteer/packages/browsers/test/src/firefox/firefox-data.spec.ts b/remote/test/puppeteer/packages/browsers/test/src/firefox/firefox-data.spec.ts index b5dd2db0b3..22962b8a68 100644 --- a/remote/test/puppeteer/packages/browsers/test/src/firefox/firefox-data.spec.ts +++ b/remote/test/puppeteer/packages/browsers/test/src/firefox/firefox-data.spec.ts @@ -18,7 +18,7 @@ import { } from '../../../lib/cjs/browser-data/firefox.js'; describe('Firefox', () => { - it('should resolve download URLs', () => { + it('should resolve download URLs for Nightly', () => { assert.strictEqual( resolveDownloadUrl(BrowserPlatform.LINUX, '111.0a1'), 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.linux-x86_64.tar.bz2' @@ -41,6 +41,75 @@ describe('Firefox', () => { ); }); + it('should resolve download URLs for beta', () => { + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.LINUX, 'beta_115.0b8'), + 'https://archive.mozilla.org/pub/firefox/releases/115.0b8/linux-x86_64/en-US/firefox-115.0b8.tar.bz2' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC, 'beta_115.0b8'), + 'https://archive.mozilla.org/pub/firefox/releases/115.0b8/mac/en-US/Firefox 115.0b8.dmg' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC_ARM, 'beta_115.0b8'), + 'https://archive.mozilla.org/pub/firefox/releases/115.0b8/mac/en-US/Firefox 115.0b8.dmg' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN32, 'beta_115.0b8'), + 'https://archive.mozilla.org/pub/firefox/releases/115.0b8/win32/en-US/Firefox Setup 115.0b8.exe' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN64, 'beta_115.0b8'), + 'https://archive.mozilla.org/pub/firefox/releases/115.0b8/win64/en-US/Firefox Setup 115.0b8.exe' + ); + }); + + it('should resolve download URLs for stable', () => { + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.LINUX, 'stable_111.0.1'), + 'https://archive.mozilla.org/pub/firefox/releases/111.0.1/linux-x86_64/en-US/firefox-111.0.1.tar.bz2' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC, 'stable_111.0.1'), + 'https://archive.mozilla.org/pub/firefox/releases/111.0.1/mac/en-US/Firefox 111.0.1.dmg' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC_ARM, 'stable_111.0.1'), + 'https://archive.mozilla.org/pub/firefox/releases/111.0.1/mac/en-US/Firefox 111.0.1.dmg' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN32, 'stable_111.0.1'), + 'https://archive.mozilla.org/pub/firefox/releases/111.0.1/win32/en-US/Firefox Setup 111.0.1.exe' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN64, 'stable_111.0.1'), + 'https://archive.mozilla.org/pub/firefox/releases/111.0.1/win64/en-US/Firefox Setup 111.0.1.exe' + ); + }); + + it('should resolve download URLs for devedition', () => { + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.LINUX, 'devedition_115.0b8'), + 'https://archive.mozilla.org/pub/devedition/releases/115.0b8/linux-x86_64/en-US/firefox-115.0b8.tar.bz2' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC, 'devedition_115.0b8'), + 'https://archive.mozilla.org/pub/devedition/releases/115.0b8/mac/en-US/Firefox 115.0b8.dmg' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC_ARM, 'devedition_115.0b8'), + 'https://archive.mozilla.org/pub/devedition/releases/115.0b8/mac/en-US/Firefox 115.0b8.dmg' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN32, 'devedition_115.0b8'), + 'https://archive.mozilla.org/pub/devedition/releases/115.0b8/win32/en-US/Firefox Setup 115.0b8.exe' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN64, 'devedition_115.0b8'), + 'https://archive.mozilla.org/pub/devedition/releases/115.0b8/win64/en-US/Firefox Setup 115.0b8.exe' + ); + }); + it('should resolve executable paths', () => { assert.strictEqual( relativeExecutablePath(BrowserPlatform.LINUX, '111.0a1'), diff --git a/remote/test/puppeteer/packages/browsers/test/src/versions.ts b/remote/test/puppeteer/packages/browsers/test/src/versions.ts index 83f26ca41a..097973b82d 100644 --- a/remote/test/puppeteer/packages/browsers/test/src/versions.ts +++ b/remote/test/puppeteer/packages/browsers/test/src/versions.ts @@ -6,6 +6,6 @@ export const testChromeBuildId = '121.0.6167.85'; export const testChromiumBuildId = '1083080'; -export const testFirefoxBuildId = '125.0a1'; +export const testFirefoxBuildId = '126.0a1'; export const testChromeDriverBuildId = '121.0.6167.85'; export const testChromeHeadlessShellBuildId = '121.0.6167.85'; diff --git a/remote/test/puppeteer/packages/ng-schematics/.eslintignore b/remote/test/puppeteer/packages/ng-schematics/.eslintignore index 8424d7004d..c4c05af793 100644 --- a/remote/test/puppeteer/packages/ng-schematics/.eslintignore +++ b/remote/test/puppeteer/packages/ng-schematics/.eslintignore @@ -1,5 +1,5 @@ # Ignore File that will be copied to Angular /files/ -# Ignore sandbox enviroment +# Ignore sandbox environment ./sandbox/ diff --git a/remote/test/puppeteer/packages/ng-schematics/README.md b/remote/test/puppeteer/packages/ng-schematics/README.md index 975f74a704..2c84b7bc9e 100644 --- a/remote/test/puppeteer/packages/ng-schematics/README.md +++ b/remote/test/puppeteer/packages/ng-schematics/README.md @@ -85,7 +85,7 @@ node tools/smoke.mjs The schematics utilize `@angular-devkit/schematics/testing` for verifying correct file creation and `package.json` updates. To execute the test suit: -```bash +```bash npm2yarn npm run test ``` diff --git a/remote/test/puppeteer/packages/puppeteer-core/CHANGELOG.md b/remote/test/puppeteer/packages/puppeteer-core/CHANGELOG.md index 5076077c9f..eeaecef87d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/CHANGELOG.md +++ b/remote/test/puppeteer/packages/puppeteer-core/CHANGELOG.md @@ -20,6 +20,104 @@ All notable changes to this project will be documented in this file. See [standa * dependencies * @puppeteer/browsers bumped from 1.5.1 to 1.6.0 +## [22.6.5](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.6.4...puppeteer-core-v22.6.5) (2024-04-15) + + +### Bug Fixes + +* remove NetworkServiceInProcess2 set by default ([#12261](https://github.com/puppeteer/puppeteer/issues/12261)) ([ff4f70f](https://github.com/puppeteer/puppeteer/commit/ff4f70f4ae7ca8deb0becbec2e49b35322dba336)), closes [#12257](https://github.com/puppeteer/puppeteer/issues/12257) +* use setImmediate to reduce flakiness when processing events ([#12264](https://github.com/puppeteer/puppeteer/issues/12264)) ([73403b3](https://github.com/puppeteer/puppeteer/commit/73403b323ec0dd8a08c164cb2c07751451215788)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @puppeteer/browsers bumped from 2.2.1 to 2.2.2 + +## [22.6.4](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.6.3...puppeteer-core-v22.6.4) (2024-04-11) + + +### Bug Fixes + +* **a11y:** query only unignored nodes ([#12224](https://github.com/puppeteer/puppeteer/issues/12224)) ([e20cd64](https://github.com/puppeteer/puppeteer/commit/e20cd64fff374c4113777912c193f4a5d7d04297)) +* retain stale main frame for longer ([#12225](https://github.com/puppeteer/puppeteer/issues/12225)) ([aa5b182](https://github.com/puppeteer/puppeteer/commit/aa5b1824a5c82005fcfc05b002facfbbb9810f8f)) +* roll to Chrome 123.0.6312.122 (r1262506) ([#12248](https://github.com/puppeteer/puppeteer/issues/12248)) ([50b6659](https://github.com/puppeteer/puppeteer/commit/50b66591e70a7b6907d86594d7dacee6e76afc2d)) +* **webdriver:** suppress error for error code errors ([5f7254c](https://github.com/puppeteer/puppeteer/commit/5f7254c41c7c1bda82477488f10254d204373d54)) + +## [22.6.3](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.6.2...puppeteer-core-v22.6.3) (2024-04-05) + + +### Bug Fixes + +* check if executablePath exists ([#12201](https://github.com/puppeteer/puppeteer/issues/12201)) ([4ec0280](https://github.com/puppeteer/puppeteer/commit/4ec02800801d441238d6160a933f88f98c5f7165)) +* roll to Chrome 123.0.6312.105 (r1262506) ([#12209](https://github.com/puppeteer/puppeteer/issues/12209)) ([ee31272](https://github.com/puppeteer/puppeteer/commit/ee312721152cce61a9e9cb2b78b71b40c4fa9e64)) +* wait for fonts before pdf printing ([#12175](https://github.com/puppeteer/puppeteer/issues/12175)) ([59bffce](https://github.com/puppeteer/puppeteer/commit/59bffce9720b4d5e5204b26b335735e0a5ca9cc1)) +* **webdriver:** request redirect chain ([#12168](https://github.com/puppeteer/puppeteer/issues/12168)) ([d345055](https://github.com/puppeteer/puppeteer/commit/d345055af3c63effbdfb2751274b9d7137b8a308)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @puppeteer/browsers bumped from 2.2.0 to 2.2.1 + +## [22.6.2](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.6.1...puppeteer-core-v22.6.2) (2024-03-28) + + +### Bug Fixes + +* roll to Chrome 123.0.6312.86 (r1262506) ([#12156](https://github.com/puppeteer/puppeteer/issues/12156)) ([29637f2](https://github.com/puppeteer/puppeteer/commit/29637f2b8f2dc1d684dbbb62d1a75857e016be33)) + +## [22.6.1](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.6.0...puppeteer-core-v22.6.1) (2024-03-25) + + +### Bug Fixes + +* apply timeout to waiting for a response ([#12142](https://github.com/puppeteer/puppeteer/issues/12142)) ([ac1767d](https://github.com/puppeteer/puppeteer/commit/ac1767da0b4214ced548a62dd737e2863f92c715)) +* reload should not resolve early on fragment navigations ([#12119](https://github.com/puppeteer/puppeteer/issues/12119)) ([d476031](https://github.com/puppeteer/puppeteer/commit/d4760317c9bd359c9ecdb5f36231449dae16a8d2)) +* support clip in ElementHandle.screenshot ([#12115](https://github.com/puppeteer/puppeteer/issues/12115)) ([b096ffa](https://github.com/puppeteer/puppeteer/commit/b096ffaa0359078bd5748b53b67e87c9453c7196)) + +## [22.6.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.5.0...puppeteer-core-v22.6.0) (2024-03-20) + + +### Features + +* roll to Chrome 123.0.6312.58 (r1262506) ([#12110](https://github.com/puppeteer/puppeteer/issues/12110)) ([6f5b3bc](https://github.com/puppeteer/puppeteer/commit/6f5b3bc9b88c6d3204dda396f8963591ea6eb883)) + + +### Bug Fixes + +* **webdriver:** emit RequestServedFromCache for requests ([#12104](https://github.com/puppeteer/puppeteer/issues/12104)) ([6ba6bef](https://github.com/puppeteer/puppeteer/commit/6ba6bef1b99742543942cef2f6c840bd543f5dee)) + +## [22.5.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.4.1...puppeteer-core-v22.5.0) (2024-03-15) + + +### Features + +* deprecate `Frame.prototype.name` ([#12084](https://github.com/puppeteer/puppeteer/issues/12084)) ([0203b45](https://github.com/puppeteer/puppeteer/commit/0203b4533dfec503f9ce7fcd07c3493021a9cecb)) + + +### Bug Fixes + +* fix keyboard.sendCharacter ([#12088](https://github.com/puppeteer/puppeteer/issues/12088)) ([2637622](https://github.com/puppeteer/puppeteer/commit/26376224d557ce30c911f670c5e7625dd1a1df72)) +* roll to Chrome 122.0.6261.128 (r1250580) ([#12078](https://github.com/puppeteer/puppeteer/issues/12078)) ([ef7a9ea](https://github.com/puppeteer/puppeteer/commit/ef7a9eac16dcb466b220bcb0bc06a1eac3492354)) +* **webdriver:** emit CDP events ([#12058](https://github.com/puppeteer/puppeteer/issues/12058)) ([9afe424](https://github.com/puppeteer/puppeteer/commit/9afe4246bb58c30a13215a254f9326935b24ece3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @puppeteer/browsers bumped from 2.1.0 to 2.2.0 + +## [22.4.1](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.4.0...puppeteer-core-v22.4.1) (2024-03-08) + + +### Bug Fixes + +* roll to Chrome 122.0.6261.111 (r1250580) ([#12055](https://github.com/puppeteer/puppeteer/issues/12055)) ([9b31bca](https://github.com/puppeteer/puppeteer/commit/9b31bca01adeb2ce04c97d9fcb3c6b6461469f07)) + ## [22.4.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v22.3.0...puppeteer-core-v22.4.0) (2024-03-05) diff --git a/remote/test/puppeteer/packages/puppeteer-core/Herebyfile.mjs b/remote/test/puppeteer/packages/puppeteer-core/Herebyfile.mjs index 972a080ba0..426d908c0c 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/Herebyfile.mjs +++ b/remote/test/puppeteer/packages/puppeteer-core/Herebyfile.mjs @@ -110,8 +110,10 @@ export const buildTask = task({ bundle: true, allowOverwrite: true, format, - target: 'node16', - minify: true, + target: 'node18', + // Do not minify for readability and leave minification to + // consumers. + minify: false, legalComments: 'inline', }) ); diff --git a/remote/test/puppeteer/packages/puppeteer-core/package.json b/remote/test/puppeteer/packages/puppeteer-core/package.json index 1d4d564c4f..fe0eedbe24 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/package.json +++ b/remote/test/puppeteer/packages/puppeteer-core/package.json @@ -1,6 +1,6 @@ { "name": "puppeteer-core", - "version": "22.4.0", + "version": "22.6.5", "description": "A high-level API to control headless Chrome over the DevTools Protocol", "keywords": [ "puppeteer", @@ -119,11 +119,10 @@ "author": "The Chromium Authors", "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.1.0", - "chromium-bidi": "0.5.12", - "cross-fetch": "4.0.0", + "@puppeteer/browsers": "2.2.2", + "chromium-bidi": "0.5.17", "debug": "4.3.4", - "devtools-protocol": "0.0.1249869", + "devtools-protocol": "0.0.1262051", "ws": "8.16.0" }, "devDependencies": { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/Browser.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/Browser.ts index 6d7ea19d49..67835e4aa2 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/Browser.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/Browser.ts @@ -197,7 +197,7 @@ export interface DebugInfo { * - connected to via {@link Puppeteer.connect} or * - launched by {@link PuppeteerNode.launch}. * - * {@link Browser} {@link EventEmitter | emits} various events which are + * {@link Browser} {@link EventEmitter.emit | emits} various events which are * documented in the {@link BrowserEvent} enum. * * @example Using a {@link Browser} to create a {@link Page}: diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/BrowserContext.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/BrowserContext.ts index 5e6a5d5d5c..7cedd5f3a4 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/BrowserContext.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/BrowserContext.ts @@ -151,7 +151,7 @@ export abstract class BrowserContext extends EventEmitter<BrowserContextEvents> * * @deprecated In Chrome, the * {@link Browser.defaultBrowserContext | default browser context} can also be - * "icognito" if configured via the arguments and in such cases this getter + * "incognito" if configured via the arguments and in such cases this getter * returns wrong results (see * https://github.com/puppeteer/puppeteer/issues/8836). Also, the term * "incognito" is not applicable to other browsers. To migrate, check the diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/CDPSession.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/CDPSession.ts index 3a1fdf1e24..d80b9d9013 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/CDPSession.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/CDPSession.ts @@ -74,7 +74,7 @@ export interface CommandOptions { * @example * * ```ts - * const client = await page.target().createCDPSession(); + * const client = await page.createCDPSession(); * await client.send('Animation.enable'); * client.on('Animation.animationCreated', () => * console.log('Animation created!') diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/ElementHandle.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/ElementHandle.ts index 9b1326f998..84e61f33c1 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/ElementHandle.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/ElementHandle.ts @@ -623,7 +623,7 @@ export abstract class ElementHandle< /** * This method scrolls element into view if needed, and then - * uses {@link Page} to hover over the center of the element. + * uses {@link Page.mouse} to hover over the center of the element. * If the element is detached from DOM, the method throws an error. */ @throwIfDisposed() @@ -636,7 +636,7 @@ export abstract class ElementHandle< /** * This method scrolls element into view if needed, and then - * uses {@link Page | Page.mouse} to click in the center of the element. + * uses {@link Page.mouse} to click in the center of the element. * If the element is detached from DOM, the method throws an error. */ @throwIfDisposed() @@ -1236,9 +1236,9 @@ export abstract class ElementHandle< this: ElementHandle<Element>, options: Readonly<ElementScreenshotOptions> = {} ): Promise<string | Buffer> { - const {scrollIntoView = true} = options; + const {scrollIntoView = true, clip} = options; - let clip = await this.#nonEmptyVisibleBoundingBox(); + let elementClip = await this.#nonEmptyVisibleBoundingBox(); const page = this.frame.page(); @@ -1247,7 +1247,7 @@ export abstract class ElementHandle< await this.scrollIntoViewIfNeeded(); // We measure again just in case. - clip = await this.#nonEmptyVisibleBoundingBox(); + elementClip = await this.#nonEmptyVisibleBoundingBox(); } const [pageLeft, pageTop] = await this.evaluate(() => { @@ -1259,10 +1259,16 @@ export abstract class ElementHandle< window.visualViewport.pageTop, ] as const; }); - clip.x += pageLeft; - clip.y += pageTop; + elementClip.x += pageLeft; + elementClip.y += pageTop; + if (clip) { + elementClip.x += clip.x; + elementClip.y += clip.y; + elementClip.height = clip.height; + elementClip.width = clip.width; + } - return await page.screenshot({...options, clip}); + return await page.screenshot({...options, clip: elementClip}); } async #nonEmptyVisibleBoundingBox() { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/Frame.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/Frame.ts index 19b5eb7fa0..ff476cd054 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/Frame.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/Frame.ts @@ -18,7 +18,6 @@ import type {PuppeteerLifeCycleEvent} from '../cdp/LifecycleWatcher.js'; import {EventEmitter, type EventType} from '../common/EventEmitter.js'; import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js'; import {transposeIterableHandle} from '../common/HandleIterator.js'; -import {LazyArg} from '../common/LazyArg.js'; import type { Awaitable, EvaluateFunc, @@ -63,6 +62,10 @@ export interface WaitForOptions { * @defaultValue `'load'` */ waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; + /** + * @internal + */ + ignoreSameDocumentNavigation?: boolean; } /** @@ -405,7 +408,7 @@ export abstract class Frame extends EventEmitter<FrameEvents> { } /** - * @internal + * @returns The frame element associated with this frame (if any). */ @throwIfDetached async frameElement(): Promise<HandleFor<HTMLIFrameElement> | null> { @@ -447,7 +450,7 @@ export abstract class Frame extends EventEmitter<FrameEvents> { } /** - * Behaves identically to {@link Page.evaluate} except it's run within the + * Behaves identically to {@link Page.evaluate} except it's run within * the context of this frame. * * @see {@link Page.evaluate} for details. @@ -760,6 +763,13 @@ export abstract class Frame extends EventEmitter<FrameEvents> { * @remarks * This value is calculated once when the frame is created, and will not * update if the attribute is changed later. + * + * @deprecated Use + * + * ```ts + * const element = await frame.frameElement(); + * const nameOrId = await element.evaluate(frame => frame.name ?? frame.id); + * ``` */ name(): string { return this._name || ''; @@ -830,42 +840,37 @@ export abstract class Frame extends EventEmitter<FrameEvents> { return await this.mainRealm().transferHandle( await this.isolatedRealm().evaluateHandle( - async ({Deferred}, {url, id, type, content}) => { - const deferred = Deferred.create<void>(); - const script = document.createElement('script'); - script.type = type; - script.text = content; - if (url) { - script.src = url; - script.addEventListener( - 'load', - () => { - return deferred.resolve(); - }, - {once: true} - ); + async ({url, id, type, content}) => { + return await new Promise<HTMLScriptElement>((resolve, reject) => { + const script = document.createElement('script'); + script.type = type; + script.text = content; script.addEventListener( 'error', event => { - deferred.reject( - new Error(event.message ?? 'Could not load script') - ); + reject(new Error(event.message ?? 'Could not load script')); }, {once: true} ); - } else { - deferred.resolve(); - } - if (id) { - script.id = id; - } - document.head.appendChild(script); - await deferred.valueOrThrow(); - return script; + if (id) { + script.id = id; + } + if (url) { + script.src = url; + script.addEventListener( + 'load', + () => { + resolve(script); + }, + {once: true} + ); + document.head.appendChild(script); + } else { + document.head.appendChild(script); + resolve(script); + } + }); }, - LazyArg.create(context => { - return context.puppeteerUtil; - }), {...options, type, content} ) ); @@ -915,46 +920,42 @@ export abstract class Frame extends EventEmitter<FrameEvents> { } return await this.mainRealm().transferHandle( - await this.isolatedRealm().evaluateHandle( - async ({Deferred}, {url, content}) => { - const deferred = Deferred.create<void>(); - let element: HTMLStyleElement | HTMLLinkElement; - if (!url) { - element = document.createElement('style'); - element.appendChild(document.createTextNode(content!)); - } else { - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = url; - element = link; + await this.isolatedRealm().evaluateHandle(async ({url, content}) => { + return await new Promise<HTMLStyleElement | HTMLLinkElement>( + (resolve, reject) => { + let element: HTMLStyleElement | HTMLLinkElement; + if (!url) { + element = document.createElement('style'); + element.appendChild(document.createTextNode(content!)); + } else { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = url; + element = link; + } + element.addEventListener( + 'load', + () => { + resolve(element); + }, + {once: true} + ); + element.addEventListener( + 'error', + event => { + reject( + new Error( + (event as ErrorEvent).message ?? 'Could not load style' + ) + ); + }, + {once: true} + ); + document.head.appendChild(element); + return element; } - element.addEventListener( - 'load', - () => { - deferred.resolve(); - }, - {once: true} - ); - element.addEventListener( - 'error', - event => { - deferred.reject( - new Error( - (event as ErrorEvent).message ?? 'Could not load style' - ) - ); - }, - {once: true} - ); - document.head.appendChild(element); - await deferred.valueOrThrow(); - return element; - }, - LazyArg.create(context => { - return context.puppeteerUtil; - }), - options - ) + ); + }, options) ); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts index d72f088686..674abc61f2 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPRequest.ts @@ -5,6 +5,10 @@ */ import type {Protocol} from 'devtools-protocol'; +import type {ProtocolError} from '../common/Errors.js'; +import {debugError} from '../common/util.js'; +import {assert} from '../util/assert.js'; + import type {CDPSession} from './CDPSession.js'; import type {Frame} from './Frame.js'; import type {HTTPResponse} from './HTTPResponse.js'; @@ -118,6 +122,29 @@ export abstract class HTTPRequest { _redirectChain: HTTPRequest[] = []; /** + * @internal + */ + protected interception: { + enabled: boolean; + handled: boolean; + handlers: Array<() => void | PromiseLike<any>>; + resolutionState: InterceptResolutionState; + requestOverrides: ContinueRequestOverrides; + response: Partial<ResponseForRequest> | null; + abortReason: Protocol.Network.ErrorReason | null; + } = { + enabled: false, + handled: false, + handlers: [], + resolutionState: { + action: InterceptResolutionAction.None, + }, + requestOverrides: {}, + response: null, + abortReason: null, + }; + + /** * Warning! Using this client can break Puppeteer. Use with caution. * * @experimental @@ -139,18 +166,27 @@ export abstract class HTTPRequest { * if the interception is allowed to continue (ie, `abort()` and * `respond()` aren't called). */ - abstract continueRequestOverrides(): ContinueRequestOverrides; + continueRequestOverrides(): ContinueRequestOverrides { + assert(this.interception.enabled, 'Request Interception is not enabled!'); + return this.interception.requestOverrides; + } /** * The `ResponseForRequest` that gets used if the * interception is allowed to respond (ie, `abort()` is not called). */ - abstract responseForRequest(): Partial<ResponseForRequest> | null; + responseForRequest(): Partial<ResponseForRequest> | null { + assert(this.interception.enabled, 'Request Interception is not enabled!'); + return this.interception.response; + } /** * The most recent reason for aborting the request */ - abstract abortErrorReason(): Protocol.Network.ErrorReason | null; + abortErrorReason(): Protocol.Network.ErrorReason | null { + assert(this.interception.enabled, 'Request Interception is not enabled!'); + return this.interception.abortReason; + } /** * An InterceptResolutionState object describing the current resolution @@ -163,13 +199,23 @@ export abstract class HTTPRequest { * InterceptResolutionAction is one of: `abort`, `respond`, `continue`, * `disabled`, `none`, or `already-handled`. */ - abstract interceptResolutionState(): InterceptResolutionState; + interceptResolutionState(): InterceptResolutionState { + if (!this.interception.enabled) { + return {action: InterceptResolutionAction.Disabled}; + } + if (this.interception.handled) { + return {action: InterceptResolutionAction.AlreadyHandled}; + } + return {...this.interception.resolutionState}; + } /** * Is `true` if the intercept resolution has already been handled, * `false` otherwise. */ - abstract isInterceptResolutionHandled(): boolean; + isInterceptResolutionHandled(): boolean { + return this.interception.handled; + } /** * Adds an async request handler to the processing queue. @@ -177,15 +223,51 @@ export abstract class HTTPRequest { * but they are guaranteed to resolve before the request interception * is finalized. */ - abstract enqueueInterceptAction( + enqueueInterceptAction( pendingHandler: () => void | PromiseLike<unknown> - ): void; + ): void { + this.interception.handlers.push(pendingHandler); + } + + /** + * @internal + */ + abstract _abort( + errorReason: Protocol.Network.ErrorReason | null + ): Promise<void>; + + /** + * @internal + */ + abstract _respond(response: Partial<ResponseForRequest>): Promise<void>; + + /** + * @internal + */ + abstract _continue(overrides: ContinueRequestOverrides): Promise<void>; /** * Awaits pending interception handlers and then decides how to fulfill * the request interception. */ - abstract finalizeInterceptions(): Promise<void>; + async finalizeInterceptions(): Promise<void> { + await this.interception.handlers.reduce((promiseChain, interceptAction) => { + return promiseChain.then(interceptAction); + }, Promise.resolve()); + this.interception.handlers = []; // TODO: verify this is correct top let gc run + const {action} = this.interceptResolutionState(); + switch (action) { + case 'abort': + return await this._abort(this.interception.abortReason); + case 'respond': + if (this.interception.response === null) { + throw new Error('Response is missing for the interception'); + } + return await this._respond(this.interception.response); + case 'continue': + return await this._continue(this.interception.requestOverrides); + } + } /** * Contains the request's resource type as it was perceived by the rendering @@ -323,10 +405,42 @@ export abstract class HTTPRequest { * * Exception is immediately thrown if the request interception is not enabled. */ - abstract continue( - overrides?: ContinueRequestOverrides, + async continue( + overrides: ContinueRequestOverrides = {}, priority?: number - ): Promise<void>; + ): Promise<void> { + // Request interception is not supported for data: urls. + if (this.url().startsWith('data:')) { + return; + } + assert(this.interception.enabled, 'Request Interception is not enabled!'); + assert(!this.interception.handled, 'Request is already handled!'); + if (priority === undefined) { + return await this._continue(overrides); + } + this.interception.requestOverrides = overrides; + if ( + this.interception.resolutionState.priority === undefined || + priority > this.interception.resolutionState.priority + ) { + this.interception.resolutionState = { + action: InterceptResolutionAction.Continue, + priority, + }; + return; + } + if (priority === this.interception.resolutionState.priority) { + if ( + this.interception.resolutionState.action === 'abort' || + this.interception.resolutionState.action === 'respond' + ) { + return; + } + this.interception.resolutionState.action = + InterceptResolutionAction.Continue; + } + return; + } /** * Fulfills a request with the given response. @@ -360,10 +474,38 @@ export abstract class HTTPRequest { * * Exception is immediately thrown if the request interception is not enabled. */ - abstract respond( + async respond( response: Partial<ResponseForRequest>, priority?: number - ): Promise<void>; + ): Promise<void> { + // Mocking responses for dataURL requests is not currently supported. + if (this.url().startsWith('data:')) { + return; + } + assert(this.interception.enabled, 'Request Interception is not enabled!'); + assert(!this.interception.handled, 'Request is already handled!'); + if (priority === undefined) { + return await this._respond(response); + } + this.interception.response = response; + if ( + this.interception.resolutionState.priority === undefined || + priority > this.interception.resolutionState.priority + ) { + this.interception.resolutionState = { + action: InterceptResolutionAction.Respond, + priority, + }; + return; + } + if (priority === this.interception.resolutionState.priority) { + if (this.interception.resolutionState.action === 'abort') { + return; + } + this.interception.resolutionState.action = + InterceptResolutionAction.Respond; + } + } /** * Aborts a request. @@ -379,7 +521,33 @@ export abstract class HTTPRequest { * {@link Page.setRequestInterception}. If it is not enabled, this method will * throw an exception immediately. */ - abstract abort(errorCode?: ErrorCode, priority?: number): Promise<void>; + async abort( + errorCode: ErrorCode = 'failed', + priority?: number + ): Promise<void> { + // Request interception is not supported for data: urls. + if (this.url().startsWith('data:')) { + return; + } + const errorReason = errorReasons[errorCode]; + assert(errorReason, 'Unknown error code: ' + errorCode); + assert(this.interception.enabled, 'Request Interception is not enabled!'); + assert(!this.interception.handled, 'Request is already handled!'); + if (priority === undefined) { + return await this._abort(errorReason); + } + this.interception.abortReason = errorReason; + if ( + this.interception.resolutionState.priority === undefined || + priority >= this.interception.resolutionState.priority + ) { + this.interception.resolutionState = { + action: InterceptResolutionAction.Abort, + priority, + }; + return; + } + } } /** @@ -513,3 +681,33 @@ export const STATUS_TEXTS: Record<string, string> = { '510': 'Not Extended', '511': 'Network Authentication Required', } as const; + +const errorReasons: Record<ErrorCode, Protocol.Network.ErrorReason> = { + aborted: 'Aborted', + accessdenied: 'AccessDenied', + addressunreachable: 'AddressUnreachable', + blockedbyclient: 'BlockedByClient', + blockedbyresponse: 'BlockedByResponse', + connectionaborted: 'ConnectionAborted', + connectionclosed: 'ConnectionClosed', + connectionfailed: 'ConnectionFailed', + connectionrefused: 'ConnectionRefused', + connectionreset: 'ConnectionReset', + internetdisconnected: 'InternetDisconnected', + namenotresolved: 'NameNotResolved', + timedout: 'TimedOut', + failed: 'Failed', +} as const; + +/** + * @internal + */ +export function handleError(error: ProtocolError): void { + if (error.originalMessage.includes('Invalid header')) { + throw error; + } + // In certain cases, protocol will return error if the request was + // already canceled or the page was closed. We should tolerate these + // errors. + debugError(error); +} diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPResponse.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPResponse.ts index 906479eb43..f5ea35722e 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPResponse.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/HTTPResponse.ts @@ -81,11 +81,18 @@ export abstract class HTTPResponse { /** * Promise which resolves to a buffer with response body. + * + * @remarks + * + * The buffer might be re-encoded by the browser + * based on HTTP-headers or other heuristics. If the browser + * failed to detect the correct encoding, the buffer might + * be encoded incorrectly. See https://github.com/puppeteer/puppeteer/issues/6478. */ abstract buffer(): Promise<Buffer>; /** - * Promise which resolves to a text representation of response body. + * Promise which resolves to a text (utf8) representation of response body. */ async text(): Promise<string> { const content = await this.buffer(); diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/Page.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/Page.ts index b094d14b2f..1c1874a856 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/Page.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/Page.ts @@ -32,7 +32,7 @@ import type {HTTPResponse} from '../api/HTTPResponse.js'; import type {Accessibility} from '../cdp/Accessibility.js'; import type {Coverage} from '../cdp/Coverage.js'; import type {DeviceRequestPrompt} from '../cdp/DeviceRequestPrompt.js'; -import type {Credentials, NetworkConditions} from '../cdp/NetworkManager.js'; +import type {NetworkConditions} from '../cdp/NetworkManager.js'; import type {Tracing} from '../cdp/Tracing.js'; import type {ConsoleMessage} from '../common/ConsoleMessage.js'; import type { @@ -134,6 +134,14 @@ export interface Metrics { /** * @public */ +export interface Credentials { + username: string; + password: string; +} + +/** + * @public + */ export interface WaitForNetworkIdleOptions extends WaitTimeoutOptions { /** * Time (in milliseconds) the network should be idle. @@ -274,7 +282,7 @@ export interface ScreenshotOptions { */ path?: string; /** - * Specifies the region of the page to clip. + * Specifies the region of the page/element to clip. */ clip?: ScreenshotClip; /** @@ -644,7 +652,7 @@ export abstract class Page extends EventEmitter<PageEvents> { * * @deprecated We no longer support intercepting drag payloads. Use the new * drag APIs found on {@link ElementHandle} to drag (or just use the - * {@link Page | Page.mouse}). + * {@link Page.mouse}). */ abstract isDragInterceptionEnabled(): boolean; @@ -875,7 +883,7 @@ export abstract class Page extends EventEmitter<PageEvents> { * * @deprecated We no longer support intercepting drag payloads. Use the new * drag APIs found on {@link ElementHandle} to drag (or just use the - * {@link Page | Page.mouse}). + * {@link Page.mouse}). */ abstract setDragInterception(enabled: boolean): Promise<void>; @@ -1342,7 +1350,7 @@ export abstract class Page extends EventEmitter<PageEvents> { * * Functions installed via `page.exposeFunction` survive navigations. * - * :::note + * ::: * * @example * An example of adding an `md5` function into the page: @@ -2116,7 +2124,8 @@ export abstract class Page extends EventEmitter<PageEvents> { * * This is either the viewport set with the previous {@link Page.setViewport} * call or the default viewport set via - * {@link BrowserConnectOptions | BrowserConnectOptions.defaultViewport}. + * {@link BrowserConnectOptions.defaultViewport | + * BrowserConnectOptions.defaultViewport}. */ abstract viewport(): Viewport | null; @@ -2458,7 +2467,7 @@ export abstract class Page extends EventEmitter<PageEvents> { }; if (options.type === undefined && options.path !== undefined) { const filePath = options.path; - // Note we cannot use Node.js here due to browser compatability. + // Note we cannot use Node.js here due to browser compatibility. const extension = filePath .slice(filePath.lastIndexOf('.') + 1) .toLowerCase(); @@ -2609,7 +2618,7 @@ export abstract class Page extends EventEmitter<PageEvents> { /** * This method fetches an element with `selector`, scrolls it into view if - * needed, and then uses {@link Page | Page.mouse} to click in the center of the + * needed, and then uses {@link Page.mouse} to click in the center of the * element. If there's no element matching `selector`, the method throws an * error. * @@ -2660,7 +2669,7 @@ export abstract class Page extends EventEmitter<PageEvents> { /** * This method fetches an element with `selector`, scrolls it into view if - * needed, and then uses {@link Page | Page.mouse} + * needed, and then uses {@link Page.mouse} * to hover over the center of the element. * If there's no element matching `selector`, the method throws an error. * @param selector - A @@ -2709,7 +2718,7 @@ export abstract class Page extends EventEmitter<PageEvents> { /** * This method fetches an element with `selector`, scrolls it into view if - * needed, and then uses {@link Page | Page.touchscreen} + * needed, and then uses {@link Page.touchscreen} * to tap in the center of the element. * If there's no element matching `selector`, the method throws an error. * @param selector - A diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/api/locators/locators.ts b/remote/test/puppeteer/packages/puppeteer-core/src/api/locators/locators.ts index d88cc0a17d..ea77dc94a1 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/api/locators/locators.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/api/locators/locators.ts @@ -172,19 +172,23 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { }); }, retryAndRaceWithSignalAndTimer: <T>( - signal?: AbortSignal + signal?: AbortSignal, + cause?: Error ): OperatorFunction<T, T> => { const candidates = []; if (signal) { candidates.push( fromEvent(signal, 'abort').pipe( map(() => { + if (signal.reason instanceof Error) { + signal.reason.cause = cause; + } throw signal.reason; }) ) ); } - candidates.push(timeout(this._timeout)); + candidates.push(timeout(this._timeout, cause)); return pipe( retry({delay: RETRY_DELAY}), raceWith<T, never[]>(...candidates) @@ -368,6 +372,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { options?: Readonly<LocatorClickOptions> ): Observable<void> { const signal = options?.signal; + const cause = new Error('Locator.click'); return this._wait(options).pipe( this.operators.conditions( [ @@ -388,7 +393,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { }) ); }), - this.operators.retryAndRaceWithSignalAndTimer(signal) + this.operators.retryAndRaceWithSignalAndTimer(signal, cause) ); } @@ -398,6 +403,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { options?: Readonly<ActionOptions> ): Observable<void> { const signal = options?.signal; + const cause = new Error('Locator.fill'); return this._wait(options).pipe( this.operators.conditions( [ @@ -521,7 +527,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { }) ); }), - this.operators.retryAndRaceWithSignalAndTimer(signal) + this.operators.retryAndRaceWithSignalAndTimer(signal, cause) ); } @@ -530,6 +536,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { options?: Readonly<ActionOptions> ): Observable<void> { const signal = options?.signal; + const cause = new Error('Locator.hover'); return this._wait(options).pipe( this.operators.conditions( [ @@ -549,7 +556,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { }) ); }), - this.operators.retryAndRaceWithSignalAndTimer(signal) + this.operators.retryAndRaceWithSignalAndTimer(signal, cause) ); } @@ -558,6 +565,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { options?: Readonly<LocatorScrollOptions> ): Observable<void> { const signal = options?.signal; + const cause = new Error('Locator.scroll'); return this._wait(options).pipe( this.operators.conditions( [ @@ -590,7 +598,7 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { }) ); }), - this.operators.retryAndRaceWithSignalAndTimer(signal) + this.operators.retryAndRaceWithSignalAndTimer(signal, cause) ); } @@ -617,9 +625,10 @@ export abstract class Locator<T> extends EventEmitter<LocatorEvents> { * @public */ async waitHandle(options?: Readonly<ActionOptions>): Promise<HandleFor<T>> { + const cause = new Error('Locator.waitHandle'); return await firstValueFrom( this._wait(options).pipe( - this.operators.retryAndRaceWithSignalAndTimer(options?.signal) + this.operators.retryAndRaceWithSignalAndTimer(options?.signal, cause) ) ); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/BidiOverCdp.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/BidiOverCdp.ts index ace35a52b0..6f58d3e6a9 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/BidiOverCdp.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/BidiOverCdp.ts @@ -25,9 +25,7 @@ const bidiServerLogger = (prefix: string, ...args: unknown[]): void => { */ export async function connectBidiOverCdp( cdp: CdpConnection, - // TODO: replace with `BidiMapper.MapperOptions`, once it's exported in - // https://github.com/puppeteer/puppeteer/pull/11415. - options: {acceptInsecureCerts: boolean} + options: BidiMapper.MapperOptions ): Promise<BidiConnection> { const transportBiDi = new NoOpTransport(); const cdpConnectionAdapter = new CdpConnectionAdapter(cdp); diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Browser.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Browser.ts index 8798d8325d..2e6b80459b 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Browser.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Browser.ts @@ -19,7 +19,6 @@ import { import {BrowserContextEvent} from '../api/BrowserContext.js'; import type {Page} from '../api/Page.js'; import type {Target} from '../api/Target.js'; -import {UnsupportedOperation} from '../common/Errors.js'; import {EventEmitter} from '../common/EventEmitter.js'; import {debugError} from '../common/util.js'; import type {Viewport} from '../common/Viewport.js'; @@ -50,7 +49,7 @@ export class BidiBrowser extends Browser { readonly protocol = 'webDriverBiDi'; // TODO: Update generator to include fully module - static readonly subscribeModules: string[] = [ + static readonly subscribeModules: [string, ...string[]] = [ 'browsingContext', 'network', 'log', @@ -133,8 +132,8 @@ export class BidiBrowser extends Browser { return !this.#browserName.toLocaleLowerCase().includes('firefox'); } - override userAgent(): never { - throw new UnsupportedOperation(); + override async userAgent(): Promise<string> { + return this.#browserCore.session.capabilities.userAgent; } #createBrowserContext(userContext: UserContext) { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/CDPSession.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/CDPSession.ts index 1e0c503498..5782056259 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/CDPSession.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/CDPSession.ts @@ -5,6 +5,7 @@ */ import type ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js'; +import type {CommandOptions} from '../api/CDPSession.js'; import {CDPSession} from '../api/CDPSession.js'; import type {Connection as CdpConnection} from '../cdp/Connection.js'; import {TargetCloseError, UnsupportedOperation} from '../common/Errors.js'; @@ -61,7 +62,8 @@ export class BidiCdpSession extends CDPSession { override async send<T extends keyof ProtocolMapping.Commands>( method: T, - params?: ProtocolMapping.Commands[T]['paramsType'][0] + params?: ProtocolMapping.Commands[T]['paramsType'][0], + options?: CommandOptions ): Promise<ProtocolMapping.Commands[T]['returnType']> { if (this.#connection === undefined) { throw new UnsupportedOperation( @@ -74,11 +76,15 @@ export class BidiCdpSession extends CDPSession { ); } const session = await this.#sessionId.valueOrThrow(); - const {result} = await this.#connection.send('cdp.sendCommand', { - method: method, - params: params, - session, - }); + const {result} = await this.#connection.send( + 'cdp.sendCommand', + { + method: method, + params: params, + session, + }, + options?.timeout + ); return result.result; } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Connection.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Connection.ts index dd688c309a..5e19390371 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Connection.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Connection.ts @@ -97,11 +97,12 @@ export class BidiConnection send<T extends keyof Commands>( method: T, - params: Commands[T]['params'] + params: Commands[T]['params'], + timeout?: number ): Promise<{result: Commands[T]['returnType']}> { assert(!this.#closed, 'Protocol error: Connection closed.'); - return this.#callbacks.create(method, this.#timeout, id => { + return this.#callbacks.create(method, timeout ?? this.#timeout, id => { const stringifiedMessage = JSON.stringify({ id, method, diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Frame.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Frame.ts index f2bfd5f64e..ce44c0637d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Frame.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Frame.ts @@ -37,6 +37,7 @@ import {TargetCloseError, UnsupportedOperation} from '../common/Errors.js'; import type {TimeoutSettings} from '../common/TimeoutSettings.js'; import type {Awaitable, NodeFor} from '../common/types.js'; import {debugError, fromEmitterEvent, timeout} from '../common/util.js'; +import {isErrorLike} from '../util/ErrorLike.js'; import {BidiCdpSession} from './CDPSession.js'; import type {BrowsingContext} from './core/BrowsingContext.js'; @@ -114,13 +115,13 @@ export class BidiFrame extends Frame { this.browsingContext.on('request', ({request}) => { const httpRequest = BidiHTTPRequest.from(request, this); request.once('success', () => { - // SAFETY: BidiHTTPRequest will create this before here. this.page().trustedEmitter.emit(PageEvent.RequestFinished, httpRequest); }); request.once('error', () => { this.page().trustedEmitter.emit(PageEvent.RequestFailed, httpRequest); }); + void httpRequest.finalizeInterceptions(); }); this.browsingContext.on('navigation', ({navigation}) => { @@ -300,10 +301,18 @@ export class BidiFrame extends Frame { // readiness=interactive. // // Related: https://bugzilla.mozilla.org/show_bug.cgi?id=1846601 - this.browsingContext.navigate( - url, - Bidi.BrowsingContext.ReadinessState.Interactive - ), + this.browsingContext + .navigate(url, Bidi.BrowsingContext.ReadinessState.Interactive) + .catch(error => { + if ( + isErrorLike(error) && + error.message.includes('net::ERR_HTTP_RESPONSE_CODE_FAILURE') + ) { + return; + } + + throw error; + }), ]).catch( rewriteNavigationError( url, @@ -351,11 +360,7 @@ export class BidiFrame extends Frame { }), raceWith( fromEmitterEvent(navigation, 'fragment'), - fromEmitterEvent(navigation, 'failed').pipe( - map(({url}) => { - throw new Error(`Navigation failed: ${url}`); - }) - ), + fromEmitterEvent(navigation, 'failed'), fromEmitterEvent(navigation, 'aborted').pipe( map(({url}) => { throw new Error(`Navigation aborted: ${url}`); @@ -401,11 +406,9 @@ export class BidiFrame extends Frame { if (!request) { return null; } - const httpRequest = requests.get(request)!; - const lastRedirect = httpRequest.redirectChain().at(-1); - return ( - lastRedirect !== undefined ? lastRedirect : httpRequest - ).response(); + const lastRequest = request.lastRedirect ?? request; + const httpRequest = requests.get(lastRequest)!; + return httpRequest.response(); }), raceWith( timeout(ms), @@ -471,6 +474,7 @@ export class BidiFrame extends Frame { targetId: this._id, flatten: true, }); + await this.browsingContext.subscribe([Bidi.ChromiumBidi.BiDiModule.Cdp]); return new BidiCdpSession(this, sessionId); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPRequest.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPRequest.ts index e75bb0cf3c..39ce4fec4b 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPRequest.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPRequest.ts @@ -10,7 +10,12 @@ import type { ContinueRequestOverrides, ResponseForRequest, } from '../api/HTTPRequest.js'; -import {HTTPRequest, type ResourceType} from '../api/HTTPRequest.js'; +import { + HTTPRequest, + STATUS_TEXTS, + type ResourceType, + handleError, +} from '../api/HTTPRequest.js'; import {PageEvent} from '../api/Page.js'; import {UnsupportedOperation} from '../common/Errors.js'; @@ -26,41 +31,61 @@ export const requests = new WeakMap<Request, BidiHTTPRequest>(); export class BidiHTTPRequest extends HTTPRequest { static from( bidiRequest: Request, - frame: BidiFrame | undefined + frame: BidiFrame, + redirect?: BidiHTTPRequest ): BidiHTTPRequest { - const request = new BidiHTTPRequest(bidiRequest, frame); + const request = new BidiHTTPRequest(bidiRequest, frame, redirect); request.#initialize(); return request; } - - #redirect: BidiHTTPRequest | undefined; + #redirectBy: BidiHTTPRequest | undefined; #response: BidiHTTPResponse | null = null; override readonly id: string; - readonly #frame: BidiFrame | undefined; + readonly #frame: BidiFrame; readonly #request: Request; - private constructor(request: Request, frame: BidiFrame | undefined) { + private constructor( + request: Request, + frame: BidiFrame, + redirectBy?: BidiHTTPRequest + ) { super(); requests.set(request, this); + this.interception.enabled = request.isBlocked; + this.#request = request; this.#frame = frame; + this.#redirectBy = redirectBy; this.id = request.id; } override get client(): CDPSession { - throw new UnsupportedOperation(); + return this.#frame.client; } #initialize() { this.#request.on('redirect', request => { - this.#redirect = BidiHTTPRequest.from(request, this.#frame); + const httpRequest = BidiHTTPRequest.from(request, this.#frame, this); + void httpRequest.finalizeInterceptions(); }); this.#request.once('success', data => { this.#response = BidiHTTPResponse.from(data, this); }); - - this.#frame?.page().trustedEmitter.emit(PageEvent.Request, this); + this.#request.on('authenticate', this.#handleAuthentication); + + this.#frame.page().trustedEmitter.emit(PageEvent.Request, this); + + if (Object.keys(this.#extraHTTPHeaders).length) { + this.interception.handlers.push(async () => { + await this.continue( + { + headers: this.headers(), + }, + 0 + ); + }); + } } override url(): string { @@ -68,7 +93,7 @@ export class BidiHTTPRequest extends HTTPRequest { } override resourceType(): ResourceType { - return this.initiator().type.toLowerCase() as ResourceType; + throw new UnsupportedOperation(); } override method(): string { @@ -87,12 +112,19 @@ export class BidiHTTPRequest extends HTTPRequest { throw new UnsupportedOperation(); } + get #extraHTTPHeaders(): Record<string, string> { + return this.#frame?.page()._extraHTTPHeaders ?? {}; + } + override headers(): Record<string, string> { const headers: Record<string, string> = {}; for (const header of this.#request.headers) { headers[header.name.toLowerCase()] = header.value.value; } - return headers; + return { + ...headers, + ...this.#extraHTTPHeaders, + }; } override response(): BidiHTTPResponse | null { @@ -115,65 +147,167 @@ export class BidiHTTPRequest extends HTTPRequest { } override redirectChain(): BidiHTTPRequest[] { - if (this.#redirect === undefined) { + if (this.#redirectBy === undefined) { return []; } - const redirects = [this.#redirect]; + const redirects = [this.#redirectBy]; for (const redirect of redirects) { - if (redirect.#redirect !== undefined) { - redirects.push(redirect.#redirect); + if (redirect.#redirectBy !== undefined) { + redirects.push(redirect.#redirectBy); } } return redirects; } - override enqueueInterceptAction( - pendingHandler: () => void | PromiseLike<unknown> - ): void { - // Execute the handler when interception is not supported - void pendingHandler(); + override frame(): BidiFrame { + return this.#frame; } - override frame(): BidiFrame | null { - return this.#frame ?? null; + override async continue( + overrides?: ContinueRequestOverrides, + priority?: number | undefined + ): Promise<void> { + return await super.continue( + { + headers: Object.keys(this.#extraHTTPHeaders).length + ? this.headers() + : undefined, + ...overrides, + }, + priority + ); } - override continueRequestOverrides(): never { - throw new UnsupportedOperation(); + override async _continue( + overrides: ContinueRequestOverrides = {} + ): Promise<void> { + const headers: Bidi.Network.Header[] = getBidiHeaders(overrides.headers); + this.interception.handled = true; + + return await this.#request + .continueRequest({ + url: overrides.url, + method: overrides.method, + body: overrides.postData + ? { + type: 'base64', + value: btoa(overrides.postData), + } + : undefined, + headers: headers.length > 0 ? headers : undefined, + }) + .catch(error => { + this.interception.handled = false; + return handleError(error); + }); } - override continue(_overrides: ContinueRequestOverrides = {}): never { - throw new UnsupportedOperation(); - } - - override responseForRequest(): never { - throw new UnsupportedOperation(); + override async _abort(): Promise<void> { + this.interception.handled = true; + return await this.#request.failRequest().catch(error => { + this.interception.handled = false; + throw error; + }); } - override abortErrorReason(): never { - throw new UnsupportedOperation(); - } + override async _respond( + response: Partial<ResponseForRequest>, + _priority?: number + ): Promise<void> { + this.interception.handled = true; + const responseBody: string | undefined = + response.body && response.body instanceof Uint8Array + ? response.body.toString('base64') + : response.body + ? btoa(response.body) + : undefined; + + const headers: Bidi.Network.Header[] = getBidiHeaders(response.headers); + const hasContentLength = headers.some(header => { + return header.name === 'content-length'; + }); - override interceptResolutionState(): never { - throw new UnsupportedOperation(); - } + if (response.contentType) { + headers.push({ + name: 'content-type', + value: { + type: 'string', + value: response.contentType, + }, + }); + } - override isInterceptResolutionHandled(): never { - throw new UnsupportedOperation(); + if (responseBody && !hasContentLength) { + const encoder = new TextEncoder(); + headers.push({ + name: 'content-length', + value: { + type: 'string', + value: String(encoder.encode(responseBody).byteLength), + }, + }); + } + const status = response.status || 200; + + return await this.#request + .provideResponse({ + statusCode: status, + headers: headers.length > 0 ? headers : undefined, + reasonPhrase: STATUS_TEXTS[status], + body: responseBody + ? { + type: 'base64', + value: responseBody, + } + : undefined, + }) + .catch(error => { + this.interception.handled = false; + throw error; + }); } - override finalizeInterceptions(): never { - throw new UnsupportedOperation(); - } + #authenticationHandled = false; + #handleAuthentication = async () => { + if (!this.#frame) { + return; + } + const credentials = this.#frame.page()._credentials; + if (credentials && !this.#authenticationHandled) { + this.#authenticationHandled = true; + void this.#request.continueWithAuth({ + action: 'provideCredentials', + credentials: { + type: 'password', + username: credentials.username, + password: credentials.password, + }, + }); + } else { + void this.#request.continueWithAuth({ + action: 'cancel', + }); + } + }; +} - override abort(): never { - throw new UnsupportedOperation(); +function getBidiHeaders(rawHeaders?: Record<string, unknown>) { + const headers: Bidi.Network.Header[] = []; + for (const [name, value] of Object.entries(rawHeaders ?? [])) { + if (!Object.is(value, undefined)) { + const values = Array.isArray(value) ? value : [value]; + + for (const value of values) { + headers.push({ + name: name.toLowerCase(), + value: { + type: 'string', + value: String(value), + }, + }); + } + } } - override respond( - _response: Partial<ResponseForRequest>, - _priority?: number - ): never { - throw new UnsupportedOperation(); - } + return headers; } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPResponse.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPResponse.ts index bad44ff089..ffb2ac298a 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPResponse.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/HTTPResponse.ts @@ -40,6 +40,12 @@ export class BidiHTTPResponse extends HTTPResponse { } #initialize() { + if (this.#data.fromCache) { + this.#request + .frame() + ?.page() + .trustedEmitter.emit(PageEvent.RequestServedFromCache, this.#request); + } this.#request.frame()?.page().trustedEmitter.emit(PageEvent.Response, this); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Page.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Page.ts index c662496a18..55a2e79310 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Page.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Page.ts @@ -13,8 +13,9 @@ import type {BoundingBox} from '../api/ElementHandle.js'; import type {WaitForOptions} from '../api/Frame.js'; import type {HTTPResponse} from '../api/HTTPResponse.js'; import type { - MediaFeature, + Credentials, GeolocationOptions, + MediaFeature, PageEvents, } from '../api/Page.js'; import { @@ -27,13 +28,22 @@ import {Accessibility} from '../cdp/Accessibility.js'; import {Coverage} from '../cdp/Coverage.js'; import {EmulationManager} from '../cdp/EmulationManager.js'; import {Tracing} from '../cdp/Tracing.js'; -import type {Cookie, CookieParam, CookieSameSite} from '../common/Cookie.js'; -import type {DeleteCookiesRequest} from '../common/Cookie.js'; +import type { + Cookie, + CookieParam, + CookieSameSite, + DeleteCookiesRequest, +} from '../common/Cookie.js'; import {UnsupportedOperation} from '../common/Errors.js'; import {EventEmitter} from '../common/EventEmitter.js'; import type {PDFOptions} from '../common/PDFOptions.js'; import type {Awaitable} from '../common/types.js'; -import {evaluationString, parsePDFOptions, timeout} from '../common/util.js'; +import { + evaluationString, + isString, + parsePDFOptions, + timeout, +} from '../common/util.js'; import type {Viewport} from '../common/Viewport.js'; import {assert} from '../util/assert.js'; import {bubble} from '../util/decorators.js'; @@ -43,7 +53,6 @@ import type {BidiBrowser} from './Browser.js'; import type {BidiBrowserContext} from './BrowserContext.js'; import type {BidiCdpSession} from './CDPSession.js'; import type {BrowsingContext} from './core/BrowsingContext.js'; -import {BidiElementHandle} from './ElementHandle.js'; import {BidiFrame} from './Frame.js'; import type {BidiHTTPResponse} from './HTTPResponse.js'; import {BidiKeyboard, BidiMouse, BidiTouchscreen} from './Input.js'; @@ -161,21 +170,28 @@ export class BidiPage extends Page { } async focusedFrame(): Promise<BidiFrame> { - using frame = await this.mainFrame() + using handle = (await this.mainFrame() .isolatedRealm() .evaluateHandle(() => { - let frame: HTMLIFrameElement | undefined; - let win: Window | null = window; - while (win?.document.activeElement instanceof HTMLIFrameElement) { - frame = win.document.activeElement; - win = frame.contentWindow; + let win = window; + while ( + win.document.activeElement instanceof win.HTMLIFrameElement || + win.document.activeElement instanceof win.HTMLFrameElement + ) { + if (win.document.activeElement.contentWindow === null) { + break; + } + win = win.document.activeElement.contentWindow as typeof win; } - return frame; - }); - if (!(frame instanceof BidiElementHandle)) { - return this.mainFrame(); - } - return await frame.contentFrame(); + return win; + })) as BidiJSHandle<Window & typeof globalThis>; + const value = handle.remoteValue(); + assert(value.type === 'window'); + const frame = this.frames().find(frame => { + return frame._id === value.value.context; + }); + assert(frame); + return frame; } override frames(): BidiFrame[] { @@ -311,6 +327,17 @@ export class BidiPage extends Page { preferCSSPageSize, } = parsePDFOptions(options, 'cm'); const pageRanges = ranges ? ranges.split(', ') : []; + + await firstValueFrom( + from( + this.mainFrame() + .isolatedRealm() + .evaluate(() => { + return document.fonts.ready; + }) + ).pipe(raceWith(timeout(ms))) + ); + const data = await firstValueFrom( from( this.#frame.browsingContext.print({ @@ -489,8 +516,71 @@ export class BidiPage extends Page { return [...this.#workers]; } - override setRequestInterception(): never { - throw new UnsupportedOperation(); + #userInterception?: string; + override async setRequestInterception(enable: boolean): Promise<void> { + this.#userInterception = await this.#toggleInterception( + [Bidi.Network.InterceptPhase.BeforeRequestSent], + this.#userInterception, + enable + ); + } + + /** + * @internal + */ + _extraHTTPHeaders: Record<string, string> = {}; + #extraHeadersInterception?: string; + override async setExtraHTTPHeaders( + headers: Record<string, string> + ): Promise<void> { + const extraHTTPHeaders: Record<string, string> = {}; + for (const [key, value] of Object.entries(headers)) { + assert( + isString(value), + `Expected value of header "${key}" to be String, but "${typeof value}" is found.` + ); + extraHTTPHeaders[key.toLowerCase()] = value; + } + this._extraHTTPHeaders = extraHTTPHeaders; + + this.#extraHeadersInterception = await this.#toggleInterception( + [Bidi.Network.InterceptPhase.BeforeRequestSent], + this.#extraHeadersInterception, + Boolean(Object.keys(this._extraHTTPHeaders).length) + ); + } + + /** + * @internal + */ + _credentials: Credentials | null = null; + #authInterception?: string; + override async authenticate(credentials: Credentials | null): Promise<void> { + this.#authInterception = await this.#toggleInterception( + [Bidi.Network.InterceptPhase.AuthRequired], + this.#authInterception, + Boolean(credentials) + ); + + this._credentials = credentials; + } + + async #toggleInterception( + phases: [Bidi.Network.InterceptPhase, ...Bidi.Network.InterceptPhase[]], + interception: string | undefined, + expected: boolean + ): Promise<string | undefined> { + if (expected && !interception) { + return await this.#frame.browsingContext.addIntercept({ + phases, + }); + } else if (!expected && interception) { + await this.#frame.browsingContext.userContext.browser.removeIntercept( + interception + ); + return; + } + return interception; } override setDragInterception(): never { @@ -603,14 +693,6 @@ export class BidiPage extends Page { await this.#frame.removeExposedFunction(name); } - override authenticate(): never { - throw new UnsupportedOperation(); - } - - override setExtraHTTPHeaders(): never { - throw new UnsupportedOperation(); - } - override metrics(): never { throw new UnsupportedOperation(); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts index efeabc3a59..5f51895585 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Browser.ts @@ -51,20 +51,17 @@ export class Browser extends EventEmitter<{ return browser; } - // keep-sorted start #closed = false; #reason: string | undefined; readonly #disposables = new DisposableStack(); readonly #userContexts = new Map<string, UserContext>(); readonly session: Session; readonly #sharedWorkers = new Map<string, SharedWorkerRealm>(); - // keep-sorted end private constructor(session: Session) { super(); - // keep-sorted start + this.session = session; - // keep-sorted end } async #initialize() { @@ -141,7 +138,6 @@ export class Browser extends EventEmitter<{ return userContext; } - // keep-sorted start block=yes get closed(): boolean { return this.#closed; } @@ -158,7 +154,6 @@ export class Browser extends EventEmitter<{ get userContexts(): Iterable<UserContext> { return this.#userContexts.values(); } - // keep-sorted end @inertIfDisposed dispose(reason?: string, closed = false): void { @@ -203,6 +198,16 @@ export class Browser extends EventEmitter<{ // SAFETY: By definition of `disposed`, `#reason` is defined. return browser.#reason!; }) + async removeIntercept(intercept: Bidi.Network.Intercept): Promise<void> { + await this.session.send('network.removeIntercept', { + intercept, + }); + } + + @throwIfDisposed<Browser>(browser => { + // SAFETY: By definition of `disposed`, `#reason` is defined. + return browser.#reason!; + }) async removePreloadScript(script: string): Promise<void> { await this.session.send('script.removePreloadScript', { script, diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts index 07309576a3..ed3a235f5d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/BrowsingContext.ts @@ -21,6 +21,14 @@ import {UserPrompt} from './UserPrompt.js'; /** * @internal */ +export type AddInterceptOptions = Omit< + Bidi.Network.AddInterceptParameters, + 'contexts' +>; + +/** + * @internal + */ export type CaptureScreenshotOptions = Omit< Bidi.BrowsingContext.CaptureScreenshotParameters, 'context' @@ -121,7 +129,6 @@ export class BrowsingContext extends EventEmitter<{ return browsingContext; } - // keep-sorted start #navigation: Navigation | undefined; #reason?: string; #url: string; @@ -133,7 +140,6 @@ export class BrowsingContext extends EventEmitter<{ readonly id: string; readonly parent: BrowsingContext | undefined; readonly userContext: UserContext; - // keep-sorted end private constructor( context: UserContext, @@ -142,12 +148,11 @@ export class BrowsingContext extends EventEmitter<{ url: string ) { super(); - // keep-sorted start + this.#url = url; this.id = id; this.parent = parent; this.userContext = context; - // keep-sorted end this.defaultRealm = this.#createWindowRealm(); } @@ -275,7 +280,6 @@ export class BrowsingContext extends EventEmitter<{ }); } - // keep-sorted start block=yes get #session() { return this.userContext.browser.session; } @@ -306,7 +310,6 @@ export class BrowsingContext extends EventEmitter<{ get url(): string { return this.#url; } - // keep-sorted end #createWindowRealm(sandbox?: string) { const realm = WindowRealm.from(this, sandbox); @@ -478,7 +481,7 @@ export class BrowsingContext extends EventEmitter<{ functionDeclaration, { ...options, - contexts: [this, ...(options.contexts ?? [])], + contexts: [this], } ); } @@ -487,6 +490,21 @@ export class BrowsingContext extends EventEmitter<{ // SAFETY: Disposal implies this exists. return context.#reason!; }) + async addIntercept(options: AddInterceptOptions): Promise<string> { + const { + result: {intercept}, + } = await this.userContext.browser.session.send('network.addIntercept', { + ...options, + contexts: [this.id], + }); + + return intercept; + } + + @throwIfDisposed<BrowsingContext>(context => { + // SAFETY: Disposal implies this exists. + return context.#reason!; + }) async removePreloadScript(script: string): Promise<void> { await this.userContext.browser.removePreloadScript(script); } @@ -539,6 +557,22 @@ export class BrowsingContext extends EventEmitter<{ }); } + @throwIfDisposed<BrowsingContext>(context => { + // SAFETY: Disposal implies this exists. + return context.#reason!; + }) + async subscribe(events: [string, ...string[]]): Promise<void> { + await this.#session.subscribe(events, [this.id]); + } + + @throwIfDisposed<BrowsingContext>(context => { + // SAFETY: Disposal implies this exists. + return context.#reason!; + }) + async addInterception(events: [string, ...string[]]): Promise<void> { + await this.#session.subscribe(events, [this.id]); + } + [disposeSymbol](): void { this.#reason ??= 'Browsing context already closed, probably because the user context closed.'; diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts index 9c26a03503..7e5da052bb 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Connection.ts @@ -149,6 +149,31 @@ export interface Commands { params: Bidi.Storage.SetCookieParameters; returnType: Bidi.Storage.SetCookieParameters; }; + + 'network.addIntercept': { + params: Bidi.Network.AddInterceptParameters; + returnType: Bidi.Network.AddInterceptResult; + }; + 'network.removeIntercept': { + params: Bidi.Network.RemoveInterceptParameters; + returnType: Bidi.EmptyResult; + }; + 'network.continueRequest': { + params: Bidi.Network.ContinueRequestParameters; + returnType: Bidi.EmptyResult; + }; + 'network.continueWithAuth': { + params: Bidi.Network.ContinueWithAuthParameters; + returnType: Bidi.EmptyResult; + }; + 'network.failRequest': { + params: Bidi.Network.FailRequestParameters; + returnType: Bidi.EmptyResult; + }; + 'network.provideResponse': { + params: Bidi.Network.ProvideResponseParameters; + returnType: Bidi.EmptyResult; + }; } /** diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts index 50040164a5..d50cf091f9 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Navigation.ts @@ -6,7 +6,6 @@ import {EventEmitter} from '../../common/EventEmitter.js'; import {inertIfDisposed} from '../../util/decorators.js'; -import {Deferred} from '../../util/Deferred.js'; import {DisposableStack, disposeSymbol} from '../../util/disposable.js'; import type {BrowsingContext} from './BrowsingContext.js'; @@ -39,19 +38,16 @@ export class Navigation extends EventEmitter<{ return navigation; } - // keep-sorted start #request: Request | undefined; #navigation: Navigation | undefined; readonly #browsingContext: BrowsingContext; readonly #disposables = new DisposableStack(); - readonly #id = new Deferred<string | null>(); - // keep-sorted end + #id?: string | null; private constructor(context: BrowsingContext) { super(); - // keep-sorted start + this.#browsingContext = context; - // keep-sorted end } #initialize() { @@ -69,7 +65,6 @@ export class Navigation extends EventEmitter<{ browsingContextEmitter.on('request', ({request}) => { if ( request.navigation === undefined || - this.#request !== undefined || // If a request with a navigation ID comes in, then the navigation ID is // for this navigation. !this.#matches(request.navigation) @@ -79,6 +74,13 @@ export class Navigation extends EventEmitter<{ this.#request = request; this.emit('request', request); + const requestEmitter = this.#disposables.use( + new EventEmitter(this.#request) + ); + + requestEmitter.on('redirect', request => { + this.#request = request; + }); }); const sessionEmitter = this.#disposables.use( @@ -139,14 +141,13 @@ export class Navigation extends EventEmitter<{ if (this.#navigation !== undefined && !this.#navigation.disposed) { return false; } - if (!this.#id.resolved()) { - this.#id.resolve(navigation); + if (this.#id === undefined) { + this.#id = navigation; return true; } - return this.#id.value() === navigation; + return this.#id === navigation; } - // keep-sorted start block=yes get #session() { return this.#browsingContext.userContext.browser.session; } @@ -159,7 +160,6 @@ export class Navigation extends EventEmitter<{ get navigation(): Navigation | undefined { return this.#navigation; } - // keep-sorted end @inertIfDisposed private dispose(): void { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts index 392194cec8..87298f8730 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Realm.ts @@ -44,22 +44,19 @@ export abstract class Realm extends EventEmitter<{ /** Emitted when a shared worker is created in the realm. */ sharedworker: SharedWorkerRealm; }> { - // keep-sorted start #reason?: string; protected readonly disposables = new DisposableStack(); readonly id: string; readonly origin: string; - // keep-sorted end + protected executionContextId?: number; protected constructor(id: string, origin: string) { super(); - // keep-sorted start + this.id = id; this.origin = origin; - // keep-sorted end } - // keep-sorted start block=yes get disposed(): boolean { return this.#reason !== undefined; } @@ -67,7 +64,6 @@ export abstract class Realm extends EventEmitter<{ get target(): Bidi.Script.Target { return {realm: this.id}; } - // keep-sorted end @inertIfDisposed protected dispose(reason?: string): void { @@ -127,11 +123,15 @@ export abstract class Realm extends EventEmitter<{ return realm.#reason!; }) async resolveExecutionContextId(): Promise<number> { - const {result} = await (this.session.connection as BidiConnection).send( - 'cdp.resolveRealm', - {realm: this.id} - ); - return result.executionContextId; + if (!this.executionContextId) { + const {result} = await (this.session.connection as BidiConnection).send( + 'cdp.resolveRealm', + {realm: this.id} + ); + this.executionContextId = result.executionContextId; + } + + return this.executionContextId; } [disposeSymbol](): void { @@ -154,19 +154,16 @@ export class WindowRealm extends Realm { return realm; } - // keep-sorted start readonly browsingContext: BrowsingContext; readonly sandbox?: string; - // keep-sorted end readonly #workers = new Map<string, DedicatedWorkerRealm>(); private constructor(context: BrowsingContext, sandbox?: string) { super('', ''); - // keep-sorted start + this.browsingContext = context; this.sandbox = sandbox; - // keep-sorted end } #initialize(): void { @@ -188,6 +185,7 @@ export class WindowRealm extends Realm { } (this as any).id = info.realm; (this as any).origin = info.origin; + this.executionContextId = undefined; this.emit('updated', this); }); sessionEmitter.on('script.realmCreated', info => { @@ -242,10 +240,8 @@ export class DedicatedWorkerRealm extends Realm { return realm; } - // keep-sorted start readonly #workers = new Map<string, DedicatedWorkerRealm>(); readonly owners: Set<DedicatedWorkerOwnerRealm>; - // keep-sorted end private constructor( owner: DedicatedWorkerOwnerRealm, @@ -300,10 +296,8 @@ export class SharedWorkerRealm extends Realm { return realm; } - // keep-sorted start readonly #workers = new Map<string, DedicatedWorkerRealm>(); readonly browser: Browser; - // keep-sorted end private constructor(browser: Browser, id: string, origin: string) { super(id, origin); diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts index fd616b668d..241bebf3f9 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Request.ts @@ -19,6 +19,8 @@ export class Request extends EventEmitter<{ /** Emitted when the request is redirected. */ redirect: Request; /** Emitted when the request succeeds. */ + authenticate: void; + /** Emitted when the request succeeds. */ success: Bidi.Network.ResponseData; /** Emitted when the request fails. */ error: string; @@ -32,24 +34,21 @@ export class Request extends EventEmitter<{ return request; } - // keep-sorted start #error?: string; #redirect?: Request; #response?: Bidi.Network.ResponseData; readonly #browsingContext: BrowsingContext; readonly #disposables = new DisposableStack(); readonly #event: Bidi.Network.BeforeRequestSentParameters; - // keep-sorted end private constructor( browsingContext: BrowsingContext, event: Bidi.Network.BeforeRequestSentParameters ) { super(); - // keep-sorted start + this.#browsingContext = browsingContext; this.#event = event; - // keep-sorted end } #initialize() { @@ -77,6 +76,17 @@ export class Request extends EventEmitter<{ this.emit('redirect', this.#redirect); this.dispose(); }); + sessionEmitter.on('network.authRequired', event => { + if ( + event.context !== this.#browsingContext.id || + event.request.request !== this.id || + // Don't try to authenticate for events that are not blocked + !event.isBlocked + ) { + return; + } + this.emit('authenticate', undefined); + }); sessionEmitter.on('network.fetchError', event => { if ( event.context !== this.#browsingContext.id || @@ -107,7 +117,6 @@ export class Request extends EventEmitter<{ }); } - // keep-sorted start block=yes get #session() { return this.#browsingContext.userContext.browser.session; } @@ -135,13 +144,82 @@ export class Request extends EventEmitter<{ get redirect(): Request | undefined { return this.#redirect; } + get lastRedirect(): Request | undefined { + let redirect = this.#redirect; + while (redirect) { + if (redirect && !redirect.#redirect) { + return redirect; + } + redirect = redirect.#redirect; + } + return redirect; + } get response(): Bidi.Network.ResponseData | undefined { return this.#response; } get url(): string { return this.#event.request.url; } - // keep-sorted end + get isBlocked(): boolean { + return this.#event.isBlocked; + } + + async continueRequest({ + url, + method, + headers, + cookies, + body, + }: Omit<Bidi.Network.ContinueRequestParameters, 'request'>): Promise<void> { + await this.#session.send('network.continueRequest', { + request: this.id, + url, + method, + headers, + body, + cookies, + }); + } + + async failRequest(): Promise<void> { + await this.#session.send('network.failRequest', { + request: this.id, + }); + } + + async provideResponse({ + statusCode, + reasonPhrase, + headers, + body, + }: Omit<Bidi.Network.ProvideResponseParameters, 'request'>): Promise<void> { + await this.#session.send('network.provideResponse', { + request: this.id, + statusCode, + reasonPhrase, + headers, + body, + }); + } + + async continueWithAuth( + parameters: + | Bidi.Network.ContinueWithAuthCredentials + | Bidi.Network.ContinueWithAuthNoCredentials + ): Promise<void> { + if (parameters.action === 'provideCredentials') { + await this.#session.send('network.continueWithAuth', { + request: this.id, + action: parameters.action, + credentials: parameters.credentials, + }); + } else { + await this.#session.send('network.continueWithAuth', { + request: this.id, + action: parameters.action, + }); + } + } @inertIfDisposed private dispose(): void { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts index ffd39769e7..3957556c69 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/Session.ts @@ -71,8 +71,9 @@ export class Session platformName: '', setWindowRect: false, webSocketUrl: '', + userAgent: '', }, - }; + } satisfies Bidi.Session.NewResult; } const session = new Session(connection, result); @@ -80,21 +81,18 @@ export class Session return session; } - // keep-sorted start #reason: string | undefined; readonly #disposables = new DisposableStack(); readonly #info: Bidi.Session.NewResult; readonly browser!: Browser; @bubble() accessor connection: Connection; - // keep-sorted end private constructor(connection: Connection, info: Bidi.Session.NewResult) { super(); - // keep-sorted start + this.#info = info; this.connection = connection; - // keep-sorted end } async #initialize(): Promise<void> { @@ -120,7 +118,6 @@ export class Session }); } - // keep-sorted start block=yes get capabilities(): Bidi.Session.NewResult['capabilities'] { return this.#info.capabilities; } @@ -133,7 +130,6 @@ export class Session get id(): string { return this.#info.sessionId; } - // keep-sorted end @inertIfDisposed private dispose(reason?: string): void { @@ -163,9 +159,27 @@ export class Session // SAFETY: By definition of `disposed`, `#reason` is defined. return session.#reason!; }) - async subscribe(events: string[]): Promise<void> { + async subscribe( + events: [string, ...string[]], + contexts?: [string, ...string[]] + ): Promise<void> { + await this.send('session.subscribe', { + events, + contexts, + }); + } + + @throwIfDisposed<Session>(session => { + // SAFETY: By definition of `disposed`, `#reason` is defined. + return session.#reason!; + }) + async addIntercepts( + events: [string, ...string[]], + contexts?: [string, ...string[]] + ): Promise<void> { await this.send('session.subscribe', { events, + contexts, }); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts index 72859c6a53..a6af520522 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserContext.ts @@ -52,21 +52,18 @@ export class UserContext extends EventEmitter<{ return context; } - // keep-sorted start #reason?: string; // Note these are only top-level contexts. readonly #browsingContexts = new Map<string, BrowsingContext>(); readonly #disposables = new DisposableStack(); readonly #id: string; readonly browser: Browser; - // keep-sorted end private constructor(browser: Browser, id: string) { super(); - // keep-sorted start + this.#id = id; this.browser = browser; - // keep-sorted end } #initialize() { @@ -110,7 +107,6 @@ export class UserContext extends EventEmitter<{ }); } - // keep-sorted start block=yes get #session() { return this.browser.session; } @@ -126,7 +122,6 @@ export class UserContext extends EventEmitter<{ get id(): string { return this.#id; } - // keep-sorted end @inertIfDisposed private dispose(reason?: string): void { @@ -227,8 +222,7 @@ export class UserContext extends EventEmitter<{ origin, descriptor, state, - // @ts-expect-error not standard implementation. - 'goog:userContext': this.#id, + userContext: this.#id, }); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserPrompt.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserPrompt.ts index 073233bed0..13a455e4ac 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserPrompt.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/core/UserPrompt.ts @@ -49,23 +49,20 @@ export class UserPrompt extends EventEmitter<{ return userPrompt; } - // keep-sorted start #reason?: string; #result?: UserPromptResult; readonly #disposables = new DisposableStack(); readonly browsingContext: BrowsingContext; readonly info: Bidi.BrowsingContext.UserPromptOpenedParameters; - // keep-sorted end private constructor( context: BrowsingContext, info: Bidi.BrowsingContext.UserPromptOpenedParameters ) { super(); - // keep-sorted start + this.browsingContext = context; this.info = info; - // keep-sorted end } #initialize() { @@ -89,7 +86,6 @@ export class UserPrompt extends EventEmitter<{ }); } - // keep-sorted start block=yes get #session() { return this.browsingContext.userContext.browser.session; } @@ -105,7 +101,6 @@ export class UserPrompt extends EventEmitter<{ get result(): UserPromptResult | undefined { return this.#result; } - // keep-sorted end @inertIfDisposed private dispose(reason?: string): void { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/AriaQueryHandler.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/AriaQueryHandler.ts index 2286723758..2b9b14fdc7 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/AriaQueryHandler.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/AriaQueryHandler.ts @@ -27,7 +27,16 @@ const queryAXTree = async ( role, }); return nodes.filter((node: Protocol.Accessibility.AXNode) => { - return !node.role || !NON_ELEMENT_NODE_ROLES.has(node.role.value); + if (node.ignored) { + return false; + } + if (!node.role) { + return false; + } + if (NON_ELEMENT_NODE_ROLES.has(node.role.value)) { + return false; + } + return true; }); }; diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Browser.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Browser.ts index 5c8a4c24da..9ae2cbfff1 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Browser.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Browser.ts @@ -12,20 +12,18 @@ import type {DebugInfo} from '../api/Browser.js'; import { Browser as BrowserBase, BrowserEvent, - WEB_PERMISSION_TO_PROTOCOL_PERMISSION, type BrowserCloseCallback, type BrowserContextOptions, type IsPageTargetCallback, - type Permission, type TargetFilterCallback, } from '../api/Browser.js'; -import {BrowserContext, BrowserContextEvent} from '../api/BrowserContext.js'; +import {BrowserContextEvent} from '../api/BrowserContext.js'; import {CDPSessionEvent, type CDPSession} from '../api/CDPSession.js'; import type {Page} from '../api/Page.js'; import type {Target} from '../api/Target.js'; import type {Viewport} from '../common/Viewport.js'; -import {assert} from '../util/assert.js'; +import {CdpBrowserContext} from './BrowserContext.js'; import {ChromeTargetManager} from './ChromeTargetManager.js'; import type {Connection} from './Connection.js'; import {FirefoxTargetManager} from './FirefoxTargetManager.js'; @@ -424,90 +422,3 @@ export class CdpBrowser extends BrowserBase { }; } } - -/** - * @internal - */ -export class CdpBrowserContext extends BrowserContext { - #connection: Connection; - #browser: CdpBrowser; - #id?: string; - - constructor(connection: Connection, browser: CdpBrowser, contextId?: string) { - super(); - this.#connection = connection; - this.#browser = browser; - this.#id = contextId; - } - - override get id(): string | undefined { - return this.#id; - } - - override targets(): CdpTarget[] { - return this.#browser.targets().filter(target => { - return target.browserContext() === this; - }); - } - - override async pages(): Promise<Page[]> { - const pages = await Promise.all( - this.targets() - .filter(target => { - return ( - target.type() === 'page' || - (target.type() === 'other' && - this.#browser._getIsPageTargetCallback()?.(target)) - ); - }) - .map(target => { - return target.page(); - }) - ); - return pages.filter((page): page is Page => { - return !!page; - }); - } - - override isIncognito(): boolean { - return !!this.#id; - } - - override async overridePermissions( - origin: string, - permissions: Permission[] - ): Promise<void> { - const protocolPermissions = permissions.map(permission => { - const protocolPermission = - WEB_PERMISSION_TO_PROTOCOL_PERMISSION.get(permission); - if (!protocolPermission) { - throw new Error('Unknown permission: ' + permission); - } - return protocolPermission; - }); - await this.#connection.send('Browser.grantPermissions', { - origin, - browserContextId: this.#id || undefined, - permissions: protocolPermissions, - }); - } - - override async clearPermissionOverrides(): Promise<void> { - await this.#connection.send('Browser.resetPermissions', { - browserContextId: this.#id || undefined, - }); - } - - override newPage(): Promise<Page> { - return this.#browser._createPageInContext(this.#id); - } - - override browser(): CdpBrowser { - return this.#browser; - } - - override async close(): Promise<void> { - assert(this.#id, 'Non-incognito profiles cannot be closed!'); - await this.#browser._disposeContext(this.#id); - } -} diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/BrowserContext.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/BrowserContext.ts new file mode 100644 index 0000000000..f2279617f1 --- /dev/null +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/BrowserContext.ts @@ -0,0 +1,104 @@ +/** + * @license + * Copyright 2024 Google Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + WEB_PERMISSION_TO_PROTOCOL_PERMISSION, + type Permission, +} from '../api/Browser.js'; +import {BrowserContext} from '../api/BrowserContext.js'; +import type {Page} from '../api/Page.js'; +import {assert} from '../util/assert.js'; + +import type {CdpBrowser} from './Browser.js'; +import type {Connection} from './Connection.js'; +import type {CdpTarget} from './Target.js'; + +/** + * @internal + */ +export class CdpBrowserContext extends BrowserContext { + #connection: Connection; + #browser: CdpBrowser; + #id?: string; + + constructor(connection: Connection, browser: CdpBrowser, contextId?: string) { + super(); + this.#connection = connection; + this.#browser = browser; + this.#id = contextId; + } + + override get id(): string | undefined { + return this.#id; + } + + override targets(): CdpTarget[] { + return this.#browser.targets().filter(target => { + return target.browserContext() === this; + }); + } + + override async pages(): Promise<Page[]> { + const pages = await Promise.all( + this.targets() + .filter(target => { + return ( + target.type() === 'page' || + (target.type() === 'other' && + this.#browser._getIsPageTargetCallback()?.(target)) + ); + }) + .map(target => { + return target.page(); + }) + ); + return pages.filter((page): page is Page => { + return !!page; + }); + } + + override isIncognito(): boolean { + return !!this.#id; + } + + override async overridePermissions( + origin: string, + permissions: Permission[] + ): Promise<void> { + const protocolPermissions = permissions.map(permission => { + const protocolPermission = + WEB_PERMISSION_TO_PROTOCOL_PERMISSION.get(permission); + if (!protocolPermission) { + throw new Error('Unknown permission: ' + permission); + } + return protocolPermission; + }); + await this.#connection.send('Browser.grantPermissions', { + origin, + browserContextId: this.#id || undefined, + permissions: protocolPermissions, + }); + } + + override async clearPermissionOverrides(): Promise<void> { + await this.#connection.send('Browser.resetPermissions', { + browserContextId: this.#id || undefined, + }); + } + + override newPage(): Promise<Page> { + return this.#browser._createPageInContext(this.#id); + } + + override browser(): CdpBrowser { + return this.#browser; + } + + override async close(): Promise<void> { + assert(this.#id, 'Non-incognito profiles cannot be closed!'); + await this.#browser._disposeContext(this.#id); + } +} diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts index edc7009b11..c7c2885a65 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Frame.ts @@ -206,6 +206,7 @@ export class CdpFrame extends Frame { options: { timeout?: number; waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; + ignoreSameDocumentNavigation?: boolean; } = {} ): Promise<HTTPResponse | null> { const { @@ -220,14 +221,22 @@ export class CdpFrame extends Frame { ); const error = await Deferred.race([ watcher.terminationPromise(), - watcher.sameDocumentNavigationPromise(), + ...(options.ignoreSameDocumentNavigation + ? [] + : [watcher.sameDocumentNavigationPromise()]), watcher.newDocumentNavigationPromise(), ]); try { if (error) { throw error; } - return await watcher.navigationResponse(); + const result = await Deferred.race< + Error | HTTPResponse | null | undefined + >([watcher.terminationPromise(), watcher.navigationResponse()]); + if (result instanceof Error) { + throw error; + } + return result || null; } finally { watcher.dispose(); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/FrameTree.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/FrameTree.ts index 7ee1b86b5f..dcd341c9e3 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/FrameTree.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/FrameTree.ts @@ -21,6 +21,7 @@ export class FrameTree<FrameType extends Frame> { // frameID -> childFrameIDs #childIds = new Map<string, Set<string>>(); #mainFrame?: FrameType; + #isMainFrameStale = false; #waitRequests = new Map<string, Set<Deferred<FrameType>>>(); getMainFrame(): FrameType | undefined { @@ -59,8 +60,9 @@ export class FrameTree<FrameType extends Frame> { this.#childIds.set(frame._parentId, new Set()); } this.#childIds.get(frame._parentId)!.add(frame._id); - } else if (!this.#mainFrame) { + } else if (!this.#mainFrame || this.#isMainFrameStale) { this.#mainFrame = frame; + this.#isMainFrameStale = false; } this.#waitRequests.get(frame._id)?.forEach(request => { return request.resolve(frame); @@ -73,7 +75,7 @@ export class FrameTree<FrameType extends Frame> { if (frame._parentId) { this.#childIds.get(frame._parentId)?.delete(frame._id); } else { - this.#mainFrame = undefined; + this.#isMainFrameStale = true; } } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/HTTPRequest.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/HTTPRequest.ts index 1331513e19..59fc0c940a 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/HTTPRequest.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/HTTPRequest.ts @@ -9,18 +9,14 @@ import type {CDPSession} from '../api/CDPSession.js'; import type {Frame} from '../api/Frame.js'; import { type ContinueRequestOverrides, - type ErrorCode, headersArray, HTTPRequest, - InterceptResolutionAction, - type InterceptResolutionState, type ResourceType, type ResponseForRequest, STATUS_TEXTS, + handleError, } from '../api/HTTPRequest.js'; -import type {ProtocolError} from '../common/Errors.js'; import {debugError, isString} from '../common/util.js'; -import {assert} from '../util/assert.js'; import type {CdpHTTPResponse} from './HTTPResponse.js'; @@ -34,8 +30,7 @@ export class CdpHTTPRequest extends HTTPRequest { #client: CDPSession; #isNavigationRequest: boolean; - #allowInterception: boolean; - #interceptionHandled = false; + #url: string; #resourceType: ResourceType; @@ -44,13 +39,6 @@ export class CdpHTTPRequest extends HTTPRequest { #postData?: string; #headers: Record<string, string> = {}; #frame: Frame | null; - #continueRequestOverrides: ContinueRequestOverrides; - #responseForRequest: Partial<ResponseForRequest> | null = null; - #abortErrorReason: Protocol.Network.ErrorReason | null = null; - #interceptResolutionState: InterceptResolutionState = { - action: InterceptResolutionAction.None, - }; - #interceptHandlers: Array<() => void | PromiseLike<any>>; #initiator?: Protocol.Network.Initiator; override get client(): CDPSession { @@ -96,7 +84,6 @@ export class CdpHTTPRequest extends HTTPRequest { this.#isNavigationRequest = data.requestId === data.loaderId && data.type === 'Document'; this._interceptionId = interceptionId; - this.#allowInterception = allowInterception; this.#url = data.request.url; this.#resourceType = (data.type || 'other').toLowerCase() as ResourceType; this.#method = data.request.method; @@ -104,10 +91,10 @@ export class CdpHTTPRequest extends HTTPRequest { this.#hasPostData = data.request.hasPostData ?? false; this.#frame = frame; this._redirectChain = redirectChain; - this.#continueRequestOverrides = {}; - this.#interceptHandlers = []; this.#initiator = data.initiator; + this.interception.enabled = allowInterception; + for (const [key, value] of Object.entries(data.request.headers)) { this.#headers[key.toLowerCase()] = value; } @@ -117,59 +104,6 @@ export class CdpHTTPRequest extends HTTPRequest { return this.#url; } - override continueRequestOverrides(): ContinueRequestOverrides { - assert(this.#allowInterception, 'Request Interception is not enabled!'); - return this.#continueRequestOverrides; - } - - override responseForRequest(): Partial<ResponseForRequest> | null { - assert(this.#allowInterception, 'Request Interception is not enabled!'); - return this.#responseForRequest; - } - - override abortErrorReason(): Protocol.Network.ErrorReason | null { - assert(this.#allowInterception, 'Request Interception is not enabled!'); - return this.#abortErrorReason; - } - - override interceptResolutionState(): InterceptResolutionState { - if (!this.#allowInterception) { - return {action: InterceptResolutionAction.Disabled}; - } - if (this.#interceptionHandled) { - return {action: InterceptResolutionAction.AlreadyHandled}; - } - return {...this.#interceptResolutionState}; - } - - override isInterceptResolutionHandled(): boolean { - return this.#interceptionHandled; - } - - enqueueInterceptAction( - pendingHandler: () => void | PromiseLike<unknown> - ): void { - this.#interceptHandlers.push(pendingHandler); - } - - override async finalizeInterceptions(): Promise<void> { - await this.#interceptHandlers.reduce((promiseChain, interceptAction) => { - return promiseChain.then(interceptAction); - }, Promise.resolve()); - const {action} = this.interceptResolutionState(); - switch (action) { - case 'abort': - return await this.#abort(this.#abortErrorReason); - case 'respond': - if (this.#responseForRequest === null) { - throw new Error('Response is missing for the interception'); - } - return await this.#respond(this.#responseForRequest); - case 'continue': - return await this.#continue(this.#continueRequestOverrides); - } - } - override resourceType(): ResourceType { return this.#resourceType; } @@ -231,46 +165,12 @@ export class CdpHTTPRequest extends HTTPRequest { }; } - override async continue( - overrides: ContinueRequestOverrides = {}, - priority?: number - ): Promise<void> { - // Request interception is not supported for data: urls. - if (this.#url.startsWith('data:')) { - return; - } - assert(this.#allowInterception, 'Request Interception is not enabled!'); - assert(!this.#interceptionHandled, 'Request is already handled!'); - if (priority === undefined) { - return await this.#continue(overrides); - } - this.#continueRequestOverrides = overrides; - if ( - this.#interceptResolutionState.priority === undefined || - priority > this.#interceptResolutionState.priority - ) { - this.#interceptResolutionState = { - action: InterceptResolutionAction.Continue, - priority, - }; - return; - } - if (priority === this.#interceptResolutionState.priority) { - if ( - this.#interceptResolutionState.action === 'abort' || - this.#interceptResolutionState.action === 'respond' - ) { - return; - } - this.#interceptResolutionState.action = - InterceptResolutionAction.Continue; - } - return; - } - - async #continue(overrides: ContinueRequestOverrides = {}): Promise<void> { + /** + * @internal + */ + async _continue(overrides: ContinueRequestOverrides = {}): Promise<void> { const {url, method, postData, headers} = overrides; - this.#interceptionHandled = true; + this.interception.handled = true; const postDataBinaryBase64 = postData ? Buffer.from(postData).toString('base64') @@ -290,45 +190,13 @@ export class CdpHTTPRequest extends HTTPRequest { headers: headers ? headersArray(headers) : undefined, }) .catch(error => { - this.#interceptionHandled = false; + this.interception.handled = false; return handleError(error); }); } - override async respond( - response: Partial<ResponseForRequest>, - priority?: number - ): Promise<void> { - // Mocking responses for dataURL requests is not currently supported. - if (this.#url.startsWith('data:')) { - return; - } - assert(this.#allowInterception, 'Request Interception is not enabled!'); - assert(!this.#interceptionHandled, 'Request is already handled!'); - if (priority === undefined) { - return await this.#respond(response); - } - this.#responseForRequest = response; - if ( - this.#interceptResolutionState.priority === undefined || - priority > this.#interceptResolutionState.priority - ) { - this.#interceptResolutionState = { - action: InterceptResolutionAction.Respond, - priority, - }; - return; - } - if (priority === this.#interceptResolutionState.priority) { - if (this.#interceptResolutionState.action === 'abort') { - return; - } - this.#interceptResolutionState.action = InterceptResolutionAction.Respond; - } - } - - async #respond(response: Partial<ResponseForRequest>): Promise<void> { - this.#interceptionHandled = true; + async _respond(response: Partial<ResponseForRequest>): Promise<void> { + this.interception.handled = true; const responseBody: Buffer | null = response.body && isString(response.body) @@ -371,43 +239,15 @@ export class CdpHTTPRequest extends HTTPRequest { body: responseBody ? responseBody.toString('base64') : undefined, }) .catch(error => { - this.#interceptionHandled = false; + this.interception.handled = false; return handleError(error); }); } - override async abort( - errorCode: ErrorCode = 'failed', - priority?: number - ): Promise<void> { - // Request interception is not supported for data: urls. - if (this.#url.startsWith('data:')) { - return; - } - const errorReason = errorReasons[errorCode]; - assert(errorReason, 'Unknown error code: ' + errorCode); - assert(this.#allowInterception, 'Request Interception is not enabled!'); - assert(!this.#interceptionHandled, 'Request is already handled!'); - if (priority === undefined) { - return await this.#abort(errorReason); - } - this.#abortErrorReason = errorReason; - if ( - this.#interceptResolutionState.priority === undefined || - priority >= this.#interceptResolutionState.priority - ) { - this.#interceptResolutionState = { - action: InterceptResolutionAction.Abort, - priority, - }; - return; - } - } - - async #abort( + async _abort( errorReason: Protocol.Network.ErrorReason | null ): Promise<void> { - this.#interceptionHandled = true; + this.interception.handled = true; if (this._interceptionId === undefined) { throw new Error( 'HTTPRequest is missing _interceptionId needed for Fetch.failRequest' @@ -421,30 +261,3 @@ export class CdpHTTPRequest extends HTTPRequest { .catch(handleError); } } - -const errorReasons: Record<ErrorCode, Protocol.Network.ErrorReason> = { - aborted: 'Aborted', - accessdenied: 'AccessDenied', - addressunreachable: 'AddressUnreachable', - blockedbyclient: 'BlockedByClient', - blockedbyresponse: 'BlockedByResponse', - connectionaborted: 'ConnectionAborted', - connectionclosed: 'ConnectionClosed', - connectionfailed: 'ConnectionFailed', - connectionrefused: 'ConnectionRefused', - connectionreset: 'ConnectionReset', - internetdisconnected: 'InternetDisconnected', - namenotresolved: 'NameNotResolved', - timedout: 'TimedOut', - failed: 'Failed', -} as const; - -async function handleError(error: ProtocolError) { - if (['Invalid header'].includes(error.originalMessage)) { - throw error; - } - // In certain cases, protocol will return error if the request was - // already canceled or the page was closed. We should tolerate these - // errors. - debugError(error); -} diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/NetworkManager.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/NetworkManager.ts index 4fd61116d2..70f7370f2c 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/NetworkManager.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/NetworkManager.ts @@ -8,6 +8,7 @@ import type {Protocol} from 'devtools-protocol'; import {CDPSessionEvent, type CDPSession} from '../api/CDPSession.js'; import type {Frame} from '../api/Frame.js'; +import type {Credentials} from '../api/Page.js'; import {EventEmitter, EventSubscription} from '../common/EventEmitter.js'; import { NetworkManagerEvent, @@ -27,14 +28,6 @@ import { /** * @public */ -export interface Credentials { - username: string; - password: string; -} - -/** - * @public - */ export interface NetworkConditions { /** * Download speed (bytes/s) @@ -147,18 +140,16 @@ export class NetworkManager extends EventEmitter<NetworkManagerEvents> { ); } - async setExtraHTTPHeaders( - extraHTTPHeaders: Record<string, string> - ): Promise<void> { - this.#extraHTTPHeaders = {}; - for (const key of Object.keys(extraHTTPHeaders)) { - const value = extraHTTPHeaders[key]; + async setExtraHTTPHeaders(headers: Record<string, string>): Promise<void> { + const extraHTTPHeaders: Record<string, string> = {}; + for (const [key, value] of Object.entries(headers)) { assert( isString(value), `Expected value of header "${key}" to be String, but "${typeof value}" is found.` ); - this.#extraHTTPHeaders[key.toLowerCase()] = value; + extraHTTPHeaders[key.toLowerCase()] = value; } + this.#extraHTTPHeaders = extraHTTPHeaders; await this.#applyToAllClients(this.#applyExtraHTTPHeaders.bind(this)); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Page.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Page.ts index d5341cf3bb..32ded73b42 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Page.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/Page.ts @@ -15,6 +15,7 @@ import type {Frame, WaitForOptions} from '../api/Frame.js'; import type {HTTPRequest} from '../api/HTTPRequest.js'; import type {HTTPResponse} from '../api/HTTPResponse.js'; import type {JSHandle} from '../api/JSHandle.js'; +import type {Credentials} from '../api/Page.js'; import { Page, PageEvent, @@ -71,7 +72,7 @@ import {FrameManagerEvent} from './FrameManagerEvents.js'; import {CdpKeyboard, CdpMouse, CdpTouchscreen} from './Input.js'; import {MAIN_WORLD} from './IsolatedWorlds.js'; import {releaseObject} from './JSHandle.js'; -import type {Credentials, NetworkConditions} from './NetworkManager.js'; +import type {NetworkConditions} from './NetworkManager.js'; import type {CdpTarget} from './Target.js'; import type {TargetManager} from './TargetManager.js'; import {TargetManagerEvent} from './TargetManager.js'; @@ -916,7 +917,10 @@ export class CdpPage extends Page { options?: WaitForOptions ): Promise<HTTPResponse | null> { const [result] = await Promise.all([ - this.waitForNavigation(options), + this.waitForNavigation({ + ...options, + ignoreSameDocumentNavigation: true, + }), this.#primaryTargetClient.send('Page.reload'), ]); @@ -1130,6 +1134,16 @@ export class CdpPage extends Page { await this.#emulationManager.setTransparentBackgroundColor(); } + await firstValueFrom( + from( + this.mainFrame() + .isolatedRealm() + .evaluate(() => { + return document.fonts.ready; + }) + ).pipe(raceWith(timeout(ms))) + ); + const printCommandPromise = this.#primaryTargetClient.send( 'Page.printToPDF', { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/common/CallbackRegistry.ts b/remote/test/puppeteer/packages/puppeteer-core/src/common/CallbackRegistry.ts index ea9f3d5abb..6b8379177b 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/common/CallbackRegistry.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/common/CallbackRegistry.ts @@ -31,17 +31,14 @@ export class CallbackRegistry { } catch (error) { // We still throw sync errors synchronously and clean up the scheduled // callback. - callback.promise - .valueOrThrow() - .catch(debugError) - .finally(() => { - this.#callbacks.delete(callback.id); - }); + callback.promise.catch(debugError).finally(() => { + this.#callbacks.delete(callback.id); + }); callback.reject(error as Error); throw error; } // Must only have sync code up until here. - return callback.promise.valueOrThrow().finally(() => { + return callback.promise.finally(() => { this.#callbacks.delete(callback.id); }); } @@ -148,8 +145,8 @@ export class Callback { return this.#id; } - get promise(): Deferred<unknown> { - return this.#deferred; + get promise(): Promise<unknown> { + return this.#deferred.valueOrThrow(); } get error(): ProtocolError { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/common/Configuration.ts b/remote/test/puppeteer/packages/puppeteer-core/src/common/Configuration.ts index fe71e57587..9d26cecfbb 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/common/Configuration.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/common/Configuration.ts @@ -102,7 +102,7 @@ export interface Configuration { /** * Tells Puppeteer to not chrome-headless-shell download during installation. * - * Can be overridden by `PUPPETEER_SKIP_CHROME_HEADLESSS_HELL_DOWNLOAD`. + * Can be overridden by `PUPPETEER_SKIP_CHROME_HEADLESS_SHELL_DOWNLOAD`. */ skipChromeHeadlessShellDownload?: boolean; /** diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/common/Errors.ts b/remote/test/puppeteer/packages/puppeteer-core/src/common/Errors.ts index 4d0a43ea33..46a3548cf3 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/common/Errors.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/common/Errors.ts @@ -13,8 +13,8 @@ export class PuppeteerError extends Error { /** * @internal */ - constructor(message?: string) { - super(message); + constructor(message?: string, options?: ErrorOptions) { + super(message, options); this.name = this.constructor.name; } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/common/PDFOptions.ts b/remote/test/puppeteer/packages/puppeteer-core/src/common/PDFOptions.ts index f87ec6817b..e88a94380d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/common/PDFOptions.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/common/PDFOptions.ts @@ -98,7 +98,7 @@ export interface PDFOptions { headerTemplate?: string; /** * HTML template for the print footer. Has the same constraints and support - * for special classes as {@link PDFOptions | PDFOptions.headerTemplate}. + * for special classes as {@link PDFOptions.headerTemplate}. */ footerTemplate?: string; /** diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/common/util.ts b/remote/test/puppeteer/packages/puppeteer-core/src/common/util.ts index f84453c612..44d22ae01d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/common/util.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/common/util.ts @@ -312,12 +312,12 @@ export function validateDialogType( /** * @internal */ -export function timeout(ms: number): Observable<never> { +export function timeout(ms: number, cause?: Error): Observable<never> { return ms === 0 ? NEVER : timer(ms).pipe( map(() => { - throw new TimeoutError(`Timed out after waiting ${ms}ms`); + throw new TimeoutError(`Timed out after waiting ${ms}ms`, {cause}); }) ); } diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/node/ChromeLauncher.ts b/remote/test/puppeteer/packages/puppeteer-core/src/node/ChromeLauncher.ts index 0cec3de9ae..9b71952a2e 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/node/ChromeLauncher.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/node/ChromeLauncher.ts @@ -166,6 +166,9 @@ export class ChromeLauncher extends ProductLauncher { removeMatchingFlags(options.args, '--disable-features'); } + const turnOnExperimentalFeaturesForTesting = + process.env['PUPPETEER_TEST_EXPERIMENTAL_CHROME_FEATURES'] === 'true'; + // Merge default disabled features with user-provided ones, if any. const disabledFeatures = [ 'Translate', @@ -174,9 +177,13 @@ export class ChromeLauncher extends ProductLauncher { 'MediaRouter', 'OptimizationHints', // https://crbug.com/1492053 - 'ProcessPerSiteUpToMainFrameThreshold', + turnOnExperimentalFeaturesForTesting + ? '' + : 'ProcessPerSiteUpToMainFrameThreshold', ...userDisabledFeatures, - ]; + ].filter(feature => { + return feature !== ''; + }); const userEnabledFeatures = getFeatures('--enable-features', options.args); if (options.args && userEnabledFeatures.length > 0) { @@ -185,9 +192,11 @@ export class ChromeLauncher extends ProductLauncher { // Merge default enabled features with user-provided ones, if any. const enabledFeatures = [ - 'NetworkServiceInProcess2', + // Add features to enable by default here. ...userEnabledFeatures, - ]; + ].filter(feature => { + return feature !== ''; + }); const chromeArguments = [ '--allow-pre-commit-input', @@ -201,7 +210,9 @@ export class ChromeLauncher extends ProductLauncher { '--disable-default-apps', '--disable-dev-shm-usage', '--disable-extensions', - '--disable-field-trial-config', // https://source.chromium.org/chromium/chromium/src/+/main:testing/variations/README.md + turnOnExperimentalFeaturesForTesting + ? '' + : '--disable-field-trial-config', // https://source.chromium.org/chromium/chromium/src/+/main:testing/variations/README.md '--disable-hang-monitor', '--disable-infobars', '--disable-ipc-flooding-protection', @@ -220,7 +231,9 @@ export class ChromeLauncher extends ProductLauncher { '--use-mock-keychain', `--disable-features=${disabledFeatures.join(',')}`, `--enable-features=${enabledFeatures.join(',')}`, - ]; + ].filter(arg => { + return arg !== ''; + }); const { devtools = false, headless = !devtools, diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/node/NodeWebSocketTransport.ts b/remote/test/puppeteer/packages/puppeteer-core/src/node/NodeWebSocketTransport.ts index f4ac592e4f..13f1e8349c 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/node/NodeWebSocketTransport.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/node/NodeWebSocketTransport.ts @@ -41,14 +41,18 @@ export class NodeWebSocketTransport implements ConnectionTransport { constructor(ws: NodeWebSocket) { this.#ws = ws; this.#ws.addEventListener('message', event => { - if (this.onmessage) { - this.onmessage.call(null, event.data); - } + setImmediate(() => { + if (this.onmessage) { + this.onmessage.call(null, event.data); + } + }); }); this.#ws.addEventListener('close', () => { - if (this.onclose) { - this.onclose.call(null); - } + setImmediate(() => { + if (this.onclose) { + this.onclose.call(null); + } + }); }); // Silently ignore all errors - we don't know what to do with them. this.#ws.addEventListener('error', () => {}); diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/node/ProductLauncher.ts b/remote/test/puppeteer/packages/puppeteer-core/src/node/ProductLauncher.ts index 2da07e8f7c..6f4008f23d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/node/ProductLauncher.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/node/ProductLauncher.ts @@ -98,6 +98,12 @@ export abstract class ProductLauncher { const launchArgs = await this.computeLaunchArguments(options); + if (!existsSync(launchArgs.executablePath)) { + throw new Error( + `Browser was not found at the configured executablePath (${launchArgs.executablePath})` + ); + } + const usePipe = launchArgs.args.includes('--remote-debugging-pipe'); const onProcessExit = async () => { diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/node/PuppeteerNode.ts b/remote/test/puppeteer/packages/puppeteer-core/src/node/PuppeteerNode.ts index 726ee24cbb..5a5d43bf3c 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/node/PuppeteerNode.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/node/PuppeteerNode.ts @@ -136,11 +136,11 @@ export class PuppeteerNode extends Puppeteer { * specified. * * When using with `puppeteer-core`, - * {@link LaunchOptions | options.executablePath} or - * {@link LaunchOptions | options.channel} must be provided. + * {@link LaunchOptions.executablePath | options.executablePath} or + * {@link LaunchOptions.channel | options.channel} must be provided. * * @example - * You can use {@link LaunchOptions | options.ignoreDefaultArgs} + * You can use {@link LaunchOptions.ignoreDefaultArgs | options.ignoreDefaultArgs} * to filter out `--mute-audio` from default arguments: * * ```ts diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/revisions.ts b/remote/test/puppeteer/packages/puppeteer-core/src/revisions.ts index c543cd9517..b75257ab50 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/revisions.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/revisions.ts @@ -8,7 +8,7 @@ * @internal */ export const PUPPETEER_REVISIONS = Object.freeze({ - chrome: '122.0.6261.94', - 'chrome-headless-shell': '122.0.6261.94', + chrome: '123.0.6312.122', + 'chrome-headless-shell': '123.0.6312.122', firefox: 'latest', }); diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/util/Function.ts b/remote/test/puppeteer/packages/puppeteer-core/src/util/Function.ts index 41db98830b..497a31501d 100644 --- a/remote/test/puppeteer/packages/puppeteer-core/src/util/Function.ts +++ b/remote/test/puppeteer/packages/puppeteer-core/src/util/Function.ts @@ -72,7 +72,7 @@ export const interpolateFunction = <T extends (...args: never[]) => unknown>( for (const [name, jsValue] of Object.entries(replacements)) { value = value.replace( new RegExp(`PLACEHOLDER\\(\\s*(?:'${name}'|"${name}")\\s*\\)`, 'g'), - // Wrapping this ensures tersers that accidently inline PLACEHOLDER calls + // Wrapping this ensures tersers that accidentally inline PLACEHOLDER calls // are still valid. Without, we may get calls like ()=>{...}() which is // not valid. `(${jsValue})` diff --git a/remote/test/puppeteer/packages/puppeteer/CHANGELOG.md b/remote/test/puppeteer/packages/puppeteer/CHANGELOG.md index 8f98608527..e8fa0d38b8 100644 --- a/remote/test/puppeteer/packages/puppeteer/CHANGELOG.md +++ b/remote/test/puppeteer/packages/puppeteer/CHANGELOG.md @@ -29,6 +29,121 @@ All notable changes to this project will be documented in this file. See [standa * puppeteer-core bumped from 21.0.2 to 21.0.3 * @puppeteer/browsers bumped from 1.5.1 to 1.6.0 +## [22.6.5](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.6.4...puppeteer-v22.6.5) (2024-04-15) + + +### Miscellaneous Chores + +* **puppeteer:** Synchronize puppeteer versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.6.4 to 22.6.5 + * @puppeteer/browsers bumped from 2.2.1 to 2.2.2 + +## [22.6.4](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.6.3...puppeteer-v22.6.4) (2024-04-11) + + +### Miscellaneous Chores + +* **puppeteer:** Synchronize puppeteer versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.6.3 to 22.6.4 + +## [22.6.3](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.6.2...puppeteer-v22.6.3) (2024-04-05) + + +### Bug Fixes + +* deprecate configuration via package.json ([#12176](https://github.com/puppeteer/puppeteer/issues/12176)) ([c96c762](https://github.com/puppeteer/puppeteer/commit/c96c7623bc2258ba7419812333ec42cdf83bf432)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.6.2 to 22.6.3 + * @puppeteer/browsers bumped from 2.2.0 to 2.2.1 + +## [22.6.2](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.6.1...puppeteer-v22.6.2) (2024-03-28) + + +### Miscellaneous Chores + +* **puppeteer:** Synchronize puppeteer versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.6.1 to 22.6.2 + +## [22.6.1](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.6.0...puppeteer-v22.6.1) (2024-03-25) + + +### Miscellaneous Chores + +* **puppeteer:** Synchronize puppeteer versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.6.0 to 22.6.1 + +## [22.6.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.5.0...puppeteer-v22.6.0) (2024-03-20) + + +### Features + +* roll to Chrome 123.0.6312.58 (r1262506) ([#12110](https://github.com/puppeteer/puppeteer/issues/12110)) ([6f5b3bc](https://github.com/puppeteer/puppeteer/commit/6f5b3bc9b88c6d3204dda396f8963591ea6eb883)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.5.0 to 22.6.0 + +## [22.5.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.4.1...puppeteer-v22.5.0) (2024-03-15) + + +### Miscellaneous Chores + +* **puppeteer:** Synchronize puppeteer versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.4.1 to 22.5.0 + * @puppeteer/browsers bumped from 2.1.0 to 2.2.0 + +## [22.4.1](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.4.0...puppeteer-v22.4.1) (2024-03-08) + + +### Miscellaneous Chores + +* **puppeteer:** Synchronize puppeteer versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * puppeteer-core bumped from 22.4.0 to 22.4.1 + ## [22.4.0](https://github.com/puppeteer/puppeteer/compare/puppeteer-v22.3.0...puppeteer-v22.4.0) (2024-03-05) diff --git a/remote/test/puppeteer/packages/puppeteer/package.json b/remote/test/puppeteer/packages/puppeteer/package.json index 88637f3add..6d3b385640 100644 --- a/remote/test/puppeteer/packages/puppeteer/package.json +++ b/remote/test/puppeteer/packages/puppeteer/package.json @@ -1,6 +1,6 @@ { "name": "puppeteer", - "version": "22.4.0", + "version": "22.6.5", "description": "A high-level API to control headless Chrome over the DevTools Protocol", "keywords": [ "puppeteer", @@ -124,8 +124,9 @@ "license": "Apache-2.0", "dependencies": { "cosmiconfig": "9.0.0", - "puppeteer-core": "22.4.0", - "@puppeteer/browsers": "2.1.0" + "puppeteer-core": "22.6.5", + "@puppeteer/browsers": "2.2.2", + "devtools-protocol": "0.0.1262051" }, "devDependencies": { "@types/node": "18.17.15" diff --git a/remote/test/puppeteer/packages/puppeteer/src/getConfiguration.ts b/remote/test/puppeteer/packages/puppeteer/src/getConfiguration.ts index 6fd88678a4..ddd3386a70 100644 --- a/remote/test/puppeteer/packages/puppeteer/src/getConfiguration.ts +++ b/remote/test/puppeteer/packages/puppeteer/src/getConfiguration.ts @@ -127,6 +127,17 @@ export const getConfiguration = (): Configuration => { downloadHost; } + if ( + Object.keys(process.env).some(key => { + return key.startsWith('npm_package_config_puppeteer_'); + }) && + configuration.logLevel === 'warn' + ) { + console.warn( + `Configuring Puppeteer via npm/package.json is deprecated. Use https://pptr.dev/guides/configuration instead.` + ); + } + configuration.cacheDirectory = process.env['PUPPETEER_CACHE_DIR'] ?? process.env['npm_config_puppeteer_cache_dir'] ?? diff --git a/remote/test/puppeteer/test/.eslintrc.js b/remote/test/puppeteer/test/.eslintrc.js index 5f7746a76b..ea697b0a47 100644 --- a/remote/test/puppeteer/test/.eslintrc.js +++ b/remote/test/puppeteer/test/.eslintrc.js @@ -36,6 +36,12 @@ module.exports = { selector: 'CallExpression[callee.object.name="it"] > MemberExpression > Identifier[name="deflake"], CallExpression[callee.object.name="it"] > MemberExpression > Identifier[name="deflakeOnly"]', }, + { + message: + 'No `expect` in EventHandler. They will never throw errors', + selector: + 'CallExpression[callee.property.name="on"] BlockStatement > :not(TryStatement) > ExpressionStatement > CallExpression[callee.object.callee.name="expect"]', + }, ], }, }, diff --git a/remote/test/puppeteer/test/TestExpectations.json b/remote/test/puppeteer/test/TestExpectations.json index e55f223441..9f021882f5 100644 --- a/remote/test/puppeteer/test/TestExpectations.json +++ b/remote/test/puppeteer/test/TestExpectations.json @@ -18,7 +18,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "Chrome-only feature blocked on https://github.com/GoogleChromeLabs/chromium-bidi/issues/2082" }, { "testIdPattern": "[device-request-prompt.spec] *", @@ -58,9 +58,9 @@ { "testIdPattern": "[headful.spec] *", "platforms": ["darwin", "linux", "win32"], - "parameters": ["headless", "firefox"], + "parameters": ["headless"], "expectations": ["SKIP"], - "comment": "Cannot be run in headless mode" + "comment": "Spawns headful browser, needs display or `xvfb` like which is not required for other headless tests" }, { "testIdPattern": "[idle_override.spec] *", @@ -84,20 +84,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[network.spec] network Page.authenticate *", - "platforms": ["darwin", "linux"], - "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[network.spec] network Page.authenticate *", - "platforms": ["win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[network.spec] network Page.setBypassServiceWorker *", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], @@ -112,13 +98,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[network.spec] network Page.setExtraHTTPHeaders *", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[network.spec] network Request.isNavigationRequest *", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], @@ -175,25 +154,18 @@ "comment": "Chrome-specific test" }, { - "testIdPattern": "[requestinterception-experimental.spec] *", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[requestinterception.spec] *", + "testIdPattern": "[screencast.spec] *", "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], + "parameters": ["chrome"], "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "Currently no reliable ffmpeg downloads for testing https://github.com/puppeteer/puppeteer/issues/12121" }, { "testIdPattern": "[screencast.spec] *", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "CDP-specific feature" }, { "testIdPattern": "[screenshot.spec] Screenshots Cdp *", @@ -224,20 +196,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler queryOne (Chromium web test) should find by role \"button\"", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler queryOne (Chromium web test) should find by role \"heading\"", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[autofill.spec] *", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "chrome-headless-shell"], @@ -363,14 +321,14 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[devtools.spec] DevTools target.page() should return a DevTools page if asPage is used", + "testIdPattern": "[devtools.spec] DevTools target.page() should return a DevTools page if custom isPageTarget is provided", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[devtools.spec] DevTools target.page() should return a DevTools page if custom isPageTarget is provided", + "testIdPattern": "[devtools.spec] DevTools target.page() should return Page when calling asPage on DevTools target", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], "expectations": ["SKIP"], @@ -391,13 +349,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.clickablePoint should not work if the click box is not visible due to the iframe", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should replace symbols with undefined", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp"], @@ -440,13 +391,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[frame.spec] Frame specs Frame Management should handle nested frames", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[frame.spec] Frame specs Frame Management should report frame.name()", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], @@ -482,17 +426,17 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[jshandle.spec] JSHandle Page.evaluateHandle should return the RemoteObject", + "testIdPattern": "[jshandle.spec] JSHandle JSHandle.toString should work with window subtypes", "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], + "parameters": ["cdp"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "CDP does not have special type for window" }, { - "testIdPattern": "[keyboard.spec] Keyboard should send a character with sendCharacter in iframe", + "testIdPattern": "[jshandle.spec] JSHandle Page.evaluateHandle should return the RemoteObject", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], + "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { @@ -580,6 +524,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[navigation.spec] navigation Page.goto should navigate to URL with hash and fire requests without hash", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "BiDi spec expect the request to not trim the hash" + }, + { "testIdPattern": "[navigation.spec] navigation Page.goto should send referer", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], @@ -599,11 +550,11 @@ "expectations": ["PASS"] }, { - "testIdPattern": "[network.spec] network Page.setBypassServiceWorker *", - "platforms": ["win32"], + "testIdPattern": "[network.spec] network Page.setExtraHTTPHeaders *", + "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "Firefox does not support headers override" }, { "testIdPattern": "[network.spec] network Request.initiator should return the initiator", @@ -625,6 +576,12 @@ "expectations": ["PASS"] }, { + "testIdPattern": "[network.spec] network Request.isNavigationRequest should work with request interception", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { "testIdPattern": "[network.spec] network Request.postData should be |undefined| when there is no post data", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], @@ -671,7 +628,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "See https://github.com/puppeteer/puppeteer/issues/4840" }, { "testIdPattern": "[page.spec] Page Page.addStyleTag should throw when added with content to the CSP page", @@ -681,13 +638,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[page.spec] Page Page.bringToFront should work", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[page.spec] Page Page.close should *not* run beforeunload by default", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -713,7 +663,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "BiDi does not support getting a Handle for log args" }, { "testIdPattern": "[page.spec] Page Page.Events.Console should return remote objects", @@ -744,18 +694,11 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[page.spec] Page Page.exposeFunction should work with loading frames", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "Missing request interception" - }, - { "testIdPattern": "[page.spec] Page Page.pdf should respect timeout", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "https://github.com/puppeteer/puppeteer/issues/12152" }, { "testIdPattern": "[page.spec] Page Page.setBypassCSP *", @@ -765,13 +708,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[page.spec] Page Page.setCacheEnabled should stay disabled when toggling request interception on/off", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[page.spec] Page Page.setOfflineMode should emulate navigator.onLine", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], @@ -802,6 +738,13 @@ { "testIdPattern": "[prerender.spec] Prerender can screencast", "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome"], + "expectations": ["SKIP"], + "comment": "Currently no reliable ffmpeg downloads for testing https://github.com/puppeteer/puppeteer/issues/12121" + }, + { + "testIdPattern": "[prerender.spec] Prerender can screencast", + "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" @@ -849,6 +792,27 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should navigate to URL with hash and fire requests without hash", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "BiDi spec expect the request to not trim the hash" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Request.continue *", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs full support for continueRequest in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1850680" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Request.respond *", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs full support for continueResponse in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1853887" + }, + { "testIdPattern": "[requestinterception.spec] *", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -856,6 +820,55 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be abortable with custom error codes", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should intercept", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`request.postData()` has no eqivalent in BiDi spec" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should navigate to URL with hash and fire requests without hash", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "BiDi spec and WPT require expect the Hash" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects for subresources", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Request.continue *", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs full support for continueRequest in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1850680" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Request.respond *", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs full support for continueResponse in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1853887" + }, + { "testIdPattern": "[screencast.spec] Screencasts Page.screencast should validate options", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox"], @@ -953,53 +966,74 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[bfcache.spec] BFCache can navigate to a BFCached page containing an OOPIF and a worker", + "testIdPattern": "[accessibility.spec] Accessibility should work", "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Change in A11Y tree on Canary" }, { - "testIdPattern": "[browser.spec] Browser specs Browser.isConnected should set the browser connected state", + "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler queryAllArray $$eval should handle many elements", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS", "TIMEOUT"], + "comment": "times out flakily" + }, + { + "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler queryOne (Chromium web test) should find by role \"button\"", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Querying by a11y attributes is not standard behavior" + }, + { + "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler queryOne (Chromium web test) should find by role \"heading\"", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Querying by a11y attributes is not standard behavior" + }, + { + "testIdPattern": "[bfcache.spec] BFCache can navigate to a BFCached page containing an OOPIF and a worker", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["cdp", "firefox"], "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[browser.spec] Browser specs Browser.process should not return child_process for remote browser", + "testIdPattern": "[browser.spec] Browser specs Browser.isConnected should set the browser connected state", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[browser.spec] Browser specs Browser.userAgent should include Browser engine", + "testIdPattern": "[browser.spec] Browser specs Browser.process should keep connected after the last page is closed", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "The new headless does not allow opening a tab after the browser was closed" }, { - "testIdPattern": "[browser.spec] Browser specs Browser.userAgent should include Browser engine", + "testIdPattern": "[browser.spec] Browser specs Browser.process should not return child_process for remote browser", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], + "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[browser.spec] Browser specs Browser.version should return version", + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should deny permission when not listed", "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], + "parameters": ["cdp", "firefox"], "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should deny permission when not listed", "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox"], + "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217" }, { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should fail when bad permission is given", @@ -1015,6 +1049,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should grant permission when listed", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217" + }, + { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should grant persistent-storage", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1022,6 +1063,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should grant persistent-storage", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217" + }, + { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should isolate permissions between browser contexts", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1029,6 +1077,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should isolate permissions between browser contexts", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217" + }, + { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should reset permissions", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1036,6 +1091,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should reset permissions", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217" + }, + { "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should trigger permission onchange", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1043,10 +1105,11 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[browsercontext.spec] BrowserContext should create new incognito context", + "testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should trigger permission onchange", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["PASS"] + "expectations": ["FAIL"], + "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217" }, { "testIdPattern": "[browsercontext.spec] BrowserContext should fire target events", @@ -1063,12 +1126,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[browsercontext.spec] BrowserContext should have default context", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["PASS"] - }, - { "testIdPattern": "[browsercontext.spec] BrowserContext should wait for a target", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1106,13 +1163,6 @@ { "testIdPattern": "[CDPSession.spec] Target.createCDPSession should respect custom timeout", "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[CDPSession.spec] Target.createCDPSession should respect custom timeout", - "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" @@ -1139,13 +1189,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[chromiumonly.spec] Chromium-Specific Page Tests Page.setRequestInterception should work with intervention headers", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[click.spec] Page.click should click on checkbox label and toggle", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1160,20 +1203,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[click.spec] Page.click should click the button with deviceScaleFactor set", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[click.spec] Page.click should click the button with fixed position inside an iframe", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[click.spec] Page.click should click the button with fixed position inside an iframe", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1248,7 +1277,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "Firefox default partition key is inconsistent: #12004" }, { "testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should delete cookie", @@ -1269,7 +1298,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "Firefox default partition key is inconsistent: #12004" }, { "testIdPattern": "[cookies.spec] Cookie specs Page.deleteCookie should not delete cookie for different domain", @@ -1311,7 +1340,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[cookies.spec] Cookie specs Page.setCookie should set cookie with reasonable defaults", @@ -1332,7 +1361,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[cookies.spec] Cookie specs Page.setCookie should set multiple cookies", @@ -1346,7 +1375,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[cookies.spec] Cookie specs Page.setCookie should set secure same-site cookies from a frame", @@ -1367,7 +1396,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[debugInfo.spec] DebugInfo Browser.debugInfo should work", @@ -1381,7 +1410,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL", "PASS"], - "comment": "https://github.com/puppeteer/puppeteer/issues/12010" + "comment": "Firefox CDP does not support isolation so this test might fail if other tests set cookies" }, { "testIdPattern": "[defaultbrowsercontext.spec] DefaultBrowserContext page.deleteCookie() should work", @@ -1395,7 +1424,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[defaultbrowsercontext.spec] DefaultBrowserContext page.setCookie() should work", @@ -1409,7 +1438,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884648" + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[device-request-prompt.spec] device request prompt does not crash", @@ -1594,13 +1623,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[emulation.spec] Emulation Page.viewport should load correct pictures when emulation dpr", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[emulation.spec] Emulation Page.viewport should support landscape emulation", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1622,13 +1644,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[emulation.spec] Emulation Page.viewport should update media queries when resoltion changes", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should simulate a user gesture", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -1755,6 +1770,12 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[idle_override.spec] Emulate idle state changing idle state emulation causes change of the IdleDetector state", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors Response.securityDetails Network redirects should report SecurityDetails", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], @@ -1813,25 +1834,11 @@ { "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with request interception", "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with request interception", - "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with request interception", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[input.spec] input tests FileChooser.accept should accept single file", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], @@ -2212,13 +2219,6 @@ { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Browser.disconnect should reject navigation when browser closes", "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "https://github.com/puppeteer/puppeteer/issues/11849" - }, - { - "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Browser.disconnect should reject navigation when browser closes", - "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL", "PASS"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" @@ -2228,7 +2228,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "https://github.com/puppeteer/puppeteer/issues/11849" }, { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.connect should be able to close remote browser", @@ -2350,20 +2350,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.launch can launch and close the browser", - "platforms": ["win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.launch should be able to launch Chrome", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.launch should be able to launch Firefox", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], @@ -2546,20 +2532,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[navigation.spec] navigation Page.goto should navigate to URL with hash and fire requests without hash", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[navigation.spec] navigation Page.goto should not leak listeners during navigation", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[navigation.spec] navigation Page.goto should send referer", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -2696,8 +2668,7 @@ "testIdPattern": "[network.spec] network Network Events Page.Events.RequestServedFromCache", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "expectations": ["PASS"] }, { "testIdPattern": "[network.spec] network Network Events Page.Events.Response", @@ -2742,6 +2713,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[network.spec] network Page.authenticate should allow disable authentication", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL", "PASS"], + "comment": "FAIL: The Puppeteer implementation does not expect 2 responseCompleted events (that AuthRequired triggered). PASS: Only one event on late beta (Bug 1893664)." + }, + { "testIdPattern": "[network.spec] network Page.authenticate should fail if wrong credentials", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -2756,6 +2734,13 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[network.spec] network Page.authenticate should not disable caching", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Firefox returns `fromCache: false`" + }, + { "testIdPattern": "[network.spec] network Page.authenticate should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -2763,6 +2748,20 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[network.spec] network Page.authenticate should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["FAIL", "PASS"], + "comment": "Flaky see https://github.com/puppeteer/puppeteer/issues/12253" + }, + { + "testIdPattern": "[network.spec] network Page.authenticate should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["TIMEOUT"], + "comment": "When navigating to page with authentication the command response (error) never comes without interception" + }, + { "testIdPattern": "[network.spec] network Page.setExtraHTTPHeaders should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -2847,13 +2846,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[network.spec] network Response.buffer should throw if the response does not have a body", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[network.spec] network Response.buffer should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -2890,13 +2882,6 @@ }, { "testIdPattern": "[network.spec] network Response.fromServiceWorker Response.fromServiceWorker", - "platforms": ["win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[network.spec] network Response.fromServiceWorker Response.fromServiceWorker", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["SKIP"], @@ -2931,13 +2916,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[network.spec] network Response.text should wait until response completes", - "platforms": ["win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[network.spec] network Response.text should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -2980,38 +2958,17 @@ "comment": "Failed previously and currently times out" }, { - "testIdPattern": "[oopif.spec] OOPIF should load oopif iframes with subresources and request interception", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[oopif.spec] OOPIF should load oopif iframes with subresources and request interception", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { - "testIdPattern": "[oopif.spec] OOPIF should report google.com frame", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[oopif.spec] OOPIF should report google.com frame", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], + "expectations": ["FAIL", "TIMEOUT"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { "testIdPattern": "[oopif.spec] OOPIF should support lazy OOP frames", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["FAIL"], + "expectations": ["FAIL", "TIMEOUT"], "comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=187816" }, { @@ -3082,7 +3039,7 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "chrome"], "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "See https://github.com/puppeteer/puppeteer/issues/4840" }, { "testIdPattern": "[page.spec] Page Page.addScriptTag should throw when added with content to the CSP page", @@ -3311,6 +3268,13 @@ { "testIdPattern": "[page.spec] Page Page.setCacheEnabled should stay disabled when toggling request interception on/off", "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + }, + { + "testIdPattern": "[page.spec] Page Page.setCacheEnabled should stay disabled when toggling request interception on/off", + "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" @@ -3369,14 +3333,14 @@ "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "headful"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "only works in the old headless code" }, { "testIdPattern": "[pdf.spec] Page.pdf can print to PDF with outline", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "headless"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "only works in the old headless code" }, { "testIdPattern": "[proxy.spec] request proxy in incognito browser context should proxy requests when configured at context level", @@ -3426,6 +3390,158 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should be able to fetch dataURL and fire dataURL requests", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should be able to remove headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs investigation, on Firefox the test is passing even if the origin header is not removed" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should be abortable with custom error codes", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Test relies on chrome-only error code (Firefox currently outputs NS_ERROR_ABORT)" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should cache if cache enabled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should cooperatively continue by priority", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs full support for continueRequest in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1850680" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should cooperatively respond by priority", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs full support for continueRequest in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1850680" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should intercept", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs Puppeteer support for BidiHTTPRequest.postData" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should load fonts if cache enabled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should navigate to dataURL and fire dataURL requests", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should not cache if cache disabled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should send referer", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should show custom HTTP headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work when header manipulation headers with redirect", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs investigation, on Firefox the test is passing even if headers are not actually modified" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with custom referer headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with encoded server - 2", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with equal requests", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "TODO: Needs investigation, it looks like Firefox lets the request go also to the server" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with file URLs", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for file URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1826210" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with redirects", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for BidiHTTPRequest.resourceType" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with redirects for subresources", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for BidiHTTPRequest.resourceType" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with requests without networkId", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Test requires CDP" + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Request.continue should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Request.respond should indicate already-handled if an intercept has been handled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { "testIdPattern": "[requestinterception-experimental.spec] request interception \"after each\" hook in \"request interception\"", "platforms": ["win32"], "parameters": ["cdp", "chrome"], @@ -3454,6 +3570,55 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be able to fetch dataURL and fire dataURL requests", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be able to remove headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs investigation, on Firefox the test is passing even if the origin header is not removed" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be abortable with custom error codes", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Test relies on chrome-only error code (Firefox currently outputs NS_ERROR_ABORT)" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should cache if cache enabled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should intercept", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs Puppeteer support for BidiHTTPRequest.postData" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should load fonts if cache enabled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should navigate to dataURL and fire dataURL requests", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" + }, + { "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should navigate to URL with hash and fire requests without hash", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "chrome"], @@ -3461,6 +3626,117 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should not cache if cache disabled", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for enabling cache in BiDi without CDP https://github.com/w3c/webdriver-bidi/issues/582" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should send referer", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should send referer", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should show custom HTTP headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should show custom HTTP headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work when header manipulation headers with redirect", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs investigation, on Firefox the test is passing even if headers are not actually modified" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with custom referer headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with custom referer headers", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Firefox does not support headers override" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with encoded server - 2", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for data URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1805176" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with equal requests", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "TODO: Needs investigation" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with file URLs", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for file URIs in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1826210" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for BidiHTTPRequest.resourceType" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects for subresources", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "TODO: Needs support for BidiHTTPRequest.resourceType" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with requests without networkId", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"], + "comment": "Test requires CDP" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with requests without networkId", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "CDP specific issue, maybe we can support it from BiDi+" + }, + { + "testIdPattern": "[requestinterception.spec] request interception Request.continue should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { "testIdPattern": "[screenshot.spec] Screenshots Cdp should use scale for clip", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], @@ -3789,6 +4065,12 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { + "testIdPattern": "[worker.spec] Workers should report errors", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { "testIdPattern": "[CDPSession.spec] Target.createCDPSession should send events", "platforms": ["win32"], "parameters": ["cdp", "chrome", "headless"], @@ -3810,14 +4092,14 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[devtools.spec] DevTools target.page() should return a DevTools page if asPage is used", + "testIdPattern": "[devtools.spec] DevTools target.page() should return a DevTools page if custom isPageTarget is provided", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "chrome", "chrome-headless-shell"], "expectations": ["SKIP"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[devtools.spec] DevTools target.page() should return a DevTools page if custom isPageTarget is provided", + "testIdPattern": "[devtools.spec] DevTools target.page() should return Page when calling asPage on DevTools target", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "chrome", "chrome-headless-shell"], "expectations": ["SKIP"], @@ -3845,13 +4127,6 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Puppeteer.launch userDataDir option restores preferences", - "platforms": ["win32"], - "parameters": ["firefox", "headless", "webDriverBiDi"], - "expectations": ["SKIP"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" - }, - { "testIdPattern": "[network.spec] network Network Events Page.Events.Request", "platforms": ["linux"], "parameters": ["cdp", "chrome", "headless"], @@ -3873,52 +4148,52 @@ "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be abortable", + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should be abortable with custom error codes", "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "chrome", "headful"], - "expectations": ["FAIL", "PASS"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" }, { - "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects", - "platforms": ["win32"], - "parameters": ["cdp", "chrome", "headless"], - "expectations": ["FAIL", "PASS"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should intercept", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`request.postData()` has no eqivalent in BiDi spec" }, { - "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects", - "platforms": ["win32"], - "parameters": ["cdp", "chrome", "headful"], - "expectations": ["FAIL", "PASS"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with redirects", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "headless", "webDriverBiDi"], + "expectations": ["FAIL"], + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" }, { - "testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work for an element with an offset", + "testIdPattern": "[requestinterception-experimental.spec] cooperative request interception Page.setRequestInterception should work with redirects for subresources", "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox", "headful"], + "parameters": ["chrome", "headless", "webDriverBiDi"], "expectations": ["FAIL"], - "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" + "comment": "`HTTPRequest.resourceType()` has no eqivalent in BiDi spec" }, { - "testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work for an element with an offset", + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should be abortable", "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox", "headless"], - "expectations": ["FAIL"], + "parameters": ["cdp", "chrome", "headful"], + "expectations": ["FAIL", "PASS"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work with a rotated element", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox", "headful"], - "expectations": ["FAIL"], + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects", + "platforms": ["win32"], + "parameters": ["cdp", "chrome", "headless"], + "expectations": ["FAIL", "PASS"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { - "testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work with a rotated element", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox", "headless"], - "expectations": ["FAIL"], + "testIdPattern": "[requestinterception.spec] request interception Page.setRequestInterception should work with redirects", + "platforms": ["win32"], + "parameters": ["cdp", "chrome", "headful"], + "expectations": ["FAIL", "PASS"], "comment": "TODO: add a comment explaining why this expectation is required (include links to issues)" }, { diff --git a/remote/test/puppeteer/test/TestSuites.json b/remote/test/puppeteer/test/TestSuites.json index 3c36f8d7a4..1b36a3ef4c 100644 --- a/remote/test/puppeteer/test/TestSuites.json +++ b/remote/test/puppeteer/test/TestSuites.json @@ -45,7 +45,7 @@ { "id": "chrome-bidi", "platforms": ["linux"], - "parameters": ["chrome", "chrome-headless-shell", "webDriverBiDi"], + "parameters": ["chrome", "headless", "webDriverBiDi"], "expectedLineCoverage": 56 } ], diff --git a/remote/test/puppeteer/test/golden-chrome/screenshot-element-clip.png b/remote/test/puppeteer/test/golden-chrome/screenshot-element-clip.png Binary files differnew file mode 100644 index 0000000000..609952cd3d --- /dev/null +++ b/remote/test/puppeteer/test/golden-chrome/screenshot-element-clip.png diff --git a/remote/test/puppeteer/test/golden-firefox/screenshot-element-clip.png b/remote/test/puppeteer/test/golden-firefox/screenshot-element-clip.png Binary files differnew file mode 100644 index 0000000000..609952cd3d --- /dev/null +++ b/remote/test/puppeteer/test/golden-firefox/screenshot-element-clip.png diff --git a/remote/test/puppeteer/test/installation/assets/puppeteer-core/launch.js b/remote/test/puppeteer/test/installation/assets/puppeteer-core/launch.js index 4776d7e261..b0982cdd90 100644 --- a/remote/test/puppeteer/test/installation/assets/puppeteer-core/launch.js +++ b/remote/test/puppeteer/test/installation/assets/puppeteer-core/launch.js @@ -13,7 +13,11 @@ import puppeteer from 'puppeteer-core'; executablePath: 'node', }); } catch (error) { - if (error.message.includes('Failed to launch the browser process')) { + if ( + error.message.includes( + 'Browser was not found at the configured executablePath (node)' + ) + ) { process.exit(0); } console.error(error); diff --git a/remote/test/puppeteer/test/installation/package.json b/remote/test/puppeteer/test/installation/package.json index bd21ac4b0a..17ee450391 100644 --- a/remote/test/puppeteer/test/installation/package.json +++ b/remote/test/puppeteer/test/installation/package.json @@ -44,7 +44,7 @@ "assets" ], "dependencies": { - "glob": "10.3.10", - "mocha": "10.3.0" + "glob": "10.3.12", + "mocha": "10.4.0" } } diff --git a/remote/test/puppeteer/test/installation/src/puppeteer-firefox.spec.ts b/remote/test/puppeteer/test/installation/src/puppeteer-firefox.spec.ts index b599af01dc..addaebbbd0 100644 --- a/remote/test/puppeteer/test/installation/src/puppeteer-firefox.spec.ts +++ b/remote/test/puppeteer/test/installation/src/puppeteer-firefox.spec.ts @@ -5,6 +5,8 @@ */ import assert from 'assert'; +import {spawnSync} from 'child_process'; +import {existsSync} from 'fs'; import {readdir} from 'fs/promises'; import {platform} from 'os'; import {join} from 'path'; @@ -49,3 +51,36 @@ import {readAsset} from './util.js'; }); } ); + +describe('Firefox download', () => { + configureSandbox({ + dependencies: ['@puppeteer/browsers', 'puppeteer-core', 'puppeteer'], + env: cwd => { + return { + PUPPETEER_CACHE_DIR: join(cwd, '.cache', 'puppeteer'), + PUPPETEER_SKIP_DOWNLOAD: 'true', + }; + }, + }); + + it('can download Firefox stable', async function () { + assert.ok(!existsSync(join(this.sandbox, '.cache', 'puppeteer'))); + const result = spawnSync( + 'npx', + ['puppeteer', 'browsers', 'install', 'firefox@stable'], + { + // npx is not found without the shell flag on Windows. + shell: process.platform === 'win32', + cwd: this.sandbox, + env: { + ...process.env, + PUPPETEER_CACHE_DIR: join(this.sandbox, '.cache', 'puppeteer'), + }, + } + ); + assert.strictEqual(result.status, 0); + const files = await readdir(join(this.sandbox, '.cache', 'puppeteer')); + assert.equal(files.length, 1); + assert.equal(files[0], 'firefox'); + }); +}); diff --git a/remote/test/puppeteer/test/src/ariaqueryhandler.spec.ts b/remote/test/puppeteer/test/src/ariaqueryhandler.spec.ts index 0ffb8ae6a5..4ab1df3a0a 100644 --- a/remote/test/puppeteer/test/src/ariaqueryhandler.spec.ts +++ b/remote/test/puppeteer/test/src/ariaqueryhandler.spec.ts @@ -697,20 +697,13 @@ describe('AriaQueryHandler', () => { ElementHandle<HTMLButtonElement> >; const ids = await getIds(found); - expect(ids).toEqual([ - 'node5', - 'node6', - 'node7', - 'node8', - 'node10', - 'node21', - ]); + expect(ids).toEqual(['node5', 'node6', 'node8', 'node10', 'node21']); }); it('should find by role "heading"', async () => { const {page} = await setupPage(); const found = await page.$$('aria/[role="heading"]'); const ids = await getIds(found); - expect(ids).toEqual(['shown', 'hidden', 'node11', 'node13']); + expect(ids).toEqual(['shown', 'node11', 'node13']); }); it('should find both ignored and unignored', async () => { const {page} = await setupPage(); diff --git a/remote/test/puppeteer/test/src/browser.spec.ts b/remote/test/puppeteer/test/src/browser.spec.ts index b8e0c8bb07..edfa075c4d 100644 --- a/remote/test/puppeteer/test/src/browser.spec.ts +++ b/remote/test/puppeteer/test/src/browser.spec.ts @@ -6,7 +6,7 @@ import expect from 'expect'; -import {getTestState, setupTestBrowserHooks} from './mocha-utils.js'; +import {getTestState, launch, setupTestBrowserHooks} from './mocha-utils.js'; describe('Browser specs', function () { setupTestBrowserHooks(); @@ -64,6 +64,23 @@ describe('Browser specs', function () { expect(remoteBrowser.process()).toBe(null); await remoteBrowser.disconnect(); }); + it('should keep connected after the last page is closed', async () => { + const {browser, close} = await launch({}, {createContext: false}); + try { + const pages = await browser.pages(); + await Promise.all( + pages.map(page => { + return page.close(); + }) + ); + // Verify the browser is still connected. + expect(browser.connected).toBe(true); + // Verify the browser can open a new page. + await browser.newPage(); + } finally { + await close(); + } + }); }); describe('Browser.isConnected', () => { diff --git a/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts b/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts index 887152f097..01b0009433 100644 --- a/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts +++ b/remote/test/puppeteer/test/src/cdp/CDPSession.spec.ts @@ -134,7 +134,7 @@ describe('Target.createCDPSession', function () { } ) ).rejects.toThrowError( - `Runtime.evaluate timed out. Increase the 'protocolTimeout' setting in launch/connect calls for a higher timeout if needed.` + /Increase the 'protocolTimeout' setting in launch\/connect calls for a higher timeout if needed./gi ); }); diff --git a/remote/test/puppeteer/test/src/cdp/devtools.spec.ts b/remote/test/puppeteer/test/src/cdp/devtools.spec.ts index c48b4c353b..0f9330d15f 100644 --- a/remote/test/puppeteer/test/src/cdp/devtools.spec.ts +++ b/remote/test/puppeteer/test/src/cdp/devtools.spec.ts @@ -67,9 +67,9 @@ describe('DevTools', function () { return 2 * 3; }) ).toBe(6); - expect(await browser.pages()).toContainEqual(page); + expect(await browser.pages()).toContain(page); }); - it('target.page() should return a DevTools page if asPage is used', async function () { + it('target.page() should return Page when calling asPage on DevTools target', async function () { const {puppeteer} = await getTestState({skipLaunch: true}); const originalBrowser = await launchBrowser(launchOptions); @@ -87,7 +87,8 @@ describe('DevTools', function () { return 2 * 3; }) ).toBe(6); - expect(await browser.pages()).toContainEqual(page); + // The page won't be part of browser.pages() if a custom isPageTarget is not provided + expect(await browser.pages()).not.toContain(page); }); it('should open devtools when "devtools: true" option is given', async () => { const browser = await launchBrowser( diff --git a/remote/test/puppeteer/test/src/elementhandle.spec.ts b/remote/test/puppeteer/test/src/elementhandle.spec.ts index e0f1e41878..434ac9ca40 100644 --- a/remote/test/puppeteer/test/src/elementhandle.spec.ts +++ b/remote/test/puppeteer/test/src/elementhandle.spec.ts @@ -385,8 +385,15 @@ describe('ElementHandle specs', function () { await page.setContent( `<iframe name='frame' style='position: absolute; left: -100px' srcdoc="<button style='width: 10px; height: 10px;'></button>"></iframe>` ); - const frame = await page.waitForFrame(frame => { - return frame.name() === 'frame'; + const frame = await page.waitForFrame(async frame => { + using element = await frame.frameElement(); + if (!element) { + return false; + } + const name = await element.evaluate(frame => { + return frame.name; + }); + return name === 'frame'; }); using handle = await frame.locator('button').waitHandle(); @@ -395,8 +402,15 @@ describe('ElementHandle specs', function () { await page.setContent( `<iframe name='frame2' style='position: absolute; top: -100px' srcdoc="<button style='width: 10px; height: 10px;'></button>"></iframe>` ); - const frame2 = await page.waitForFrame(frame => { - return frame.name() === 'frame2'; + const frame2 = await page.waitForFrame(async frame => { + using element = await frame.frameElement(); + if (!element) { + return false; + } + const name = await element.evaluate(frame => { + return frame.name; + }); + return name === 'frame2'; }); using handle2 = await frame2.locator('button').waitHandle(); diff --git a/remote/test/puppeteer/test/src/frame.spec.ts b/remote/test/puppeteer/test/src/frame.spec.ts index a49fb19482..758725f932 100644 --- a/remote/test/puppeteer/test/src/frame.spec.ts +++ b/remote/test/puppeteer/test/src/frame.spec.ts @@ -7,6 +7,7 @@ import expect from 'expect'; import {CDPSession} from 'puppeteer-core/internal/api/CDPSession.js'; import type {Frame} from 'puppeteer-core/internal/api/Frame.js'; +import {assert} from 'puppeteer-core/internal/util/assert.js'; import {getTestState, setupTestBrowserHooks} from './mocha-utils.js'; import { @@ -78,7 +79,7 @@ describe('Frame specs', function () { const {page, server} = await getTestState(); await page.goto(server.PREFIX + '/frames/nested-frames.html'); - expect(dumpFrames(page.mainFrame())).toEqual([ + expect(await dumpFrames(page.mainFrame())).toEqual([ 'http://localhost:<PORT>/frames/nested-frames.html', ' http://localhost:<PORT>/frames/two-frames.html (2frames)', ' http://localhost:<PORT>/frames/frame.html (uno)', @@ -232,23 +233,6 @@ describe('Frame specs', function () { expect(page.frames()).toHaveLength(2); expect(page.frames()[1]!.url()).toBe(server.EMPTY_PAGE); }); - it('should report frame.name()', async () => { - const {page, server} = await getTestState(); - - await attachFrame(page, 'theFrameId', server.EMPTY_PAGE); - await page.evaluate((url: string) => { - const frame = document.createElement('iframe'); - frame.name = 'theFrameName'; - frame.src = url; - document.body.appendChild(frame); - return new Promise(x => { - return (frame.onload = x); - }); - }, server.EMPTY_PAGE); - expect(page.frames()[0]!.name()).toBe(''); - expect(page.frames()[1]!.name()).toBe('theFrameId'); - expect(page.frames()[2]!.name()).toBe('theFrameName'); - }); it('should report frame.parent()', async () => { const {page, server} = await getTestState(); @@ -306,4 +290,35 @@ describe('Frame specs', function () { expect(page.mainFrame().client).toBeInstanceOf(CDPSession); }); }); + + describe('Frame.prototype.frameElement', function () { + it('should work', async () => { + const {page, server} = await getTestState(); + + await attachFrame(page, 'theFrameId', server.EMPTY_PAGE); + await page.evaluate((url: string) => { + const frame = document.createElement('iframe'); + frame.name = 'theFrameName'; + frame.src = url; + document.body.appendChild(frame); + return new Promise(x => { + return (frame.onload = x); + }); + }, server.EMPTY_PAGE); + using frame0 = await page.frames()[0]?.frameElement(); + assert(!frame0); + using frame1 = await page.frames()[1]?.frameElement(); + assert(frame1); + using frame2 = await page.frames()[2]?.frameElement(); + assert(frame2); + const name1 = await frame1.evaluate(frame => { + return frame.id; + }); + expect(name1).toBe('theFrameId'); + const name2 = await frame2.evaluate(frame => { + return frame.name; + }); + expect(name2).toBe('theFrameName'); + }); + }); }); diff --git a/remote/test/puppeteer/test/src/jshandle.spec.ts b/remote/test/puppeteer/test/src/jshandle.spec.ts index 28097811e4..0c5de6cde0 100644 --- a/remote/test/puppeteer/test/src/jshandle.spec.ts +++ b/remote/test/puppeteer/test/src/jshandle.spec.ts @@ -326,6 +326,16 @@ describe('JSHandle', function () { 'JSHandle@proxy' ); }); + it('should work with window subtypes', async () => { + const {page} = await getTestState(); + + expect((await page.evaluateHandle('window')).toString()).toBe( + 'JSHandle@window' + ); + expect((await page.evaluateHandle('globalThis')).toString()).toBe( + 'JSHandle@window' + ); + }); }); describe('JSHandle[Symbol.dispose]', () => { diff --git a/remote/test/puppeteer/test/src/keyboard.spec.ts b/remote/test/puppeteer/test/src/keyboard.spec.ts index 9157465242..c6cc78c68b 100644 --- a/remote/test/puppeteer/test/src/keyboard.spec.ts +++ b/remote/test/puppeteer/test/src/keyboard.spec.ts @@ -186,8 +186,15 @@ describe('Keyboard', function () { await page.setContent(` <iframe srcdoc="<iframe name='test' srcdoc='<textarea></textarea>'></iframe>"</iframe> `); - const frame = await page.waitForFrame(frame => { - return frame.name() === 'test'; + const frame = await page.waitForFrame(async frame => { + using element = await frame.frameElement(); + if (!element) { + return false; + } + const name = await element.evaluate(frame => { + return frame.name; + }); + return name === 'test'; }); await frame.focus('textarea'); diff --git a/remote/test/puppeteer/test/src/launcher.spec.ts b/remote/test/puppeteer/test/src/launcher.spec.ts index 876f8d1624..e1eefaceb1 100644 --- a/remote/test/puppeteer/test/src/launcher.spec.ts +++ b/remote/test/puppeteer/test/src/launcher.spec.ts @@ -116,6 +116,30 @@ describe('Launcher specs', function () { const {close} = await launch({}); await close(); }); + + it('can launch multiple instances without node warnings', async () => { + const instances = []; + let warning = null; + const warningHandler: NodeJS.WarningListener = w => { + return (warning = w); + }; + process.on('warning', warningHandler); + process.setMaxListeners(1); + try { + for (let i = 0; i < 2; i++) { + instances.push(launch({})); + } + await Promise.all( + (await Promise.all(instances)).map(instance => { + return instance.close(); + }) + ); + } finally { + process.setMaxListeners(10); + } + process.off('warning', warningHandler); + expect(warning).toBe(null); + }); it('should have default url when launching browser', async function () { const {browser, close} = await launch({}, {createContext: false}); try { @@ -166,7 +190,9 @@ describe('Launcher specs', function () { }).catch(error => { return (waitError = error); }); - expect(waitError.message).toContain('Failed to launch'); + expect(waitError.message).toBe( + 'Browser was not found at the configured executablePath (random-invalid-path)' + ); }); it('userDataDir option', async () => { const userDataDir = await mkdtemp(TMP_FOLDER); @@ -591,6 +617,20 @@ describe('Launcher specs', function () { }); expect(error.message).toContain('either pipe or debugging port'); }); + + it('throws an error if executable path is not valid with pipe=true', async () => { + const options = { + executablePath: '/tmp/does-not-exist', + pipe: true, + }; + let error!: Error; + await launch(options).catch(error_ => { + return (error = error_); + }); + expect(error.message).toContain( + 'Browser was not found at the configured executablePath (/tmp/does-not-exist)' + ); + }); }); describe('Puppeteer.launch', function () { @@ -793,7 +833,7 @@ describe('Launcher specs', function () { const restoredPage = pages.find(page => { return page.url() === server.PREFIX + '/frames/nested-frames.html'; })!; - expect(dumpFrames(restoredPage.mainFrame())).toEqual([ + expect(await dumpFrames(restoredPage.mainFrame())).toEqual([ 'http://localhost:<PORT>/frames/nested-frames.html', ' http://localhost:<PORT>/frames/two-frames.html (2frames)', ' http://localhost:<PORT>/frames/frame.html (uno)', diff --git a/remote/test/puppeteer/test/src/navigation.spec.ts b/remote/test/puppeteer/test/src/navigation.spec.ts index dd59c98349..927dd02cd5 100644 --- a/remote/test/puppeteer/test/src/navigation.spec.ts +++ b/remote/test/puppeteer/test/src/navigation.spec.ts @@ -112,6 +112,27 @@ describe('navigation', function () { const response = await page.goto(server.PREFIX + '/grid.html'); expect(response!.status()).toBe(200); }); + it('should work when reload causes history API in beforeunload', async () => { + const {page, server} = await getTestState(); + + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => { + window.addEventListener( + 'beforeunload', + () => { + return history.replaceState(null, 'initial', window.location.href); + }, + false + ); + }); + await page.reload(); + // Evaluate still works. + expect( + await page.evaluate(() => { + return 1; + }) + ).toBe(1); + }); it('should navigate to empty page with networkidle0', async () => { const {page, server} = await getTestState(); diff --git a/remote/test/puppeteer/test/src/network.spec.ts b/remote/test/puppeteer/test/src/network.spec.ts index c6f51a3412..9d5b28d0c3 100644 --- a/remote/test/puppeteer/test/src/network.spec.ts +++ b/remote/test/puppeteer/test/src/network.spec.ts @@ -722,7 +722,7 @@ describe('network', function () { } catch (error) { // In headful, an error is thrown instead of 401. if ( - !(error as Error).message.startsWith( + !(error as Error).message?.includes( 'net::ERR_INVALID_AUTH_CREDENTIALS' ) ) { @@ -772,7 +772,7 @@ describe('network', function () { } catch (error) { // In headful, an error is thrown instead of 401. if ( - !(error as Error).message.startsWith( + !(error as Error).message?.includes( 'net::ERR_INVALID_AUTH_CREDENTIALS' ) ) { diff --git a/remote/test/puppeteer/test/src/page.spec.ts b/remote/test/puppeteer/test/src/page.spec.ts index d83920d3ff..bc07b1d259 100644 --- a/remote/test/puppeteer/test/src/page.spec.ts +++ b/remote/test/puppeteer/test/src/page.spec.ts @@ -506,11 +506,14 @@ describe('Page', function () { console.log(1, 2, 3, globalThis); }); const log = await logPromise; - expect(log.text()).toBe('1 2 3 JSHandle@object'); + + expect(log.text()).atLeastOneToContain([ + '1 2 3 JSHandle@object', + '1 2 3 JSHandle@window', + ]); expect(log.args()).toHaveLength(4); - expect(await (await log.args()[3]!.getProperty('test')).jsonValue()).toBe( - 1 - ); + using property = await log.args()[3]!.getProperty('test'); + expect(await property.jsonValue()).toBe(1); }); it('should trigger correct Log', async () => { const {page, server, isChrome} = await getTestState(); @@ -1210,13 +1213,15 @@ describe('Page', function () { expect(result).toBe(36); await page.removeExposedFunction('compute'); - let error: Error | null = null; - await page + const error = await page .evaluate(async function () { return (globalThis as any).compute(9, 4); }) - .catch(_error => { - return (error = _error); + .then(() => { + return null; + }) + .catch(error => { + return error; }); expect(error).toBeTruthy(); }); diff --git a/remote/test/puppeteer/test/src/requestinterception-experimental.spec.ts b/remote/test/puppeteer/test/src/requestinterception-experimental.spec.ts index 966554fd5d..ce3429f0b7 100644 --- a/remote/test/puppeteer/test/src/requestinterception-experimental.spec.ts +++ b/remote/test/puppeteer/test/src/requestinterception-experimental.spec.ts @@ -23,8 +23,7 @@ describe('cooperative request interception', function () { describe('Page.setRequestInterception', function () { const expectedActions: ActionResult[] = ['abort', 'continue', 'respond']; - while (expectedActions.length > 0) { - const expectedAction = expectedActions.pop(); + for (const expectedAction of expectedActions) { it(`should cooperatively ${expectedAction} by priority`, async () => { const {page, server} = await getTestState(); @@ -94,24 +93,36 @@ describe('cooperative request interception', function () { const {page, server} = await getTestState(); await page.setRequestInterception(true); + let requestError; page.on('request', request => { if (isFavicon(request)) { void request.continue({}, 0); return; } - expect(request.url()).toContain('empty.html'); - expect(request.headers()['user-agent']).toBeTruthy(); - expect(request.method()).toBe('GET'); - expect(request.postData()).toBe(undefined); - expect(request.isNavigationRequest()).toBe(true); - expect(request.resourceType()).toBe('document'); - expect(request.frame() === page.mainFrame()).toBe(true); - expect(request.frame()!.url()).toBe('about:blank'); - void request.continue({}, 0); + try { + expect(request).toBeTruthy(); + expect(request.url()).toContain('empty.html'); + expect(request.headers()['user-agent']).toBeTruthy(); + expect(request.method()).toBe('GET'); + expect(request.postData()).toBe(undefined); + expect(request.isNavigationRequest()).toBe(true); + expect(request.resourceType()).toBe('document'); + expect(request.frame()!.url()).toBe('about:blank'); + expect(request.frame() === page.mainFrame()).toBe(true); + } catch (error) { + requestError = error; + } finally { + void request.continue({}, 0); + } }); + const response = (await page.goto(server.EMPTY_PAGE))!; - expect(response!.ok()).toBe(true); - expect(response!.remoteAddress().port).toBe(server.PORT); + if (requestError) { + throw requestError; + } + + expect(response.ok()).toBe(true); + expect(response.remoteAddress().port).toBe(server.PORT); }); // @see https://github.com/puppeteer/puppeteer/pull/3105 it('should work when POST is redirected with 302', async () => { @@ -141,16 +152,24 @@ describe('cooperative request interception', function () { server.setRedirect('/rrredirect', '/empty.html'); await page.setRequestInterception(true); + let requestError; page.on('request', request => { const headers = Object.assign({}, request.headers(), { foo: 'bar', }); void request.continue({headers}, 0); - - expect(request.continueRequestOverrides()).toEqual({headers}); + try { + expect(request.continueRequestOverrides()).toEqual({headers}); + } catch (error) { + requestError = error; + } }); // Make sure that the goto does not time out. await page.goto(server.PREFIX + '/rrredirect'); + + if (requestError) { + throw requestError; + } }); // @see https://github.com/puppeteer/puppeteer/issues/4743 it('should be able to remove headers', async () => { @@ -220,11 +239,20 @@ describe('cooperative request interception', function () { foo: 'bar', }); await page.setRequestInterception(true); + let requestError; page.on('request', request => { - expect(request.headers()['foo']).toBe('bar'); - void request.continue({}, 0); + try { + expect(request.headers()['foo']).toBe('bar'); + } catch (error) { + requestError = error; + } finally { + void request.continue({}, 0); + } }); const response = await page.goto(server.EMPTY_PAGE); + if (requestError) { + throw requestError; + } expect(response!.ok()).toBe(true); }); // @see https://github.com/puppeteer/puppeteer/issues/4337 @@ -250,11 +278,20 @@ describe('cooperative request interception', function () { await page.setExtraHTTPHeaders({referer: server.EMPTY_PAGE}); await page.setRequestInterception(true); + let requestError; page.on('request', request => { - expect(request.headers()['referer']).toBe(server.EMPTY_PAGE); - void request.continue({}, 0); + try { + expect(request.headers()['referer']).toBe(server.EMPTY_PAGE); + } catch (error) { + requestError = error; + } finally { + void request.continue({}, 0); + } }); const response = await page.goto(server.EMPTY_PAGE); + if (requestError) { + throw requestError; + } expect(response!.ok()).toBe(true); }); it('should be abortable', async () => { @@ -340,7 +377,7 @@ describe('cooperative request interception', function () { if (isChrome) { expect(error.message).toContain('net::ERR_FAILED'); } else { - expect(error.message).toContain('NS_ERROR_FAILURE'); + expect(error.message).toContain('NS_ERROR_ABORT'); } }); it('should work with redirects', async () => { @@ -947,14 +984,26 @@ describe('cooperative request interception', function () { page.on('request', request => { void request.continue(); }); + let requestError; page.on('request', request => { - expect(request.isInterceptResolutionHandled()).toBeTruthy(); + try { + expect(request.isInterceptResolutionHandled()).toBeTruthy(); + } catch (error) { + requestError = error; + } }); page.on('request', request => { const {action} = request.interceptResolutionState(); - expect(action).toBe(InterceptResolutionAction.AlreadyHandled); + try { + expect(action).toBe(InterceptResolutionAction.AlreadyHandled); + } catch (error) { + requestError = error; + } }); await page.goto(server.EMPTY_PAGE); + if (requestError) { + throw requestError; + } }); }); }); diff --git a/remote/test/puppeteer/test/src/requestinterception.spec.ts b/remote/test/puppeteer/test/src/requestinterception.spec.ts index 4b88d30a3b..2f73ae6974 100644 --- a/remote/test/puppeteer/test/src/requestinterception.spec.ts +++ b/remote/test/puppeteer/test/src/requestinterception.spec.ts @@ -22,23 +22,34 @@ describe('request interception', function () { const {page, server} = await getTestState(); await page.setRequestInterception(true); + let requestError; page.on('request', request => { if (isFavicon(request)) { void request.continue(); return; } - expect(request.url()).toContain('empty.html'); - expect(request.headers()['user-agent']).toBeTruthy(); - expect(request.headers()['accept']).toBeTruthy(); - expect(request.method()).toBe('GET'); - expect(request.postData()).toBe(undefined); - expect(request.isNavigationRequest()).toBe(true); - expect(request.resourceType()).toBe('document'); - expect(request.frame() === page.mainFrame()).toBe(true); - expect(request.frame()!.url()).toBe('about:blank'); - void request.continue(); + try { + expect(request).toBeTruthy(); + expect(request.url()).toContain('empty.html'); + expect(request.headers()['user-agent']).toBeTruthy(); + expect(request.method()).toBe('GET'); + expect(request.postData()).toBe(undefined); + expect(request.isNavigationRequest()).toBe(true); + expect(request.resourceType()).toBe('document'); + expect(request.frame()!.url()).toBe('about:blank'); + expect(request.frame() === page.mainFrame()).toBe(true); + } catch (error) { + requestError = error; + } finally { + void request.continue(); + } }); + const response = (await page.goto(server.EMPTY_PAGE))!; + if (requestError) { + throw requestError; + } + expect(response.ok()).toBe(true); expect(response.remoteAddress().port).toBe(server.PORT); }); @@ -76,7 +87,11 @@ describe('request interception', function () { }); void request.continue({headers}); }); - await page.goto(server.PREFIX + '/rrredirect'); + const [request] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.goto(server.PREFIX + '/rrredirect'), + ]); + expect(request.headers['foo']).toBe('bar'); }); // @see https://github.com/puppeteer/puppeteer/issues/4743 it('should be able to remove headers', async () => { @@ -162,11 +177,21 @@ describe('request interception', function () { foo: 'bar', }); await page.setRequestInterception(true); + let requestError; page.on('request', request => { - expect(request.headers()['foo']).toBe('bar'); - void request.continue(); + try { + expect(request.headers()['foo']).toBe('bar'); + } catch (error) { + requestError = error; + } finally { + void request.continue(); + } }); + const response = (await page.goto(server.EMPTY_PAGE))!; + if (requestError) { + throw requestError; + } expect(response.ok()).toBe(true); }); // @see https://github.com/puppeteer/puppeteer/issues/4337 @@ -192,11 +217,13 @@ describe('request interception', function () { await page.setExtraHTTPHeaders({referer: server.EMPTY_PAGE}); await page.setRequestInterception(true); - page.on('request', request => { - expect(request.headers()['referer']).toBe(server.EMPTY_PAGE); + let request!: HTTPRequest; + page.on('request', req => { + request = req; void request.continue(); }); const response = (await page.goto(server.EMPTY_PAGE))!; + expect(request.headers()['referer']).toBe(server.EMPTY_PAGE); expect(response.ok()).toBe(true); }); it('should be abortable', async () => { @@ -267,7 +294,7 @@ describe('request interception', function () { if (isChrome) { expect(error.message).toContain('net::ERR_FAILED'); } else { - expect(error.message).toContain('NS_ERROR_FAILURE'); + expect(error.message).toContain('NS_ERROR_ABORT'); } }); it('should work with redirects', async () => { @@ -493,7 +520,7 @@ describe('request interception', function () { ))!; expect(response.status()).toBe(200); }); - it('should work wit h encoded server - 2', async () => { + it('should work with encoded server - 2', async () => { const {page, server} = await getTestState(); // The requestWillBeSent will report URL as-is, whereas interception will diff --git a/remote/test/puppeteer/test/src/screenshot.spec.ts b/remote/test/puppeteer/test/src/screenshot.spec.ts index 9176d0c920..9880581217 100644 --- a/remote/test/puppeteer/test/src/screenshot.spec.ts +++ b/remote/test/puppeteer/test/src/screenshot.spec.ts @@ -393,6 +393,33 @@ describe('Screenshots', function () { await context.close(); }); + + it('should use element clip', async () => { + const {page} = await getTestState(); + + await page.setViewport({width: 500, height: 500}); + await page.setContent(` + something above + <style>div { + border: 2px solid blue; + background: green; + width: 50px; + height: 50px; + } + </style> + <div></div> + `); + using elementHandle = (await page.$('div'))!; + const screenshot = await elementHandle.screenshot({ + clip: { + x: 10, + y: 10, + width: 20, + height: 20, + }, + }); + expect(screenshot).toBeGolden('screenshot-element-clip.png'); + }); }); describe('Cdp', () => { diff --git a/remote/test/puppeteer/test/src/utils.ts b/remote/test/puppeteer/test/src/utils.ts index d1bad65a16..d0dc08c33b 100644 --- a/remote/test/puppeteer/test/src/utils.ts +++ b/remote/test/puppeteer/test/src/utils.ts @@ -112,15 +112,24 @@ export async function navigateFrame( } } -export const dumpFrames = (frame: Frame, indentation?: string): string[] => { +export const dumpFrames = async ( + frame: Frame, + indentation?: string +): Promise<string[]> => { indentation = indentation || ''; let description = frame.url().replace(/:\d{4,5}\//, ':<PORT>/'); - if (frame.name()) { - description += ' (' + frame.name() + ')'; + using element = await frame.frameElement(); + if (element) { + const nameOrId = await element.evaluate(frame => { + return frame.name || frame.id; + }); + if (nameOrId) { + description += ' (' + nameOrId + ')'; + } } const result = [indentation + description]; for (const child of frame.childFrames()) { - result.push(...dumpFrames(child, ' ' + indentation)); + result.push(...(await dumpFrames(child, ' ' + indentation))); } return result; }; diff --git a/remote/test/puppeteer/tools/analyze_issue.mjs b/remote/test/puppeteer/tools/analyze_issue.mjs index eff6a4122e..359ce93b87 100755 --- a/remote/test/puppeteer/tools/analyze_issue.mjs +++ b/remote/test/puppeteer/tools/analyze_issue.mjs @@ -36,9 +36,7 @@ const LAST_PUPPETEER_VERSION = packageJson.version; if (!LAST_PUPPETEER_VERSION) { core.setFailed('No maintained version found.'); } -const LAST_SUPPORTED_NODE_VERSION = removeVersionPrefix( - packageJson.engines.node.slice(2).trim() -); +const LAST_SUPPORTED_NODE_VERSION = packageJson.engines.node; const SUPPORTED_OSES = ['windows', 'macos', 'linux']; const SUPPORTED_PACKAGE_MANAGERS = ['yarn', 'npm', 'pnpm']; @@ -65,7 +63,7 @@ This issue has an invalid package manager version: \`${value}\`. Versions must f }, unsupportedNodeVersion(value) { return formatMessage(` -This issue has an unsupported Node.js version: \`${value}\`. Only versions above \`v${LAST_SUPPORTED_NODE_VERSION}\` are supported. Please verify the issue on a supported version of Node.js and update the form. +This issue has an unsupported Node.js version: \`${value}\`. Only versions satisfying \`${LAST_SUPPORTED_NODE_VERSION}\` are supported. Please verify the issue on a supported version of Node.js and update the form. `); }, invalidNodeVersion(value) { @@ -109,8 +107,8 @@ This issue has an invalid Puppeteer version: \`${value}\`. Versions must follow let set = () => { return void 0; }; - let j = 1; - let i = 1; + let j = 0; + let i = 0; for (; i < lines.length; ++i) { if (lines[i].startsWith('### Bug behavior')) { set(lines.slice(j, i).join('\n').trim()); @@ -211,7 +209,7 @@ This issue has an invalid Puppeteer version: \`${value}\`. Versions must follow ); core.setFailed('Invalid Node version'); } - if (semver.lt(nodeVersion, LAST_SUPPORTED_NODE_VERSION)) { + if (!semver.satisfies(nodeVersion, LAST_SUPPORTED_NODE_VERSION)) { core.setOutput( 'errorMessage', ERROR_MESSAGES.unsupportedNodeVersion(nodeVersion) diff --git a/remote/test/puppeteer/tools/docgen/package.json b/remote/test/puppeteer/tools/docgen/package.json index 82f6d4d6c4..a89323e31b 100644 --- a/remote/test/puppeteer/tools/docgen/package.json +++ b/remote/test/puppeteer/tools/docgen/package.json @@ -24,10 +24,10 @@ } }, "devDependencies": { - "@microsoft/api-extractor": "7.42.2", - "@microsoft/api-documenter": "7.23.35", - "@microsoft/api-extractor-model": "7.28.13", + "@microsoft/api-extractor": "7.43.1", + "@microsoft/api-documenter": "7.24.2", + "@microsoft/api-extractor-model": "7.28.14", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "4.0.2" + "@rushstack/node-core-library": "4.1.0" } } diff --git a/remote/test/puppeteer/tools/docgen/src/custom_markdown_documenter.ts b/remote/test/puppeteer/tools/docgen/src/custom_markdown_documenter.ts index d63a8b96ef..abf48e200c 100644 --- a/remote/test/puppeteer/tools/docgen/src/custom_markdown_documenter.ts +++ b/remote/test/puppeteer/tools/docgen/src/custom_markdown_documenter.ts @@ -69,6 +69,9 @@ import { DocSection, StandardTags, StringBuilder, + DocHtmlStartTag, + DocHtmlEndTag, + DocHtmlAttribute, type TSDocConfiguration, } from '@microsoft/tsdoc'; import { @@ -85,7 +88,7 @@ export interface IMarkdownDocumenterOptions { export class CustomMarkdownEmitter extends ApiFormatterMarkdownEmitter { protected override getEscapedText(text: string): string { - const textWithBackslashes: string = text + const textWithBackslashes = text .replace(/\\/g, '\\\\') // first replace the escape character .replace(/[*#[\]_|`~]/g, x => { return '\\' + x; @@ -98,15 +101,6 @@ export class CustomMarkdownEmitter extends ApiFormatterMarkdownEmitter { .replace(/\}/g, '}'); return textWithBackslashes; } - - protected override getTableEscapedText(text: string): string { - return text - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/\|/g, '|'); - } } /** @@ -156,12 +150,12 @@ export class MarkdownDocumenter { } private _writeApiItemPage(apiItem: ApiItem): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; - const output: DocSection = new DocSection({ - configuration: this._tsdocConfiguration, + const configuration = this._tsdocConfiguration; + const output = new DocSection({ + configuration, }); - const scopedName: string = apiItem.getScopedNameWithinPackage(); + const scopedName = apiItem.getScopedNameWithinPackage(); switch (apiItem.kind) { case ApiItemKind.Class: @@ -259,7 +253,7 @@ export class MarkdownDocumenter { new DocNoteBox({configuration: this._tsdocConfiguration}, [ new DocParagraph({configuration: this._tsdocConfiguration}, [ new DocPlainText({ - configuration: this._tsdocConfiguration, + configuration, text: 'Warning: This API is now obsolete. ', }), ]), @@ -369,11 +363,11 @@ export class MarkdownDocumenter { this._writeRemarksSection(output, apiItem); } - const filename: string = path.join( + const filename = path.join( this._outputFolder, this._getFilenameForApiItem(apiItem) ); - const stringBuilder: StringBuilder = new StringBuilder(); + const stringBuilder = new StringBuilder(); this._markdownEmitter.emit(stringBuilder, output, { contextApiItem: apiItem, @@ -382,7 +376,7 @@ export class MarkdownDocumenter { }, }); - let pageContent: string = stringBuilder.toString(); + let pageContent = stringBuilder.toString(); if (this._pluginLoader.markdownDocumenterFeature) { // Allow the plugin to customize the pageContent @@ -413,18 +407,15 @@ export class MarkdownDocumenter { output: DocSection, apiItem: ApiDeclaredItem ): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; if (apiItem instanceof ApiClass) { if (apiItem.extendsType) { - const extendsParagraph: DocParagraph = new DocParagraph( - {configuration}, - [ - new DocEmphasisSpan({configuration, bold: true}, [ - new DocPlainText({configuration, text: 'Extends: '}), - ]), - ] - ); + const extendsParagraph = new DocParagraph({configuration}, [ + new DocEmphasisSpan({configuration, bold: true}, [ + new DocPlainText({configuration, text: 'Extends: '}), + ]), + ]); this._appendExcerptWithHyperlinks( extendsParagraph, apiItem.extendsType.excerpt @@ -432,14 +423,11 @@ export class MarkdownDocumenter { output.appendNode(extendsParagraph); } if (apiItem.implementsTypes.length > 0) { - const extendsParagraph: DocParagraph = new DocParagraph( - {configuration}, - [ - new DocEmphasisSpan({configuration, bold: true}, [ - new DocPlainText({configuration, text: 'Implements: '}), - ]), - ] - ); + const extendsParagraph = new DocParagraph({configuration}, [ + new DocEmphasisSpan({configuration, bold: true}, [ + new DocPlainText({configuration, text: 'Implements: '}), + ]), + ]); let needsComma = false; for (const implementsType of apiItem.implementsTypes) { if (needsComma) { @@ -459,14 +447,11 @@ export class MarkdownDocumenter { if (apiItem instanceof ApiInterface) { if (apiItem.extendsTypes.length > 0) { - const extendsParagraph: DocParagraph = new DocParagraph( - {configuration}, - [ - new DocEmphasisSpan({configuration, bold: true}, [ - new DocPlainText({configuration, text: 'Extends: '}), - ]), - ] - ); + const extendsParagraph = new DocParagraph({configuration}, [ + new DocEmphasisSpan({configuration, bold: true}, [ + new DocPlainText({configuration, text: 'Extends: '}), + ]), + ]); let needsComma = false; for (const extendsType of apiItem.extendsTypes) { if (needsComma) { @@ -496,14 +481,11 @@ export class MarkdownDocumenter { ); }); if (refs.length > 0) { - const referencesParagraph: DocParagraph = new DocParagraph( - {configuration}, - [ - new DocEmphasisSpan({configuration, bold: true}, [ - new DocPlainText({configuration, text: 'References: '}), - ]), - ] - ); + const referencesParagraph = new DocParagraph({configuration}, [ + new DocEmphasisSpan({configuration, bold: true}, [ + new DocPlainText({configuration, text: 'References: '}), + ]), + ]); let needsComma = false; const visited = new Set<string>(); for (const ref of refs) { @@ -548,6 +530,8 @@ export class MarkdownDocumenter { } private _writeRemarksSection(output: DocSection, apiItem: ApiItem): void { + const configuration = this._tsdocConfiguration; + if (apiItem instanceof ApiDocumentedItem) { const tsdocComment: DocComment | undefined = apiItem.tsdocComment; @@ -556,7 +540,7 @@ export class MarkdownDocumenter { if (tsdocComment.remarksBlock) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Remarks', }) ); @@ -580,7 +564,7 @@ export class MarkdownDocumenter { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: heading, }) ); @@ -627,15 +611,15 @@ export class MarkdownDocumenter { * GENERATE PAGE: MODEL */ private _writeModelTable(output: DocSection, apiModel: ApiModel): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const packagesTable: DocTable = new DocTable({ + const packagesTable = new DocTable({ configuration, headerTitles: ['Package', 'Description'], }); for (const apiMember of apiModel.members) { - const row: DocTableRow = new DocTableRow({configuration}, [ + const row = new DocTableRow({configuration}, [ this._createTitleCell(apiMember), this._createDescriptionCell(apiMember), ]); @@ -651,7 +635,7 @@ export class MarkdownDocumenter { if (packagesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Packages', }) ); @@ -666,39 +650,39 @@ export class MarkdownDocumenter { output: DocSection, apiContainer: ApiPackage | ApiNamespace ): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const classesTable: DocTable = new DocTable({ + const classesTable = new DocTable({ configuration, headerTitles: ['Class', 'Description'], }); - const enumerationsTable: DocTable = new DocTable({ + const enumerationsTable = new DocTable({ configuration, headerTitles: ['Enumeration', 'Description'], }); - const functionsTable: DocTable = new DocTable({ + const functionsTable = new DocTable({ configuration, headerTitles: ['Function', 'Description'], }); - const interfacesTable: DocTable = new DocTable({ + const interfacesTable = new DocTable({ configuration, headerTitles: ['Interface', 'Description'], }); - const namespacesTable: DocTable = new DocTable({ + const namespacesTable = new DocTable({ configuration, headerTitles: ['Namespace', 'Description'], }); - const variablesTable: DocTable = new DocTable({ + const variablesTable = new DocTable({ configuration, headerTitles: ['Variable', 'Description'], }); - const typeAliasesTable: DocTable = new DocTable({ + const typeAliasesTable = new DocTable({ configuration, headerTitles: ['Type Alias', 'Description'], }); @@ -709,7 +693,7 @@ export class MarkdownDocumenter { : (apiContainer as ApiNamespace).members; for (const apiMember of apiMembers) { - const row: DocTableRow = new DocTableRow({configuration}, [ + const row = new DocTableRow({configuration}, [ this._createTitleCell(apiMember), this._createDescriptionCell(apiMember), ]); @@ -755,7 +739,7 @@ export class MarkdownDocumenter { if (classesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Classes', }) ); @@ -765,7 +749,7 @@ export class MarkdownDocumenter { if (enumerationsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Enumerations', }) ); @@ -774,7 +758,7 @@ export class MarkdownDocumenter { if (functionsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Functions', }) ); @@ -784,7 +768,7 @@ export class MarkdownDocumenter { if (interfacesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Interfaces', }) ); @@ -794,7 +778,7 @@ export class MarkdownDocumenter { if (namespacesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Namespaces', }) ); @@ -804,7 +788,7 @@ export class MarkdownDocumenter { if (variablesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Variables', }) ); @@ -814,7 +798,7 @@ export class MarkdownDocumenter { if (typeAliasesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Type Aliases', }) ); @@ -826,24 +810,24 @@ export class MarkdownDocumenter { * GENERATE PAGE: CLASS */ private _writeClassTables(output: DocSection, apiClass: ApiClass): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const eventsTable: DocTable = new DocTable({ + const eventsTable = new DocTable({ configuration, headerTitles: ['Property', 'Modifiers', 'Type', 'Description'], }); - const constructorsTable: DocTable = new DocTable({ + const constructorsTable = new DocTable({ configuration, headerTitles: ['Constructor', 'Modifiers', 'Description'], }); - const propertiesTable: DocTable = new DocTable({ + const propertiesTable = new DocTable({ configuration, headerTitles: ['Property', 'Modifiers', 'Type', 'Description'], }); - const methodsTable: DocTable = new DocTable({ + const methodsTable = new DocTable({ configuration, headerTitles: ['Method', 'Modifiers', 'Description'], }); @@ -902,7 +886,7 @@ export class MarkdownDocumenter { if (eventsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Events', }) ); @@ -912,7 +896,7 @@ export class MarkdownDocumenter { if (constructorsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Constructors', }) ); @@ -922,7 +906,7 @@ export class MarkdownDocumenter { if (propertiesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Properties', }) ); @@ -932,7 +916,7 @@ export class MarkdownDocumenter { if (methodsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Methods', }) ); @@ -944,9 +928,9 @@ export class MarkdownDocumenter { * GENERATE PAGE: ENUM */ private _writeEnumTables(output: DocSection, apiEnum: ApiEnum): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const enumMembersTable: DocTable = new DocTable({ + const enumMembersTable = new DocTable({ configuration, headerTitles: ['Member', 'Value', 'Description'], }); @@ -971,7 +955,7 @@ export class MarkdownDocumenter { if (enumMembersTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Enumeration Members', }) ); @@ -986,19 +970,19 @@ export class MarkdownDocumenter { output: DocSection, apiClass: ApiInterface ): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const eventsTable: DocTable = new DocTable({ + const eventsTable = new DocTable({ configuration, headerTitles: ['Property', 'Modifiers', 'Type', 'Description'], }); - const propertiesTable: DocTable = new DocTable({ + const propertiesTable = new DocTable({ configuration, headerTitles: ['Property', 'Modifiers', 'Type', 'Description', 'Default'], }); - const methodsTable: DocTable = new DocTable({ + const methodsTable = new DocTable({ configuration, headerTitles: ['Method', 'Description'], }); @@ -1046,7 +1030,7 @@ export class MarkdownDocumenter { if (eventsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Events', }) ); @@ -1056,7 +1040,7 @@ export class MarkdownDocumenter { if (propertiesTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Properties', }) ); @@ -1066,7 +1050,7 @@ export class MarkdownDocumenter { if (methodsTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Methods', }) ); @@ -1081,14 +1065,14 @@ export class MarkdownDocumenter { output: DocSection, apiParameterListMixin: ApiParameterListMixin ): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const parametersTable: DocTable = new DocTable({ + const parametersTable = new DocTable({ configuration, headerTitles: ['Parameter', 'Type', 'Description'], }); for (const apiParameter of apiParameterListMixin.parameters) { - const parameterDescription: DocSection = new DocSection({configuration}); + const parameterDescription = new DocSection({configuration}); if (apiParameter.isOptional) { parameterDescription.appendNodesInParagraph([ @@ -1126,7 +1110,7 @@ export class MarkdownDocumenter { if (parametersTable.rows.length > 0) { output.appendNode( new DocHeading({ - configuration: this._tsdocConfiguration, + configuration, title: 'Parameters', }) ); @@ -1161,9 +1145,9 @@ export class MarkdownDocumenter { } private _createParagraphForTypeExcerpt(excerpt: Excerpt): DocParagraph { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const paragraph: DocParagraph = new DocParagraph({configuration}); + const paragraph = new DocParagraph({configuration}); if (!excerpt.text.trim()) { paragraph.appendNode( new DocPlainText({configuration, text: '(not declared)'}) @@ -1188,13 +1172,13 @@ export class MarkdownDocumenter { docNodeContainer: DocNodeContainer, token: ExcerptToken ): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; // Markdown doesn't provide a standardized syntax for hyperlinks inside code // spans, so we will render the type expression as DocPlainText. Instead of // creating multiple DocParagraphs, we can simply discard any newlines and // let the renderer do normal word-wrapping. - const unwrappedTokenText: string = token.text.replace(/[\r\n]+/g, ' '); + const unwrappedTokenText = token.text.replace(/[\r\n]+/g, ' '); // If it's hyperlinkable, then append a DocLinkTag if (token.kind === ExcerptTokenKind.Reference && token.canonicalReference) { @@ -1226,12 +1210,23 @@ export class MarkdownDocumenter { } private _createTitleCell(apiItem: ApiItem, plain = false): DocTableCell { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const text: string = Utilities.getConciseSignature(apiItem); + const text = Utilities.getConciseSignature(apiItem); return new DocTableCell({configuration}, [ new DocParagraph({configuration}, [ + new DocHtmlStartTag({ + configuration, + name: 'span', + htmlAttributes: [ + new DocHtmlAttribute({ + configuration, + name: 'id', + value: `"${Utilities.getSafeFilenameForName(apiItem.displayName)}"`, + }), + ], + }), plain ? new DocPlainText({configuration, text}) : new DocLinkTag({ @@ -1240,6 +1235,10 @@ export class MarkdownDocumenter { linkText: text, urlDestination: this._getLinkFilenameForApiItem(apiItem), }), + new DocHtmlEndTag({ + configuration, + name: 'span', + }), ]), ]); } @@ -1254,9 +1253,9 @@ export class MarkdownDocumenter { * cast. */ private _createDescriptionCell(apiItem: ApiItem): DocTableCell { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const section: DocSection = new DocSection({configuration}); + const section = new DocSection({configuration}); if (ApiReleaseTagMixin.isBaseClassOf(apiItem)) { if (apiItem.releaseTag === ReleaseTag.Beta) { @@ -1275,6 +1274,20 @@ export class MarkdownDocumenter { section, apiItem.tsdocComment.summarySection ); + + if (apiItem.tsdocComment.deprecatedBlock) { + section.appendNode( + new DocParagraph({configuration}, [ + new DocEmphasisSpan({configuration, bold: true}, [ + new DocPlainText({configuration, text: 'Deprecated: '}), + ]), + ]) + ); + + section.appendNodes( + apiItem.tsdocComment.deprecatedBlock.content.getChildNodes() + ); + } } } @@ -1282,7 +1295,7 @@ export class MarkdownDocumenter { } private _createDefaultCell(apiItem: ApiItem): DocTableCell { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; if (apiItem instanceof ApiDocumentedItem) { const block = apiItem.tsdocComment?.customBlocks.find(block => { @@ -1300,57 +1313,56 @@ export class MarkdownDocumenter { } private _createModifiersCell(apiItem: ApiItem): DocTableCell { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; + + const section = new DocSection({configuration}); - const section: DocSection = new DocSection({configuration}); + const codes = []; if (ApiProtectedMixin.isBaseClassOf(apiItem)) { if (apiItem.isProtected) { - section.appendNode( - new DocParagraph({configuration}, [ - new DocCodeSpan({configuration, code: 'protected'}), - ]) - ); + codes.push('protected'); } } if (ApiReadonlyMixin.isBaseClassOf(apiItem)) { if (apiItem.isReadonly) { - section.appendNode( - new DocParagraph({configuration}, [ - new DocCodeSpan({configuration, code: 'readonly'}), - ]) - ); + codes.push('readonly'); } } if (ApiStaticMixin.isBaseClassOf(apiItem)) { if (apiItem.isStatic) { - section.appendNode( - new DocParagraph({configuration}, [ - new DocCodeSpan({configuration, code: 'static'}), - ]) - ); + codes.push('static'); } } if (ApiOptionalMixin.isBaseClassOf(apiItem)) { if (apiItem.isOptional) { - section.appendNode( - new DocParagraph({configuration}, [ - new DocCodeSpan({configuration, code: 'optional'}), - ]) - ); + codes.push('optional'); } } + if (apiItem instanceof ApiDocumentedItem) { + if (apiItem.tsdocComment?.deprecatedBlock) { + codes.push('deprecated'); + } + } + if (codes.length) { + section.appendNode( + new DocParagraph({configuration}, [ + new DocCodeSpan({configuration, code: codes.join(', ')}), + ]) + ); + } + return new DocTableCell({configuration}, section.nodes); } private _createPropertyTypeCell(apiItem: ApiItem): DocTableCell { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const section: DocSection = new DocSection({configuration}); + const section = new DocSection({configuration}); if (apiItem instanceof ApiPropertyItem) { section.appendNode( @@ -1362,9 +1374,9 @@ export class MarkdownDocumenter { } private _createInitializerCell(apiItem: ApiItem): DocTableCell { - const configuration: TSDocConfiguration = this._tsdocConfiguration; + const configuration = this._tsdocConfiguration; - const section: DocSection = new DocSection({configuration}); + const section = new DocSection({configuration}); if (ApiInitializerMixin.isBaseClassOf(apiItem)) { if (apiItem.initializerExcerpt) { @@ -1381,8 +1393,8 @@ export class MarkdownDocumenter { } private _writeBetaWarning(output: DocSection): void { - const configuration: TSDocConfiguration = this._tsdocConfiguration; - const betaWarning: string = + const configuration = this._tsdocConfiguration; + const betaWarning = 'This API is provided as a preview for developers and may change' + ' based on feedback that we receive. Do not use this API in a production environment.'; output.appendNode( @@ -1427,7 +1439,7 @@ export class MarkdownDocumenter { let baseName = ''; for (const hierarchyItem of apiItem.getHierarchy()) { // For overloaded methods, add a suffix such as "MyClass.myMethod_2". - let qualifiedName: string = hierarchyItem.displayName; + let qualifiedName = hierarchyItem.displayName; if (ApiParameterListMixin.isBaseClassOf(hierarchyItem)) { if (hierarchyItem.overloadIndex > 1) { // Subtract one for compatibility with earlier releases of API Documenter. @@ -1448,15 +1460,16 @@ export class MarkdownDocumenter { return baseName.slice(0, baseName.length - 1); } - private _getFilenameForApiItem(apiItem: ApiItem): string { + private _getFilenameForApiItem(apiItem: ApiItem, link = false): string { if (apiItem.kind === ApiItemKind.Package) { return 'index.md'; } let baseName = ''; + let suffix = ''; for (const hierarchyItem of apiItem.getHierarchy()) { // For overloaded methods, add a suffix such as "MyClass.myMethod_2". - let qualifiedName: string = Utilities.getSafeFilenameForName( + let qualifiedName = Utilities.getSafeFilenameForName( hierarchyItem.displayName ); if (ApiParameterListMixin.isBaseClassOf(hierarchyItem)) { @@ -1471,6 +1484,9 @@ export class MarkdownDocumenter { case ApiItemKind.Model: case ApiItemKind.EntryPoint: case ApiItemKind.EnumMember: + // Properties don't have separate pages + case ApiItemKind.Property: + case ApiItemKind.PropertySignature: break; case ApiItemKind.Package: baseName = Utilities.getSafeFilenameForName( @@ -1480,12 +1496,26 @@ export class MarkdownDocumenter { default: baseName += '.' + qualifiedName; } + + if (link) { + switch (hierarchyItem.kind) { + case ApiItemKind.Property: + case ApiItemKind.PropertySignature: + suffix = + '#' + + Utilities.getSafeFilenameForName( + PackageName.getUnscopedName(hierarchyItem.displayName) + ); + break; + } + } } - return baseName + '.md'; + + return `${baseName}.md${suffix}`; } private _getLinkFilenameForApiItem(apiItem: ApiItem): string { - return './' + this._getFilenameForApiItem(apiItem); + return './' + this._getFilenameForApiItem(apiItem, true); } private _deleteOldOutputFiles(): void { diff --git a/remote/test/puppeteer/tools/doctest/package.json b/remote/test/puppeteer/tools/doctest/package.json index 1d0adb633b..ee3be2a6ea 100644 --- a/remote/test/puppeteer/tools/doctest/package.json +++ b/remote/test/puppeteer/tools/doctest/package.json @@ -24,13 +24,13 @@ } }, "devDependencies": { - "@swc/core": "1.4.2", + "@swc/core": "1.4.13", "@types/doctrine": "0.0.9", "@types/source-map-support": "0.5.10", "@types/yargs": "17.0.32", "acorn": "8.11.3", "doctrine": "3.0.0", - "glob": "10.3.10", + "glob": "10.3.12", "pkg-dir": "8.0.0", "source-map-support": "0.5.21", "source-map": "0.7.4", diff --git a/remote/test/puppeteer/tools/eslint/package.json b/remote/test/puppeteer/tools/eslint/package.json index c7f7f4f38d..97976a37f2 100644 --- a/remote/test/puppeteer/tools/eslint/package.json +++ b/remote/test/puppeteer/tools/eslint/package.json @@ -33,6 +33,6 @@ "license": "Apache-2.0", "devDependencies": { "@prettier/sync": "0.5.1", - "@typescript-eslint/utils": "7.1.0" + "@typescript-eslint/utils": "7.6.0" } } diff --git a/remote/test/puppeteer/tools/mocha-runner/package.json b/remote/test/puppeteer/tools/mocha-runner/package.json index a817020d5e..94a31ae2e1 100644 --- a/remote/test/puppeteer/tools/mocha-runner/package.json +++ b/remote/test/puppeteer/tools/mocha-runner/package.json @@ -36,7 +36,7 @@ "devDependencies": { "@types/yargs": "17.0.32", "c8": "9.1.0", - "glob": "10.3.10", + "glob": "10.3.12", "yargs": "17.7.2", "zod": "3.22.4" } diff --git a/remote/test/puppeteer/tools/sort-test-expectations.mjs b/remote/test/puppeteer/tools/sort-test-expectations.mjs index 972d244874..a80f0cfbd2 100644 --- a/remote/test/puppeteer/tools/sort-test-expectations.mjs +++ b/remote/test/puppeteer/tools/sort-test-expectations.mjs @@ -78,14 +78,14 @@ const toBeRemoved = new Set(); for (let i = testExpectations.length - 1; i >= 0; i--) { const expectation = testExpectations[i]; const params = new Set(expectation.parameters); - const labels = new Set(expectation.expectations); + const expectations = new Set(expectation.expectations); const platforms = new Set(expectation.platforms); let foundMatch = false; for (let j = i - 1; j >= 0; j--) { const candidate = testExpectations[j]; const candidateParams = new Set(candidate.parameters); - const candidateLabels = new Set(candidate.expectations); + const candidateExpectations = new Set(candidate.expectations); const candidatePlatforms = new Set(candidate.platforms); if ( @@ -93,11 +93,11 @@ for (let i = testExpectations.length - 1; i >= 0; i--) { expectation.testIdPattern, candidate.testIdPattern ) && - isSubset(candidateParams, params) && - isSubset(candidatePlatforms, platforms) + isSubset(candidatePlatforms, platforms) && + (isSubset(params, candidateParams) || isSubset(candidateParams, params)) ) { foundMatch = true; - if (isSubset(candidateLabels, labels)) { + if (isSubset(candidateExpectations, expectations)) { console.log('removing', expectation, 'already covered by', candidate); toBeRemoved.add(expectation); } @@ -105,7 +105,7 @@ for (let i = testExpectations.length - 1; i >= 0; i--) { } } - if (!foundMatch && isSubset(new Set(['PASS']), labels)) { + if (!foundMatch && isSubset(new Set(['PASS']), expectations)) { console.log( 'removing', expectation, diff --git a/remote/test/puppeteer/tools/update_chrome_revision.mjs b/remote/test/puppeteer/tools/update_chrome_revision.mjs index 64eeef74d5..0083bc0bbb 100644 --- a/remote/test/puppeteer/tools/update_chrome_revision.mjs +++ b/remote/test/puppeteer/tools/update_chrome_revision.mjs @@ -99,6 +99,12 @@ async function updateDevToolsProtocolVersion(revision) { `"devtools-protocol": "${currentProtocol}"`, `"devtools-protocol": "${bestNewProtocol}"` ); + + await replaceInFile( + './packages/puppeteer/package.json', + `"devtools-protocol": "${currentProtocol}"`, + `"devtools-protocol": "${bestNewProtocol}"` + ); } async function updateVersionFileLastMaintained(oldVersion, newVersion) { diff --git a/remote/test/puppeteer/versions.js b/remote/test/puppeteer/versions.js index 05d8429789..ca01e45511 100644 --- a/remote/test/puppeteer/versions.js +++ b/remote/test/puppeteer/versions.js @@ -7,6 +7,12 @@ const versionsPerRelease = new Map([ // This is a mapping from Chrome version => Puppeteer version. // In Chrome roll patches, use `NEXT` for the Puppeteer version. + ['123.0.6312.122', 'v22.6.4'], + ['123.0.6312.105', 'v22.6.3'], + ['123.0.6312.86', 'v22.6.2'], + ['123.0.6312.58', 'v22.6.0'], + ['122.0.6261.128', 'v22.5.0'], + ['122.0.6261.111', 'v22.4.1'], ['122.0.6261.94', 'v22.4.0'], ['122.0.6261.69', 'v22.3.0'], ['122.0.6261.57', 'v22.2.0'], @@ -65,7 +71,7 @@ const versionsPerRelease = new Map([ ]); // Should not be more than 2 major versions behind Chrome Stable (https://chromestatus.com/roadmap). -const lastMaintainedChromeVersion = '119.0.6045.105'; +const lastMaintainedChromeVersion = '120.0.6099.109'; if (!versionsPerRelease.has(lastMaintainedChromeVersion)) { throw new Error( |