diff --git a/remote/puppeteer-expected.json b/remote/puppeteer-expected.json index 7b4842daf2096..f4becc45e196f 100644 --- a/remote/puppeteer-expected.json +++ b/remote/puppeteer-expected.json @@ -332,6 +332,12 @@ "ElementHandle specs Custom queries should wait correctly with waitFor (elementhandle.spec.ts)": [ "PASS" ], + "ElementHandle specs Custom queries should work when both queryOne and queryAll are registered (elementhandle.spec.ts)": [ + "PASS" + ], + "ElementHandle specs Custom queries should eval when both queryOne and queryAll are registered (elementhandle.spec.ts)": [ + "PASS" + ], "Emulation Page.viewport should get the proper viewport size (emulation.spec.ts)": [ "PASS" ], @@ -632,6 +638,12 @@ "Frame specs Frame Management should report different frame instance when frame re-attaches (frame.spec.ts)": [ "PASS" ], + "Frame specs Frame Management should support url fragment (frame.spec.ts)": [ + "PASS" + ], + "Emulate idle state changing idle state emulation causes change of the IdleDetector state (idle_override.spec.ts)": [ + "FAIL" + ], "ignoreHTTPSErrors should work (ignorehttpserrors.spec.ts)": [ "PASS" ], @@ -929,6 +941,9 @@ "Mouse should set modifier keys on click (mouse.spec.ts)": [ "PASS" ], + "Mouse should send mouse wheel events (mouse.spec.ts)": [ + "FAIL" + ], "Mouse should tween mouse movement (mouse.spec.ts)": [ "PASS", "FAIL" ], @@ -1211,9 +1226,6 @@ "Page Page.Events.Load should fire when expected (page.spec.ts)": [ "PASS" ], - "Page Async stacks should work (page.spec.ts)": [ - "TIMEOUT" - ], "Page removing and adding event handlers should correctly fire event handlers as they are added and then removed (page.spec.ts)": [ "FAIL" ], @@ -1295,7 +1307,7 @@ "Page Page.Events.Console should have location when fetch fails (page.spec.ts)": [ "FAIL" ], - "Page Page.Events.Console should have location for console API calls (page.spec.ts)": [ + "Page Page.Events.Console should have location and stack trace for console API calls (page.spec.ts)": [ "FAIL" ], "Page Page.Events.Console should not throw when there are console messages in detached iframes (page.spec.ts)": [ @@ -1565,9 +1577,24 @@ "querySelector Page.$eval should throw error if no element is found (queryselector.spec.ts)": [ "PASS" ], + "querySelector pierceHandler should find first element in shadow (queryselector.spec.ts)": [ + "PASS" + ], + "querySelector pierceHandler should find all elements in shadow (queryselector.spec.ts)": [ + "PASS" + ], "querySelector Page.$$eval should work (queryselector.spec.ts)": [ "PASS" ], + "querySelector Page.$$eval should accept extra arguments (queryselector.spec.ts)": [ + "PASS" + ], + "querySelector Page.$$eval should accept ElementHandles as arguments (queryselector.spec.ts)": [ + "PASS" + ], + "querySelector Page.$$eval should handle many elements (queryselector.spec.ts)": [ + "PASS" + ], "querySelector Page.$ should query existing element (queryselector.spec.ts)": [ "PASS" ], @@ -1625,6 +1652,27 @@ "querySelector ElementHandle.$x should return null for non-existing element (queryselector.spec.ts)": [ "PASS" ], + "querySelector QueryAll should have registered handler (queryselector.spec.ts)": [ + "PASS" + ], + "querySelector QueryAll $$ should query existing elements (queryselector.spec.ts)": [ + "PASS" + ], + "querySelector QueryAll $$ should return empty array for non-existing elements (queryselector.spec.ts)": [ + "PASS" + ], + "querySelector QueryAll $$eval should work (queryselector.spec.ts)": [ + "PASS" + ], + "querySelector QueryAll $$eval should accept extra arguments (queryselector.spec.ts)": [ + "PASS" + ], + "querySelector QueryAll $$eval should accept ElementHandles as arguments (queryselector.spec.ts)": [ + "PASS" + ], + "querySelector QueryAll $$eval should handle many elements (queryselector.spec.ts)": [ + "PASS" + ], "request interception Page.setRequestInterception should intercept (requestinterception.spec.ts)": [ "FAIL" ], @@ -1685,7 +1733,7 @@ "request interception Page.setRequestInterception should be able to fetch dataURL and fire dataURL requests (requestinterception.spec.ts)": [ "FAIL" ], - "request interception Page.setRequestInterception should navigate to URL with hash and and fire requests without hash (requestinterception.spec.ts)": [ + "request interception Page.setRequestInterception should navigate to URL with hash and fire requests without hash (requestinterception.spec.ts)": [ "FAIL" ], "request interception Page.setRequestInterception should work with encoded server (requestinterception.spec.ts)": [ @@ -1868,6 +1916,9 @@ "waittask specs Page.waitFor should wait for predicate with arguments (waittask.spec.ts)": [ "PASS" ], + "waittask specs Page.waitFor should log a deprecation warning (waittask.spec.ts)": [ + "PASS" + ], "waittask specs Frame.waitForFunction should accept a string (waittask.spec.ts)": [ "PASS" ], @@ -1925,6 +1976,12 @@ "waittask specs Frame.waitForFunction should survive navigations (waittask.spec.ts)": [ "PASS" ], + "waittask specs Page.waitForTimeout waits for the given timeout before resolving (waittask.spec.ts)": [ + "PASS" + ], + "waittask specs Frame.waitForTimeout waits for the given timeout before resolving (waittask.spec.ts)": [ + "PASS" + ], "waittask specs Frame.waitForSelector should immediately resolve promise if node exists (waittask.spec.ts)": [ "PASS" ], diff --git a/remote/test/puppeteer/.eslintignore b/remote/test/puppeteer/.eslintignore index 54c6043ef961a..f4c17fcc0f25e 100644 --- a/remote/test/puppeteer/.eslintignore +++ b/remote/test/puppeteer/.eslintignore @@ -6,9 +6,9 @@ node6/* node6-test/* experimental/ lib/ -src/externs.d.ts -src/protocol.d.ts /index.d.ts # We ignore this file because it uses ES imports which we don't yet use # in the Puppeteer src, so it trips up the ESLint-TypeScript parser. utils/doclint/generate_types/test/test.ts +vendor/ +web-test-runner.config.mjs diff --git a/remote/test/puppeteer/.eslintrc.js b/remote/test/puppeteer/.eslintrc.js index 32b5d16114941..a1de9853eeffd 100644 --- a/remote/test/puppeteer/.eslintrc.js +++ b/remote/test/puppeteer/.eslintrc.js @@ -1,118 +1,183 @@ module.exports = { - "root": true, - "env": { - "node": true, - "es6": true - }, + root: true, + env: { + node: true, + es6: true, + }, - "parser": "@typescript-eslint/parser", + parser: '@typescript-eslint/parser', - "plugins": [ - "mocha", - "@typescript-eslint", - "unicorn" - ], + plugins: ['mocha', '@typescript-eslint', 'unicorn', 'import'], + + extends: ['plugin:prettier/recommended'], - "extends": [ - "plugin:prettier/recommended" + rules: { + // Error if files are not formatted with Prettier correctly. + 'prettier/prettier': 2, + // syntax preferences + quotes: [ + 2, + 'single', + { + avoidEscape: true, + allowTemplateLiterals: true, + }, + ], + 'spaced-comment': [ + 2, + 'always', + { + markers: ['*'], + }, + ], + eqeqeq: [2], + 'accessor-pairs': [ + 2, + { + getWithoutSet: false, + setWithoutGet: false, + }, ], + 'new-parens': 2, + 'func-call-spacing': 2, + 'prefer-const': 2, - "rules": { - // Error if files are not formatted with Prettier correctly. - "prettier/prettier": 2, - // syntax preferences - "quotes": [2, "single", { - "avoidEscape": true, - "allowTemplateLiterals": true - }], - "spaced-comment": [2, "always", { - "markers": ["*"] - }], - "eqeqeq": [2], - "accessor-pairs": [2, { - "getWithoutSet": false, - "setWithoutGet": false - }], - "new-parens": 2, - "func-call-spacing": 2, - "prefer-const": 2, + 'max-len': [ + 2, + { + /* this setting doesn't impact things as we use Prettier to format + * our code and hence dictate the line length. + * Prettier aims for 80 but sometimes makes the decision to go just + * over 80 chars as it decides that's better than wrapping. ESLint's + * rule defaults to 80 but therefore conflicts with Prettier. So we + * set it to something far higher than Prettier would allow to avoid + * it causing issues and conflicting with Prettier. + */ + code: 200, + comments: 90, + ignoreTemplateLiterals: true, + ignoreUrls: true, + ignoreStrings: true, + ignoreRegExpLiterals: true, + }, + ], + // anti-patterns + 'no-var': 2, + 'no-with': 2, + 'no-multi-str': 2, + 'no-caller': 2, + 'no-implied-eval': 2, + 'no-labels': 2, + 'no-new-object': 2, + 'no-octal-escape': 2, + 'no-self-compare': 2, + 'no-shadow-restricted-names': 2, + 'no-cond-assign': 2, + 'no-debugger': 2, + 'no-dupe-keys': 2, + 'no-duplicate-case': 2, + 'no-empty-character-class': 2, + 'no-unreachable': 2, + 'no-unsafe-negation': 2, + radix: 2, + 'valid-typeof': 2, + 'no-unused-vars': [ + 2, + { + args: 'none', + vars: 'local', + varsIgnorePattern: + '([fx]?describe|[fx]?it|beforeAll|beforeEach|afterAll|afterEach)', + }, + ], + 'no-implicit-globals': [2], - "max-len": [2, { - /* this setting doesn't impact things as we use Prettier to format - * our code and hence dictate the line length. - * Prettier aims for 80 but sometimes makes the decision to go just - * over 80 chars as it decides that's better than wrapping. ESLint's - * rule defaults to 80 but therefore conflicts with Prettier. So we - * set it to something far higher than Prettier would allow to avoid - * it causing issues and conflicting with Prettier. - */ - "code": 200, - "comments": 90, - "ignoreTemplateLiterals": true, - "ignoreUrls": true, - "ignoreStrings": true, - "ignoreRegExpLiterals": true - }], - // anti-patterns - "no-var": 2, - "no-with": 2, - "no-multi-str": 2, - "no-caller": 2, - "no-implied-eval": 2, - "no-labels": 2, - "no-new-object": 2, - "no-octal-escape": 2, - "no-self-compare": 2, - "no-shadow-restricted-names": 2, - "no-cond-assign": 2, - "no-debugger": 2, - "no-dupe-keys": 2, - "no-duplicate-case": 2, - "no-empty-character-class": 2, - "no-unreachable": 2, - "no-unsafe-negation": 2, - "radix": 2, - "valid-typeof": 2, - "no-unused-vars": [2, { "args": "none", "vars": "local", "varsIgnorePattern": "([fx]?describe|[fx]?it|beforeAll|beforeEach|afterAll|afterEach)" }], - "no-implicit-globals": [2], + // es2015 features + 'require-yield': 2, + 'template-curly-spacing': [2, 'never'], - // es2015 features - "require-yield": 2, - "template-curly-spacing": [2, "never"], + // ensure we don't have any it.only or describe.only in prod + 'mocha/no-exclusive-tests': 'error', - // ensure we don't have any it.only or describe.only in prod - "mocha/no-exclusive-tests": "error", + // enforce the variable in a catch block is named error + 'unicorn/catch-error-name': 'error', - // enforce the variable in a catch block is named error - "unicorn/catch-error-name": "error" + 'no-restricted-imports': [ + 'error', + { + patterns: ['*Events'], + paths: [ + { + name: 'mitt', + message: + 'Import Mitt from the vendored location: vendor/mitt/src/index.js', + }, + ], + }, + ], + 'import/extensions': ['error', 'ignorePackages'], + }, + overrides: [ + { + files: ['*.ts'], + extends: [ + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + ], + rules: { + 'no-unused-vars': 0, + '@typescript-eslint/no-unused-vars': 2, + 'func-call-spacing': 0, + '@typescript-eslint/func-call-spacing': 2, + semi: 0, + '@typescript-eslint/semi': 2, + '@typescript-eslint/no-empty-function': 0, + '@typescript-eslint/no-use-before-define': 0, + // We have to use any on some types so the warning isn't valuable. + '@typescript-eslint/no-explicit-any': 0, + // We don't require explicit return types on basic functions or + // dummy functions in tests, for example + '@typescript-eslint/explicit-function-return-type': 0, + // We know it's bad and use it very sparingly but it's needed :( + '@typescript-eslint/ban-ts-ignore': 0, + /** + * This is the default options (as per + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md), + * + * Unfortunately there's no way to + */ + '@typescript-eslint/ban-types': [ + 'error', + { + extendDefaults: true, + types: { + /* + * Puppeteer's API accepts generic functions in many places so it's + * not a useful linting rule to ban the `Function` type. This turns off + * the banning of the `Function` type which is a default rule. + */ + Function: false, + }, + }, + ], + '@typescript-eslint/array-type': [ + 2, + { + default: 'array-simple', + }, + ], + }, + }, + { + files: ['test-browser/**/*.js'], + parserOptions: { + sourceType: 'module', + }, + env: { + es6: true, + browser: true, + es2020: true, + }, }, - "overrides": [ - { - "files": ["*.ts"], - "extends": [ - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended', - ], - "rules": { - "no-unused-vars": 0, - "@typescript-eslint/no-unused-vars": 2, - "func-call-spacing": 0, - "@typescript-eslint/func-call-spacing": 2, - "semi": 0, - "@typescript-eslint/semi": 2, - "@typescript-eslint/no-empty-function": 0, - "@typescript-eslint/no-use-before-define": 0, - // We have to use any on some types so the warning isn't valuable. - "@typescript-eslint/no-explicit-any": 0, - // We don't require explicit return types on basic functions or - // dummy functions in tests, for example - "@typescript-eslint/explicit-function-return-type": 0, - // We know it's bad and use it very sparingly but it's needed :( - "@typescript-eslint/ban-ts-ignore": 0, - "@typescript-eslint/array-type": [2, { - "default": "array-simple" - }] - } - } - ] + ], }; diff --git a/remote/test/puppeteer/.github/workflows/publish-on-tag.yml b/remote/test/puppeteer/.github/workflows/publish-on-tag.yml new file mode 100644 index 0000000000000..b16c1be3fb57b --- /dev/null +++ b/remote/test/puppeteer/.github/workflows/publish-on-tag.yml @@ -0,0 +1,30 @@ +name: publish-on-tag + +on: + push: + tags: + - '*' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install dependencies + run: npm install + - name: Build + run: npm run build + - name: Publish puppeteer + env: + NPM_TOKEN: ${{secrets.NPM_TOKEN_PUPPETEER}} + run: | + npm config set '//wombat-dressing-room.appspot.com/:_authToken' '${NPM_TOKEN}' + npm publish + - name: Publish puppeteer-core + env: + NPM_TOKEN: ${{secrets.NPM_TOKEN_PUPPETEER_CORE}} + run: | + utils/prepare_puppeteer_core.js + npm config set '//wombat-dressing-room.appspot.com/:_authToken' '${NPM_TOKEN}' + npm publish diff --git a/remote/test/puppeteer/.npmrc b/remote/test/puppeteer/.npmrc new file mode 100644 index 0000000000000..f4335b67c183f --- /dev/null +++ b/remote/test/puppeteer/.npmrc @@ -0,0 +1,2 @@ +registry=https://wombat-dressing-room.appspot.com/ +access=public diff --git a/remote/test/puppeteer/.travis.yml b/remote/test/puppeteer/.travis.yml index 8da18b9a8579a..3dccb9acedfca 100644 --- a/remote/test/puppeteer/.travis.yml +++ b/remote/test/puppeteer/.travis.yml @@ -1,11 +1,17 @@ language: node_js services: xvfb +# Throughout this file, the following `node_js` versions are being used: +# +# - node_js: '10' # The maintenance LTS version. +# - node_js: '12' # The oldest major active LTS version. +# - node_js: '14' # The newest major active LTS version. + jobs: include: - - os: "osx" + - os: 'osx' name: 'Unit tests: macOS/Chromium' - node_js: "10.19.0" + node_js: '10' # The maintenance LTS version. osx_image: xcode11.4 env: - CHROMIUM=true @@ -14,11 +20,11 @@ jobs: script: - ls .local-chromium .local-firefox - npm run tsc - - travis_retry npm run unit + - npm run unit - - os: "windows" + - os: 'windows' name: 'Unit tests: Windows/Chromium' - node_js: "10.19.0" + node_js: '10' # The maintenance LTS version. env: - CHROMIUM=true before_install: @@ -28,8 +34,21 @@ jobs: - npm run tsc - travis_retry npm run unit - # Runs unit tests on Linux + Chromium - - node_js: "10.19.0" + # Node <10.17's fs.promises module was experimental and doesn't behave as + # expected. This problem was fixed in Node 10.19, but we run the unit tests + # through on 10.15 to make sure we don't cause any regressions when using + # fs.promises. See https://github.com/puppeteer/puppeteer/issues/6548 for an + # example. + - node_js: '10.15.0' + name: 'Node 10.15 Unit tests: Linux/Chromium' + env: + - CHROMIUM=true + before_install: + - PUPPETEER_PRODUCT=firefox npm install + script: + - npm run unit + + - node_js: '10' # The maintenance LTS version. name: 'Unit tests [with coverage]: Linux/Chromium' env: - CHROMIUM=true @@ -39,53 +58,61 @@ jobs: - travis_retry npm run unit-with-coverage - npm run assert-unit-coverage - - node_js: "12.16.3" + - node_js: '12' # The oldest major active LTS version. name: 'Unit tests [Node 12]: Linux/Chromium' env: - CHROMIUM=true before_install: - PUPPETEER_PRODUCT=firefox npm install script: - - travis_retry npm run unit + - npm run unit - - node_js: "14.2.0" + - node_js: '14' # The newest major active LTS version. name: 'Unit tests [Node 14]: Linux/Chromium' env: - CHROMIUM=true before_install: - PUPPETEER_PRODUCT=firefox npm install script: - - travis_retry npm run unit + - npm run unit + + - node_js: '12' # The oldest major active LTS version. + name: 'Browser tests: Linux/Chromium' + addons: + chrome: stable + env: + - CHROMIUM=true + script: + - npm run test-browser - # This bot runs all the extra checks that aren't the main Puppeteer unit tests - - node_js: "10.19.0" + # This bot runs all the extra checks that aren't the main Puppeteer unit tests. + - node_js: '10' # The maintenance LTS version. name: 'Extra tests: Linux/Chromium' env: - CHROMIUM=true script: - - npm run compare-protocol-d-ts - npm run lint - - npm run test-doclint - - npm run ensure-new-docs-up-to-date + # Ensure that we can generate the new docs without erroring + - npm run generate-docs + - npm run ensure-correct-devtools-protocol-revision # This bot runs separately as it changes package.json to test puppeteer-core # and we don't want that leaking into other bots and causing issues. - - node_js: "10.19.0" + - node_js: '10' # The maintenance LTS version. name: 'Test bundling and install of packages' env: - CHROMIUM=true script: - npm run test-install - # Runs unit tests on Linux + Firefox - - node_js: "10.19.0" + - node_js: '10' # The maintenance LTS version. name: 'Unit tests: Linux/Firefox' env: - FIREFOX=true before_install: - PUPPETEER_PRODUCT=firefox npm install script: - - travis_retry npm run funit + - npm run funit notifications: email: false diff --git a/remote/test/puppeteer/.versionrc b/remote/test/puppeteer/.versionrc new file mode 100644 index 0000000000000..a0247ed4aba84 --- /dev/null +++ b/remote/test/puppeteer/.versionrc @@ -0,0 +1,3 @@ +{ + "releaseCommitMessageFormat": "chore(release): mark v{{currentTag}}" +} diff --git a/remote/test/puppeteer/CHANGELOG.md b/remote/test/puppeteer/CHANGELOG.md new file mode 100644 index 0000000000000..35e3a06ddbcb1 --- /dev/null +++ b/remote/test/puppeteer/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [5.5.0](https://github.com/puppeteer/puppeteer/compare/v5.4.1...v5.5.0) (2020-11-16) + + +### Features + +* **chromium:** roll Chromium to r818858 ([#6526](https://github.com/puppeteer/puppeteer/issues/6526)) ([b549256](https://github.com/puppeteer/puppeteer/commit/b54925695200cad32f470f8eb407259606447a85)) + + +### Bug Fixes + +* **common:** fix generic type of `_isClosedPromise` ([#6579](https://github.com/puppeteer/puppeteer/issues/6579)) ([122f074](https://github.com/puppeteer/puppeteer/commit/122f074f92f47a7b9aa08091851e51a07632d23b)) +* **domworld:** fix missing binding for waittasks ([#6562](https://github.com/puppeteer/puppeteer/issues/6562)) ([67da1cf](https://github.com/puppeteer/puppeteer/commit/67da1cf866703f5f581c9cce4923697ac38129ef)) + +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. diff --git a/remote/test/puppeteer/CONTRIBUTING.md b/remote/test/puppeteer/CONTRIBUTING.md index 5cf0d56fce334..54bfb306eda6f 100644 --- a/remote/test/puppeteer/CONTRIBUTING.md +++ b/remote/test/puppeteer/CONTRIBUTING.md @@ -5,6 +5,10 @@ * [Code reviews](#code-reviews) * [Code Style](#code-style) * [TypeScript guidelines](#typescript-guidelines) + * [Project structure and TypeScript compilation](#project-structure-and-typescript-compilation) + - [Shipping CJS and ESM bundles](#shipping-cjs-and-esm-bundles) + - [tsconfig for the tests](#tsconfig-for-the-tests) + - [Root `tsconfig.json`](#root-tsconfigjson) * [API guidelines](#api-guidelines) * [Commit Messages](#commit-messages) * [Writing Documentation](#writing-documentation) @@ -13,8 +17,9 @@ * [Public API Coverage](#public-api-coverage) * [Debugging Puppeteer](#debugging-puppeteer) - [For Project Maintainers](#for-project-maintainers) + * [Rolling new Chromium version](#rolling-new-chromium-version) + - [Bisecting upstream changes](#bisecting-upstream-changes) * [Releasing to npm](#releasing-to-npm) - * [Updating npm dist tags](#updating-npm-dist-tags) # How to Contribute @@ -85,6 +90,42 @@ npm run tsc - Try to avoid the use of `any` when possible. Consider `unknown` as a better alternative. You are able to use `any` if needbe, but it will generate an ESLint warning. +## Project structure and TypeScript compilation + +The code in Puppeteer is split primarily into two folders: + +- `src` contains all source code +- `vendor` contains all dependencies that we've vendored into the codebase. See the [`vendor/README.md`](https://github.com/puppeteer/puppeteer/blob/main/vendor/README.md) for details. + +We structure these using TypeScript's project references, which lets us treat each folder like a standalone TypeScript project. + +### Shipping CJS and ESM bundles + +Currently Puppeteer ships two bundles; a CommonJS version for Node and an ESM bundle for the browser. Therefore we maintain two `tsconfig` files for each project; `tsconfig.esm.json` and `tsconfig.cjs.json`. At build time we compile twice, once outputting to CJS and another time to output to ESM. + +We compile into the `lib` directory which is what we publish on the npm repository and it's structured like so: + +``` +lib +- cjs + - puppeteer <== the output of compiling `src/tsconfig.cjs.json` + - vendor <== the output of compiling `vendor/tsconfig.cjs.json` +- esm + - puppeteer <== the output of compiling `src/tsconfig.esm.json` + - vendor <== the output of compiling `vendor/tsconfig.esm.json` +``` + +The main entry point for the Node module Puppeteer is `cjs-entry.js`. This imports `lib/cjs/puppeteer/index.js` and exposes it to Node users. + +### tsconfig for the tests + +We also maintain `test/tsconfig.test.json`. This is **only used to compile the unit test `*.spec.ts` files**. When the tests are run, we first compile Puppeteer as normal before running the unit tests **against the compiled output**. Doing this lets the test run against the compiled code we ship to users so it gives us more confidence in our compiled output being correct. + +### Root `tsconfig.json` + +The root `tsconfig.json` exists for the API Extractor; it has to find a `tsconfig.json` in the project's root directory. It is _not_ used for anything else. + + ## API guidelines When authoring new API methods, consider the following: @@ -97,40 +138,19 @@ When authoring new API methods, consider the following: ## Commit Messages -Commit messages should follow the Semantic Commit Messages format: - -``` -label(namespace): title +Commit messages should follow [the Conventional Commits format](https://www.conventionalcommits.org/en/v1.0.0/#summary). This is enforced via `npm run lint`. -description - -footer -``` - -1. *label* is one of the following: - - `fix` - puppeteer bug fixes. - - `feat` - puppeteer features. - - `docs` - changes to docs, e.g. `docs(api.md): ..` to change documentation. - - `test` - changes to puppeteer tests infrastructure. - - `style` - puppeteer code style: spaces/alignment/wrapping etc. - - `chore` - build-related work, e.g. doclint changes / travis / appveyor. -2. *namespace* is put in parenthesis after label and is optional. Must be lowercase. -3. *title* is a brief summary of changes. -4. *description* is **optional**, new-line separated from title and is in present tense. -5. *footer* is **optional**, new-line separated from *description* and contains "fixes" / "references" attribution to github issues. -6. *footer* should also include "BREAKING CHANGE" if current API clients will break due to this change. It should explain what changed and how to get the old behavior. - -Example: +In particular, breaking changes should clearly be noted as “BREAKING CHANGE:” in the commit message footer. Example: ``` fix(page): fix page.pizza method This patch fixes page.pizza so that it works with iframes. -Fixes #123, Fixes #234 +Issues: #123, #234 BREAKING CHANGE: page.pizza now delivers pizza at home by default. -To deliver to a different location, use "deliver" option: +To deliver to a different location, use the "deliver" option: `page.pizza({deliver: 'work'})`. ``` @@ -153,6 +173,9 @@ For all dependencies (both installation and development): A barrier for introducing new installation dependencies is especially high: - **Do not add** installation dependency unless it's critical to project success. +There are additional considerations for dependencies that are environment agonistic. See the [`vendor/README.md`](https://github.com/puppeteer/puppeteer/blob/main/vendor/README.md) for details. + + ## Running & Writing Tests - Every feature should be accompanied by a test. @@ -226,47 +249,42 @@ See [Debugging Tips](README.md#debugging-tips) in the readme. # For Project Maintainers +## Rolling new Chromium version + +The following steps are needed to update the Chromium version. + +1. Find a suitable Chromium revision + Not all revisions have builds for all platforms, so we need to find one that does. + To do so, run `utils/check_availability.js -rb` to find the latest suitable beta Chromium revision (see `utils/check_availability.js -help` for more options). +1. Update `src/revisions.ts` with the found revision number. +1. Run `npm run ensure-correct-devtools-protocol-revision`. + If it fails, update `package.json` with the expected `devtools-protocol` version. +1. Run `npm run tsc` and `npm install` and ensure that all tests pass. If a test fails, [bisect](#bisecting-upstream-changes) the upstream cause of the failure, and either update the test expectations accordingly (if it was an intended change) or work around the changes in Puppeteer (if it’s not desirable to change Puppeteer’s observable behavior). +1. Update `versions.js` with the new Chromium-to-Puppeteer version mapping. +1. Commit and push your changes and open a pull request. + +### Bisecting upstream changes + +Sometimes, performing a Chromium roll causes tests to fail. To figure out the cause, you need to bisect Chromium revisions to figure out the earliest possible revision that changed the behavior. The script in `utils/bisect.js` can be helpful here. Given a Node.js script that calls `process.exit(1)` for bad revisions, run this from the Puppeteer repository’s root directory: + +```sh +node utils/bisect.js --good 686378 --bad 706915 script.js +``` + ## Releasing to npm Releasing to npm consists of the following phases: 1. Source Code: mark a release. - 1. Bump `package.json` version following the SEMVER rules. - 2. Run `npm run doc` to update the docs accordingly. - 3. Update the “Releases per Chromium Version” list in [`docs/api.md`](https://github.com/puppeteer/puppeteer/blob/main/docs/api.md) to include the new version. Note: only do this when the Chrome revision is different from the previous release. - 4. Send a PR titled `'chore: mark version vXXX.YYY.ZZZ'` ([example](https://github.com/puppeteer/puppeteer/pull/5078)). - 5. Make sure the PR passes **all checks**. + 1. Run `npm run release` to bump the version number in `package.json` and populate the changelog. + 1. Run `npm run doc` to update the docs accordingly. + 1. Send a PR titled `'chore(release): mark vXXX.YYY.ZZZ'` ([example](https://github.com/puppeteer/puppeteer/pull/5078)). + 1. Make sure the PR passes **all checks**. - **WHY**: there are linters in place that help to avoid unnecessary errors, e.g. [like this](https://github.com/puppeteer/puppeteer/pull/2446) - 6. Merge the PR. - 7. Once merged, publish the release notes using [GitHub's “draft new release tag” option](https://github.com/puppeteer/puppeteer/releases/new). + 1. Merge the PR. + 1. Once merged, publish the release notes from `CHANGELOG.md` using [GitHub’s “draft new release tag” option](https://github.com/puppeteer/puppeteer/releases/new). - **NOTE**: tag names are prefixed with `'v'`, e.g. for version `1.4.0` the tag is `v1.4.0`. - - For the “raw notes” section, use `git log --pretty="%h - %s" v2.0.0..HEAD`. -2. Publish `puppeteer` to npm. - 1. On your local machine, pull from [upstream](https://github.com/puppeteer/puppeteer) and make sure the last commit is the one just merged. - 2. Run `git status` and make sure there are no untracked files. - - **WHY**: this is to avoid adding unnecessary files to the npm package. - 3. Run [`npx pkgfiles`](https://www.npmjs.com/package/pkgfiles) to make sure you don't publish anything unnecessary. - 4. Run `npm publish`. This publishes the `puppeteer` package. -3. Publish `puppeteer-core` to npm. - 1. Run `./utils/prepare_puppeteer_core.js`. The script changes the name inside `package.json` to `puppeteer-core`. - 2. Run `npm publish`. This publishes the `puppeteer-core` package. - 3. Run `git reset --hard` to reset the changes to `package.json`. -4. Source Code: mark post-release. - 1. Bump `package.json` version to `-post` version, run `npm run doc` to update the “released APIs” section at the top of `docs/api.md` accordingly, and send a PR titled `'chore: bump version to vXXX.YYY.ZZZ-post'` ([example](https://github.com/puppeteer/puppeteer/commit/d02440d1eac98028e29f4e1cf55413062a259156)) + 1. As soon as the Git tag is created by completing the previous step, our CI automatically `npm publish`es the new releases for both the `puppeteer` and `puppeteer-core` packages. +1. Source Code: mark post-release. + 1. Bump `package.json` version to the `-post` version, run `npm run doc` to update the “released APIs” section at the top of `docs/api.md` accordingly, and send a PR titled `'chore: bump version to vXXX.YYY.ZZZ-post'` ([example](https://github.com/puppeteer/puppeteer/commit/d02440d1eac98028e29f4e1cf55413062a259156)) - **NOTE**: no other commits should be landed in-between release commit and bump commit. - -## Updating npm dist tags - -For both `puppeteer` and `puppeteer-core` we maintain `chrome-*` npm dist tags, e.g. `chrome-75` and so on. These tags match the Puppeteer version that corresponds to the `chrome-*` release. - -These tags are updated on every Puppeteer release. - -Managing tags 101: - -```bash -# List tags -$ npm dist-tag ls puppeteer -# Add tags -$ npm dist-tag add puppeteer@3.0.0 chrome-81 -$ npm dist-tag add puppeteer-core@3.0.0 chrome-81 -``` diff --git a/remote/test/puppeteer/LICENSE b/remote/test/puppeteer/LICENSE index afdfe50e72e0e..d2c171df74e93 100644 --- a/remote/test/puppeteer/LICENSE +++ b/remote/test/puppeteer/LICENSE @@ -1,7 +1,7 @@ Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -193,7 +193,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/remote/test/puppeteer/README.md b/remote/test/puppeteer/README.md index c76cbbbe24e2a..d50b8245e0b78 100644 --- a/remote/test/puppeteer/README.md +++ b/remote/test/puppeteer/README.md @@ -6,7 +6,7 @@ -###### [API](https://github.com/puppeteer/puppeteer/blob/v5.0.0/docs/api.md) | [FAQ](#faq) | [Contributing](https://github.com/puppeteer/puppeteer/blob/main/CONTRIBUTING.md) | [Troubleshooting](https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md) +###### [API](https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md) | [FAQ](#faq) | [Contributing](https://github.com/puppeteer/puppeteer/blob/main/CONTRIBUTING.md) | [Troubleshooting](https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md) > Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). Puppeteer runs [headless](https://developers.google.com/web/updates/2017/04/headless-chrome) by default, but can be configured to run full (non-headless) Chrome or Chromium. @@ -37,7 +37,7 @@ npm i puppeteer # or "yarn add puppeteer" ``` -Note: When you install Puppeteer, it downloads a recent version of Chromium (~170MB Mac, ~282MB Linux, ~280MB Win) that is guaranteed to work with the API. To skip the download, or to download a different browser, see [Environment variables](https://github.com/puppeteer/puppeteer/blob/v5.0.0/docs/api.md#environment-variables). +Note: When you install Puppeteer, it downloads a recent version of Chromium (~170MB Mac, ~282MB Linux, ~280MB Win) that is guaranteed to work with the API. To skip the download, or to download a different browser, see [Environment variables](https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md#environment-variables). ### puppeteer-core @@ -63,7 +63,7 @@ Note: Prior to v1.18.1, Puppeteer required at least Node v6.4.0. Versions from v Node 8.9.0+. Starting from v3.0.0 Puppeteer starts to rely on Node 10.18.1+. All examples below use async/await which is only supported in Node v7.6.0 or greater. Puppeteer will be familiar to people using other browser testing frameworks. You create an instance -of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://github.com/puppeteer/puppeteer/blob/v5.0.0/docs/api.md#). +of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md#). **Example** - navigating to https://example.com and saving a screenshot as *example.png*: @@ -88,7 +88,7 @@ Execute script on the command line node example.js ``` -Puppeteer sets an initial page size to 800×600px, which defines the screenshot size. The page size can be customized with [`Page.setViewport()`](https://github.com/puppeteer/puppeteer/blob/v5.0.0/docs/api.md#pagesetviewportviewport). +Puppeteer sets an initial page size to 800×600px, which defines the screenshot size. The page size can be customized with [`Page.setViewport()`](https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md#pagesetviewportviewport). **Example** - create a PDF. @@ -113,7 +113,7 @@ Execute script on the command line node hn.js ``` -See [`Page.pdf()`](https://github.com/puppeteer/puppeteer/blob/v5.0.0/docs/api.md#pagepdfoptions) for more information about creating pdfs. +See [`Page.pdf()`](https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md#pagepdfoptions) for more information about creating pdfs. **Example** - evaluate script in the context of the page @@ -148,7 +148,7 @@ Execute script on the command line node get-dimensions.js ``` -See [`Page.evaluate()`](https://github.com/puppeteer/puppeteer/blob/v5.0.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`. +See [`Page.evaluate()`](https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`. @@ -157,7 +157,7 @@ See [`Page.evaluate()`](https://github.com/puppeteer/puppeteer/blob/v5.0.0/docs/ **1. Uses Headless mode** -Puppeteer launches Chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). To launch a full version of Chromium, set the [`headless` option](https://github.com/puppeteer/puppeteer/blob/v5.0.0/docs/api.md#puppeteerlaunchoptions) when launching a browser: +Puppeteer launches Chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). To launch a full version of Chromium, set the [`headless` option](https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md#puppeteerlaunchoptions) when launching a browser: ```js const browser = await puppeteer.launch({headless: false}); // default is true @@ -173,7 +173,7 @@ pass in the executable's path when creating a `Browser` instance: const browser = await puppeteer.launch({executablePath: '/path/to/Chrome'}); ``` -You can also use Puppeteer with Firefox Nightly (experimental support). See [`Puppeteer.launch()`](https://github.com/puppeteer/puppeteer/blob/v5.0.0/docs/api.md#puppeteerlaunchoptions) for more information. +You can also use Puppeteer with Firefox Nightly (experimental support). See [`Puppeteer.launch()`](https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md#puppeteerlaunchoptions) 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/+/master/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users. @@ -185,7 +185,7 @@ Puppeteer creates its own browser user profile which it **cleans up on every run ## Resources -- [API Documentation](https://github.com/puppeteer/puppeteer/blob/v5.0.0/docs/api.md) +- [API Documentation](https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md) - [Examples](https://github.com/puppeteer/puppeteer/tree/main/examples/) - [Community list of Puppeteer resources](https://github.com/transitive-bullshit/awesome-puppeteer) @@ -328,7 +328,7 @@ See [Contributing](https://github.com/puppeteer/puppeteer/blob/main/CONTRIBUTING Official Firefox support is currently experimental. The ongoing collaboration with Mozilla aims to support common end-to-end testing use cases, for which developers expect cross-browser coverage. The Puppeteer team needs input from users to stabilize Firefox support and to bring missing APIs to our attention. -From Puppeteer v2.1.0 onwards you can specify [`puppeteer.launch({product: 'firefox'})`](https://github.com/puppeteer/puppeteer/blob/v5.0.0/docs/api.md#puppeteerlaunchoptions) to run your Puppeteer scripts in Firefox Nightly, without any additional custom patches. While [an older experiment](https://www.npmjs.com/package/puppeteer-firefox) required a patched version of Firefox, [the current approach](https://wiki.mozilla.org/Remote) works with “stock” Firefox. +From Puppeteer v2.1.0 onwards you can specify [`puppeteer.launch({product: 'firefox'})`](https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md#puppeteerlaunchoptions) to run your Puppeteer scripts in Firefox Nightly, without any additional custom patches. While [an older experiment](https://www.npmjs.com/package/puppeteer-firefox) required a patched version of Firefox, [the current approach](https://wiki.mozilla.org/Remote) works with “stock” Firefox. We will continue to collaborate with other browser vendors to bring Puppeteer support to browsers such as Safari. This effort includes exploration of a standard for executing cross-browser commands (instead of relying on the non-standard DevTools Protocol used by Chrome). @@ -424,7 +424,7 @@ await page.evaluate(() => { You may find that Puppeteer does not behave as expected when controlling pages that incorporate audio and video. (For example, [video playback/screenshots is likely to fail](https://github.com/puppeteer/puppeteer/issues/291).) There are two reasons for this: -* Puppeteer is bundled with Chromium — not Chrome — and so by default, it inherits all of [Chromium's media-related limitations](https://www.chromium.org/audio-video). This means that Puppeteer does not support licensed formats such as AAC or H.264. (However, it is possible to force Puppeteer to use a separately-installed version Chrome instead of Chromium via the [`executablePath` option to `puppeteer.launch`](https://github.com/puppeteer/puppeteer/blob/v5.0.0/docs/api.md#puppeteerlaunchoptions). You should only use this configuration if you need an official release of Chrome that supports these media formats.) +* Puppeteer is bundled with Chromium — not Chrome — and so by default, it inherits all of [Chromium's media-related limitations](https://www.chromium.org/audio-video). This means that Puppeteer does not support licensed formats such as AAC or H.264. (However, it is possible to force Puppeteer to use a separately-installed version Chrome instead of Chromium via the [`executablePath` option to `puppeteer.launch`](https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md#puppeteerlaunchoptions). You should only use this configuration if you need an official release of Chrome that supports these media formats.) * Since Puppeteer (in all configurations) controls a desktop version of Chromium/Chrome, features that are only supported by the mobile version of Chrome are not supported. This means that Puppeteer [does not support HTTP Live Streaming (HLS)](https://caniuse.com/#feat=http-live-streaming). #### Q: I am having trouble installing / running Puppeteer in my test environment. Where should I look for help? diff --git a/remote/test/puppeteer/api-extractor.json b/remote/test/puppeteer/api-extractor.json index 8cc8240aa41e6..3e7b9e66f625e 100644 --- a/remote/test/puppeteer/api-extractor.json +++ b/remote/test/puppeteer/api-extractor.json @@ -1,7 +1,7 @@ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - "mainEntryPointFilePath": "/lib/cjs/api-docs-entry.d.ts", - "bundledPackages": [ ], + "mainEntryPointFilePath": "/lib/cjs/puppeteer/api-docs-entry.d.ts", + "bundledPackages": [ "devtools-protocol" ], "apiReport": { "enabled": false diff --git a/remote/test/puppeteer/cjs-entry-core.js b/remote/test/puppeteer/cjs-entry-core.js index efcdb39027f7e..446726fafa35a 100644 --- a/remote/test/puppeteer/cjs-entry-core.js +++ b/remote/test/puppeteer/cjs-entry-core.js @@ -25,5 +25,5 @@ * This means that we can publish to CJS and ESM whilst maintaining the expected * import behaviour for CJS and ESM users. */ -const puppeteerExport = require('./lib/cjs/index-core'); +const puppeteerExport = require('./lib/cjs/puppeteer/node-puppeteer-core'); module.exports = puppeteerExport.default; diff --git a/remote/test/puppeteer/cjs-entry.js b/remote/test/puppeteer/cjs-entry.js index 424ffadf1a14e..d1840a9bea1bf 100644 --- a/remote/test/puppeteer/cjs-entry.js +++ b/remote/test/puppeteer/cjs-entry.js @@ -25,5 +25,5 @@ * This means that we can publish to CJS and ESM whilst maintaining the expected * import behaviour for CJS and ESM users. */ -const puppeteerExport = require('./lib/cjs/index'); +const puppeteerExport = require('./lib/cjs/puppeteer/node'); module.exports = puppeteerExport.default; diff --git a/remote/test/puppeteer/commitlint.config.js b/remote/test/puppeteer/commitlint.config.js new file mode 100644 index 0000000000000..2e216cd897cd3 --- /dev/null +++ b/remote/test/puppeteer/commitlint.config.js @@ -0,0 +1,22 @@ +/** + * Copyright 2020 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'body-max-line-length': [0, 'always', 100], + 'footer-max-line-length': [0, 'always', 100], + }, +}; diff --git a/remote/test/puppeteer/docs/api.md b/remote/test/puppeteer/docs/api.md deleted file mode 100644 index e3d2c8c198d20..0000000000000 --- a/remote/test/puppeteer/docs/api.md +++ /dev/null @@ -1,4099 +0,0 @@ - -# Puppeteer API v5.0.0 - - -- Interactive Documentation: https://pptr.dev -- API Translations: [中文|Chinese](https://zhaoqize.github.io/puppeteer-api-zh_CN/#/) -- Troubleshooting: [troubleshooting.md](https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md) -- Releases per Chromium Version: - * Chromium 83.0.4103.0 - [Puppeteer v3.1.0](https://github.com/puppeteer/puppeteer/blob/v3.1.0/docs/api.md) - * Chromium 81.0.4044.0 - [Puppeteer v3.0.0](https://github.com/puppeteer/puppeteer/blob/v3.0.0/docs/api.md) - * Chromium 80.0.3987.0 - [Puppeteer v2.1.0](https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md) - * Chromium 79.0.3942.0 - [Puppeteer v2.0.0](https://github.com/puppeteer/puppeteer/blob/v2.0.0/docs/api.md) - * Chromium 78.0.3882.0 - [Puppeteer v1.20.0](https://github.com/puppeteer/puppeteer/blob/v1.20.0/docs/api.md) - * Chromium 77.0.3803.0 - [Puppeteer v1.19.0](https://github.com/puppeteer/puppeteer/blob/v1.19.0/docs/api.md) - * Chromium 76.0.3803.0 - [Puppeteer v1.17.0](https://github.com/puppeteer/puppeteer/blob/v1.17.0/docs/api.md) - * Chromium 75.0.3765.0 - [Puppeteer v1.15.0](https://github.com/puppeteer/puppeteer/blob/v1.15.0/docs/api.md) - * Chromium 74.0.3723.0 - [Puppeteer v1.13.0](https://github.com/puppeteer/puppeteer/blob/v1.13.0/docs/api.md) - * Chromium 73.0.3679.0 - [Puppeteer v1.12.2](https://github.com/puppeteer/puppeteer/blob/v1.12.2/docs/api.md) - * [All releases](https://github.com/puppeteer/puppeteer/releases) - - -##### Table of Contents - - -- [Overview](#overview) -- [puppeteer vs puppeteer-core](#puppeteer-vs-puppeteer-core) -- [Environment Variables](#environment-variables) -- [Working with Chrome Extensions](#working-with-chrome-extensions) -- [class: Puppeteer](#class-puppeteer) - * [puppeteer.connect(options)](#puppeteerconnectoptions) - * [puppeteer.createBrowserFetcher([options])](#puppeteercreatebrowserfetcheroptions) - * [puppeteer.defaultArgs([options])](#puppeteerdefaultargsoptions) - * [puppeteer.devices](#puppeteerdevices) - * [puppeteer.errors](#puppeteererrors) - * [puppeteer.executablePath()](#puppeteerexecutablepath) - * [puppeteer.launch([options])](#puppeteerlaunchoptions) - * [puppeteer.product](#puppeteerproduct) -- [class: BrowserFetcher](#class-browserfetcher) - * [browserFetcher.canDownload(revision)](#browserfetchercandownloadrevision) - * [browserFetcher.download(revision[, progressCallback])](#browserfetcherdownloadrevision-progresscallback) - * [browserFetcher.host()](#browserfetcherhost) - * [browserFetcher.localRevisions()](#browserfetcherlocalrevisions) - * [browserFetcher.platform()](#browserfetcherplatform) - * [browserFetcher.product()](#browserfetcherproduct) - * [browserFetcher.remove(revision)](#browserfetcherremoverevision) - * [browserFetcher.revisionInfo(revision)](#browserfetcherrevisioninforevision) -- [class: Browser](#class-browser) - * [event: 'disconnected'](#event-disconnected) - * [event: 'targetchanged'](#event-targetchanged) - * [event: 'targetcreated'](#event-targetcreated) - * [event: 'targetdestroyed'](#event-targetdestroyed) - * [browser.browserContexts()](#browserbrowsercontexts) - * [browser.close()](#browserclose) - * [browser.createIncognitoBrowserContext()](#browsercreateincognitobrowsercontext) - * [browser.defaultBrowserContext()](#browserdefaultbrowsercontext) - * [browser.disconnect()](#browserdisconnect) - * [browser.isConnected()](#browserisconnected) - * [browser.newPage()](#browsernewpage) - * [browser.pages()](#browserpages) - * [browser.process()](#browserprocess) - * [browser.target()](#browsertarget) - * [browser.targets()](#browsertargets) - * [browser.userAgent()](#browseruseragent) - * [browser.version()](#browserversion) - * [browser.waitForTarget(predicate[, options])](#browserwaitfortargetpredicate-options) - * [browser.wsEndpoint()](#browserwsendpoint) -- [class: BrowserContext](#class-browsercontext) - * [event: 'targetchanged'](#event-targetchanged-1) - * [event: 'targetcreated'](#event-targetcreated-1) - * [event: 'targetdestroyed'](#event-targetdestroyed-1) - * [browserContext.browser()](#browsercontextbrowser) - * [browserContext.clearPermissionOverrides()](#browsercontextclearpermissionoverrides) - * [browserContext.close()](#browsercontextclose) - * [browserContext.isIncognito()](#browsercontextisincognito) - * [browserContext.newPage()](#browsercontextnewpage) - * [browserContext.overridePermissions(origin, permissions)](#browsercontextoverridepermissionsorigin-permissions) - * [browserContext.pages()](#browsercontextpages) - * [browserContext.targets()](#browsercontexttargets) - * [browserContext.waitForTarget(predicate[, options])](#browsercontextwaitfortargetpredicate-options) -- [class: Page](#class-page) - * [event: 'close'](#event-close) - * [event: 'console'](#event-console) - * [event: 'dialog'](#event-dialog) - * [event: 'domcontentloaded'](#event-domcontentloaded) - * [event: 'error'](#event-error) - * [event: 'frameattached'](#event-frameattached) - * [event: 'framedetached'](#event-framedetached) - * [event: 'framenavigated'](#event-framenavigated) - * [event: 'load'](#event-load) - * [event: 'metrics'](#event-metrics) - * [event: 'pageerror'](#event-pageerror) - * [event: 'popup'](#event-popup) - * [event: 'request'](#event-request) - * [event: 'requestfailed'](#event-requestfailed) - * [event: 'requestfinished'](#event-requestfinished) - * [event: 'response'](#event-response) - * [event: 'workercreated'](#event-workercreated) - * [event: 'workerdestroyed'](#event-workerdestroyed) - * [page.$(selector)](#pageselector) - * [page.$$(selector)](#pageselector-1) - * [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args) - * [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args-1) - * [page.$x(expression)](#pagexexpression) - * [page.accessibility](#pageaccessibility) - * [page.addScriptTag(options)](#pageaddscripttagoptions) - * [page.addStyleTag(options)](#pageaddstyletagoptions) - * [page.authenticate(credentials)](#pageauthenticatecredentials) - * [page.bringToFront()](#pagebringtofront) - * [page.browser()](#pagebrowser) - * [page.browserContext()](#pagebrowsercontext) - * [page.click(selector[, options])](#pageclickselector-options) - * [page.close([options])](#pagecloseoptions) - * [page.content()](#pagecontent) - * [page.cookies([...urls])](#pagecookiesurls) - * [page.coverage](#pagecoverage) - * [page.deleteCookie(...cookies)](#pagedeletecookiecookies) - * [page.emulate(options)](#pageemulateoptions) - * [page.emulateMediaFeatures(features)](#pageemulatemediafeaturesfeatures) - * [page.emulateMediaType(type)](#pageemulatemediatypetype) - * [page.emulateTimezone(timezoneId)](#pageemulatetimezonetimezoneid) - * [page.emulateVisionDeficiency(type)](#pageemulatevisiondeficiencytype) - * [page.evaluate(pageFunction[, ...args])](#pageevaluatepagefunction-args) - * [page.evaluateHandle(pageFunction[, ...args])](#pageevaluatehandlepagefunction-args) - * [page.evaluateOnNewDocument(pageFunction[, ...args])](#pageevaluateonnewdocumentpagefunction-args) - * [page.exposeFunction(name, puppeteerFunction)](#pageexposefunctionname-puppeteerfunction) - * [page.focus(selector)](#pagefocusselector) - * [page.frames()](#pageframes) - * [page.goBack([options])](#pagegobackoptions) - * [page.goForward([options])](#pagegoforwardoptions) - * [page.goto(url[, options])](#pagegotourl-options) - * [page.hover(selector)](#pagehoverselector) - * [page.isClosed()](#pageisclosed) - * [page.isJavaScriptEnabled()](#pageisjavascriptenabled) - * [page.keyboard](#pagekeyboard) - * [page.mainFrame()](#pagemainframe) - * [page.metrics()](#pagemetrics) - * [page.mouse](#pagemouse) - * [page.pdf([options])](#pagepdfoptions) - * [page.queryObjects(prototypeHandle)](#pagequeryobjectsprototypehandle) - * [page.reload([options])](#pagereloadoptions) - * [page.screenshot([options])](#pagescreenshotoptions) - * [page.select(selector, ...values)](#pageselectselector-values) - * [page.setBypassCSP(enabled)](#pagesetbypasscspenabled) - * [page.setCacheEnabled([enabled])](#pagesetcacheenabledenabled) - * [page.setContent(html[, options])](#pagesetcontenthtml-options) - * [page.setCookie(...cookies)](#pagesetcookiecookies) - * [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) - * [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) - * [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders) - * [page.setGeolocation(options)](#pagesetgeolocationoptions) - * [page.setJavaScriptEnabled(enabled)](#pagesetjavascriptenabledenabled) - * [page.setOfflineMode(enabled)](#pagesetofflinemodeenabled) - * [page.setRequestInterception(value)](#pagesetrequestinterceptionvalue) - * [page.setUserAgent(userAgent)](#pagesetuseragentuseragent) - * [page.setViewport(viewport)](#pagesetviewportviewport) - * [page.tap(selector)](#pagetapselector) - * [page.target()](#pagetarget) - * [page.title()](#pagetitle) - * [page.touchscreen](#pagetouchscreen) - * [page.tracing](#pagetracing) - * [page.type(selector, text[, options])](#pagetypeselector-text-options) - * [page.url()](#pageurl) - * [page.viewport()](#pageviewport) - * [page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#pagewaitforselectororfunctionortimeout-options-args) - * [page.waitForFileChooser([options])](#pagewaitforfilechooseroptions) - * [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args) - * [page.waitForNavigation([options])](#pagewaitfornavigationoptions) - * [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options) - * [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options) - * [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) - * [page.waitForXPath(xpath[, options])](#pagewaitforxpathxpath-options) - * [page.workers()](#pageworkers) - * [GeolocationOptions](#geolocationoptions) - * [WaitTimeoutOptions](#waittimeoutoptions) -- [class: WebWorker](#class-webworker) - * [webWorker.evaluate(pageFunction[, ...args])](#webworkerevaluatepagefunction-args) - * [webWorker.evaluateHandle(pageFunction[, ...args])](#webworkerevaluatehandlepagefunction-args) - * [webWorker.executionContext()](#webworkerexecutioncontext) - * [webWorker.url()](#webworkerurl) -- [class: Accessibility](#class-accessibility) - * [accessibility.snapshot([options])](#accessibilitysnapshotoptions) -- [class: Keyboard](#class-keyboard) - * [keyboard.down(key[, options])](#keyboarddownkey-options) - * [keyboard.press(key[, options])](#keyboardpresskey-options) - * [keyboard.sendCharacter(char)](#keyboardsendcharacterchar) - * [keyboard.type(text[, options])](#keyboardtypetext-options) - * [keyboard.up(key)](#keyboardupkey) -- [class: Mouse](#class-mouse) - * [mouse.click(x, y[, options])](#mouseclickx-y-options) - * [mouse.down([options])](#mousedownoptions) - * [mouse.move(x, y[, options])](#mousemovex-y-options) - * [mouse.up([options])](#mouseupoptions) -- [class: Touchscreen](#class-touchscreen) - * [touchscreen.tap(x, y)](#touchscreentapx-y) -- [class: Tracing](#class-tracing) - * [tracing.start([options])](#tracingstartoptions) - * [tracing.stop()](#tracingstop) -- [class: FileChooser](#class-filechooser) - * [fileChooser.accept(filePaths)](#filechooseracceptfilepaths) - * [fileChooser.cancel()](#filechoosercancel) - * [fileChooser.isMultiple()](#filechooserismultiple) -- [class: Dialog](#class-dialog) - * [dialog.accept([promptText])](#dialogacceptprompttext) - * [dialog.defaultValue()](#dialogdefaultvalue) - * [dialog.dismiss()](#dialogdismiss) - * [dialog.message()](#dialogmessage) - * [dialog.type()](#dialogtype) -- [class: ConsoleMessage](#class-consolemessage) - * [consoleMessage.args()](#consolemessageargs) - * [consoleMessage.location()](#consolemessagelocation) - * [consoleMessage.text()](#consolemessagetext) - * [consoleMessage.type()](#consolemessagetype) -- [class: Frame](#class-frame) - * [frame.$(selector)](#frameselector) - * [frame.$$(selector)](#frameselector-1) - * [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args) - * [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args-1) - * [frame.$x(expression)](#framexexpression) - * [frame.addScriptTag(options)](#frameaddscripttagoptions) - * [frame.addStyleTag(options)](#frameaddstyletagoptions) - * [frame.childFrames()](#framechildframes) - * [frame.click(selector[, options])](#frameclickselector-options) - * [frame.content()](#framecontent) - * [frame.evaluate(pageFunction[, ...args])](#frameevaluatepagefunction-args) - * [frame.evaluateHandle(pageFunction[, ...args])](#frameevaluatehandlepagefunction-args) - * [frame.executionContext()](#frameexecutioncontext) - * [frame.focus(selector)](#framefocusselector) - * [frame.goto(url[, options])](#framegotourl-options) - * [frame.hover(selector)](#framehoverselector) - * [frame.isDetached()](#frameisdetached) - * [frame.name()](#framename) - * [frame.parentFrame()](#frameparentframe) - * [frame.select(selector, ...values)](#frameselectselector-values) - * [frame.setContent(html[, options])](#framesetcontenthtml-options) - * [frame.tap(selector)](#frametapselector) - * [frame.title()](#frametitle) - * [frame.type(selector, text[, options])](#frametypeselector-text-options) - * [frame.url()](#frameurl) - * [frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-args) - * [frame.waitForFunction(pageFunction[, options[, ...args]])](#framewaitforfunctionpagefunction-options-args) - * [frame.waitForNavigation([options])](#framewaitfornavigationoptions) - * [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options) - * [frame.waitForXPath(xpath[, options])](#framewaitforxpathxpath-options) -- [class: ExecutionContext](#class-executioncontext) - * [executionContext.evaluate(pageFunction[, ...args])](#executioncontextevaluatepagefunction-args) - * [executionContext.evaluateHandle(pageFunction[, ...args])](#executioncontextevaluatehandlepagefunction-args) - * [executionContext.frame()](#executioncontextframe) - * [executionContext.queryObjects(prototypeHandle)](#executioncontextqueryobjectsprototypehandle) -- [class: JSHandle](#class-jshandle) - * [jsHandle.asElement()](#jshandleaselement) - * [jsHandle.dispose()](#jshandledispose) - * [jsHandle.evaluate(pageFunction[, ...args])](#jshandleevaluatepagefunction-args) - * [jsHandle.evaluateHandle(pageFunction[, ...args])](#jshandleevaluatehandlepagefunction-args) - * [jsHandle.executionContext()](#jshandleexecutioncontext) - * [jsHandle.getProperties()](#jshandlegetproperties) - * [jsHandle.getProperty(propertyName)](#jshandlegetpropertypropertyname) - * [jsHandle.jsonValue()](#jshandlejsonvalue) -- [class: ElementHandle](#class-elementhandle) - * [elementHandle.$(selector)](#elementhandleselector) - * [elementHandle.$$(selector)](#elementhandleselector-1) - * [elementHandle.$$eval(selector, pageFunction[, ...args])](#elementhandleevalselector-pagefunction-args) - * [elementHandle.$eval(selector, pageFunction[, ...args])](#elementhandleevalselector-pagefunction-args-1) - * [elementHandle.$x(expression)](#elementhandlexexpression) - * [elementHandle.asElement()](#elementhandleaselement) - * [elementHandle.boundingBox()](#elementhandleboundingbox) - * [elementHandle.boxModel()](#elementhandleboxmodel) - * [elementHandle.click([options])](#elementhandleclickoptions) - * [elementHandle.contentFrame()](#elementhandlecontentframe) - * [elementHandle.dispose()](#elementhandledispose) - * [elementHandle.evaluate(pageFunction[, ...args])](#elementhandleevaluatepagefunction-args) - * [elementHandle.evaluateHandle(pageFunction[, ...args])](#elementhandleevaluatehandlepagefunction-args) - * [elementHandle.executionContext()](#elementhandleexecutioncontext) - * [elementHandle.focus()](#elementhandlefocus) - * [elementHandle.getProperties()](#elementhandlegetproperties) - * [elementHandle.getProperty(propertyName)](#elementhandlegetpropertypropertyname) - * [elementHandle.hover()](#elementhandlehover) - * [elementHandle.isIntersectingViewport()](#elementhandleisintersectingviewport) - * [elementHandle.jsonValue()](#elementhandlejsonvalue) - * [elementHandle.press(key[, options])](#elementhandlepresskey-options) - * [elementHandle.screenshot([options])](#elementhandlescreenshotoptions) - * [elementHandle.select(...values)](#elementhandleselectvalues) - * [elementHandle.tap()](#elementhandletap) - * [elementHandle.toString()](#elementhandletostring) - * [elementHandle.type(text[, options])](#elementhandletypetext-options) - * [elementHandle.uploadFile(...filePaths)](#elementhandleuploadfilefilepaths) -- [class: HTTPRequest](#class-httprequest) - * [httpRequest.abort([errorCode])](#httprequestaborterrorcode) - * [httpRequest.continue([overrides])](#httprequestcontinueoverrides) - * [httpRequest.failure()](#httprequestfailure) - * [httpRequest.frame()](#httprequestframe) - * [httpRequest.headers()](#httprequestheaders) - * [httpRequest.isNavigationRequest()](#httprequestisnavigationrequest) - * [httpRequest.method()](#httprequestmethod) - * [httpRequest.postData()](#httprequestpostdata) - * [httpRequest.redirectChain()](#httprequestredirectchain) - * [httpRequest.resourceType()](#httprequestresourcetype) - * [httpRequest.respond(response)](#httprequestrespondresponse) - * [httpRequest.response()](#httprequestresponse) - * [httpRequest.url()](#httprequesturl) -- [class: HTTPResponse](#class-httpresponse) - * [httpResponse.buffer()](#httpresponsebuffer) - * [httpResponse.frame()](#httpresponseframe) - * [httpResponse.fromCache()](#httpresponsefromcache) - * [httpResponse.fromServiceWorker()](#httpresponsefromserviceworker) - * [httpResponse.headers()](#httpresponseheaders) - * [httpResponse.json()](#httpresponsejson) - * [httpResponse.ok()](#httpresponseok) - * [httpResponse.remoteAddress()](#httpresponseremoteaddress) - * [httpResponse.request()](#httpresponserequest) - * [httpResponse.securityDetails()](#httpresponsesecuritydetails) - * [httpResponse.status()](#httpresponsestatus) - * [httpResponse.statusText()](#httpresponsestatustext) - * [httpResponse.text()](#httpresponsetext) - * [httpResponse.url()](#httpresponseurl) -- [class: SecurityDetails](#class-securitydetails) - * [securityDetails.issuer()](#securitydetailsissuer) - * [securityDetails.protocol()](#securitydetailsprotocol) - * [securityDetails.subjectAlternativeNames()](#securitydetailssubjectalternativenames) - * [securityDetails.subjectName()](#securitydetailssubjectname) - * [securityDetails.validFrom()](#securitydetailsvalidfrom) - * [securityDetails.validTo()](#securitydetailsvalidto) -- [class: Target](#class-target) - * [target.browser()](#targetbrowser) - * [target.browserContext()](#targetbrowsercontext) - * [target.createCDPSession()](#targetcreatecdpsession) - * [target.opener()](#targetopener) - * [target.page()](#targetpage) - * [target.type()](#targettype) - * [target.url()](#targeturl) - * [target.worker()](#targetworker) -- [class: CDPSession](#class-cdpsession) - * [cdpSession.detach()](#cdpsessiondetach) - * [cdpSession.send(method[, params])](#cdpsessionsendmethod-params) -- [class: Coverage](#class-coverage) - * [coverage.startCSSCoverage([options])](#coveragestartcsscoverageoptions) - * [coverage.startJSCoverage([options])](#coveragestartjscoverageoptions) - * [coverage.stopCSSCoverage()](#coveragestopcsscoverage) - * [coverage.stopJSCoverage()](#coveragestopjscoverage) -- [class: TimeoutError](#class-timeouterror) -- [class: EventEmitter](#class-eventemitter) - * [eventEmitter.addListener(event, handler)](#eventemitteraddlistenerevent-handler) - * [eventEmitter.emit(event, [eventData])](#eventemitteremitevent-eventdata) - * [eventEmitter.listenerCount(event)](#eventemitterlistenercountevent) - * [eventEmitter.off(event, handler)](#eventemitteroffevent-handler) - * [eventEmitter.on(event, handler)](#eventemitteronevent-handler) - * [eventEmitter.once(event, handler)](#eventemitteronceevent-handler) - * [eventEmitter.removeAllListeners([event])](#eventemitterremovealllistenersevent) - * [eventEmitter.removeListener(event, handler)](#eventemitterremovelistenerevent-handler) - - -### Overview - -Puppeteer is a Node library which provides a high-level API to control Chromium or Chrome over the DevTools Protocol. - -The Puppeteer API is hierarchical and mirrors the browser structure. - -> **NOTE** On the following diagram, faded entities are not currently represented in Puppeteer. - -![puppeteer overview](https://user-images.githubusercontent.com/81942/86137523-ab2ba080-baed-11ea-9d4b-30eda784585a.png) - -- [`Puppeteer`](#class-puppeteer) communicates with the browser using [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). -- [`Browser`](#class-browser) instance can own multiple browser contexts. -- [`BrowserContext`](#class-browsercontext) instance defines a browsing session and can own multiple pages. -- [`Page`](#class-page) has at least one frame: main frame. There might be other frames created by [iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) or [frame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/frame) tags. -- [`Frame`](#class-frame) has at least one execution context - the default execution context - where the frame's JavaScript is executed. A Frame might have additional execution contexts that are associated with [extensions](https://developer.chrome.com/extensions). -- [`Worker`](#class-worker) has a single execution context and facilitates interacting with [WebWorkers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). - -(Diagram source: [link](https://docs.google.com/drawings/d/1Q_AM6KYs9kbyLZF-Lpp5mtpAWth73Cq8IKCsWYgi8MM/edit?usp=sharing)) - -### puppeteer vs puppeteer-core - -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 -Chromium, which it then drives using `puppeteer-core`. Being an end-user product, `puppeteer` supports a bunch of convenient `PUPPETEER_*` env variables to tweak its behavior. - -`puppeteer-core` is a *library* to help drive anything that supports DevTools protocol. `puppeteer-core` doesn't download Chromium when installed. Being a library, `puppeteer-core` is fully driven -through its programmatic interface and disregards all the `PUPPETEER_*` env variables. - -To sum up, the only differences between `puppeteer-core` and `puppeteer` are: -- `puppeteer-core` doesn't automatically download Chromium when installed. -- `puppeteer-core` ignores all `PUPPETEER_*` env variables. - -In most cases, you'll be fine using the `puppeteer` package. - -However, you should use `puppeteer-core` if: -- you're building another end-user product or library atop of DevTools protocol. For example, one might build a PDF generator using `puppeteer-core` and write a custom `install.js` script that downloads [`headless_shell`](https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md) instead of Chromium to save disk space. -- you're bundling Puppeteer to use in Chrome Extension / browser with the DevTools protocol where downloading an additional Chromium binary is unnecessary. -- you're building a set of tools where `puppeteer-core` is one of the ingredients and you want to postpone `install.js` script execution until Chromium is about to be used. - -When using `puppeteer-core`, remember to change the *include* line: - -```js -const puppeteer = require('puppeteer-core'); -``` - -You will then need to call [`puppeteer.connect([options])`](#puppeteerconnectoptions) or [`puppeteer.launch([options])`](#puppeteerlaunchoptions) with an explicit `executablePath` option. - -### Environment Variables - -Puppeteer looks for certain [environment variables](https://en.wikipedia.org/wiki/Environment_variable) to aid its operations. -If Puppeteer doesn't find them in the environment during the installation step, a lowercased variant of these variables will be used from the [npm config](https://docs.npmjs.com/cli/config). - -- `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` - defines HTTP proxy settings that are used to download and run Chromium. -- `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD` - do not download bundled Chromium during installation step. -- `PUPPETEER_DOWNLOAD_HOST` - overwrite URL prefix that is used to download Chromium. Note: this includes protocol and might even include path prefix. Defaults to `https://storage.googleapis.com`. -- `PUPPETEER_CHROMIUM_REVISION` - specify a certain version of Chromium you'd like Puppeteer to use. See [puppeteer.launch([options])](#puppeteerlaunchoptions) on how executable path is inferred. **BEWARE**: Puppeteer is only [guaranteed to work](https://github.com/puppeteer/puppeteer/#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk. -- `PUPPETEER_EXECUTABLE_PATH` - specify an executable path to be used in `puppeteer.launch`. See [puppeteer.launch([options])](#puppeteerlaunchoptions) on how the executable path is inferred. **BEWARE**: Puppeteer is only [guaranteed to work](https://github.com/puppeteer/puppeteer/#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk. -- `PUPPETEER_PRODUCT` - specify which browser you'd like Puppeteer to use. Must be one of `chrome` or `firefox`. This can also be used during installation to fetch the recommended browser binary. Setting `product` programmatically in [puppeteer.launch([options])](#puppeteerlaunchoptions) supersedes this environment variable. The product is exposed in [`puppeteer.product`](#puppeteerproduct) - -> **NOTE** PUPPETEER_* env variables are not accounted for in the [`puppeteer-core`](https://www.npmjs.com/package/puppeteer-core) package. - - -### Working with Chrome Extensions - -Puppeteer can be used for testing Chrome Extensions. - -> **NOTE** Extensions in Chrome / Chromium currently only work in non-headless mode. - -The following is code for getting a handle to the [background page](https://developer.chrome.com/extensions/background_pages) of an extension whose source is located in `./my-extension`: -```js -const puppeteer = require('puppeteer'); - -(async () => { - const pathToExtension = require('path').join(__dirname, 'my-extension'); - const browser = await puppeteer.launch({ - headless: false, - args: [ - `--disable-extensions-except=${pathToExtension}`, - `--load-extension=${pathToExtension}` - ] - }); - const targets = await browser.targets(); - const backgroundPageTarget = targets.find(target => target.type() === 'background_page'); - const backgroundPage = await backgroundPageTarget.page(); - // Test the background page as you would any other page. - await browser.close(); -})(); -``` - -> **NOTE** It is not yet possible to test extension popups or content scripts. - -### class: Puppeteer - -Puppeteer module provides a method to launch a Chromium instance. -The following is a typical example of using Puppeteer to drive automation: -```js -const puppeteer = require('puppeteer'); - -(async () => { - const browser = await puppeteer.launch(); - const page = await browser.newPage(); - await page.goto('https://www.google.com'); - // other actions... - await browser.close(); -})(); -``` - -#### puppeteer.connect(options) -- `options` <[Object]> - - `browserWSEndpoint` a [browser websocket endpoint](#browserwsendpoint) to connect to. - - `browserURL` a browser url to connect to, in format `http://${host}:${port}`. Use interchangeably with `browserWSEndpoint` to let Puppeteer fetch it from [metadata endpoint](https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target). - - `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`. - - `defaultViewport` Sets a consistent viewport for each page. Defaults to an 800x600 viewport. `null` disables the default viewport. - - `width` <[number]> page width in pixels. - - `height` <[number]> page height in pixels. - - `deviceScaleFactor` <[number]> Specify device scale factor (can be thought of as dpr). Defaults to `1`. - - `isMobile` <[boolean]> Whether the `meta viewport` tag is taken into account. Defaults to `false`. - - `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false` - - `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`. - - `slowMo` <[number]> Slows down Puppeteer operations by the specified amount of milliseconds. Useful so that you can see what is going on. - - `transport` <[ConnectionTransport]> **Experimental** Specify a custom transport object for Puppeteer to use. - - `product` <[string]> Possible values are: `chrome`, `firefox`. Defaults to `chrome`. -- returns: <[Promise]<[Browser]>> - -This methods attaches Puppeteer to an existing browser instance. - -#### puppeteer.createBrowserFetcher([options]) -- `options` <[Object]> - - `host` <[string]> A download host to be used. Defaults to `https://storage.googleapis.com`. If the `product` is `firefox`, this defaults to `https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central`. - - `path` <[string]> A path for the downloads folder. Defaults to `/.local-chromium`, where `` is puppeteer's package root. If the `product` is `firefox`, this defaults to `/.local-firefox`. - - `platform` <"linux"|"mac"|"win32"|"win64"> [string] for the current platform. Possible values are: `mac`, `win32`, `win64`, `linux`. Defaults to the current platform. - - `product` <"chrome"|"firefox"> [string] for the product to run. Possible values are: `chrome`, `firefox`. Defaults to `chrome`. -- returns: <[BrowserFetcher]> - -#### puppeteer.defaultArgs([options]) -- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields: - - `headless` <[boolean]> Whether to run browser in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). Defaults to `true` unless the `devtools` option is `true`. - - `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. The list of Chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/). - - `userDataDir` <[string]> Path to a [User Data Directory](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md). - - `devtools` <[boolean]> Whether to auto-open a DevTools panel for each tab. If this option is `true`, the `headless` option will be set `false`. -- returns: <[Array]<[string]>> - -The default flags that Chromium will be launched with. - -#### puppeteer.devices -- returns: <[Object]> - -Returns a list of devices to be used with [`page.emulate(options)`](#pageemulateoptions). Actual list of -devices can be found in [src/common/DeviceDescriptors.js](https://github.com/puppeteer/puppeteer/blob/main/src/common/DeviceDescriptors.ts). - -```js -const puppeteer = require('puppeteer'); -const iPhone = puppeteer.devices['iPhone 6']; - -(async () => { - const browser = await puppeteer.launch(); - const page = await browser.newPage(); - await page.emulate(iPhone); - await page.goto('https://www.google.com'); - // other actions... - await browser.close(); -})(); -``` - -#### puppeteer.errors -- returns: <[Object]> - - `TimeoutError` <[function]> A class of [TimeoutError]. - -Puppeteer methods might throw errors if they are unable to fulfill a request. For example, [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) -might fail if the selector doesn't match any nodes during the given timeframe. - -For certain types of errors Puppeteer uses specific error classes. -These classes are available via [`puppeteer.errors`](#puppeteererrors) - -An example of handling a timeout error: -```js -try { - await page.waitForSelector('.foo'); -} catch (e) { - if (e instanceof puppeteer.errors.TimeoutError) { - // Do something if this is a timeout. - } -} -``` - -> **NOTE** The old way (Puppeteer versions <= v1.14.0) errors can be obtained with `require('puppeteer/Errors')`. - -#### puppeteer.executablePath() -- returns: <[string]> A path where Puppeteer expects to find the bundled browser. The browser binary might not be there if the download was skipped with [`PUPPETEER_SKIP_DOWNLOAD`](#environment-variables). - -> **NOTE** `puppeteer.executablePath()` is affected by the `PUPPETEER_EXECUTABLE_PATH` and `PUPPETEER_CHROMIUM_REVISION` env variables. See [Environment Variables](#environment-variables) for details. - - -#### puppeteer.launch([options]) -- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields: - - `product` <[string]> Which browser to launch. At this time, this is either `chrome` or `firefox`. See also `PUPPETEER_PRODUCT`. - - `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`. - - `headless` <[boolean]> Whether to run browser in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). Defaults to `true` unless the `devtools` option is `true`. - - `executablePath` <[string]> Path to a browser executable to run instead of the bundled Chromium. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Puppeteer is only [guaranteed to work](https://github.com/puppeteer/puppeteer/#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk. - - `slowMo` <[number]> Slows down Puppeteer operations by the specified amount of milliseconds. Useful so that you can see what is going on. - - `defaultViewport` Sets a consistent viewport for each page. Defaults to an 800x600 viewport. `null` disables the default viewport. - - `width` <[number]> page width in pixels. - - `height` <[number]> page height in pixels. - - `deviceScaleFactor` <[number]> Specify device scale factor (can be thought of as dpr). Defaults to `1`. - - `isMobile` <[boolean]> Whether the `meta viewport` tag is taken into account. Defaults to `false`. - - `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false` - - `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`. - - `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. The list of Chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/), and here is the list of [Firefox flags](https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options). - - `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use [`puppeteer.defaultArgs()`](#puppeteerdefaultargsoptions). If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`. - - `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`. - - `handleSIGTERM` <[boolean]> Close the browser process on SIGTERM. Defaults to `true`. - - `handleSIGHUP` <[boolean]> Close the browser process on SIGHUP. Defaults to `true`. - - `timeout` <[number]> Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. - - `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`. - - `userDataDir` <[string]> Path to a [User Data Directory](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md). - - `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`. - - `devtools` <[boolean]> Whether to auto-open a DevTools panel for each tab. If this option is `true`, the `headless` option will be set `false`. - - `pipe` <[boolean]> Connects to the browser over a pipe instead of a WebSocket. Defaults to `false`. - - `extraPrefsFirefox` <[Object]> Additional [preferences](https://developer.mozilla.org/en-US/docs/Mozilla/Preferences/Preference_reference) that can be passed to Firefox (see `PUPPETEER_PRODUCT`) -- returns: <[Promise]<[Browser]>> Promise which resolves to browser instance. - - -You can use `ignoreDefaultArgs` to filter out `--mute-audio` from default arguments: -```js -const browser = await puppeteer.launch({ - ignoreDefaultArgs: ['--mute-audio'] -}); -``` - -> **NOTE** Puppeteer can also be used to control the Chrome browser, but it works best with the version of Chromium it is bundled with. There is no guarantee it will work with any other version. Use `executablePath` option with extreme caution. -> -> If Google Chrome (rather than Chromium) is preferred, a [Chrome Canary](https://www.google.com/chrome/browser/canary.html) or [Dev Channel](https://www.chromium.org/getting-involved/dev-channel) build is suggested. -> -> In [puppeteer.launch([options])](#puppeteerlaunchoptions) above, any mention of Chromium also applies to Chrome. -> -> 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/+/lkgr/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users. - -#### puppeteer.product -- returns: <[string]> returns the name of the browser that is under automation (`"chrome"` or `"firefox"`) - -The product is set by the `PUPPETEER_PRODUCT` environment variable or the `product` option in [puppeteer.launch([options])](#puppeteerlaunchoptions) and defaults to `chrome`. Firefox support is experimental. - - -### class: BrowserFetcher - -BrowserFetcher can download and manage different versions of Chromium and Firefox. - -BrowserFetcher operates on revision strings that specify a precise version of Chromium, e.g. `"533271"`. Revision strings can be obtained from [omahaproxy.appspot.com](http://omahaproxy.appspot.com/). - -In the Firefox case, BrowserFetcher downloads Firefox Nightly and operates on version numbers such as `"75"`. - -An example of using BrowserFetcher to download a specific version of Chromium and running -Puppeteer against it: - -```js -const browserFetcher = puppeteer.createBrowserFetcher(); -const revisionInfo = await browserFetcher.download('533271'); -const browser = await puppeteer.launch({executablePath: revisionInfo.executablePath}) -``` - -> **NOTE** BrowserFetcher is not designed to work concurrently with other -> instances of BrowserFetcher that share the same downloads directory. - -#### browserFetcher.canDownload(revision) -- `revision` <[string]> a revision to check availability. -- returns: <[Promise]<[boolean]>> returns `true` if the revision could be downloaded from the host. - -The method initiates a HEAD request to check if the revision is available. - -#### browserFetcher.download(revision[, progressCallback]) -- `revision` <[string]> a revision to download. -- `progressCallback` <[function]([number], [number])> A function that will be called with two arguments: - - `downloadedBytes` <[number]> how many bytes have been downloaded - - `totalBytes` <[number]> how large is the total download. -- returns: <[Promise]<[Object]>> Resolves with revision information when the revision is downloaded and extracted - - `revision` <[string]> the revision the info was created from - - `folderPath` <[string]> path to the extracted revision folder - - `executablePath` <[string]> path to the revision executable - - `url` <[string]> URL this revision can be downloaded from - - `local` <[boolean]> whether the revision is locally available on disk - -The method initiates a GET request to download the revision from the host. - -#### browserFetcher.host() -- returns: <[string]> The download host being used. - -#### browserFetcher.localRevisions() -- returns: <[Promise]<[Array]<[string]>>> A list of all revisions (for the current `product`) available locally on disk. - -#### browserFetcher.platform() -- returns: <[string]> One of `mac`, `linux`, `win32` or `win64`. - -#### browserFetcher.product() -- returns: <[string]> One of `chrome` or `firefox`. - -#### browserFetcher.remove(revision) -- `revision` <[string]> a revision to remove for the current `product`. The method will throw if the revision has not been downloaded. -- returns: <[Promise]> Resolves when the revision has been removed. - -#### browserFetcher.revisionInfo(revision) -- `revision` <[string]> a revision to get info for. -- returns: <[Object]> - - `revision` <[string]> the revision the info was created from - - `folderPath` <[string]> path to the extracted revision folder - - `executablePath` <[string]> path to the revision executable - - `url` <[string]> URL this revision can be downloaded from - - `local` <[boolean]> whether the revision is locally available on disk - - `product` <[string]> one of `chrome` or `firefox` - -> **NOTE** Many BrowserFetcher methods, like `remove` and `revisionInfo` -> are affected by the choice of `product`. See [puppeteer.createBrowserFetcher([options])](#puppeteercreatebrowserfetcheroptions). - -### class: Browser - -* extends: [EventEmitter](#class-eventemitter) - -A Browser is created when Puppeteer connects to a Chromium instance, either through [`puppeteer.launch`](#puppeteerlaunchoptions) or [`puppeteer.connect`](#puppeteerconnectoptions). - -An example of using a [Browser] to create a [Page]: -```js -const puppeteer = require('puppeteer'); - -(async () => { - const browser = await puppeteer.launch(); - const page = await browser.newPage(); - await page.goto('https://example.com'); - await browser.close(); -})(); -``` - -An example of disconnecting from and reconnecting to a [Browser]: -```js -const puppeteer = require('puppeteer'); - -(async () => { - const browser = await puppeteer.launch(); - // Store the endpoint to be able to reconnect to Chromium - const browserWSEndpoint = browser.wsEndpoint(); - // Disconnect puppeteer from Chromium - browser.disconnect(); - - // Use the endpoint to reestablish a connection - const browser2 = await puppeteer.connect({browserWSEndpoint}); - // Close Chromium - await browser2.close(); -})(); -``` -#### event: 'disconnected' -Emitted when Puppeteer gets disconnected from the Chromium instance. This might happen because of one of the following: -- Chromium is closed or crashed -- The [`browser.disconnect`](#browserdisconnect) method was called - -#### event: 'targetchanged' -- <[Target]> - -Emitted when the url of a target changes. - -> **NOTE** This includes target changes in incognito browser contexts. - - -#### event: 'targetcreated' -- <[Target]> - -Emitted when a target is created, for example when a new page is opened by [`window.open`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) or [`browser.newPage`](#browsernewpage). - -> **NOTE** This includes target creations in incognito browser contexts. - -#### event: 'targetdestroyed' -- <[Target]> - -Emitted when a target is destroyed, for example when a page is closed. - -> **NOTE** This includes target destructions in incognito browser contexts. - -#### browser.browserContexts() -- returns: <[Array]<[BrowserContext]>> - -Returns an array of all open browser contexts. In a newly created browser, this will return -a single instance of [BrowserContext]. - -#### browser.close() -- returns: <[Promise]> - -Closes Chromium and all of its pages (if any were opened). The [Browser] object itself is considered to be disposed and cannot be used anymore. - -#### browser.createIncognitoBrowserContext() -- returns: <[Promise]<[BrowserContext]>> - -Creates a new incognito browser context. This won't share cookies/cache with other browser contexts. - -```js -(async () => { - const browser = await puppeteer.launch(); - // Create a new incognito browser context. - const context = await browser.createIncognitoBrowserContext(); - // Create a new page in a pristine context. - const page = await context.newPage(); - // Do stuff - await page.goto('https://example.com'); -})(); -``` - -#### browser.defaultBrowserContext() -- returns: <[BrowserContext]> - -Returns the default browser context. The default browser context can not be closed. - -#### browser.disconnect() - -Disconnects Puppeteer from the browser, but leaves the Chromium process running. After calling `disconnect`, the [Browser] object is considered disposed and cannot be used anymore. - -#### browser.isConnected() - -- returns: <[boolean]> - -Indicates that the browser is connected. - -#### browser.newPage() -- returns: <[Promise]<[Page]>> - -Promise which resolves to a new [Page] object. The [Page] is created in a default browser context. - -#### browser.pages() -- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using [target.page()](#targetpage). - -An array of all pages inside the Browser. In case of multiple browser contexts, -the method will return an array with all the pages in all browser contexts. - -#### browser.process() -- returns: Spawned browser process. Returns `null` if the browser instance was created with [`puppeteer.connect`](#puppeteerconnectoptions) method. - -#### browser.target() -- returns: <[Target]> - -A target associated with the browser. - -#### browser.targets() -- returns: <[Array]<[Target]>> - -An array of all active targets inside the Browser. In case of multiple browser contexts, -the method will return an array with all the targets in all browser contexts. - -#### browser.userAgent() -- returns: <[Promise]<[string]>> Promise which resolves to the browser's original user agent. - -> **NOTE** Pages can override browser user agent with [page.setUserAgent](#pagesetuseragentuseragent) - -#### browser.version() -- returns: <[Promise]<[string]>> For headless Chromium, this is similar to `HeadlessChrome/61.0.3153.0`. For non-headless, this is similar to `Chrome/61.0.3153.0`. - -> **NOTE** the format of browser.version() might change with future releases of Chromium. - -#### browser.waitForTarget(predicate[, options]) -- `predicate` <[function]\([Target]\):[boolean]> A function to be run for every target -- `options` <[Object]> - - `timeout` <[number]> Maximum wait time in milliseconds. Pass `0` to disable the timeout. Defaults to 30 seconds. -- returns: <[Promise]<[Target]>> Promise which resolves to the first target found that matches the `predicate` function. - -This searches for a target in all browser contexts. - -An example of finding a target for a page opened via `window.open`: -```js -await page.evaluate(() => window.open('https://www.example.com/')); -const newWindowTarget = await browser.waitForTarget(target => target.url() === 'https://www.example.com/'); -``` - -#### browser.wsEndpoint() -- returns: <[string]> Browser websocket url. - -Browser websocket endpoint which can be used as an argument to -[puppeteer.connect](#puppeteerconnectoptions). The format is `ws://${host}:${port}/devtools/browser/` - -You can find the `webSocketDebuggerUrl` from `http://${host}:${port}/json/version`. Learn more about the [devtools protocol](https://chromedevtools.github.io/devtools-protocol) and the [browser endpoint](https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target). - -### class: BrowserContext - -* extends: [EventEmitter](#class-eventemitter) - -BrowserContexts provide a way to operate multiple independent browser sessions. When a browser is launched, it has -a single BrowserContext used by default. The method `browser.newPage()` creates a page in the default browser context. - -If a page opens another page, e.g. with a `window.open` call, the popup will belong to the parent page's browser -context. - -Puppeteer allows creation of "incognito" browser contexts with `browser.createIncognitoBrowserContext()` method. -"Incognito" browser contexts don't write any browsing data to disk. - -```js -// Create a new incognito browser context -const context = await browser.createIncognitoBrowserContext(); -// Create a new page inside context. -const page = await context.newPage(); -// ... do stuff with page ... -await page.goto('https://example.com'); -// Dispose context once it's no longer needed. -await context.close(); -``` - -#### event: 'targetchanged' -- <[Target]> - -Emitted when the url of a target inside the browser context changes. - -#### event: 'targetcreated' -- <[Target]> - -Emitted when a new target is created inside the browser context, for example when a new page is opened by [`window.open`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) or [`browserContext.newPage`](#browsercontextnewpage). - -#### event: 'targetdestroyed' -- <[Target]> - -Emitted when a target inside the browser context is destroyed, for example when a page is closed. - -#### browserContext.browser() -- returns: <[Browser]> - -The browser this browser context belongs to. - -#### browserContext.clearPermissionOverrides() -- returns: <[Promise]> - -Clears all permission overrides for the browser context. - -```js -const context = browser.defaultBrowserContext(); -context.overridePermissions('https://example.com', ['clipboard-read']); -// do stuff .. -context.clearPermissionOverrides(); -``` - -#### browserContext.close() -- returns: <[Promise]> - -Closes the browser context. All the targets that belong to the browser context -will be closed. - -> **NOTE** only incognito browser contexts can be closed. - -#### browserContext.isIncognito() -- returns: <[boolean]> - -Returns whether BrowserContext is incognito. -The default browser context is the only non-incognito browser context. - -> **NOTE** the default browser context cannot be closed. - -#### browserContext.newPage() -- returns: <[Promise]<[Page]>> - -Creates a new page in the browser context. - - -#### browserContext.overridePermissions(origin, permissions) -- `origin` <[string]> The [origin] to grant permissions to, e.g. "https://example.com". -- `permissions` <[Array]<[string]>> An array of permissions to grant. All permissions that are not listed here will be automatically denied. Permissions can be one of the following values: - - `'geolocation'` - - `'midi'` - - `'midi-sysex'` (system-exclusive midi) - - `'notifications'` - - `'push'` - - `'camera'` - - `'microphone'` - - `'background-sync'` - - `'ambient-light-sensor'` - - `'accelerometer'` - - `'gyroscope'` - - `'magnetometer'` - - `'accessibility-events'` - - `'clipboard-read'` - - `'clipboard-write'` - - `'payment-handler'` -- returns: <[Promise]> - - -```js -const context = browser.defaultBrowserContext(); -await context.overridePermissions('https://html5demos.com', ['geolocation']); -``` - - -#### browserContext.pages() -- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using [target.page()](#targetpage). - -An array of all pages inside the browser context. - -#### browserContext.targets() -- returns: <[Array]<[Target]>> - -An array of all active targets inside the browser context. - -#### browserContext.waitForTarget(predicate[, options]) -- `predicate` <[function]\([Target]\):[boolean]> A function to be run for every target -- `options` <[Object]> - - `timeout` <[number]> Maximum wait time in milliseconds. Pass `0` to disable the timeout. Defaults to 30 seconds. -- returns: <[Promise]<[Target]>> Promise which resolves to the first target found that matches the `predicate` function. - -This searches for a target in this specific browser context. - -An example of finding a target for a page opened via `window.open`: -```js -await page.evaluate(() => window.open('https://www.example.com/')); -const newWindowTarget = await browserContext.waitForTarget(target => target.url() === 'https://www.example.com/'); -``` - -### class: Page - -* extends: [EventEmitter](#class-eventemitter) - -Page provides methods to interact with a single tab or [extension background page](https://developer.chrome.com/extensions/background_pages) in Chromium. One [Browser] instance might have multiple [Page] instances. - -This example creates a page, navigates it to a URL, and then saves a screenshot: -```js -const puppeteer = require('puppeteer'); - -(async () => { - const browser = await puppeteer.launch(); - const page = await browser.newPage(); - await page.goto('https://example.com'); - await page.screenshot({path: 'screenshot.png'}); - await browser.close(); -})(); -``` - -The Page class emits various events (described below) which can be handled using -any of the [`EventEmitter`](#class-eventemitter) methods, such as `on`, `once` -or `off`. - -This example logs a message for a single page `load` event: -```js -page.once('load', () => console.log('Page loaded!')); -``` - -To unsubscribe from events use the `off` method: - -```js -function logRequest(interceptedRequest) { - console.log('A request was made:', interceptedRequest.url()); -} -page.on('request', logRequest); -// Sometime later... -page.off('request', logRequest); -``` - -#### event: 'close' - -Emitted when the page closes. - -#### event: 'console' -- <[ConsoleMessage]> - -Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also emitted if the page throws an error or a warning. - -The arguments passed into `console.log` appear as arguments on the event handler. - -An example of handling `console` event: -```js -page.on('console', msg => { - for (let i = 0; i < msg.args().length; ++i) - console.log(`${i}: ${msg.args()[i]}`); -}); -page.evaluate(() => console.log('hello', 5, {foo: 'bar'})); -``` - -#### event: 'dialog' -- <[Dialog]> - -Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Puppeteer can respond to the dialog via [Dialog]'s [accept](#dialogacceptprompttext) or [dismiss](#dialogdismiss) methods. - -#### event: 'domcontentloaded' - -Emitted when the JavaScript [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded) event is dispatched. - -#### event: 'error' -- <[Error]> - -Emitted when the page crashes. - -> **NOTE** `error` event has a special meaning in Node, see [error events](https://nodejs.org/api/events.html#events_error_events) for details. - -#### event: 'frameattached' -- <[Frame]> - -Emitted when a frame is attached. - -#### event: 'framedetached' -- <[Frame]> - -Emitted when a frame is detached. - -#### event: 'framenavigated' -- <[Frame]> - -Emitted when a frame is navigated to a new url. - -#### event: 'load' - -Emitted when the JavaScript [`load`](https://developer.mozilla.org/en-US/docs/Web/Events/load) event is dispatched. - -#### event: 'metrics' -- <[Object]> - - `title` <[string]> The title passed to `console.timeStamp`. - - `metrics` <[Object]> Object containing metrics as key/value pairs. The values - of metrics are of <[number]> type. - -Emitted when the JavaScript code makes a call to `console.timeStamp`. For the list -of metrics see `page.metrics`. - -#### event: 'pageerror' -- <[Error]> The exception message - -Emitted when an uncaught exception happens within the page. - -#### event: 'popup' -- <[Page]> Page corresponding to "popup" window - -Emitted when the page opens a new tab or window. - -```js -const [popup] = await Promise.all([ - new Promise(resolve => page.once('popup', resolve)), - page.click('a[target=_blank]'), -]); -``` - -```js -const [popup] = await Promise.all([ - new Promise(resolve => page.once('popup', resolve)), - page.evaluate(() => window.open('https://example.com')), -]); -``` - -#### event: 'request' -- <[HTTPRequest]> - -Emitted when a page issues a request. The [request] object is read-only. -In order to intercept and mutate requests, see `page.setRequestInterception`. - -#### event: 'requestfailed' -- <[HTTPRequest]> - -Emitted when a request fails, for example by timing out. - -> **NOTE** HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will complete with [`'requestfinished'`](#event-requestfinished) event and not with [`'requestfailed'`](#event-requestfailed). - -#### event: 'requestfinished' -- <[HTTPRequest]> - -Emitted when a request finishes successfully. - -#### event: 'response' -- <[HTTPResponse]> - -Emitted when a [response] is received. - -#### event: 'workercreated' -- <[Worker]> - -Emitted when a dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is spawned by the page. - -#### event: 'workerdestroyed' -- <[Worker]> - -Emitted when a dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is terminated. - -#### page.$(selector) -- `selector` <[string]> A [selector] to query page for -- returns: <[Promise]> - -The method runs `document.querySelector` within the page. If no element matches the selector, the return value resolves to `null`. - -Shortcut for [page.mainFrame().$(selector)](#frameselector). - -#### page.$$(selector) -- `selector` <[string]> A [selector] to query page for -- returns: <[Promise]<[Array]<[ElementHandle]>>> - -The method runs `document.querySelectorAll` within the page. If no elements match the selector, the return value resolves to `[]`. - -Shortcut for [page.mainFrame().$$(selector)](#frameselector-1). - -#### page.$$eval(selector, pageFunction[, ...args]) -- `selector` <[string]> A [selector] to query page for -- `pageFunction` <[function]\([Array]<[Element]>\)> Function to be evaluated in browser context -- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` -- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` - -This method runs `Array.from(document.querySelectorAll(selector))` within the page and passes it as the first argument to `pageFunction`. - -If `pageFunction` returns a [Promise], then `page.$$eval` would wait for the promise to resolve and return its value. - -Examples: -```js -const divCount = await page.$$eval('div', divs => divs.length); -``` - -```js -const options = await page.$$eval('div > span.options', options => options.map(option => option.textContent)); -``` - -#### page.$eval(selector, pageFunction[, ...args]) -- `selector` <[string]> A [selector] to query page for -- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context -- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` -- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` - -This method runs `document.querySelector` within the page and passes it as the first argument to `pageFunction`. If there's no element matching `selector`, the method throws an error. - -If `pageFunction` returns a [Promise], then `page.$eval` would wait for the promise to resolve and return its value. - -Examples: -```js -const searchValue = await page.$eval('#search', el => el.value); -const preloadHref = await page.$eval('link[rel=preload]', el => el.href); -const html = await page.$eval('.main-container', e => e.outerHTML); -``` - -Shortcut for [page.mainFrame().$eval(selector, pageFunction)](#frameevalselector-pagefunction-args). - -#### page.$x(expression) -- `expression` <[string]> Expression to [evaluate](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate). -- returns: <[Promise]<[Array]<[ElementHandle]>>> - -The method evaluates the XPath expression. - -Shortcut for [page.mainFrame().$x(expression)](#framexexpression) - -#### page.accessibility -- returns: <[Accessibility]> - -#### page.addScriptTag(options) -- `options` <[Object]> - - `url` <[string]> URL of a script to be added. - - `path` <[string]> Path to the JavaScript file to be injected into frame. If `path` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). - - `content` <[string]> Raw JavaScript content to be injected into frame. - - `type` <[string]> Script type. Use 'module' in order to load a Javascript ES6 module. See [script](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script) for more details. -- returns: <[Promise]<[ElementHandle]>> which resolves to the added tag when the script's onload fires or when the script content was injected into frame. - -Adds a ` + Accessible Name + + + + +
+
+
+
item1
+
item2
+
+
item3
+
+ +
+ ` + ); + }); + const getIds = async (elements: ElementHandle[]) => + Promise.all( + elements.map((element) => + element.evaluate((element: Element) => element.id) + ) + ); + it('should find by name "foo"', async () => { + const { page } = getTestState(); + const found = await page.$$('aria/foo'); + const ids = await getIds(found); + expect(ids).toEqual(['node3', 'node5', 'node6']); + }); + it('should find by name "bar"', async () => { + const { page } = getTestState(); + const found = await page.$$('aria/bar'); + const ids = await getIds(found); + expect(ids).toEqual(['node1', 'node2', 'node8']); + }); + it('should find treeitem by name', async () => { + const { page } = getTestState(); + const found = await page.$$('aria/item1 item2 item3'); + const ids = await getIds(found); + expect(ids).toEqual(['node30']); + }); + it('should find by role "button"', async () => { + const { page } = getTestState(); + const found = await page.$$('aria/[role="button"]'); + const ids = await getIds(found); + expect(ids).toEqual(['node5', 'node6', 'node8', 'node10', 'node21']); + }); + it('should find by role "heading"', async () => { + const { page } = getTestState(); + const found = await page.$$('aria/[role="heading"]'); + const ids = await getIds(found); + expect(ids).toEqual(['shown', 'hidden', 'node11', 'node13']); + }); + it('should find both ignored and unignored', async () => { + const { page } = getTestState(); + const found = await page.$$('aria/title'); + const ids = await getIds(found); + expect(ids).toEqual(['shown', 'hidden']); + }); + }); +}); diff --git a/remote/test/puppeteer/test/assets/consolelog.html b/remote/test/puppeteer/test/assets/consolelog.html index 7fa1b211a4df0..4a27803aa9a28 100644 --- a/remote/test/puppeteer/test/assets/consolelog.html +++ b/remote/test/puppeteer/test/assets/consolelog.html @@ -5,7 +5,13 @@ diff --git a/remote/test/puppeteer/test/assets/frames/one-frame-url-fragment.html b/remote/test/puppeteer/test/assets/frames/one-frame-url-fragment.html new file mode 100644 index 0000000000000..d1462641ff267 --- /dev/null +++ b/remote/test/puppeteer/test/assets/frames/one-frame-url-fragment.html @@ -0,0 +1 @@ + diff --git a/remote/test/puppeteer/test/assets/idle-detector.html b/remote/test/puppeteer/test/assets/idle-detector.html new file mode 100644 index 0000000000000..83b496c03dfaa --- /dev/null +++ b/remote/test/puppeteer/test/assets/idle-detector.html @@ -0,0 +1,23 @@ + +
+ diff --git a/remote/test/puppeteer/test/assets/input/wheel.html b/remote/test/puppeteer/test/assets/input/wheel.html new file mode 100644 index 0000000000000..3d093a993e70f --- /dev/null +++ b/remote/test/puppeteer/test/assets/input/wheel.html @@ -0,0 +1,43 @@ + + + + + + Element: wheel event - Scaling_an_element_via_the_wheel - code sample + + +
Scale me with your mouse wheel.
+ + + diff --git a/remote/test/puppeteer/test/browser.spec.ts b/remote/test/puppeteer/test/browser.spec.ts index af2c71f8fcf8d..0d06e7f60e954 100644 --- a/remote/test/puppeteer/test/browser.spec.ts +++ b/remote/test/puppeteer/test/browser.spec.ts @@ -15,7 +15,7 @@ */ import expect from 'expect'; -import { getTestState, setupTestBrowserHooks } from './mocha-utils'; +import { getTestState, setupTestBrowserHooks } from './mocha-utils'; // eslint-disable-line import/extensions describe('Browser specs', function () { setupTestBrowserHooks(); diff --git a/remote/test/puppeteer/test/browsercontext.spec.ts b/remote/test/puppeteer/test/browsercontext.spec.ts index 9f8eed2191e7b..dd2be4b6732a3 100644 --- a/remote/test/puppeteer/test/browsercontext.spec.ts +++ b/remote/test/puppeteer/test/browsercontext.spec.ts @@ -18,9 +18,8 @@ import expect from 'expect'; import { getTestState, setupTestBrowserHooks, - itFailsFirefox, -} from './mocha-utils'; -import utils from './utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions +import utils from './utils.js'; describe('BrowserContext', function () { setupTestBrowserHooks(); @@ -66,7 +65,10 @@ describe('BrowserContext', function () { await page.goto(server.EMPTY_PAGE); const [popupTarget] = await Promise.all([ utils.waitEvent(browser, 'targetcreated'), - page.evaluate((url) => window.open(url), server.EMPTY_PAGE), + page.evaluate<(url: string) => void>( + (url) => window.open(url), + server.EMPTY_PAGE + ), ]); expect(popupTarget.browserContext()).toBe(context); await context.close(); diff --git a/remote/test/puppeteer/test/chromiumonly.spec.ts b/remote/test/puppeteer/test/chromiumonly.spec.ts index 8c0ee81e9eb4f..7b64a70fd396c 100644 --- a/remote/test/puppeteer/test/chromiumonly.spec.ts +++ b/remote/test/puppeteer/test/chromiumonly.spec.ts @@ -19,7 +19,7 @@ import { setupTestBrowserHooks, setupTestPageAndContextHooks, describeChromeOnly, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describeChromeOnly('Chromium-Specific Launcher tests', function () { describe('Puppeteer.launch |browserURL| option', function () { @@ -84,7 +84,7 @@ describeChromeOnly('Chromium-Specific Launcher tests', function () { .connect({ browserURL }) .catch((error_) => (error = error_)); expect(error.message).toContain( - 'Failed to fetch browser webSocket url from' + 'Failed to fetch browser webSocket URL from' ); originalBrowser.close(); }); diff --git a/remote/test/puppeteer/test/click.spec.ts b/remote/test/puppeteer/test/click.spec.ts index 9dd77a44d539a..48d4b64409e93 100644 --- a/remote/test/puppeteer/test/click.spec.ts +++ b/remote/test/puppeteer/test/click.spec.ts @@ -20,8 +20,8 @@ import { setupTestPageAndContextHooks, setupTestBrowserHooks, itFailsFirefox, -} from './mocha-utils'; -import utils from './utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions +import utils from './utils.js'; describe('Page.click', function () { setupTestBrowserHooks(); @@ -328,7 +328,8 @@ describe('Page.click', function () { await frame.click('button'); expect(await frame.evaluate(() => globalThis.result)).toBe('Clicked'); }); - it('should click the button with deviceScaleFactor set', + it( + 'should click the button with deviceScaleFactor set', async () => { const { page, server } = getTestState(); diff --git a/remote/test/puppeteer/test/cookies.spec.ts b/remote/test/puppeteer/test/cookies.spec.ts index c814dc814cddb..6d3d9a8cab492 100644 --- a/remote/test/puppeteer/test/cookies.spec.ts +++ b/remote/test/puppeteer/test/cookies.spec.ts @@ -19,7 +19,7 @@ import { setupTestBrowserHooks, setupTestPageAndContextHooks, itFailsFirefox, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('Cookie specs', () => { setupTestBrowserHooks(); @@ -389,9 +389,9 @@ describe('Cookie specs', () => { await page.goto(server.PREFIX + '/grid.html'); await page.setCookie({ name: 'localhost-cookie', value: 'best' }); - await page.evaluate((src) => { + await page.evaluate<(src: string) => Promise>((src) => { let fulfill; - const promise = new Promise((x) => (fulfill = x)); + const promise = new Promise((x) => (fulfill = x)); const iframe = document.createElement('iframe'); document.body.appendChild(iframe); iframe.onload = fulfill; @@ -454,9 +454,9 @@ describe('Cookie specs', () => { try { await page.goto(httpsServer.PREFIX + '/grid.html'); - await page.evaluate((src) => { + await page.evaluate<(src: string) => Promise>((src) => { let fulfill; - const promise = new Promise((x) => (fulfill = x)); + const promise = new Promise((x) => (fulfill = x)); const iframe = document.createElement('iframe'); document.body.appendChild(iframe); iframe.onload = fulfill; diff --git a/remote/test/puppeteer/test/coverage-utils.js b/remote/test/puppeteer/test/coverage-utils.js index 2e6084bb7dbd6..c23e507f9de2a 100644 --- a/remote/test/puppeteer/test/coverage-utils.js +++ b/remote/test/puppeteer/test/coverage-utils.js @@ -34,13 +34,44 @@ const path = require('path'); const fs = require('fs'); /** - * @param {Map} apiCoverage - * @param {Object} events - * @param {string} className - * @param {!Object} classType + * This object is also used by DocLint to know which classes to check are + * documented. It's a pretty hacky solution but DocLint is going away soon as + * part of the TSDoc migration. */ -function traceAPICoverage(apiCoverage, events, className, classType) { - className = className.substring(0, 1).toLowerCase() + className.substring(1); +const MODULES_TO_CHECK_FOR_COVERAGE = { + Accessibility: '../lib/cjs/puppeteer/common/Accessibility', + Browser: '../lib/cjs/puppeteer/common/Browser', + BrowserContext: '../lib/cjs/puppeteer/common/Browser', + BrowserFetcher: '../lib/cjs/puppeteer/node/BrowserFetcher', + CDPSession: '../lib/cjs/puppeteer/common/Connection', + ConsoleMessage: '../lib/cjs/puppeteer/common/ConsoleMessage', + Coverage: '../lib/cjs/puppeteer/common/Coverage', + Dialog: '../lib/cjs/puppeteer/common/Dialog', + ElementHandle: '../lib/cjs/puppeteer/common/JSHandle', + ExecutionContext: '../lib/cjs/puppeteer/common/ExecutionContext', + EventEmitter: '../lib/cjs/puppeteer/common/EventEmitter', + FileChooser: '../lib/cjs/puppeteer/common/FileChooser', + Frame: '../lib/cjs/puppeteer/common/FrameManager', + JSHandle: '../lib/cjs/puppeteer/common/JSHandle', + Keyboard: '../lib/cjs/puppeteer/common/Input', + Mouse: '../lib/cjs/puppeteer/common/Input', + Page: '../lib/cjs/puppeteer/common/Page', + Puppeteer: '../lib/cjs/puppeteer/common/Puppeteer', + PuppeteerNode: '../lib/cjs/puppeteer/node/Puppeteer', + HTTPRequest: '../lib/cjs/puppeteer/common/HTTPRequest', + HTTPResponse: '../lib/cjs/puppeteer/common/HTTPResponse', + SecurityDetails: '../lib/cjs/puppeteer/common/SecurityDetails', + Target: '../lib/cjs/puppeteer/common/Target', + TimeoutError: '../lib/cjs/puppeteer/common/Errors', + Touchscreen: '../lib/cjs/puppeteer/common/Input', + Tracing: '../lib/cjs/puppeteer/common/Tracing', + WebWorker: '../lib/cjs/puppeteer/common/WebWorker', +}; + +function traceAPICoverage(apiCoverage, className, modulePath) { + const loadedModule = require(modulePath); + const classType = loadedModule[className]; + if (!classType || !classType.prototype) { console.error( `Coverage error: could not find class for ${className}. Is src/api.ts up to date?` @@ -63,8 +94,14 @@ function traceAPICoverage(apiCoverage, events, className, classType) { }); } - if (events[classType.name]) { - for (const event of Object.values(events[classType.name])) { + /** + * If classes emit events, those events are exposed via an object in the same + * module named XEmittedEvents, where X is the name of the class. For example, + * the Page module exposes PageEmittedEvents. + */ + const eventsName = `${className}EmittedEvents`; + if (loadedModule[eventsName]) { + for (const event of Object.values(loadedModule[eventsName])) { if (typeof event !== 'symbol') apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, false); } @@ -108,10 +145,11 @@ const trackCoverage = () => { return { beforeAll: () => { - const api = require('../src/api'); - const events = require('../src/common/Events'); - for (const [className, classType] of Object.entries(api)) - traceAPICoverage(coverageMap, events, className, classType); + for (const [className, moduleFilePath] of Object.entries( + MODULES_TO_CHECK_FOR_COVERAGE + )) { + traceAPICoverage(coverageMap, className, moduleFilePath); + } }, afterAll: () => { writeCoverage(coverageMap); @@ -122,4 +160,5 @@ const trackCoverage = () => { module.exports = { trackCoverage, getCoverageResults, + MODULES_TO_CHECK_FOR_COVERAGE, }; diff --git a/remote/test/puppeteer/test/coverage.spec.ts b/remote/test/puppeteer/test/coverage.spec.ts index a75a6a8770cf6..d3aa3bf22582a 100644 --- a/remote/test/puppeteer/test/coverage.spec.ts +++ b/remote/test/puppeteer/test/coverage.spec.ts @@ -20,7 +20,7 @@ import { setupTestPageAndContextHooks, setupTestBrowserHooks, describeChromeOnly, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('Coverage specs', function () { describeChromeOnly('JSCoverage', function () { @@ -264,7 +264,7 @@ describe('Coverage specs', function () { const { page, server } = getTestState(); await page.coverage.startCSSCoverage(); - await page.evaluate(async (url) => { + await page.evaluate<(url: string) => Promise>(async (url) => { document.body.textContent = 'hello, world'; const link = document.createElement('link'); diff --git a/remote/test/puppeteer/test/defaultbrowsercontext.spec.ts b/remote/test/puppeteer/test/defaultbrowsercontext.spec.ts index 73b4b19adec62..23521ae762c40 100644 --- a/remote/test/puppeteer/test/defaultbrowsercontext.spec.ts +++ b/remote/test/puppeteer/test/defaultbrowsercontext.spec.ts @@ -19,7 +19,7 @@ import { setupTestBrowserHooks, setupTestPageAndContextHooks, itFailsFirefox, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('DefaultBrowserContext', function () { setupTestBrowserHooks(); diff --git a/remote/test/puppeteer/test/dialog.spec.ts b/remote/test/puppeteer/test/dialog.spec.ts index cfb236dcb4657..0064020fff5d7 100644 --- a/remote/test/puppeteer/test/dialog.spec.ts +++ b/remote/test/puppeteer/test/dialog.spec.ts @@ -20,8 +20,7 @@ import { getTestState, setupTestPageAndContextHooks, setupTestBrowserHooks, - itFailsFirefox, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('Page.Events.Dialog', function () { setupTestBrowserHooks(); diff --git a/remote/test/puppeteer/test/elementhandle.spec.ts b/remote/test/puppeteer/test/elementhandle.spec.ts index 46de86c65c0c7..617ff8e0ecfdc 100644 --- a/remote/test/puppeteer/test/elementhandle.spec.ts +++ b/remote/test/puppeteer/test/elementhandle.spec.ts @@ -15,16 +15,15 @@ */ import expect from 'expect'; +import sinon from 'sinon'; import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, - describeFailsFirefox, - itFailsFirefox, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions -import utils from './utils'; -import { ElementHandle } from '../src/common/JSHandle'; +import utils from './utils.js'; +import { ElementHandle } from '../lib/cjs/puppeteer/common/JSHandle.js'; describe('ElementHandle specs', function () { setupTestBrowserHooks(); @@ -49,7 +48,7 @@ describe('ElementHandle specs', function () { const elementHandle = await nestedFrame.$('div'); const box = await elementHandle.boundingBox(); if (isChrome) - expect(box).toEqual({ x: 28, y: 260, width: 264, height: 18 }); + expect(box).toEqual({ x: 28, y: 182, width: 264, height: 18 }); else expect(box).toEqual({ x: 28, y: 182, width: 254, height: 18 }); }); it('should return null for invisible elements', async () => { @@ -67,7 +66,7 @@ describe('ElementHandle specs', function () { '
hello
' ); const elementHandle = await page.$('div'); - await page.evaluate( + await page.evaluate<(element: HTMLElement) => void>( (element) => (element.style.height = '200px'), elementHandle ); @@ -84,7 +83,7 @@ describe('ElementHandle specs', function () { `); const element = await page.$('#therect'); const pptrBoundingBox = await element.boundingBox(); - const webBoundingBox = await page.evaluate((e) => { + const webBoundingBox = await page.evaluate((e: HTMLElement) => { const rect = e.getBoundingClientRect(); return { x: rect.x, y: rect.y, width: rect.width, height: rect.height }; }, element); @@ -198,11 +197,10 @@ describe('ElementHandle specs', function () { const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/button.html'); - const buttonTextNode = await page.evaluateHandle( + const buttonTextNode = await page.evaluateHandle( () => document.querySelector('button').firstChild ); let error = null; - // @ts-expect-error await buttonTextNode.click().catch((error_) => (error = error_)); expect(error.message).toBe('Node is not of type HTMLElement'); }); @@ -211,7 +209,7 @@ describe('ElementHandle specs', function () { await page.goto(server.PREFIX + '/input/button.html'); const button = await page.$('button'); - await page.evaluate((button) => button.remove(), button); + await page.evaluate((button: HTMLElement) => button.remove(), button); let error = null; await button.click().catch((error_) => (error = error_)); expect(error.message).toBe('Node is detached from document'); @@ -221,7 +219,10 @@ describe('ElementHandle specs', function () { await page.goto(server.PREFIX + '/input/button.html'); const button = await page.$('button'); - await page.evaluate((button) => (button.style.display = 'none'), button); + await page.evaluate( + (button: HTMLElement) => (button.style.display = 'none'), + button + ); const error = await button.click().catch((error_) => error_); expect(error.message).toBe( 'Node is either not visible or not an HTMLElement' @@ -233,7 +234,7 @@ describe('ElementHandle specs', function () { await page.goto(server.PREFIX + '/input/button.html'); const button = await page.$('button'); await page.evaluate( - (button) => (button.parentElement.style.display = 'none'), + (button: HTMLElement) => (button.parentElement.style.display = 'none'), button ); const error = await button.click().catch((error_) => error_); @@ -283,22 +284,29 @@ describe('ElementHandle specs', function () { describe('Custom queries', function () { this.afterEach(() => { const { puppeteer } = getTestState(); - puppeteer.__experimental_clearQueryHandlers(); + puppeteer.clearCustomQueryHandlers(); }); it('should register and unregister', async () => { const { page, puppeteer } = getTestState(); await page.setContent('
'); // Register. - puppeteer.__experimental_registerCustomQueryHandler( - 'getById', - (element, selector) => document.querySelector(`[id="${selector}"]`) - ); + puppeteer.registerCustomQueryHandler('getById', { + queryOne: (element, selector) => + document.querySelector(`[id="${selector}"]`), + }); const element = await page.$('getById/foo'); - expect(await page.evaluate((element) => element.id, element)).toBe('foo'); + expect( + await page.evaluate<(element: HTMLElement) => string>( + (element) => element.id, + element + ) + ).toBe('foo'); + const handlerNamesAfterRegistering = puppeteer.customQueryHandlerNames(); + expect(handlerNamesAfterRegistering.includes('getById')).toBeTruthy(); // Unregister. - puppeteer.__experimental_unregisterCustomQueryHandler('getById'); + puppeteer.unregisterCustomQueryHandler('getById'); try { await page.$('getById/foo'); throw new Error('Custom query handler name not set - throw expected'); @@ -309,15 +317,15 @@ describe('ElementHandle specs', function () { ) ); } + const handlerNamesAfterUnregistering = puppeteer.customQueryHandlerNames(); + expect(handlerNamesAfterUnregistering.includes('getById')).toBeFalsy(); }); it('should throw with invalid query names', () => { try { const { puppeteer } = getTestState(); - puppeteer.__experimental_registerCustomQueryHandler( - '1/2/3', - // @ts-expect-error - () => {} - ); + puppeteer.registerCustomQueryHandler('1/2/3', { + queryOne: () => document.querySelector('foo'), + }); throw new Error( 'Custom query handler name was invalid - throw expected' ); @@ -332,15 +340,18 @@ describe('ElementHandle specs', function () { await page.setContent( '
Foo1
Foo2
' ); - puppeteer.__experimental_registerCustomQueryHandler( - 'getByClass', - (element, selector) => document.querySelectorAll(`.${selector}`) - ); + puppeteer.registerCustomQueryHandler('getByClass', { + queryAll: (element, selector) => + document.querySelectorAll(`.${selector}`), + }); const elements = await page.$$('getByClass/foo'); const classNames = await Promise.all( elements.map( async (element) => - await page.evaluate((element) => element.className, element) + await page.evaluate<(element: HTMLElement) => string>( + (element) => element.className, + element + ) ) ); @@ -351,10 +362,10 @@ describe('ElementHandle specs', function () { await page.setContent( '
Foo1
Foo2
' ); - puppeteer.__experimental_registerCustomQueryHandler( - 'getByClass', - (element, selector) => document.querySelectorAll(`.${selector}`) - ); + puppeteer.registerCustomQueryHandler('getByClass', { + queryAll: (element, selector) => + document.querySelectorAll(`.${selector}`), + }); const elements = await page.$$eval( 'getByClass/foo', (divs) => divs.length @@ -364,10 +375,9 @@ describe('ElementHandle specs', function () { }); it('should wait correctly with waitForSelector', async () => { const { page, puppeteer } = getTestState(); - puppeteer.__experimental_registerCustomQueryHandler( - 'getByClass', - (element, selector) => element.querySelector(`.${selector}`) - ); + puppeteer.registerCustomQueryHandler('getByClass', { + queryOne: (element, selector) => element.querySelector(`.${selector}`), + }); const waitFor = page.waitForSelector('getByClass/foo'); // Set the page content after the waitFor has been started. @@ -378,12 +388,14 @@ describe('ElementHandle specs', function () { expect(element).toBeDefined(); }); + it('should wait correctly with waitFor', async () => { + /* page.waitFor is deprecated so we silence the warning to avoid test noise */ + sinon.stub(console, 'warn').callsFake(() => {}); const { page, puppeteer } = getTestState(); - puppeteer.__experimental_registerCustomQueryHandler( - 'getByClass', - (element, selector) => element.querySelector(`.${selector}`) - ); + puppeteer.registerCustomQueryHandler('getByClass', { + queryOne: (element, selector) => element.querySelector(`.${selector}`), + }); const waitFor = page.waitFor('getByClass/foo'); // Set the page content after the waitFor has been started. @@ -394,5 +406,44 @@ describe('ElementHandle specs', function () { expect(element).toBeDefined(); }); + it('should work when both queryOne and queryAll are registered', async () => { + const { page, puppeteer } = getTestState(); + await page.setContent( + '
Foo2
' + ); + puppeteer.registerCustomQueryHandler('getByClass', { + queryOne: (element, selector) => element.querySelector(`.${selector}`), + queryAll: (element, selector) => + element.querySelectorAll(`.${selector}`), + }); + + const element = await page.$('getByClass/foo'); + expect(element).toBeDefined(); + + const elements = await page.$$('getByClass/foo'); + expect(elements.length).toBe(3); + }); + it('should eval when both queryOne and queryAll are registered', async () => { + const { page, puppeteer } = getTestState(); + await page.setContent( + '
text
content
' + ); + puppeteer.registerCustomQueryHandler('getByClass', { + queryOne: (element, selector) => element.querySelector(`.${selector}`), + queryAll: (element, selector) => + element.querySelectorAll(`.${selector}`), + }); + + const txtContent = await page.$eval( + 'getByClass/foo', + (div) => div.textContent + ); + expect(txtContent).toBe('text'); + + const txtContents = await page.$$eval('getByClass/foo', (divs) => + divs.map((d) => d.textContent).join('') + ); + expect(txtContents).toBe('textcontent'); + }); }); }); diff --git a/remote/test/puppeteer/test/emulation.spec.ts b/remote/test/puppeteer/test/emulation.spec.ts index c4d015d17ca8a..f594127a63f4e 100644 --- a/remote/test/puppeteer/test/emulation.spec.ts +++ b/remote/test/puppeteer/test/emulation.spec.ts @@ -19,7 +19,7 @@ import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('Emulation', () => { setupTestBrowserHooks(); @@ -88,18 +88,15 @@ describe('Emulation', () => { 'YES' ); }); - it( - 'should detect touch when applying viewport with touches', - async () => { - const { page, server } = getTestState(); - - await page.setViewport({ width: 800, height: 600, hasTouch: true }); - await page.addScriptTag({ url: server.PREFIX + '/modernizr.js' }); - expect( - await page.evaluate(() => globalThis.Modernizr.touchevents) - ).toBe(true); - } - ); + it('should detect touch when applying viewport with touches', async () => { + const { page, server } = getTestState(); + + await page.setViewport({ width: 800, height: 600, hasTouch: true }); + await page.addScriptTag({ url: server.PREFIX + '/modernizr.js' }); + expect(await page.evaluate(() => globalThis.Modernizr.touchevents)).toBe( + true + ); + }); it('should support landscape emulation', async () => { const { page, server } = getTestState(); @@ -136,7 +133,7 @@ describe('Emulation', () => { await page.goto(server.PREFIX + '/input/button.html'); const button = await page.$('button'); await page.evaluate( - (button) => (button.style.marginTop = '200px'), + (button: HTMLElement) => (button.style.marginTop = '200px'), button ); await button.click(); diff --git a/remote/test/puppeteer/test/evaluation.spec.ts b/remote/test/puppeteer/test/evaluation.spec.ts index 28c0ffa5f673c..3e9423728a3d5 100644 --- a/remote/test/puppeteer/test/evaluation.spec.ts +++ b/remote/test/puppeteer/test/evaluation.spec.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import utils from './utils'; +import utils from './utils.js'; import expect from 'expect'; import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions const bigint = typeof BigInt !== 'undefined'; @@ -38,7 +38,7 @@ describe('Evaluation specs', function () { (bigint ? it : xit)('should transfer BigInt', async () => { const { page } = getTestState(); - const result = await page.evaluate((a) => a, BigInt(42)); + const result = await page.evaluate((a: BigInt) => a, BigInt(42)); expect(result).toBe(BigInt(42)); }); it('should transfer NaN', async () => { @@ -153,7 +153,11 @@ describe('Evaluation specs', function () { // Setup inpage callback, which calls Page.evaluate await page.exposeFunction('callController', async function (a, b) { - return await page.evaluate((a, b) => a * b, a, b); + return await page.evaluate<(a: number, b: number) => number>( + (a, b) => a * b, + a, + b + ); }); const result = await page.evaluate(async function () { return await globalThis.callController(9, 3); @@ -275,7 +279,7 @@ describe('Evaluation specs', function () { .jsonValue() .catch((error_) => error_.message); const error = await page - .evaluate((errorText) => { + .evaluate<(errorText: string) => Error>((errorText) => { throw new Error(errorText); }, errorText) .catch((error_) => error_); @@ -304,7 +308,10 @@ describe('Evaluation specs', function () { await page.setContent('
42
'); const element = await page.$('section'); - const text = await page.evaluate((e) => e.textContent, element); + const text = await page.evaluate<(e: HTMLElement) => string>( + (e) => e.textContent, + element + ); expect(text).toBe('42'); }); it('should throw if underlying element was disposed', async () => { @@ -316,7 +323,7 @@ describe('Evaluation specs', function () { await element.dispose(); let error = null; await page - .evaluate((e) => e.textContent, element) + .evaluate((e: HTMLElement) => e.textContent, element) .catch((error_) => (error = error_)); expect(error.message).toContain('JSHandle is disposed'); }); @@ -329,7 +336,7 @@ describe('Evaluation specs', function () { const bodyHandle = await page.frames()[1].$('body'); let error = null; await page - .evaluate((body) => body.innerHTML, bodyHandle) + .evaluate((body: HTMLElement) => body.innerHTML, bodyHandle) .catch((error_) => (error = error_)); expect(error).toBeTruthy(); expect(error.message).toContain( @@ -377,7 +384,7 @@ describe('Evaluation specs', function () { it('should transfer 100Mb of data from page to node.js', async function () { const { page } = getTestState(); - const a = await page.evaluate(() => + const a = await page.evaluate<() => string>(() => Array(100 * 1024 * 1024 + 1).join('a') ); expect(a.length).toBe(100 * 1024 * 1024); diff --git a/remote/test/puppeteer/test/fixtures.spec.ts b/remote/test/puppeteer/test/fixtures.spec.ts index 8a45c103067f7..8eca362071bdf 100644 --- a/remote/test/puppeteer/test/fixtures.spec.ts +++ b/remote/test/puppeteer/test/fixtures.spec.ts @@ -17,7 +17,7 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import expect from 'expect'; -import { getTestState, itChromeOnly } from './mocha-utils'; +import { getTestState, itChromeOnly } from './mocha-utils'; // eslint-disable-line import/extensions import path from 'path'; diff --git a/remote/test/puppeteer/test/frame.spec.ts b/remote/test/puppeteer/test/frame.spec.ts index 0a914365956d4..6f226c12ea6d5 100644 --- a/remote/test/puppeteer/test/frame.spec.ts +++ b/remote/test/puppeteer/test/frame.spec.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import utils from './utils'; +import utils from './utils.js'; import expect from 'expect'; import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('Frame specs', function () { setupTestBrowserHooks(); @@ -75,7 +75,7 @@ describe('Frame specs', function () { let error = null; await frame1.evaluate(() => 7 * 8).catch((error_) => (error = error_)); expect(error.message).toContain( - 'Execution Context is not available in detached frame' + 'Execution context is not available in detached frame' ); }); }); @@ -199,7 +199,7 @@ describe('Frame specs', function () { const { page, server } = getTestState(); await page.goto(server.PREFIX + '/shadow.html'); - await page.evaluate(async (url) => { + await page.evaluate(async (url: string) => { const frame = document.createElement('iframe'); frame.src = url; document.body.shadowRoot.appendChild(frame); @@ -212,7 +212,7 @@ describe('Frame specs', function () { const { page, server } = getTestState(); await utils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE); - await page.evaluate((url) => { + await page.evaluate((url: string) => { const frame = document.createElement('iframe'); frame.name = 'theFrameName'; frame.src = url; @@ -255,5 +255,15 @@ describe('Frame specs', function () { expect(frame1).not.toBe(frame2); } ); + it('should support url fragment', async () => { + const { page, server } = getTestState(); + + await page.goto(server.PREFIX + '/frames/one-frame-url-fragment.html'); + + expect(page.frames().length).toBe(2); + expect(page.frames()[1].url()).toBe( + server.PREFIX + '/frames/frame.html?param=value#fragment' + ); + }); }); }); diff --git a/remote/test/puppeteer/test/headful.spec.ts b/remote/test/puppeteer/test/headful.spec.ts index 88237c0d2dad4..119823eb50b42 100644 --- a/remote/test/puppeteer/test/headful.spec.ts +++ b/remote/test/puppeteer/test/headful.spec.ts @@ -23,7 +23,7 @@ import { getTestState, describeChromeOnly, itFailsWindows, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions import rimraf from 'rimraf'; const rmAsync = promisify(rimraf); diff --git a/remote/test/puppeteer/test/idle_override.spec.ts b/remote/test/puppeteer/test/idle_override.spec.ts new file mode 100644 index 0000000000000..b6a927e48ab66 --- /dev/null +++ b/remote/test/puppeteer/test/idle_override.spec.ts @@ -0,0 +1,93 @@ +/** + * Copyright 2020 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import expect from 'expect'; +import { + getTestState, + setupTestBrowserHooks, + setupTestPageAndContextHooks, +} from './mocha-utils'; // eslint-disable-line import/extensions + +describe('Emulate idle state', () => { + setupTestBrowserHooks(); + setupTestPageAndContextHooks(); + + async function getIdleState() { + const { page } = getTestState(); + + const stateElement = await page.$('#state'); + return await page.evaluate((element: HTMLElement) => { + return element.innerText; + }, stateElement); + } + + async function verifyState(expectedState: string) { + const actualState = await getIdleState(); + expect(actualState).toEqual(expectedState); + } + + it('changing idle state emulation causes change of the IdleDetector state', async () => { + const { page, server, context } = getTestState(); + await context.overridePermissions(server.PREFIX + '/idle-detector.html', [ + 'idle-detection', + ]); + + await page.goto(server.PREFIX + '/idle-detector.html'); + + // Store initial state, as soon as it is not guaranteed to be `active, unlocked`. + const initialState = await getIdleState(); + + // Emulate Idle states and verify IdleDetector updates state accordingly. + await page.emulateIdleState({ + isUserActive: false, + isScreenUnlocked: false, + }); + await verifyState('Idle state: idle, locked.'); + + await page.emulateIdleState({ + isUserActive: true, + isScreenUnlocked: false, + }); + await verifyState('Idle state: active, locked.'); + + await page.emulateIdleState({ + isUserActive: true, + isScreenUnlocked: true, + }); + await verifyState('Idle state: active, unlocked.'); + + await page.emulateIdleState({ + isUserActive: false, + isScreenUnlocked: true, + }); + await verifyState('Idle state: idle, unlocked.'); + + // Remove Idle emulation and verify IdleDetector is in initial state. + await page.emulateIdleState(); + await verifyState(initialState); + + // Emulate idle state again after removing emulation. + await page.emulateIdleState({ + isUserActive: false, + isScreenUnlocked: false, + }); + await verifyState('Idle state: idle, locked.'); + + // Remove emulation second time. + await page.emulateIdleState(); + await verifyState(initialState); + }); +}); diff --git a/remote/test/puppeteer/test/ignorehttpserrors.spec.ts b/remote/test/puppeteer/test/ignorehttpserrors.spec.ts index af5a14fde788b..81252af2989b2 100644 --- a/remote/test/puppeteer/test/ignorehttpserrors.spec.ts +++ b/remote/test/puppeteer/test/ignorehttpserrors.spec.ts @@ -17,7 +17,7 @@ import expect from 'expect'; import { getTestState, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('ignoreHTTPSErrors', function () { /* Note that this test creates its own browser rather than use diff --git a/remote/test/puppeteer/test/input.spec.ts b/remote/test/puppeteer/test/input.spec.ts index b5fa2d04e5ccb..d87aa1375f728 100644 --- a/remote/test/puppeteer/test/input.spec.ts +++ b/remote/test/puppeteer/test/input.spec.ts @@ -21,7 +21,7 @@ import { setupTestBrowserHooks, setupTestPageAndContextHooks, describeFailsFirefox, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions const FILE_TO_UPLOAD = path.join(__dirname, '/assets/file-to-upload.txt'); @@ -36,7 +36,7 @@ describe('input tests', function () { await page.goto(server.PREFIX + '/input/fileupload.html'); const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD); const input = await page.$('input'); - await page.evaluate((e) => { + await page.evaluate((e: HTMLElement) => { globalThis._inputEvents = []; e.addEventListener('change', (ev) => globalThis._inputEvents.push(ev.type) @@ -46,18 +46,18 @@ describe('input tests', function () { ); }, input); await input.uploadFile(filePath); - expect(await page.evaluate((e) => e.files[0].name, input)).toBe( - 'file-to-upload.txt' - ); - expect(await page.evaluate((e) => e.files[0].type, input)).toBe( - 'text/plain' - ); + expect( + await page.evaluate((e: HTMLInputElement) => e.files[0].name, input) + ).toBe('file-to-upload.txt'); + expect( + await page.evaluate((e: HTMLInputElement) => e.files[0].type, input) + ).toBe('text/plain'); expect(await page.evaluate(() => globalThis._inputEvents)).toEqual([ 'input', 'change', ]); expect( - await page.evaluate((e) => { + await page.evaluate((e: HTMLInputElement) => { const reader = new FileReader(); const promise = new Promise((fulfill) => (reader.onload = fulfill)); reader.readAsText(e.files[0]); diff --git a/remote/test/puppeteer/test/jshandle.spec.ts b/remote/test/puppeteer/test/jshandle.spec.ts index 486d9c46073d7..35b0f8edbebbd 100644 --- a/remote/test/puppeteer/test/jshandle.spec.ts +++ b/remote/test/puppeteer/test/jshandle.spec.ts @@ -19,7 +19,7 @@ import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('JSHandle', function () { setupTestBrowserHooks(); @@ -36,7 +36,10 @@ describe('JSHandle', function () { const { page } = getTestState(); const navigatorHandle = await page.evaluateHandle(() => navigator); - const text = await page.evaluate((e) => e.userAgent, navigatorHandle); + const text = await page.evaluate( + (e: Navigator) => e.userAgent, + navigatorHandle + ); expect(text).toContain('Mozilla'); }); it('should accept object handle to primitive types', async () => { @@ -74,7 +77,9 @@ describe('JSHandle', function () { globalThis.FOO = 123; return window; }); - expect(await page.evaluate((e) => e.FOO, aHandle)).toBe(123); + expect(await page.evaluate((e: { FOO: number }) => e.FOO, aHandle)).toBe( + 123 + ); }); it('should work with primitives', async () => { const { page } = getTestState(); @@ -83,7 +88,9 @@ describe('JSHandle', function () { globalThis.FOO = 123; return window; }); - expect(await page.evaluate((e) => e.FOO, aHandle)).toBe(123); + expect(await page.evaluate((e: { FOO: number }) => e.FOO, aHandle)).toBe( + 123 + ); }); }); @@ -192,7 +199,10 @@ describe('JSHandle', function () { const element = aHandle.asElement(); expect(element).toBeTruthy(); expect( - await page.evaluate((e) => e.nodeType === Node.TEXT_NODE, element) + await page.evaluate( + (e: HTMLElement) => e.nodeType === Node.TEXT_NODE, + element + ) ); }); }); diff --git a/remote/test/puppeteer/test/keyboard.spec.ts b/remote/test/puppeteer/test/keyboard.spec.ts index 2e32bae724b48..c6d28dac8e4da 100644 --- a/remote/test/puppeteer/test/keyboard.spec.ts +++ b/remote/test/puppeteer/test/keyboard.spec.ts @@ -14,15 +14,15 @@ * limitations under the License. */ -import utils from './utils'; +import utils from './utils.js'; import os from 'os'; import expect from 'expect'; import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; -import { KeyInput } from '../src/common/USKeyboardLayout'; +} from './mocha-utils'; // eslint-disable-line import/extensions +import { KeyInput } from '../lib/cjs/puppeteer/common/USKeyboardLayout.js'; describe('Keyboard', function () { setupTestBrowserHooks(); @@ -332,16 +332,16 @@ describe('Keyboard', function () { const { page } = getTestState(); let error = await page.keyboard - // @ts-expect-error + // @ts-expect-error bad input .press('NotARealKey') .catch((error_) => error_); expect(error.message).toBe('Unknown key: "NotARealKey"'); - // @ts-expect-error + // @ts-expect-error bad input error = await page.keyboard.press('ё').catch((error_) => error_); expect(error && error.message).toBe('Unknown key: "ё"'); - // @ts-expect-error + // @ts-expect-error bad input error = await page.keyboard.press('😊').catch((error_) => error_); expect(error && error.message).toBe('Unknown key: "😊"'); }); @@ -386,7 +386,15 @@ describe('Keyboard', function () { }); }); await page.keyboard.press('Meta'); - const [key, code, metaKey] = await page.evaluate('result'); + // Have to do this because we lose a lot of type info when evaluating a + // string not a function. This is why functions are recommended rather than + // using strings (although we'll leave this test so we have coverage of both + // approaches.) + const [key, code, metaKey] = (await page.evaluate('result')) as [ + string, + string, + boolean + ]; if (isFirefox && os.platform() !== 'darwin') expect(key).toBe('OS'); else expect(key).toBe('Meta'); diff --git a/remote/test/puppeteer/test/launcher.spec.ts b/remote/test/puppeteer/test/launcher.spec.ts index ac383741f90c0..4c9c2b6e61621 100644 --- a/remote/test/puppeteer/test/launcher.spec.ts +++ b/remote/test/puppeteer/test/launcher.spec.ts @@ -21,21 +21,21 @@ import { promisify } from 'util'; import { getTestState, itOnlyRegularInstall, - itFailsWindowsUntilDate, -} from './mocha-utils'; -import utils from './utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions +import utils from './utils.js'; import expect from 'expect'; import rimraf from 'rimraf'; -import { Page } from '../src/common/Page'; +import { Page } from '../lib/cjs/puppeteer/common/Page.js'; const rmAsync = promisify(rimraf); const mkdtempAsync = promisify(fs.mkdtemp); const readFileAsync = promisify(fs.readFile); const statAsync = promisify(fs.stat); const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); +const FIREFOX_TIMEOUT = 30 * 1000; describe('Launcher specs', function () { - if (getTestState().isFirefox) this.timeout(30 * 1000); + if (getTestState().isFirefox) this.timeout(FIREFOX_TIMEOUT); describe('Puppeteer', function () { describe('BrowserFetcher', function () { @@ -304,7 +304,7 @@ describe('Launcher specs', function () { '--headless' ); expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain( - '--user-data-dir=foo' + `--user-data-dir=${path.resolve('foo')}` ); } else if (isFirefox) { expect(puppeteer.defaultArgs()).toContain('--headless'); @@ -328,7 +328,7 @@ describe('Launcher specs', function () { '-profile' ); expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain( - 'foo' + path.resolve('foo') ); } }); @@ -440,6 +440,8 @@ describe('Launcher specs', function () { after(async () => { const { puppeteer } = getTestState(); + // @ts-expect-error launcher is a private property that users can't + // touch, but for testing purposes we need to reset it. puppeteer._lazyLauncher = undefined; puppeteer._productName = productName; }); @@ -455,6 +457,7 @@ describe('Launcher specs', function () { it('falls back to launching chrome if there is an unknown product but logs a warning', async () => { const { puppeteer } = getTestState(); const consoleStub = sinon.stub(console, 'warn'); + // @ts-expect-error purposeful bad input const browser = await puppeteer.launch({ product: 'SO_NOT_A_PRODUCT' }); const userAgent = await browser.userAgent(); await browser.close(); @@ -466,19 +469,17 @@ describe('Launcher specs', function () { }); /* We think there's a bug in the FF Windows launcher, or some - * combo of that plus it running on CI, but we're deferring fixing - * this so we can get Windows CI stable and then dig into this - * properly with help from the Mozilla folks. + * combo of that plus it running on CI, but it's hard to track down. + * See comment here: https://github.com/puppeteer/puppeteer/issues/5673#issuecomment-670141377. */ - itOnlyRegularInstall('should be able to launch Firefox', - async () => { - const { puppeteer } = getTestState(); - const browser = await puppeteer.launch({ product: 'firefox' }); - const userAgent = await browser.userAgent(); - await browser.close(); - expect(userAgent).toContain('Firefox'); - } - ); + itOnlyRegularInstall('should be able to launch Firefox', async function () { + this.timeout(FIREFOX_TIMEOUT); + const { puppeteer } = getTestState(); + const browser = await puppeteer.launch({ product: 'firefox' }); + const userAgent = await browser.userAgent(); + await browser.close(); + expect(userAgent).toContain('Firefox'); + }); }); describe('Puppeteer.connect', function () { diff --git a/remote/test/puppeteer/test/mocha-ts-require.js b/remote/test/puppeteer/test/mocha-ts-require.js new file mode 100644 index 0000000000000..a0ac64fa62b7a --- /dev/null +++ b/remote/test/puppeteer/test/mocha-ts-require.js @@ -0,0 +1,11 @@ +const path = require('path'); + +require('ts-node').register({ + /** + * We ignore the lib/ directory because that's already been TypeScript + * compiled and checked. So we don't want to check it again as part of running + * the unit tests. + */ + ignore: ['lib/*', 'node_modules'], + project: path.join(__dirname, 'tsconfig.test.json'), +}); diff --git a/remote/test/puppeteer/test/mocha-utils.ts b/remote/test/puppeteer/test/mocha-utils.ts index 517172738c8f7..d13c82701c8e7 100644 --- a/remote/test/puppeteer/test/mocha-utils.ts +++ b/remote/test/puppeteer/test/mocha-utils.ts @@ -14,19 +14,22 @@ * limitations under the License. */ -import { TestServer } from '../utils/testserver/index'; +import { TestServer } from '../utils/testserver/index.js'; import * as path from 'path'; import * as fs from 'fs'; import * as os from 'os'; import sinon from 'sinon'; -import puppeteer from '../src/index'; -import { Browser, BrowserContext } from '../src/common/Browser'; -import { Page } from '../src/common/Page'; -import { Puppeteer } from '../src/common/Puppeteer'; -import utils from './utils'; +import puppeteer from '../lib/cjs/puppeteer/node.js'; +import { + Browser, + BrowserContext, +} from '../lib/cjs/puppeteer/common/Browser.js'; +import { Page } from '../lib/cjs/puppeteer/common/Page.js'; +import { PuppeteerNode } from '../lib/cjs/puppeteer/node/Puppeteer.js'; +import utils from './utils.js'; import rimraf from 'rimraf'; -import { trackCoverage } from './coverage-utils'; +import { trackCoverage } from './coverage-utils.js'; const setupServer = async () => { const assetsPath = path.join(__dirname, 'assets'); @@ -89,8 +92,11 @@ const defaultBrowserOptions = Object.assign( `WARN: running ${product} tests with ${defaultBrowserOptions.executablePath}` ); } else { - // TODO(jackfranklin): declare updateRevision in some form for the Firefox launcher. - // @ts-expect-error + // TODO(jackfranklin): declare updateRevision in some form for the Firefox + // launcher. + // @ts-expect-error _updateRevision is defined on the FF launcher + // but not the Chrome one. The types need tidying so that TS can infer that + // properly and not error here. if (product === 'firefox') await puppeteer._launcher._updateRevision(); const executablePath = puppeteer.executablePath(); if (!fs.existsSync(executablePath)) @@ -120,7 +126,7 @@ interface PuppeteerTestState { browser: Browser; context: BrowserContext; page: Page; - puppeteer: Puppeteer; + puppeteer: PuppeteerNode; defaultBrowserOptions: { [x: string]: any; }; diff --git a/remote/test/puppeteer/test/mouse.spec.ts b/remote/test/puppeteer/test/mouse.spec.ts index c3f0cc9e8397a..fadd9b9b95aad 100644 --- a/remote/test/puppeteer/test/mouse.spec.ts +++ b/remote/test/puppeteer/test/mouse.spec.ts @@ -19,8 +19,8 @@ import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; -import { KeyInput } from '../src/common/USKeyboardLayout'; +} from './mocha-utils'; // eslint-disable-line import/extensions +import { KeyInput } from '../lib/cjs/puppeteer/common/USKeyboardLayout.js'; interface Dimensions { x: number; @@ -60,7 +60,7 @@ describe('Mouse', function () { }); }); await page.mouse.click(50, 60); - const event = await page.evaluate( + const event = await page.evaluate<() => MouseEvent>( () => globalThis.clickPromise ); expect(event.type).toBe('click'); @@ -74,13 +74,15 @@ describe('Mouse', function () { const { page, server } = getTestState(); await page.goto(server.PREFIX + '/input/textarea.html'); - const { x, y, width, height } = await page.evaluate(dimensions); + const { x, y, width, height } = await page.evaluate<() => Dimensions>( + dimensions + ); const mouse = page.mouse; await mouse.move(x + width - 4, y + height - 4); await mouse.down(); await mouse.move(x + width + 100, y + height + 100); await mouse.up(); - const newDimensions = await page.evaluate(dimensions); + const newDimensions = await page.evaluate<() => Dimensions>(dimensions); expect(newDimensions.width).toBe(Math.round(width + 104)); expect(newDimensions.height).toBe(Math.round(height + 104)); }); @@ -162,16 +164,41 @@ describe('Mouse', function () { for (const [modifier, key] of modifiers) { await page.keyboard.down(modifier); await page.click('#button-3'); - if (!(await page.evaluate((mod) => globalThis.lastEvent[mod], key))) + if ( + !(await page.evaluate((mod: string) => globalThis.lastEvent[mod], key)) + ) throw new Error(key + ' should be true'); await page.keyboard.up(modifier); } await page.click('#button-3'); for (const [modifier, key] of modifiers) { - if (await page.evaluate((mod) => globalThis.lastEvent[mod], key)) + if (await page.evaluate((mod: string) => globalThis.lastEvent[mod], key)) throw new Error(modifiers[modifier] + ' should be false'); } }); + it('should send mouse wheel events', async () => { + const { page, server } = getTestState(); + + await page.goto(server.PREFIX + '/input/wheel.html'); + const elem = await page.$('div'); + const boundingBoxBefore = await elem.boundingBox(); + expect(boundingBoxBefore).toMatchObject({ + width: 115, + height: 115, + }); + + await page.mouse.move( + boundingBoxBefore.x + boundingBoxBefore.width / 2, + boundingBoxBefore.y + boundingBoxBefore.height / 2 + ); + + await page.mouse.wheel({ deltaY: -100 }); + const boundingBoxAfter = await elem.boundingBox(); + expect(boundingBoxAfter).toMatchObject({ + width: 230, + height: 230, + }); + }); it('should tween mouse movement', async () => { const { page } = getTestState(); diff --git a/remote/test/puppeteer/test/navigation.spec.ts b/remote/test/puppeteer/test/navigation.spec.ts index dda067509229b..205d98a0b0775 100644 --- a/remote/test/puppeteer/test/navigation.spec.ts +++ b/remote/test/puppeteer/test/navigation.spec.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import utils from './utils'; +import utils from './utils.js'; import expect from 'expect'; import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions import os from 'os'; describe('navigation', function () { @@ -191,7 +191,7 @@ describe('navigation', function () { let error = null; await page - // @ts-expect-error + // @ts-expect-error purposefully passing an old option .goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' }) .catch((error_) => (error = error_)); expect(error.message).toContain( @@ -493,7 +493,7 @@ describe('navigation', function () { const [response] = await Promise.all([ page.waitForNavigation(), page.evaluate( - (url) => (window.location.href = url), + (url: string) => (window.location.href = url), server.PREFIX + '/grid.html' ), ]); @@ -731,7 +731,7 @@ describe('navigation', function () { const [response] = await Promise.all([ frame.waitForNavigation(), frame.evaluate( - (url) => (window.location.href = url), + (url: string) => (window.location.href = url), server.PREFIX + '/grid.html' ), ]); diff --git a/remote/test/puppeteer/test/network.spec.ts b/remote/test/puppeteer/test/network.spec.ts index b2b546521652a..63b2dfb948886 100644 --- a/remote/test/puppeteer/test/network.spec.ts +++ b/remote/test/puppeteer/test/network.spec.ts @@ -16,13 +16,13 @@ import fs from 'fs'; import path from 'path'; -import utils from './utils'; +import utils from './utils.js'; import expect from 'expect'; import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('network', function () { setupTestBrowserHooks(); @@ -514,7 +514,7 @@ describe('network', function () { let error = null; try { - // @ts-expect-error + // @ts-expect-error purposeful bad input await page.setExtraHTTPHeaders({ foo: 1 }); } catch (error_) { error = error_; diff --git a/remote/test/puppeteer/test/oopif.spec.ts b/remote/test/puppeteer/test/oopif.spec.ts index d58b8f568b652..845429a69fa3e 100644 --- a/remote/test/puppeteer/test/oopif.spec.ts +++ b/remote/test/puppeteer/test/oopif.spec.ts @@ -15,7 +15,7 @@ */ import expect from 'expect'; -import { getTestState, describeChromeOnly } from './mocha-utils'; +import { getTestState, describeChromeOnly } from './mocha-utils'; // eslint-disable-line import/extensions describeChromeOnly('OOPIF', function () { /* We use a special browser for this test as we need the --site-per-process flag */ diff --git a/remote/test/puppeteer/test/page.spec.ts b/remote/test/puppeteer/test/page.spec.ts index a46c2c72863a6..512c26921e5b3 100644 --- a/remote/test/puppeteer/test/page.spec.ts +++ b/remote/test/puppeteer/test/page.spec.ts @@ -15,7 +15,7 @@ */ import fs from 'fs'; import path from 'path'; -import utils from './utils'; +import utils from './utils.js'; const { waitEvent } = utils; import expect from 'expect'; import sinon from 'sinon'; @@ -24,8 +24,9 @@ import { itFailsFirefox, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; -import { Page, Metrics } from '../src/common/Page'; +} from './mocha-utils'; // eslint-disable-line import/extensions +import { Page, Metrics } from '../lib/cjs/puppeteer/common/Page.js'; +import { JSHandle } from '../lib/cjs/puppeteer/common/JSHandle.js'; describe('Page', function () { setupTestBrowserHooks(); @@ -118,21 +119,6 @@ describe('Page', function () { }); }); - describe('Async stacks', () => { - it('should work', async () => { - const { page, server } = getTestState(); - - server.setRoute('/empty.html', (req, res) => { - res.statusCode = 204; - res.end(); - }); - let error = null; - await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_)); - expect(error).not.toBe(null); - expect(error.stack).toContain(__filename); - }); - }); - // This test fails on Firefox on CI consistently but cannot be replicated // locally. Skipping for now to unblock the Mitt release and given FF support // isn't fully done yet but raising an issue to ask the FF folks to have a @@ -259,7 +245,6 @@ describe('Page', function () { await page.goto(server.EMPTY_PAGE); let error = null; await context - // @ts-expect-error .overridePermissions(server.EMPTY_PAGE, ['foo']) .catch((error_) => (error = error_)); expect(error.message).toBe('Unknown permission: foo'); @@ -411,7 +396,7 @@ describe('Page', function () { const prototypeHandle = await page.evaluateHandle(() => Set.prototype); const objectsHandle = await page.queryObjects(prototypeHandle); const count = await page.evaluate( - (objects) => objects.length, + (objects: JSHandle[]) => objects.length, objectsHandle ); expect(count).toBe(1); @@ -430,7 +415,7 @@ describe('Page', function () { const prototypeHandle = await page.evaluateHandle(() => Set.prototype); const objectsHandle = await page.queryObjects(prototypeHandle); const count = await page.evaluate( - (objects) => objects.length, + (objects: JSHandle[]) => objects.length, objectsHandle ); expect(count).toBe(1); @@ -474,6 +459,13 @@ describe('Page', function () { ]); expect(message.text()).toEqual('hello 5 JSHandle@object'); expect(message.type()).toEqual('log'); + expect(message.args()).toHaveLength(3); + expect(message.location()).toEqual({ + url: expect.any(String), + lineNumber: expect.any(Number), + columnNumber: expect.any(Number), + }); + expect(await message.args()[0].jsonValue()).toEqual('hello'); expect(await message.args()[1].jsonValue()).toEqual(5); expect(await message.args()[2].jsonValue()).toEqual({ foo: 'bar' }); @@ -529,7 +521,7 @@ describe('Page', function () { const [message] = await Promise.all([ waitEvent(page, 'console'), page.evaluate( - async (url) => fetch(url).catch(() => {}), + async (url: string) => fetch(url).catch(() => {}), server.EMPTY_PAGE ), ]); @@ -554,7 +546,7 @@ describe('Page', function () { lineNumber: undefined, }); }); - it('should have location for console API calls', async () => { + it('should have location and stack trace for console API calls', async () => { const { page, server, isChrome } = getTestState(); await page.goto(server.EMPTY_PAGE); @@ -566,9 +558,26 @@ describe('Page', function () { expect(message.type()).toBe('log'); expect(message.location()).toEqual({ url: server.PREFIX + '/consolelog.html', - lineNumber: 7, - columnNumber: isChrome ? 14 : 6, // console.|log vs |console.log + lineNumber: 8, + columnNumber: isChrome ? 16 : 8, // console.|log vs |console.log }); + expect(message.stackTrace()).toEqual([ + { + url: server.PREFIX + '/consolelog.html', + lineNumber: 8, + columnNumber: isChrome ? 16 : 8, // console.|log vs |console.log + }, + { + url: server.PREFIX + '/consolelog.html', + lineNumber: 11, + columnNumber: 8, + }, + { + url: server.PREFIX + '/consolelog.html', + lineNumber: 13, + columnNumber: 6, + }, + ]); }); // @see https://github.com/puppeteer/puppeteer/issues/3865 it('should not throw when there are console messages in detached iframes', async () => { @@ -901,8 +910,8 @@ describe('Page', function () { await page.exposeFunction('complexObject', function (a, b) { return { x: a.x + b.x }; }); - const result = await page.evaluate<{ x: number }>(async () => - globalThis.complexObject({ x: 5 }, { x: 2 }) + const result = await page.evaluate<() => Promise<{ x: number }>>( + async () => globalThis.complexObject({ x: 5 }, { x: 2 }) ); expect(result.x).toBe(7); }); @@ -1159,7 +1168,7 @@ describe('Page', function () { let error = null; try { - // @ts-expect-error + // @ts-expect-error purposefully passing bad options await page.addScriptTag('/injectedfile.js'); } catch (error_) { error = error_; @@ -1288,7 +1297,7 @@ describe('Page', function () { let error = null; try { - // @ts-expect-error + // @ts-expect-error purposefully passing bad input await page.addStyleTag('/injectedstyle.css'); } catch (error_) { error = error_; @@ -1348,7 +1357,7 @@ describe('Page', function () { }); const styleHandle = await page.$('style'); const styleContent = await page.evaluate( - (style) => style.innerHTML, + (style: HTMLStyleElement) => style.innerHTML, styleHandle ); expect(styleContent).toContain(path.join('assets', 'injectedstyle.css')); @@ -1642,7 +1651,7 @@ describe('Page', function () { await page.setContent(''); let error = null; try { - // @ts-expect-error + // @ts-expect-error purposefully passing bad input await page.select('select', 12); } catch (error_) { error = error_; diff --git a/remote/test/puppeteer/test/queryselector.spec.ts b/remote/test/puppeteer/test/queryselector.spec.ts index db2e36e8ada0e..7a147ddd01792 100644 --- a/remote/test/puppeteer/test/queryselector.spec.ts +++ b/remote/test/puppeteer/test/queryselector.spec.ts @@ -18,7 +18,8 @@ import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions +import { CustomQueryHandler } from '../lib/cjs/puppeteer/common/QueryHandler.js'; describe('querySelector', function () { setupTestBrowserHooks(); @@ -67,6 +68,48 @@ describe('querySelector', function () { }); }); + describe('pierceHandler', function () { + beforeEach(async () => { + const { page } = getTestState(); + await page.setContent( + `` + ); + }); + it('should find first element in shadow', async () => { + const { page } = getTestState(); + const div = await page.$('pierce/.foo'); + const text = await div.evaluate( + (element: Element) => element.textContent + ); + expect(text).toBe('Hello'); + }); + it('should find all elements in shadow', async () => { + const { page } = getTestState(); + const divs = await page.$$('pierce/.foo'); + const text = await Promise.all( + divs.map((div) => + div.evaluate((element: Element) => element.textContent) + ) + ); + expect(text.join(' ')).toBe('Hello World'); + }); + }); + + // The tests for $$eval are repeated later in this file in the test group 'QueryAll'. + // This is done to also test a query handler where QueryAll returns an Element[] + // as opposed to NodeListOf. describe('Page.$$eval', function () { it('should work', async () => { const { page } = getTestState(); @@ -77,6 +120,52 @@ describe('querySelector', function () { const divsCount = await page.$$eval('div', (divs) => divs.length); expect(divsCount).toBe(3); }); + it('should accept extra arguments', async () => { + const { page } = getTestState(); + await page.setContent( + '
hello
beautiful
world!
' + ); + const divsCountPlus5 = await page.$$eval( + 'div', + (divs, two: number, three: number) => divs.length + two + three, + 2, + 3 + ); + expect(divsCountPlus5).toBe(8); + }); + it('should accept ElementHandles as arguments', async () => { + const { page } = getTestState(); + await page.setContent( + '
2
2
1
3
' + ); + const divHandle = await page.$('div'); + const sum = await page.$$eval( + 'section', + (sections, div: HTMLElement) => + sections.reduce( + (acc, section) => acc + Number(section.textContent), + 0 + ) + Number(div.textContent), + divHandle + ); + expect(sum).toBe(8); + }); + it('should handle many elements', async () => { + const { page } = getTestState(); + await page.evaluate( + ` + for (var i = 0; i <= 1000; i++) { + const section = document.createElement('section'); + section.textContent = i; + document.body.appendChild(section); + } + ` + ); + const sum = await page.$$eval('section', (sections) => + sections.reduce((acc, section) => acc + Number(section.textContent), 0) + ); + expect(sum).toBe(500500); + }); }); describe('Page.$', function () { @@ -103,7 +192,7 @@ describe('querySelector', function () { const elements = await page.$$('div'); expect(elements.length).toBe(2); const promises = elements.map((element) => - page.evaluate((e) => e.textContent, element) + page.evaluate((e: HTMLElement) => e.textContent, element) ); expect(await Promise.all(promises)).toEqual(['A', 'B']); }); @@ -151,7 +240,10 @@ describe('querySelector', function () { const html = await page.$('html'); const second = await html.$('.second'); const inner = await second.$('.inner'); - const content = await page.evaluate((e) => e.textContent, inner); + const content = await page.evaluate( + (e: HTMLElement) => e.textContent, + inner + ); expect(content).toBe('A'); }); @@ -218,7 +310,7 @@ describe('querySelector', function () { '
' ); const tweet = await page.$('.tweet'); - const content = await tweet.$$eval('.like', (nodes) => + const content = await tweet.$$eval('.like', (nodes: HTMLElement[]) => nodes.map((n) => n.innerText) ); expect(content).toEqual(['100', '10']); @@ -231,7 +323,7 @@ describe('querySelector', function () { '
not-a-child-div
a1-child-div
a2-child-div
'; await page.setContent(htmlContent); const elementHandle = await page.$('#myId'); - const content = await elementHandle.$$eval('.a', (nodes) => + const content = await elementHandle.$$eval('.a', (nodes: HTMLElement[]) => nodes.map((n) => n.innerText) ); expect(content).toEqual(['a1-child-div', 'a2-child-div']); @@ -263,7 +355,7 @@ describe('querySelector', function () { const elements = await html.$$('div'); expect(elements.length).toBe(2); const promises = elements.map((element) => - page.evaluate((e) => e.textContent, element) + page.evaluate((e: HTMLElement) => e.textContent, element) ); expect(await Promise.all(promises)).toEqual(['A', 'B']); }); @@ -291,7 +383,10 @@ describe('querySelector', function () { const html = await page.$('html'); const second = await html.$x(`./body/div[contains(@class, 'second')]`); const inner = await second[0].$x(`./div[contains(@class, 'inner')]`); - const content = await page.evaluate((e) => e.textContent, inner[0]); + const content = await page.evaluate( + (e: HTMLElement) => e.textContent, + inner[0] + ); expect(content).toBe('A'); }); @@ -306,4 +401,107 @@ describe('querySelector', function () { expect(second).toEqual([]); }); }); + + // This is the same tests for `$$eval` and `$$` as above, but with a queryAll + // handler that returns an array instead of a list of nodes. + describe('QueryAll', function () { + const handler: CustomQueryHandler = { + queryAll: (element: Element, selector: string) => + Array.from(element.querySelectorAll(selector)), + }; + before(() => { + const { puppeteer } = getTestState(); + puppeteer.registerCustomQueryHandler('allArray', handler); + }); + + it('should have registered handler', async () => { + const { puppeteer } = getTestState(); + expect( + puppeteer.customQueryHandlerNames().includes('allArray') + ).toBeTruthy(); + }); + it('$$ should query existing elements', async () => { + const { page } = getTestState(); + + await page.setContent( + '
A

B
' + ); + const html = await page.$('html'); + const elements = await html.$$('allArray/div'); + expect(elements.length).toBe(2); + const promises = elements.map((element) => + page.evaluate((e: HTMLElement) => e.textContent, element) + ); + expect(await Promise.all(promises)).toEqual(['A', 'B']); + }); + + it('$$ should return empty array for non-existing elements', async () => { + const { page } = getTestState(); + + await page.setContent( + 'A
B' + ); + const html = await page.$('html'); + const elements = await html.$$('allArray/div'); + expect(elements.length).toBe(0); + }); + it('$$eval should work', async () => { + const { page } = getTestState(); + + await page.setContent( + '
hello
beautiful
world!
' + ); + const divsCount = await page.$$eval( + 'allArray/div', + (divs) => divs.length + ); + expect(divsCount).toBe(3); + }); + it('$$eval should accept extra arguments', async () => { + const { page } = getTestState(); + await page.setContent( + '
hello
beautiful
world!
' + ); + const divsCountPlus5 = await page.$$eval( + 'allArray/div', + (divs, two: number, three: number) => divs.length + two + three, + 2, + 3 + ); + expect(divsCountPlus5).toBe(8); + }); + it('$$eval should accept ElementHandles as arguments', async () => { + const { page } = getTestState(); + await page.setContent( + '
2
2
1
3
' + ); + const divHandle = await page.$('div'); + const sum = await page.$$eval( + 'allArray/section', + (sections, div: HTMLElement) => + sections.reduce( + (acc, section) => acc + Number(section.textContent), + 0 + ) + Number(div.textContent), + divHandle + ); + expect(sum).toBe(8); + }); + it('$$eval should handle many elements', async () => { + const { page } = getTestState(); + await page.evaluate( + ` + for (var i = 0; i <= 1000; i++) { + const section = document.createElement('section'); + section.textContent = i; + document.body.appendChild(section); + } + ` + ); + const sum = await page.$$eval('allArray/section', (sections) => + sections.reduce((acc, section) => acc + Number(section.textContent), 0) + ); + expect(sum).toBe(500500); + }); + }); }); diff --git a/remote/test/puppeteer/test/requestinterception.spec.ts b/remote/test/puppeteer/test/requestinterception.spec.ts index 394d90b81c5a0..462eb714c7212 100644 --- a/remote/test/puppeteer/test/requestinterception.spec.ts +++ b/remote/test/puppeteer/test/requestinterception.spec.ts @@ -16,13 +16,13 @@ import fs from 'fs'; import path from 'path'; -import utils from './utils'; +import utils from './utils.js'; import expect from 'expect'; import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('request interception', function () { setupTestBrowserHooks(); @@ -383,14 +383,14 @@ describe('request interception', function () { }); const dataURL = 'data:text/html,
yo
'; const text = await page.evaluate( - (url) => fetch(url).then((r) => r.text()), + (url: string) => fetch(url).then((r) => r.text()), dataURL ); expect(text).toBe('
yo
'); expect(requests.length).toBe(1); expect(requests[0].url()).toBe(dataURL); }); - it('should navigate to URL with hash and and fire requests without hash', async () => { + it('should navigate to URL with hash and fire requests without hash', async () => { const { page, server } = getTestState(); await page.setRequestInterception(true); diff --git a/remote/test/puppeteer/test/screenshot.spec.ts b/remote/test/puppeteer/test/screenshot.spec.ts index f3bd90ac18cea..de33b9c94f2e7 100644 --- a/remote/test/puppeteer/test/screenshot.spec.ts +++ b/remote/test/puppeteer/test/screenshot.spec.ts @@ -19,7 +19,7 @@ import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('Screenshots', function () { setupTestBrowserHooks(); @@ -280,7 +280,10 @@ describe('Screenshots', function () { await page.setContent('

remove this

'); const elementHandle = await page.$('h1'); - await page.evaluate((element) => element.remove(), elementHandle); + await page.evaluate( + (element: HTMLElement) => element.remove(), + elementHandle + ); const screenshotError = await elementHandle .screenshot() .catch((error) => error); diff --git a/remote/test/puppeteer/test/target.spec.ts b/remote/test/puppeteer/test/target.spec.ts index 9453bb99b0bcc..72cfe1d8357e5 100644 --- a/remote/test/puppeteer/test/target.spec.ts +++ b/remote/test/puppeteer/test/target.spec.ts @@ -14,15 +14,15 @@ * limitations under the License. */ -import utils from './utils'; +import utils from './utils.js'; const { waitEvent } = utils; import expect from 'expect'; import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; -import { Target } from '../src/common/Target'; +} from './mocha-utils'; // eslint-disable-line import/extensions +import { Target } from '../lib/cjs/puppeteer/common/Target.js'; describe('Target', function () { setupTestBrowserHooks(); @@ -80,7 +80,7 @@ describe('Target', function () { ) .then((target) => target.page()), page.evaluate( - (url) => window.open(url), + (url: string) => window.open(url), server.CROSS_PROCESS_PREFIX + '/empty.html' ), ]); @@ -214,7 +214,7 @@ describe('Target', function () { // Open a new page. Use window.open to connect to the page later. await Promise.all([ page.evaluate( - (url) => window.open(url), + (url: string) => window.open(url), server.PREFIX + '/one-style.html' ), server.waitForRequest('/one-style.css'), @@ -259,13 +259,13 @@ describe('Target', function () { (target) => target.url() === server.EMPTY_PAGE ); targetPromise - .then(() => (resolved = true)) - .catch((error) => { - resolved = true; - if (error instanceof puppeteer.errors.TimeoutError) { - console.error(error); - } else throw error; - }); + .then(() => (resolved = true)) + .catch((error) => { + resolved = true; + if (error instanceof puppeteer.errors.TimeoutError) { + console.error(error); + } else throw error; + }); const page = await browser.newPage(); expect(resolved).toBe(false); await page.goto(server.EMPTY_PAGE); diff --git a/remote/test/puppeteer/test/touchscreen.spec.ts b/remote/test/puppeteer/test/touchscreen.spec.ts index 16800b4204029..b7fc67bfa94b2 100644 --- a/remote/test/puppeteer/test/touchscreen.spec.ts +++ b/remote/test/puppeteer/test/touchscreen.spec.ts @@ -19,7 +19,7 @@ import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('Touchscreen', function () { setupTestBrowserHooks(); diff --git a/remote/test/puppeteer/test/tracing.spec.ts b/remote/test/puppeteer/test/tracing.spec.ts index 7b5afeac9d9b4..5e06f12b4c9d6 100644 --- a/remote/test/puppeteer/test/tracing.spec.ts +++ b/remote/test/puppeteer/test/tracing.spec.ts @@ -17,7 +17,7 @@ import fs from 'fs'; import path from 'path'; import expect from 'expect'; -import { getTestState, describeChromeOnly } from './mocha-utils'; +import { getTestState, describeChromeOnly } from './mocha-utils'; // eslint-disable-line import/extensions describeChromeOnly('Tracing', function () { let outputFile; @@ -118,4 +118,16 @@ describeChromeOnly('Tracing', function () { const trace = await page.tracing.stop(); expect(trace.toString()).toContain('screenshot'); }); + + it('should properly fail if readProtocolStream errors out', async () => { + await page.tracing.start({ path: __dirname }); + + let error: Error = null; + try { + await page.tracing.stop(); + } catch (error_) { + error = error_; + } + expect(error).toBeDefined(); + }); }); diff --git a/remote/test/puppeteer/test/tsconfig.test.json b/remote/test/puppeteer/test/tsconfig.test.json new file mode 100644 index 0000000000000..3432441200fc1 --- /dev/null +++ b/remote/test/puppeteer/test/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "module": "CommonJS" + } +} diff --git a/remote/test/puppeteer/test/waittask.spec.ts b/remote/test/puppeteer/test/waittask.spec.ts index 2801fc979961c..c65b682674f81 100644 --- a/remote/test/puppeteer/test/waittask.spec.ts +++ b/remote/test/puppeteer/test/waittask.spec.ts @@ -14,19 +14,26 @@ * limitations under the License. */ -import utils from './utils'; +import utils from './utils.js'; +import sinon from 'sinon'; import expect from 'expect'; import { getTestState, setupTestBrowserHooks, setupTestPageAndContextHooks, -} from './mocha-utils'; +} from './mocha-utils'; // eslint-disable-line import/extensions describe('waittask specs', function () { setupTestBrowserHooks(); setupTestPageAndContextHooks(); describe('Page.waitFor', function () { + /* This method is deprecated but we don't want the warnings showing up in + * tests. Until we remove this method we still want to ensure we don't break + * it. + */ + beforeEach(() => sinon.stub(console, 'warn').callsFake(() => {})); + it('should wait for selector', async () => { const { page, server } = getTestState(); @@ -86,7 +93,7 @@ describe('waittask specs', function () { const { page } = getTestState(); let error = null; - // @ts-expect-error + // @ts-expect-error purposefully passing bad type for test await page.waitFor({ foo: 'bar' }).catch((error_) => (error = error_)); expect(error.message).toContain('Unsupported target type'); }); @@ -95,6 +102,22 @@ describe('waittask specs', function () { await page.waitFor((arg1, arg2) => arg1 !== arg2, {}, 1, 2); }); + + it('should log a deprecation warning', async () => { + const { page } = getTestState(); + + await page.waitFor(() => true); + + const consoleWarnStub = console.warn as sinon.SinonSpy; + + expect(consoleWarnStub.calledOnce).toBe(true); + expect( + consoleWarnStub.firstCall.calledWith( + 'waitFor is deprecated and will be removed in a future release. See https://github.com/puppeteer/puppeteer/issues/6214 for details and how to migrate your code.' + ) + ).toBe(true); + expect((console.warn as sinon.SinonSpy).calledOnce).toBe(true); + }); }); describe('Frame.waitForFunction', function () { @@ -105,18 +128,15 @@ describe('waittask specs', function () { await page.evaluate(() => (globalThis.__FOO = 1)); await watchdog; }); - it( - 'should work when resolved right before execution context disposal', - async () => { - const { page } = getTestState(); + it('should work when resolved right before execution context disposal', async () => { + const { page } = getTestState(); - await page.evaluateOnNewDocument(() => (globalThis.__RELOADED = true)); - await page.waitForFunction(() => { - if (!globalThis.__RELOADED) window.location.reload(); - return true; - }); - } - ); + await page.evaluateOnNewDocument(() => (globalThis.__RELOADED = true)); + await page.waitForFunction(() => { + if (!globalThis.__RELOADED) window.location.reload(); + return true; + }); + }); it('should poll on interval', async () => { const { page } = getTestState(); @@ -263,7 +283,7 @@ describe('waittask specs', function () { .waitForFunction((element) => !element.parentElement, {}, div) .then(() => (resolved = true)); expect(resolved).toBe(false); - await page.evaluate((element) => element.remove(), div); + await page.evaluate((element: HTMLElement) => element.remove(), div); await waitForFunction; }); it('should respect timeout', async () => { @@ -328,6 +348,41 @@ describe('waittask specs', function () { }); }); + describe('Page.waitForTimeout', () => { + it('waits for the given timeout before resolving', async () => { + const { page, server } = getTestState(); + await page.goto(server.EMPTY_PAGE); + const startTime = Date.now(); + await page.waitForTimeout(1000); + const endTime = Date.now(); + /* In a perfect world endTime - startTime would be exactly 1000 but we + * expect some fluctuations and for it to be off by a little bit. So to + * avoid a flaky test we'll make sure it waited for roughly 1 second by + * ensuring 900 < endTime - startTime < 1100 + */ + expect(endTime - startTime).toBeGreaterThan(900); + expect(endTime - startTime).toBeLessThan(1100); + }); + }); + + describe('Frame.waitForTimeout', () => { + it('waits for the given timeout before resolving', async () => { + const { page, server } = getTestState(); + await page.goto(server.EMPTY_PAGE); + const frame = page.mainFrame(); + const startTime = Date.now(); + await frame.waitForTimeout(1000); + const endTime = Date.now(); + /* In a perfect world endTime - startTime would be exactly 1000 but we + * expect some fluctuations and for it to be off by a little bit. So to + * avoid a flaky test we'll make sure it waited for roughly 1 second by + * ensuring 900 < endTime - startTime < 1100 + */ + expect(endTime - startTime).toBeGreaterThan(900); + expect(endTime - startTime).toBeLessThan(1100); + }); + }); + describe('Frame.waitForSelector', function () { const addElement = (tag) => document.body.appendChild(document.createElement(tag)); @@ -350,9 +405,9 @@ describe('waittask specs', function () { page.waitForSelector('.zombo'), page.setContent(`
anything
`), ]); - expect(await page.evaluate((x) => x.textContent, handle)).toBe( - 'anything' - ); + expect( + await page.evaluate((x: HTMLElement) => x.textContent, handle) + ).toBe('anything'); }); it('should resolve promise when node is added', async () => { @@ -549,7 +604,7 @@ describe('waittask specs', function () { .catch((error_) => (error = error_)); expect(error).toBeTruthy(); expect(error.message).toContain( - 'waiting for selector "div" failed: timeout' + 'waiting for selector `div` failed: timeout' ); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); @@ -563,7 +618,7 @@ describe('waittask specs', function () { .catch((error_) => (error = error_)); expect(error).toBeTruthy(); expect(error.message).toContain( - 'waiting for selector "div" to be hidden failed: timeout' + 'waiting for selector `div` to be hidden failed: timeout' ); }); @@ -587,7 +642,10 @@ describe('waittask specs', function () { const waitForSelector = page.waitForSelector('.zombo'); await page.setContent(`
anything
`); expect( - await page.evaluate((x) => x.textContent, await waitForSelector) + await page.evaluate( + (x: HTMLElement) => x.textContent, + await waitForSelector + ) ).toBe('anything'); }); it('should have correct stack trace for timeout', async () => { @@ -597,7 +655,7 @@ describe('waittask specs', function () { await page .waitForSelector('.zombo', { timeout: 10 }) .catch((error_) => (error = error_)); - expect(error.stack).toContain('waiting for selector ".zombo" failed'); + expect(error.stack).toContain('waiting for selector `.zombo` failed'); // The extension is ts here as Mocha maps back via sourcemaps. expect(error.stack).toContain('waittask.spec.ts'); }); @@ -615,7 +673,10 @@ describe('waittask specs', function () { '//p[normalize-space(.)="hello world"]' ); expect( - await page.evaluate((x) => x.textContent, await waitForXPath) + await page.evaluate( + (x: HTMLElement) => x.textContent, + await waitForXPath + ) ).toBe('hello world '); }); it('should respect timeout', async () => { @@ -627,7 +688,7 @@ describe('waittask specs', function () { .catch((error_) => (error = error_)); expect(error).toBeTruthy(); expect(error.message).toContain( - 'waiting for XPath "//div" failed: timeout' + 'waiting for XPath `//div` failed: timeout' ); expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); }); @@ -682,7 +743,10 @@ describe('waittask specs', function () { const waitForXPath = page.waitForXPath('//*[@class="zombo"]'); await page.setContent(`
anything
`); expect( - await page.evaluate((x) => x.textContent, await waitForXPath) + await page.evaluate( + (x: HTMLElement) => x.textContent, + await waitForXPath + ) ).toBe('anything'); }); it('should allow you to select a text node', async () => { @@ -700,7 +764,10 @@ describe('waittask specs', function () { await page.setContent(`
some text
`); const waitForXPath = page.waitForXPath('/html/body/div'); expect( - await page.evaluate((x) => x.textContent, await waitForXPath) + await page.evaluate( + (x: HTMLElement) => x.textContent, + await waitForXPath + ) ).toBe('some text'); }); }); diff --git a/remote/test/puppeteer/test/worker.spec.ts b/remote/test/puppeteer/test/worker.spec.ts index 86aba8ca28950..2c5827361b52e 100644 --- a/remote/test/puppeteer/test/worker.spec.ts +++ b/remote/test/puppeteer/test/worker.spec.ts @@ -20,10 +20,10 @@ import { setupTestBrowserHooks, setupTestPageAndContextHooks, describeFailsFirefox, -} from './mocha-utils'; -import utils from './utils'; -import { WebWorker } from '../src/common/WebWorker'; -import { ConsoleMessage } from '../src/common/ConsoleMessage'; +} from './mocha-utils'; // eslint-disable-line import/extensions +import utils from './utils.js'; +import { WebWorker } from '../lib/cjs/puppeteer/common/WebWorker.js'; +import { ConsoleMessage } from '../lib/cjs/puppeteer/common/ConsoleMessage.js'; const { waitEvent } = utils; describeFailsFirefox('Workers', function () { @@ -60,7 +60,10 @@ describeFailsFirefox('Workers', function () { const workerDestroyedPromise = new Promise((x) => page.once('workerdestroyed', x) ); - await page.evaluate((workerObj) => workerObj.terminate(), workerObj); + await page.evaluate( + (workerObj: Worker) => workerObj.terminate(), + workerObj + ); expect(await workerDestroyedPromise).toBe(worker); const error = await workerThisObj .getProperty('self') diff --git a/remote/test/puppeteer/tsconfig.base.json b/remote/test/puppeteer/tsconfig.base.json new file mode 100644 index 0000000000000..97d1cd068a4b1 --- /dev/null +++ b/remote/test/puppeteer/tsconfig.base.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "allowJs": true, + "checkJs": true, + "target": "ESNext", + "moduleResolution": "node", + "module": "ESNext", + "declaration": true, + "declarationMap": true, + "resolveJsonModule": true + } +} diff --git a/remote/test/puppeteer/tsconfig.json b/remote/test/puppeteer/tsconfig.json index 5a62ace19cb13..69717ed6d9b2e 100644 --- a/remote/test/puppeteer/tsconfig.json +++ b/remote/test/puppeteer/tsconfig.json @@ -1,17 +1,11 @@ +/** + * This configuration only exists for the API Extractor tool. See the details in + * CONTRIBUTING.md that describes our TypeScript setup. +*/ { + "extends": "./tsconfig.base.json", "compilerOptions": { - "allowJs": true, - "checkJs": true, - "outDir": "./lib/cjs", - "target": "ESNext", - "moduleResolution": "node", - "module": "CommonJS", - "declaration": true, - "declarationMap": true, - "esModuleInterop": true, - "resolveJsonModule": true + "noEmit": true }, - "include": [ - "src" - ] + "include": ["src"] } diff --git a/remote/test/puppeteer/utils/apply_next_version.js b/remote/test/puppeteer/utils/apply_next_version.js index 53e32f5c381c3..cd62839d2a66a 100644 --- a/remote/test/puppeteer/utils/apply_next_version.js +++ b/remote/test/puppeteer/utils/apply_next_version.js @@ -26,3 +26,7 @@ fs.writeFileSync( path.join(__dirname, '..', 'package.json'), JSON.stringify(package, undefined, 2) + '\n' ); + +console.log( + 'IMPORTANT: you should update the pinned version of devtools-protocol to match the new revision.' +); diff --git a/remote/test/puppeteer/utils/check_availability.js b/remote/test/puppeteer/utils/check_availability.js index 1a5f2cab49d84..e24e2b9dc3605 100755 --- a/remote/test/puppeteer/utils/check_availability.js +++ b/remote/test/puppeteer/utils/check_availability.js @@ -17,7 +17,9 @@ const assert = require('assert'); const https = require('https'); -const BrowserFetcher = require('../lib/BrowserFetcher').BrowserFetcher; +// run `npm run dev-install` if lib dir is missing +const BrowserFetcher = require('../lib/cjs/puppeteer/node/BrowserFetcher.js') + .BrowserFetcher; const SUPPORTER_PLATFORMS = ['linux', 'mac', 'win32', 'win64']; const fetchers = SUPPORTER_PLATFORMS.map( @@ -58,7 +60,9 @@ Usage: node check_availability.js [] [] options -f full mode checks availability of all the platforms, default mode - -r roll mode checks for the most recent Chromium roll candidate + -r roll mode checks for the most recent stable Chromium roll candidate + -rb roll mode checks for the most recent beta Chromium roll candidate + -rd roll mode checks for the most recent dev Chromium roll candidate -h show this help browser version(s) @@ -69,8 +73,9 @@ Examples To check Chromium availability of a certain revision node check_availability.js [revision] - To find a Chromium roll candidate for current Stable Linux version + To find a Chromium roll candidate for current stable Linux version node check_availability.js -r + use -rb for beta and -rd for dev versions. To check Chromium availability from the latest revision in a descending order node check_availability.js @@ -95,7 +100,13 @@ function main() { case 'f': break; case 'r': - checkRollCandidate(); + checkRollCandidate('stable'); + return; + case 'rb': + checkRollCandidate('beta'); + return; + case 'rd': + checkRollCandidate('dev'); return; default: console.log(helpMessage); @@ -146,28 +157,27 @@ async function checkOmahaProxyAvailability() { stopWhenAllAvailable: false, }); } - -async function checkRollCandidate() { +async function checkRollCandidate(channel) { const omahaResponse = await fetch( - 'https://omahaproxy.appspot.com/all.json?channel=stable&os=linux' + `https://omahaproxy.appspot.com/all.json?channel=${channel}&os=linux` ); - const stableLinuxInfo = JSON.parse(omahaResponse)[0]; - if (!stableLinuxInfo) { - console.error('no stable linux information available from omahaproxy'); + const linuxInfo = JSON.parse(omahaResponse)[0]; + if (!linuxInfo) { + console.error(`no ${channel} linux information available from omahaproxy`); return; } - const stableLinuxRevision = parseInt( - stableLinuxInfo.versions[0].branch_base_position, + const linuxRevision = parseInt( + linuxInfo.versions[0].branch_base_position, 10 ); const currentRevision = parseInt( - require('../lib/cjs/revisions').PUPPETEER_REVISIONS.chromium, + require('../lib/cjs/puppeteer/revisions').PUPPETEER_REVISIONS.chromium, 10 ); checkRangeAvailability({ - fromRevision: stableLinuxRevision, + fromRevision: linuxRevision, toRevision: currentRevision, stopWhenAllAvailable: true, }); diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/JSBuilder.js b/remote/test/puppeteer/utils/doclint/check_public_api/JSBuilder.js index a1744125b903f..0994dffcff5fb 100644 --- a/remote/test/puppeteer/utils/doclint/check_public_api/JSBuilder.js +++ b/remote/test/puppeteer/utils/doclint/check_public_api/JSBuilder.js @@ -128,9 +128,19 @@ function checkSources(sources) { ); const name = symbol.getName(); if (symbol.valueDeclaration && symbol.valueDeclaration.dotDotDotToken) { - const innerType = serializeType(type.typeArguments[0], circular); - innerType.name = '...' + innerType.name; - return Documentation.Member.createProperty('...' + name, innerType); + try { + const innerType = serializeType(type.typeArguments[0], circular); + innerType.name = '...' + innerType.name; + return Documentation.Member.createProperty('...' + name, innerType); + } catch (error) { + /** + * DocLint struggles with the paramArgs type on CDPSession.send because + * it uses a complex type from the devtools-protocol method. Doclint + * isn't going to be here for much longer so we'll just silence this + * warning than try to add support which would warrant a huge rewrite. + */ + if (name !== 'paramArgs') throw error; + } } return Documentation.Member.createProperty( name, diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/index.js b/remote/test/puppeteer/utils/doclint/check_public_api/index.js index d6d04a9147fd3..84d5ca0f2a9e1 100644 --- a/remote/test/puppeteer/utils/doclint/check_public_api/index.js +++ b/remote/test/puppeteer/utils/doclint/check_public_api/index.js @@ -18,6 +18,9 @@ const jsBuilder = require('./JSBuilder'); const mdBuilder = require('./MDBuilder'); const Documentation = require('./Documentation'); const Message = require('../Message'); +const { + MODULES_TO_CHECK_FOR_COVERAGE, +} = require('../../../test/coverage-utils'); const EXCLUDE_PROPERTIES = new Set([ 'Browser.create', @@ -39,10 +42,7 @@ const EXCLUDE_PROPERTIES = new Set([ module.exports = async function lint(page, mdSources, jsSources) { const mdResult = await mdBuilder(page, mdSources); const jsResult = await jsBuilder(jsSources); - const jsDocumentation = filterJSDocumentation( - jsSources, - jsResult.documentation - ); + const jsDocumentation = filterJSDocumentation(jsResult.documentation); const mdDocumentation = mdResult.documentation; const jsErrors = jsResult.errors; @@ -124,14 +124,11 @@ function checkSorting(doc) { } /** - * @param {!Array} jsSources * @param {!Documentation} jsDocumentation * @returns {!Documentation} */ -function filterJSDocumentation(jsSources, jsDocumentation) { - const apijs = jsSources.find((source) => source.name() === 'api.js'); - let includedClasses = null; - if (apijs) includedClasses = new Set(Object.keys(require(apijs.filePath()))); +function filterJSDocumentation(jsDocumentation) { + const includedClasses = new Set(Object.keys(MODULES_TO_CHECK_FOR_COVERAGE)); // Filter private classes and methods. const classes = []; for (const cls of jsDocumentation.classesArray) { @@ -209,10 +206,25 @@ function compareDocumentations(actual, expected) { const expectedClasses = Array.from(expected.classes.keys()).sort(); const classesDiff = diff(actualClasses, expectedClasses); + /* These have been moved onto PuppeteerNode but we want to document them under + * Puppeteer. See https://github.com/puppeteer/puppeteer/pull/6504 for details. + */ + const expectedPuppeteerClassMissingMethods = new Set([ + 'createBrowserFetcher', + 'defaultArgs', + 'executablePath', + 'launch', + ]); + for (const className of classesDiff.extra) errors.push(`Non-existing class found: ${className}`); - for (const className of classesDiff.missing) + + for (const className of classesDiff.missing) { + if (className === 'PuppeteerNode') { + continue; + } errors.push(`Class not found: ${className}`); + } for (const className of classesDiff.equal) { const actualClass = actual.classes.get(className); @@ -222,6 +234,12 @@ function compareDocumentations(actual, expected) { const methodDiff = diff(actualMethods, expectedMethods); for (const methodName of methodDiff.extra) { + if ( + expectedPuppeteerClassMissingMethods.has(methodName) && + actualClass.name === 'Puppeteer' + ) { + continue; + } errors.push(`Non-existing method found: ${className}.${methodName}()`); } @@ -254,15 +272,23 @@ function compareDocumentations(actual, expected) { const actualArgs = Array.from(actualMethod.args.keys()); const expectedArgs = Array.from(expectedMethod.args.keys()); const argsDiff = diff(actualArgs, expectedArgs); + if (argsDiff.extra.length || argsDiff.missing.length) { - const text = [ - `Method ${className}.${methodName}() fails to describe its parameters:`, - ]; - for (const arg of argsDiff.missing) - text.push(`- Argument not found: ${arg}`); - for (const arg of argsDiff.extra) - text.push(`- Non-existing argument found: ${arg}`); - errors.push(text.join('\n')); + /* Doclint cannot handle the parameter type of the CDPSession send method. + * so we just ignore it. + */ + const isCdpSessionSend = + className === 'CDPSession' && methodName === 'send'; + if (!isCdpSessionSend) { + const text = [ + `Method ${className}.${methodName}() fails to describe its parameters:`, + ]; + for (const arg of argsDiff.missing) + text.push(`- Argument not found: ${arg}`); + for (const arg of argsDiff.extra) + text.push(`- Non-existing argument found: ${arg}`); + errors.push(text.join('\n')); + } } for (const arg of argsDiff.equal) @@ -277,8 +303,12 @@ function compareDocumentations(actual, expected) { expectedClass.properties.keys() ).sort(); const propertyDiff = diff(actualProperties, expectedProperties); - for (const propertyName of propertyDiff.extra) + for (const propertyName of propertyDiff.extra) { + if (className === 'Puppeteer' && propertyName === 'product') { + continue; + } errors.push(`Non-existing property found: ${className}.${propertyName}`); + } for (const propertyName of propertyDiff.missing) errors.push(`Property not found: ${className}.${propertyName}`); @@ -388,6 +418,13 @@ function compareDocumentations(actual, expected) { expectedName: 'MouseOptions', }, ], + [ + 'Method Mouse.wheel() options', + { + actualName: 'Object', + expectedName: 'MouseWheelOptions', + }, + ], [ 'Method Tracing.start() options', { @@ -641,7 +678,7 @@ function compareDocumentations(actual, expected) { 'Method Page.emulateVisionDeficiency() type', { actualName: 'string', - expectedName: 'VisionDeficiency', + expectedName: 'Object', }, ], [ @@ -662,56 +699,56 @@ function compareDocumentations(actual, expected) { 'Method EventEmitter.emit() event', { actualName: 'string|symbol', - expectedName: 'Object', + expectedName: 'EventType', }, ], [ 'Method EventEmitter.listenerCount() event', { actualName: 'string|symbol', - expectedName: 'Object', + expectedName: 'EventType', }, ], [ 'Method EventEmitter.off() event', { actualName: 'string|symbol', - expectedName: 'Object', + expectedName: 'EventType', }, ], [ 'Method EventEmitter.on() event', { actualName: 'string|symbol', - expectedName: 'Object', + expectedName: 'EventType', }, ], [ 'Method EventEmitter.once() event', { actualName: 'string|symbol', - expectedName: 'Object', + expectedName: 'EventType', }, ], [ 'Method EventEmitter.removeListener() event', { actualName: 'string|symbol', - expectedName: 'Object', + expectedName: 'EventType', }, ], [ 'Method EventEmitter.addListener() event', { actualName: 'string|symbol', - expectedName: 'Object', + expectedName: 'EventType', }, ], [ 'Method EventEmitter.removeAllListeners() event', { actualName: 'string|symbol', - expectedName: 'Object', + expectedName: 'EventType', }, ], [ @@ -749,6 +786,55 @@ function compareDocumentations(actual, expected) { expectedName: 'MouseButton', }, ], + [ + 'Method HTTPRequest.continue() overrides', + { + actualName: 'Object', + expectedName: 'ContinueRequestOverrides', + }, + ], + [ + 'Method HTTPRequest.respond() response', + { + actualName: 'Object', + expectedName: 'ResponseForRequest', + }, + ], + [ + 'Method Frame.addScriptTag() options', + { + actualName: 'Object', + expectedName: 'FrameAddScriptTagOptions', + }, + ], + [ + 'Method Frame.addStyleTag() options', + { + actualName: 'Object', + expectedName: 'FrameAddStyleTagOptions', + }, + ], + [ + 'Method Frame.waitForFunction() options', + { + actualName: 'Object', + expectedName: 'FrameWaitForFunctionOptions', + }, + ], + [ + 'Method BrowserContext.overridePermissions() permissions', + { + actualName: 'Array', + expectedName: 'Array', + }, + ], + [ + 'Method Puppeteer.connect() options', + { + actualName: 'Object', + expectedName: 'ConnectOptions', + }, + ], ]); const expectedForSource = expectedNamingMismatches.get(source); @@ -786,6 +872,17 @@ function compareDocumentations(actual, expected) { * as they will likely be considered "wrong" by DocLint too. */ if (namingMismatchIsExpected) return; + + /* Some methods cause errors in the property checks for an unknown reason + * so we support a list of methods whose parameters are not checked. + */ + const skipPropertyChecksOnMethods = new Set([ + 'Method Page.deleteCookie() ...cookies', + 'Method Page.setCookie() ...cookies', + 'Method Puppeteer.connect() options', + ]); + if (skipPropertyChecksOnMethods.has(source)) return; + const actualPropertiesMap = new Map( actual.properties.map((property) => [property.name, property.type]) ); diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/.gitignore b/remote/test/puppeteer/utils/doclint/check_public_api/test/.gitignore deleted file mode 100644 index 17526b1162a50..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -result-actual.txt -result-diff.html diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-duplicates/doc.md b/remote/test/puppeteer/utils/doclint/check_public_api/test/check-duplicates/doc.md deleted file mode 100644 index ea743ecd1e317..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-duplicates/doc.md +++ /dev/null @@ -1,15 +0,0 @@ -### class: Bar - -### class: Foo - -#### foo.test() - -#### foo.test() - -#### foo.title(arg, arg) -- `arg` <[number]> -- `arg` <[number]> - -### class: Bar - -[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type "Number" diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-duplicates/foo.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/check-duplicates/foo.js deleted file mode 100644 index 640271ae4928a..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-duplicates/foo.js +++ /dev/null @@ -1,13 +0,0 @@ -class Foo { - test() { - } - - /** - * @param {number} arg - */ - title(arg) { - } -} - -class Bar { -} diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-duplicates/result.txt b/remote/test/puppeteer/utils/doclint/check_public_api/test/check-duplicates/result.txt deleted file mode 100644 index d9994416e9163..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-duplicates/result.txt +++ /dev/null @@ -1,3 +0,0 @@ -[MarkDown] Duplicate declaration of method Foo.test() -[MarkDown] Duplicate declaration of argument Foo.title "arg" -[MarkDown] Duplicate declaration of class Bar \ No newline at end of file diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-returns/doc.md b/remote/test/puppeteer/utils/doclint/check_public_api/test/check-returns/doc.md deleted file mode 100644 index 1dffb8b6f0deb..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-returns/doc.md +++ /dev/null @@ -1,14 +0,0 @@ -### class: Foo - -#### foo.asyncFunction() - -#### foo.return42() - -#### foo.returnNothing() -- returns: <[number]> - -#### foo.www() -- returns <[string]> - -[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String" -[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type "Number" diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-returns/foo.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/check-returns/foo.js deleted file mode 100644 index 5833c14699640..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-returns/foo.js +++ /dev/null @@ -1,20 +0,0 @@ -class Foo { - return42() { - return 42; - } - - returnNothing() { - let e = () => { - return 10; - } - e(); - } - - www() { - if (1 === 1) - return 'df'; - } - - async asyncFunction() { - } -} diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-returns/result.txt b/remote/test/puppeteer/utils/doclint/check_public_api/test/check-returns/result.txt deleted file mode 100644 index ceb202b25f51b..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-returns/result.txt +++ /dev/null @@ -1,4 +0,0 @@ -[MarkDown] foo.www() has mistyped 'return' type declaration: expected exactly 'returns: ', found 'returns '. -[MarkDown] Method Foo.asyncFunction is missing return type description -[MarkDown] Method Foo.return42 is missing return type description -[MarkDown] Method Foo.returnNothing has unneeded description of return type \ No newline at end of file diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-sorting/Events.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/check-sorting/Events.js deleted file mode 100644 index 55f20be064b1d..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-sorting/Events.js +++ /dev/null @@ -1,8 +0,0 @@ -const Events = { - Foo: { - a: 'a', - b: 'b', - c: 'c', - }, -}; -module.exports = {Events}; diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-sorting/doc.md b/remote/test/puppeteer/utils/doclint/check_public_api/test/check-sorting/doc.md deleted file mode 100644 index 9b3162824a6d1..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-sorting/doc.md +++ /dev/null @@ -1,15 +0,0 @@ -### class: Foo - -#### event: 'c' - -#### event: 'a' - -#### foo.aaa() - -#### event: 'b' - -#### foo.ddd - -#### foo.ccc() - -#### foo.bbb() diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-sorting/foo.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/check-sorting/foo.js deleted file mode 100644 index 9a61bf11a4d98..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-sorting/foo.js +++ /dev/null @@ -1,12 +0,0 @@ -class Foo { - constructor() { - this.ddd = 10; - } - - aaa() {} - - bbb() {} - - ccc() {} -} - diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-sorting/result.txt b/remote/test/puppeteer/utils/doclint/check_public_api/test/check-sorting/result.txt deleted file mode 100644 index fee39688e695b..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/check-sorting/result.txt +++ /dev/null @@ -1,4 +0,0 @@ -[MarkDown] Events should go first. Event 'b' in class Foo breaks order -[MarkDown] Event 'c' in class Foo breaks alphabetic ordering of events -[MarkDown] Bad alphabetic ordering of Foo members: Foo.ddd should go after Foo.ccc() -[MarkDown] Bad alphabetic ordering of Foo members: Foo.ccc() should go after Foo.bbb() \ No newline at end of file diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-arguments/doc.md b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-arguments/doc.md deleted file mode 100644 index a991bfa66b90b..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-arguments/doc.md +++ /dev/null @@ -1,14 +0,0 @@ -### class: Foo -#### foo.bar(options) -- `options` <[Object]> - - `visibility` <[boolean]> -#### foo.foo(arg1, arg2) -- `arg1` <[string]> -- `arg2` <[string]> - -#### foo.test(...files) -- `...filePaths` <...[string]> - -[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object "Object" -[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String" -[boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type "Boolean" diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-arguments/foo.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-arguments/foo.js deleted file mode 100644 index d102f6f7ae185..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-arguments/foo.js +++ /dev/null @@ -1,19 +0,0 @@ -class Foo { - /** - * @param {string} arg1 - */ - foo(arg1, arg3 = {}) { - } - - /** - * @param {...string} filePaths - */ - test(...filePaths) { - } - - /** - * @param {{visibility?: boolean}} options - */ - bar(options) { - } -} diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-arguments/result.txt b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-arguments/result.txt deleted file mode 100644 index bd6b1f0eb10b4..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-arguments/result.txt +++ /dev/null @@ -1,4 +0,0 @@ -[MarkDown] Heading arguments for "foo.test(...files)" do not match described ones, i.e. "...files" != "...filePaths" -[MarkDown] Method Foo.foo() fails to describe its parameters: -- Argument not found: arg3 -- Non-existing argument found: arg2 \ No newline at end of file diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-classes/doc.md b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-classes/doc.md deleted file mode 100644 index 7e51ca4754d12..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-classes/doc.md +++ /dev/null @@ -1,5 +0,0 @@ -### class: Foo - -### class: Bar - -### class: Baz diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-classes/foo.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-classes/foo.js deleted file mode 100644 index f230fa0f41dde..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-classes/foo.js +++ /dev/null @@ -1,2 +0,0 @@ -class Foo { -} diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-classes/other.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-classes/other.js deleted file mode 100644 index 1b94418eb0af4..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-classes/other.js +++ /dev/null @@ -1,2 +0,0 @@ -class Other { -} diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-classes/result.txt b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-classes/result.txt deleted file mode 100644 index 2852a437c6ef6..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-classes/result.txt +++ /dev/null @@ -1,3 +0,0 @@ -[MarkDown] Non-existing class found: Bar -[MarkDown] Non-existing class found: Baz -[MarkDown] Class not found: Other \ No newline at end of file diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-events/Events.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-events/Events.js deleted file mode 100644 index 7ef8bcdfdb01d..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-events/Events.js +++ /dev/null @@ -1,8 +0,0 @@ -const Events = { - Foo: { - Start: 'start', - Finish: 'finish', - }, -}; - -module.exports = {Events}; diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-events/doc.md b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-events/doc.md deleted file mode 100644 index 3da9c7951139c..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-events/doc.md +++ /dev/null @@ -1,5 +0,0 @@ -### class: Foo - -#### event: 'start' - -#### event: 'stop' diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-events/foo.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-events/foo.js deleted file mode 100644 index fbb14b31366de..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-events/foo.js +++ /dev/null @@ -1,3 +0,0 @@ -class Foo { -} - diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-events/result.txt b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-events/result.txt deleted file mode 100644 index 8a18433aeda8c..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-events/result.txt +++ /dev/null @@ -1,2 +0,0 @@ -[MarkDown] Non-existing event found in class Foo: 'stop' -[MarkDown] Event not found in class Foo: 'finish' \ No newline at end of file diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-methods/doc.md b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-methods/doc.md deleted file mode 100644 index 489d014988332..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-methods/doc.md +++ /dev/null @@ -1,10 +0,0 @@ -### class: Foo - -#### foo.$() - -#### foo.money$$money() - -#### foo.proceed() - -#### foo.start() - diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-methods/foo.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-methods/foo.js deleted file mode 100644 index 76f44edab7730..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-methods/foo.js +++ /dev/null @@ -1,16 +0,0 @@ -class Foo { - start() { - } - - stop() { - } - - get zzz() { - } - - $() { - } - - money$$money() { - } -} diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-methods/result.txt b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-methods/result.txt deleted file mode 100644 index d919f52e99ce1..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-methods/result.txt +++ /dev/null @@ -1,3 +0,0 @@ -[MarkDown] Non-existing method found: Foo.proceed() -[MarkDown] Method not found: Foo.stop() -[MarkDown] Property not found: Foo.zzz \ No newline at end of file diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-properties/doc.md b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-properties/doc.md deleted file mode 100644 index 75d4fd106fe35..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-properties/doc.md +++ /dev/null @@ -1,5 +0,0 @@ -### class: Foo - -#### foo.a - -#### foo.c diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-properties/foo.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-properties/foo.js deleted file mode 100644 index a978379f2cefe..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-properties/foo.js +++ /dev/null @@ -1,7 +0,0 @@ -class Foo { - constructor() { - this.a = 42; - this.b = 'hello'; - this.emit('done'); - } -} diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-properties/result.txt b/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-properties/result.txt deleted file mode 100644 index 382a4d41b8fea..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/diff-properties/result.txt +++ /dev/null @@ -1,2 +0,0 @@ -[MarkDown] Non-existing property found: Foo.c -[MarkDown] Property not found: Foo.b \ No newline at end of file diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-common/Events.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-common/Events.js deleted file mode 100644 index da44ad81a1b68..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-common/Events.js +++ /dev/null @@ -1,6 +0,0 @@ -const Events = { - A: { - AnEvent: 'anevent' - }, -}; -module.exports = { Events }; diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-common/foo.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-common/foo.js deleted file mode 100644 index cf82646e3c3e9..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-common/foo.js +++ /dev/null @@ -1,13 +0,0 @@ -class A { - constructor(delegate) { - this.property1 = 1; - this._property2 = 2; - } - - get getter() { - return null; - } - - async method(foo, bar) { - } -} diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-common/result.txt b/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-common/result.txt deleted file mode 100644 index 40e9475b020b0..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-common/result.txt +++ /dev/null @@ -1,50 +0,0 @@ -{ - "classes": [ - { - "name": "A", - "members": [ - { - "name": "anevent", - "kind": "event" - }, - { - "name": "property1", - "type": { - "name": "number" - }, - "kind": "property" - }, - { - "name": "getter", - "type": { - "name": "Object" - }, - "kind": "property" - }, - { - "name": "method", - "type": { - "name": "Promise" - }, - "kind": "method", - "args": [ - { - "name": "foo", - "type": { - "name": "Object" - }, - "kind": "property" - }, - { - "name": "bar", - "type": { - "name": "Object" - }, - "kind": "property" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-inheritance/Events.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-inheritance/Events.js deleted file mode 100644 index f22e6aadc4785..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-inheritance/Events.js +++ /dev/null @@ -1,8 +0,0 @@ -const Events = { - B: { - // Event with the same name as a super class method. - foo: 'foo', - }, -}; - -module.exports = {Events}; diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-inheritance/foo.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-inheritance/foo.js deleted file mode 100644 index 1d5cb154d48e3..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-inheritance/foo.js +++ /dev/null @@ -1,15 +0,0 @@ -class A { - constructor() { - } - - foo(a) { - } - - bar() { - } -} - -class B extends A { - bar(override) { - } -} diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-inheritance/result.txt b/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-inheritance/result.txt deleted file mode 100644 index 0975d03ab39c2..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/js-builder-inheritance/result.txt +++ /dev/null @@ -1,61 +0,0 @@ -{ - "classes": [ - { - "name": "A", - "members": [ - { - "name": "foo", - "kind": "method", - "args": [ - { - "name": "a", - "type": { - "name": "Object" - }, - "kind": "property" - } - ] - }, - { - "name": "bar", - "kind": "method" - } - ] - }, - { - "name": "B", - "members": [ - { - "name": "foo", - "kind": "event" - }, - { - "name": "bar", - "kind": "method", - "args": [ - { - "name": "override", - "type": { - "name": "Object" - }, - "kind": "property" - } - ] - }, - { - "name": "foo", - "kind": "method", - "args": [ - { - "name": "a", - "type": { - "name": "Object" - }, - "kind": "property" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/md-builder-common/doc.md b/remote/test/puppeteer/utils/doclint/check_public_api/test/md-builder-common/doc.md deleted file mode 100644 index 42695c4cd42b7..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/md-builder-common/doc.md +++ /dev/null @@ -1,24 +0,0 @@ -### class: Foo - -This is a class. - -#### event: 'frame' -- <[Frame]> - -This event is dispatched. - -#### foo.$(selector) -- `selector` <[string]> A selector to query page for -- returns: <[Promise]<[ElementHandle]>> - -The method runs document.querySelector. - -#### foo.url -- <[string]> - -Contains the URL of the request. - -[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String" -[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise" -[ElementHandle]: # "ElementHandle" -[ElementHandle]: # "Frame" diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/md-builder-common/result.txt b/remote/test/puppeteer/utils/doclint/check_public_api/test/md-builder-common/result.txt deleted file mode 100644 index 46c065f26b359..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/md-builder-common/result.txt +++ /dev/null @@ -1,39 +0,0 @@ -{ - "classes": [ - { - "name": "Foo", - "members": [ - { - "name": "frame", - "type": { - "name": "[Frame]" - }, - "kind": "event" - }, - { - "name": "$", - "type": { - "name": "Promise" - }, - "kind": "method", - "args": [ - { - "name": "selector", - "type": { - "name": "string" - }, - "kind": "property" - } - ] - }, - { - "name": "url", - "type": { - "name": "string" - }, - "kind": "property" - } - ] - } - ] -} \ No newline at end of file diff --git a/remote/test/puppeteer/utils/doclint/check_public_api/test/public-api.spec.js b/remote/test/puppeteer/utils/doclint/check_public_api/test/public-api.spec.js deleted file mode 100644 index 51fedbc77e4c4..0000000000000 --- a/remote/test/puppeteer/utils/doclint/check_public_api/test/public-api.spec.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright 2017 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const path = require('path'); -const puppeteer = require('../../../..'); -const checkPublicAPI = require('..'); -const Source = require('../../Source'); -const mdBuilder = require('../MDBuilder'); -const jsBuilder = require('../JSBuilder'); -const expect = require('expect') -const GoldenUtils = require('../../../../test/golden-utils'); - -const testUtils = require('../../../../test/utils') - - -describe('DocLint Public API', function() { - let browser; - let page; - - before(async function() { - browser = await puppeteer.launch(); - page = await browser.newPage(); - }); - - after(async function() { - await browser.close(); - }); - - describe('checkPublicAPI', function() { - it('diff-classes', testLint('diff-classes')); - it('diff-methods', testLint('diff-methods')); - it('diff-properties', testLint('diff-properties')); - it('diff-arguments', testLint('diff-arguments')); - it('diff-events', testLint('diff-events')); - it('check-duplicates', testLint('check-duplicates')); - it('check-sorting', testLint('check-sorting')); - it('check-returns', testLint('check-returns')); - it('js-builder-common', testJSBuilder('js-builder-common')); - it('js-builder-inheritance', testJSBuilder('js-builder-inheritance')); - it('md-builder-common', testMDBuilder('md-builder-common')); - }); - - function testLint(testName) { - return async () => { - const dirPath = path.join(__dirname, testName); - testUtils.extendExpectWithToBeGolden(dirPath, dirPath) - - const mdSources = await Source.readdir(dirPath, '.md'); - const jsSources = await Source.readdir(dirPath, '.js'); - const messages = await checkPublicAPI(page, mdSources, jsSources); - const errors = messages.map(message => message.text); - expect(errors.join('\n')).toBeGolden('result.txt'); - } - } - - function testMDBuilder(testName) { - return async () => { - const dirPath = path.join(__dirname, testName); - testUtils.extendExpectWithToBeGolden(dirPath, dirPath); - - const sources = await Source.readdir(dirPath, '.md'); - const { documentation } = await mdBuilder(page, sources); - expect(serialize(documentation)).toBeGolden('result.txt'); - } - } - - function testJSBuilder(testName) { - return async () => { - const dirPath = path.join(__dirname, testName); - testUtils.extendExpectWithToBeGolden(dirPath, dirPath); - - const sources = await Source.readdir(dirPath, '.js'); - const { documentation } = await jsBuilder(sources); - expect(serialize(documentation)).toBeGolden('result.txt'); - } - } - - /** - * @param {import('../Documentation')} doc - */ - function serialize(doc) { - const result = { - classes: doc.classesArray.map(cls => ({ - name: cls.name, - members: cls.membersArray.map(serializeMember) - })) - }; - return JSON.stringify(result, null, 2); - } - /** - * @param {import('../Documentation').Member} member - */ - function serializeMember(member) { - return { - name: member.name, - type: serializeType(member.type), - kind: member.kind, - args: member.argsArray.length ? member.argsArray.map(serializeMember) : undefined - } - } - /** - * @param {import('../Documentation').Type} type - */ - function serializeType(type) { - if (!type) - return undefined; - return { - name: type.name, - properties: type.properties.length ? type.properties.map(serializeMember) : undefined - } - } - }) diff --git a/remote/test/puppeteer/utils/doclint/cli.js b/remote/test/puppeteer/utils/doclint/cli.js index ef551ac080f84..75775c65e5ed5 100755 --- a/remote/test/puppeteer/utils/doclint/cli.js +++ b/remote/test/puppeteer/utils/doclint/cli.js @@ -35,6 +35,14 @@ async function run() { const messages = []; let changedFiles = false; + if (!VERSION.endsWith('-post')) { + const versions = await Source.readFile( + path.join(PROJECT_DIR, 'versions.js') + ); + versions.setText(versions.text().replace(`, 'NEXT'],`, `, '${VERSION}'],`)); + await versions.save(); + } + // Documentation checks. const readme = await Source.readFile(path.join(PROJECT_DIR, 'README.md')); const contributing = await Source.readFile( @@ -72,8 +80,12 @@ async function run() { const jsSources = [ ...(await Source.readdir(path.join(PROJECT_DIR, 'lib'))), ...(await Source.readdir(path.join(PROJECT_DIR, 'lib', 'cjs'))), - ...(await Source.readdir(path.join(PROJECT_DIR, 'lib', 'cjs', 'common'))), - ...(await Source.readdir(path.join(PROJECT_DIR, 'lib', 'cjs', 'node'))), + ...(await Source.readdir( + path.join(PROJECT_DIR, 'lib', 'cjs', 'puppeteer', 'common') + )), + ...(await Source.readdir( + path.join(PROJECT_DIR, 'lib', 'cjs', 'puppeteer', 'node') + )), ]; const allSrcCode = [...jsSources, ...tsSourcesNoDefinitions]; messages.push(...(await checkPublicAPI(page, mdSources, allSrcCode))); diff --git a/remote/test/puppeteer/utils/doclint/preprocessor/index.js b/remote/test/puppeteer/utils/doclint/preprocessor/index.js index 2dac976a94793..340d973c9aad5 100644 --- a/remote/test/puppeteer/utils/doclint/preprocessor/index.js +++ b/remote/test/puppeteer/utils/doclint/preprocessor/index.js @@ -77,6 +77,8 @@ module.exports.runCommands = function (sources, version) { newText = generateTableOfContents( command.source.text().substring(command.to) ); + else if (command.name === 'versions-per-release') + newText = generateVersionsPerRelease(); if (newText === null) messages.push(Message.error(`Unknown command 'gen:${command.name}'`)); else if (applyCommand(command, newText)) changedSources.add(command.source); @@ -144,3 +146,20 @@ function generateTableOfContents(mdText) { '\n' ); } + +const generateVersionsPerRelease = () => { + const versionsPerRelease = require('../../../versions.js'); + const buffer = ['- Releases per Chromium version:']; + for (const [chromiumVersion, puppeteerVersion] of versionsPerRelease) { + if (puppeteerVersion === 'NEXT') continue; + buffer.push( + ` * Chromium ${chromiumVersion} - [Puppeteer ${puppeteerVersion}](https://github.com/puppeteer/puppeteer/blob/${puppeteerVersion}/docs/api.md)` + ); + } + buffer.push( + ` * [All releases](https://github.com/puppeteer/puppeteer/releases)` + ); + + const output = '\n' + buffer.join('\n') + '\n'; + return output; +}; diff --git a/remote/test/puppeteer/utils/protocol-types-generator/index.js b/remote/test/puppeteer/utils/protocol-types-generator/index.js deleted file mode 100644 index 5f551e6d8b145..0000000000000 --- a/remote/test/puppeteer/utils/protocol-types-generator/index.js +++ /dev/null @@ -1,276 +0,0 @@ -// @ts-check -const path = require('path'); -const puppeteer = require('../..'); -const { execSync } = require('child_process'); - -const fetchAndGenerateProtocolDefinitions = () => - puppeteer - .launch({ - pipe: false, - executablePath: process.env.BINARY, - }) - .then(async (browser) => { - const origin = browser - .wsEndpoint() - .match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1]; - const page = await browser.newPage(); - await page.goto(`http://${origin}/json/protocol`); - const json = JSON.parse( - await page.evaluate(() => document.documentElement.innerText) - ); - const version = await browser.version(); - await browser.close(); - const output = `// This is generated from /utils/protocol-types-generator/index.js - type binary = string; - -declare module Protocol {${json.domains - .map( - (domain) => `${ - domain.description - ? ` - /** - * ${domain.description} - */` - : '' - } - export module ${domain.domain} {${(domain.types || []) - .map( - (type) => - `${ - type.description - ? ` - /** - * ${type.description} - */` - : '' - }${ - type.properties - ? ` - export interface ${type.id} {${(type.properties || []) - .map( - (property) => `${ - property.description - ? ` - /** - * ${property.description} - */` - : '' - } - ${property.name}${property.optional ? '?' : ''}: ${typeOfProperty( - property - )};` - ) - .join(``)} - }` - : ` - export type ${type.id} = ${typeOfProperty(type)};` - }` - ) - .join('')} - ${(domain.events || []) - .map( - (event) => - `${ - event.description - ? ` - /** - * ${event.description} - */` - : '' - }${ - event.parameters - ? ` - export type ${event.name}Payload = {${event.parameters - .map( - (parameter) => `${ - parameter.description - ? ` - /** - * ${parameter.description} - */` - : '' - } - ${parameter.name}${parameter.optional ? '?' : ''}: ${typeOfProperty( - parameter - )};` - ) - .join(``)} - }` - : ` - export type ${event.name}Payload = void;` - }` - ) - .join('')} - ${(domain.commands || []) - .map( - (command) => `${ - command.description - ? ` - /** - * ${command.description} - */` - : '' - } - export type ${command.name}Parameters = {${(command.parameters || []) - .map( - (parameter) => `${ - parameter.description - ? ` - /** - * ${parameter.description} - */` - : '' - } - ${parameter.name}${parameter.optional ? '?' : ''}: ${typeOfProperty( - parameter - )};` - ) - .join(``)} - } - export type ${command.name}ReturnValue = {${(command.returns || []) - .map( - (retVal) => `${ - retVal.description - ? ` - /** - * ${retVal.description} - */` - : '' - } - ${retVal.name}${retVal.optional ? '?' : ''}: ${typeOfProperty( - retVal - )};` - ) - .join(``)} - }` - ) - .join('')} - } - ` - ) - .join('')} - export interface Events {${json.domains - .map((domain) => - (domain.events || []) - .map( - (event) => ` - "${domain.domain}.${event.name}": ${domain.domain}.${event.name}Payload;` - ) - .join('') - ) - .join('')} - } - export interface CommandParameters {${json.domains - .map((domain) => - (domain.commands || []) - .map( - (command) => ` - "${domain.domain}.${command.name}": ${domain.domain}.${command.name}Parameters;` - ) - .join('') - ) - .join('')} - } - export interface CommandReturnValues {${json.domains - .map((domain) => - (domain.commands || []) - .map( - (command) => ` - "${domain.domain}.${command.name}": ${domain.domain}.${command.name}ReturnValue;` - ) - .join('') - ) - .join('')} - } - } - -export default Protocol; -`; - - return { output, version }; - }); - -const protocolOutputPath = path.join(__dirname, '../../src/protocol.d.ts'); -const relativeProtocolOutputPath = path.relative( - process.cwd(), - protocolOutputPath -); - -const writeOutputToDisk = ({ output, version }) => { - require('fs').writeFileSync(protocolOutputPath, output); - console.log( - `Wrote protocol.d.ts for ${version} to ${relativeProtocolOutputPath}` - ); - console.log(`You should commit the changes.`); -}; - -const lastCommitMessage = () => { - return execSync('git log --no-merges -n 1', { encoding: 'utf8' }); -}; - -const cli = async () => { - const scriptToRun = process.argv[2]; - const changeExpected = lastCommitMessage().includes('EXPECTED_PROTOCOL_DIFF'); - - if (scriptToRun === 'update') { - writeOutputToDisk(await fetchAndGenerateProtocolDefinitions()); - } else if (scriptToRun === 'compare') { - const { output } = await fetchAndGenerateProtocolDefinitions(); - const outputOnDisk = require('fs').readFileSync(protocolOutputPath, { - encoding: 'utf8', - }); - if (output === outputOnDisk) { - console.log(`Success: ${relativeProtocolOutputPath} is up to date.`); - } else if (changeExpected) { - console.log(`Warning: ${relativeProtocolOutputPath} is out of date`); - - console.log( - ' continuing because EXPECTED_PROTOCOL_DIFF was found in the last commit message.' - ); - } else { - console.log(`Error: ${relativeProtocolOutputPath} is out of date.`); - console.log( - 'You should run `npm run update-protocol-d-ts` and commit the changes.' - ); - process.exit(1); - } - } else { - console.log(`Unknown protocol script ${scriptToRun}.`); - console.log(`Valid scripts are: - - update: fetch and update ${relativeProtocolOutputPath} - - compare: check ${relativeProtocolOutputPath} is up to date with the latest CDP. - `); - process.exit(1); - } -}; - -cli(); - -/** - * @typedef {Object} Property - * @property {string=} $ref - * @property {!Array=} enum - * @property {string=} type - * @property {!Property=} items - * @property {string=} description - */ - -/** - * @param {!Property} property - * @param {string=} domain - */ -function typeOfProperty(property, domain) { - if (property.$ref) - return property.$ref.includes('.') || !domain - ? property.$ref - : domain + '.' + property.$ref; - if (property.enum) - return property.enum.map((value) => JSON.stringify(value)).join('|'); - switch (property.type) { - case 'array': - return typeOfProperty(property.items, domain) + '[]'; - case 'integer': - return 'number'; - } - return property.type; -} diff --git a/remote/test/puppeteer/vendor/README.md b/remote/test/puppeteer/vendor/README.md new file mode 100644 index 0000000000000..bca3d5e10f3da --- /dev/null +++ b/remote/test/puppeteer/vendor/README.md @@ -0,0 +1,13 @@ +# Vendoring third party dependencies + +Because we are working towards an agnostic Puppeteer that can run in any environment (see [#6125](https://github.com/puppeteer/puppeteer/issues/6125)) we cannot import common dependencies in a way that relies on Node's resolution to find them. For example, `import mitt from 'mitt'` works fine in Node, but in an ESM build running in the browser, the browser has no idea where to find `'mitt'`. + +Therefore we put all common dependencies into this directory, `vendor`. This means there are extra criteria for these dependencies; ideally they will not depend on any other modules. If they do, we should consider an alternative way of managing our dependencies. + +The process for updating a vendored dependency is: + +1. `npm install {DEP NAME HERE}` +2. `cp -r node_modules/DEP vendor` +3. Update `eslintrc.js` to forbid importing DEP directly (see the `Mitt` rule already defined in there). +4. Use the new DEP, and run `npm run tsc` to check everything compiles successfully. +5. If the dep ships as compiled JS, you may need to disable TypeScript checking the file. Add an entry to the `excludes` property of the TSConfig files in `vendor`. (again, see the entry that's already there for Mitt as an example). Don't forget to update both the ESM and CJS config files. diff --git a/remote/test/puppeteer/vendor/mitt/README.md b/remote/test/puppeteer/vendor/mitt/README.md new file mode 100644 index 0000000000000..08a21bf7adb24 --- /dev/null +++ b/remote/test/puppeteer/vendor/mitt/README.md @@ -0,0 +1,179 @@ +

+ mitt +
+ npm + build status + gzip size +

+ +# Mitt + +> Tiny 200b functional event emitter / pubsub. + +- **Microscopic:** weighs less than 200 bytes gzipped +- **Useful:** a wildcard `"*"` event type listens to all events +- **Familiar:** same names & ideas as [Node's EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) +- **Functional:** methods don't rely on `this` +- **Great Name:** somehow [mitt](https://npm.im/mitt) wasn't taken + +Mitt was made for the browser, but works in any JavaScript runtime. It has no dependencies and supports IE9+. + +## Table of Contents + +- [Install](#install) +- [Usage](#usage) +- [Examples & Demos](#examples--demos) +- [API](#api) +- [Contribute](#contribute) +- [License](#license) + +## Install + +This project uses [node](http://nodejs.org) and [npm](https://npmjs.com). Go check them out if you don't have them locally installed. + +```sh +$ npm install --save mitt +``` + +Then with a module bundler like [rollup](http://rollupjs.org/) or [webpack](https://webpack.js.org/), use as you would anything else: + +```javascript +// using ES6 modules +import mitt from 'mitt' + +// using CommonJS modules +var mitt = require('mitt') +``` + +The [UMD](https://github.com/umdjs/umd) build is also available on [unpkg](https://unpkg.com): + +```html + +``` + +You can find the library on `window.mitt`. + +## Usage + +```js +import mitt from 'mitt' + +const emitter = mitt() + +// listen to an event +emitter.on('foo', e => console.log('foo', e) ) + +// listen to all events +emitter.on('*', (type, e) => console.log(type, e) ) + +// fire an event +emitter.emit('foo', { a: 'b' }) + +// clearing all events +emitter.all.clear() + +// working with handler references: +function onFoo() {} +emitter.on('foo', onFoo) // listen +emitter.off('foo', onFoo) // unlisten +``` + +### Typescript + +```ts +import mitt from 'mitt'; +const emitter: mitt.Emitter = mitt(); +``` + +## Examples & Demos + + + Preact + Mitt Codepen Demo +
+ preact + mitt preview +
+ +* * * + +## API + + + +#### Table of Contents + +- [mitt](#mitt) +- [all](#all) +- [on](#on) + - [Parameters](#parameters) +- [off](#off) + - [Parameters](#parameters-1) +- [emit](#emit) + - [Parameters](#parameters-2) + +### mitt + +Mitt: Tiny (~200b) functional event emitter / pubsub. + +Returns **Mitt** + +### all + +A Map of event names to registered handler functions. + +### on + +Register an event handler for the given type. + +#### Parameters + +- `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** Type of event to listen for, or `"*"` for all events +- `handler` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Function to call in response to given event + +### off + +Remove an event handler for the given type. + +#### Parameters + +- `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** Type of event to unregister `handler` from, or `"*"` +- `handler` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Handler function to remove + +### emit + +Invoke all handlers for the given type. +If present, `"*"` handlers are invoked after type-matched handlers. + +Note: Manually firing "\*" handlers is not supported. + +#### Parameters + +- `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** The event type to invoke +- `evt` **Any?** Any value (object is recommended and powerful), passed to each handler + +## Contribute + +First off, thanks for taking the time to contribute! +Now, take a moment to be sure your contributions make sense to everyone else. + +### Reporting Issues + +Found a problem? Want a new feature? First of all see if your issue or idea has [already been reported](../../issues). +If don't, just open a [new clear and descriptive issue](../../issues/new). + +### Submitting pull requests + +Pull requests are the greatest contributions, so be sure they are focused in scope, and do avoid unrelated commits. + +- Fork it! +- Clone your fork: `git clone https://github.com//mitt` +- Navigate to the newly cloned directory: `cd mitt` +- Create a new branch for the new feature: `git checkout -b my-new-feature` +- Install the tools necessary for development: `npm install` +- Make your changes. +- Commit your changes: `git commit -am 'Add some feature'` +- Push to the branch: `git push origin my-new-feature` +- Submit a pull request with full remarks documenting your changes. + +## License + +[MIT License](https://opensource.org/licenses/MIT) © [Jason Miller](https://jasonformat.com/) diff --git a/remote/test/puppeteer/vendor/mitt/dist/mitt.es.js b/remote/test/puppeteer/vendor/mitt/dist/mitt.es.js new file mode 100644 index 0000000000000..889e27282f65c --- /dev/null +++ b/remote/test/puppeteer/vendor/mitt/dist/mitt.es.js @@ -0,0 +1,2 @@ +export default function(n){return{all:n=n||new Map,on:function(t,e){var i=n.get(t);i&&i.push(e)||n.set(t,[e])},off:function(t,e){var i=n.get(t);i&&i.splice(i.indexOf(e)>>>0,1)},emit:function(t,e){(n.get(t)||[]).slice().map(function(n){n(e)}),(n.get("*")||[]).slice().map(function(n){n(t,e)})}}} +//# sourceMappingURL=mitt.es.js.map diff --git a/remote/test/puppeteer/vendor/mitt/dist/mitt.es.js.map b/remote/test/puppeteer/vendor/mitt/dist/mitt.es.js.map new file mode 100644 index 0000000000000..6576278e2da07 --- /dev/null +++ b/remote/test/puppeteer/vendor/mitt/dist/mitt.es.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mitt.es.js","sources":["../src/index.ts"],"sourcesContent":["export type EventType = string | symbol;\n\n// An event handler can take an optional event argument\n// and should not return a value\nexport type Handler = (event?: T) => void;\nexport type WildcardHandler = (type: EventType, event?: any) => void;\n\n// An array of all currently registered event handlers for a type\nexport type EventHandlerList = Array;\nexport type WildCardEventHandlerList = Array;\n\n// A map of event types and their corresponding event handlers.\nexport type EventHandlerMap = Map;\n\nexport interface Emitter {\n\tall: EventHandlerMap;\n\n\ton(type: EventType, handler: Handler): void;\n\ton(type: '*', handler: WildcardHandler): void;\n\n\toff(type: EventType, handler: Handler): void;\n\toff(type: '*', handler: WildcardHandler): void;\n\n\temit(type: EventType, event?: T): void;\n\temit(type: '*', event?: any): void;\n}\n\n/**\n * Mitt: Tiny (~200b) functional event emitter / pubsub.\n * @name mitt\n * @returns {Mitt}\n */\nexport default function mitt(all?: EventHandlerMap): Emitter {\n\tall = all || new Map();\n\n\treturn {\n\n\t\t/**\n\t\t * A Map of event names to registered handler functions.\n\t\t */\n\t\tall,\n\n\t\t/**\n\t\t * Register an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to listen for, or `\"*\"` for all events\n\t\t * @param {Function} handler Function to call in response to given event\n\t\t * @memberOf mitt\n\t\t */\n\t\ton(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tconst added = handlers && handlers.push(handler);\n\t\t\tif (!added) {\n\t\t\t\tall.set(type, [handler]);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to unregister `handler` from, or `\"*\"`\n\t\t * @param {Function} handler Handler function to remove\n\t\t * @memberOf mitt\n\t\t */\n\t\toff(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tif (handlers) {\n\t\t\t\thandlers.splice(handlers.indexOf(handler) >>> 0, 1);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Invoke all handlers for the given type.\n\t\t * If present, `\"*\"` handlers are invoked after type-matched handlers.\n\t\t *\n\t\t * Note: Manually firing \"*\" handlers is not supported.\n\t\t *\n\t\t * @param {string|symbol} type The event type to invoke\n\t\t * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler\n\t\t * @memberOf mitt\n\t\t */\n\t\temit(type: EventType, evt: T) {\n\t\t\t((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); });\n\t\t\t((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); });\n\t\t}\n\t};\n}\n"],"names":["all","Map","on","type","handler","handlers","get","push","set","off","splice","indexOf","emit","evt","slice","map"],"mappings":"wBAgC6BA,GAG5B,MAAO,CAKNA,IAPDA,EAAMA,GAAO,IAAIC,IAehBC,YAAYC,EAAiBC,GAC5B,IAAMC,EAAWL,EAAIM,IAAIH,GACXE,GAAYA,EAASE,KAAKH,IAEvCJ,EAAIQ,IAAIL,EAAM,CAACC,KAUjBK,aAAaN,EAAiBC,GAC7B,IAAMC,EAAWL,EAAIM,IAAIH,GACrBE,GACHA,EAASK,OAAOL,EAASM,QAAQP,KAAa,EAAG,IAcnDQ,cAAcT,EAAiBU,IAC5Bb,EAAIM,IAAIH,IAAS,IAAyBW,QAAQC,IAAI,SAACX,GAAcA,EAAQS,MAC7Eb,EAAIM,IAAI,MAAQ,IAAiCQ,QAAQC,IAAI,SAACX,GAAcA,EAAQD,EAAMU"} \ No newline at end of file diff --git a/remote/test/puppeteer/vendor/mitt/dist/mitt.js b/remote/test/puppeteer/vendor/mitt/dist/mitt.js new file mode 100644 index 0000000000000..2bd0cf9e44e53 --- /dev/null +++ b/remote/test/puppeteer/vendor/mitt/dist/mitt.js @@ -0,0 +1,2 @@ +module.exports=function(n){return{all:n=n||new Map,on:function(e,t){var i=n.get(e);i&&i.push(t)||n.set(e,[t])},off:function(e,t){var i=n.get(e);i&&i.splice(i.indexOf(t)>>>0,1)},emit:function(e,t){(n.get(e)||[]).slice().map(function(n){n(t)}),(n.get("*")||[]).slice().map(function(n){n(e,t)})}}}; +//# sourceMappingURL=mitt.js.map diff --git a/remote/test/puppeteer/vendor/mitt/dist/mitt.js.map b/remote/test/puppeteer/vendor/mitt/dist/mitt.js.map new file mode 100644 index 0000000000000..37f6f59ebd6bd --- /dev/null +++ b/remote/test/puppeteer/vendor/mitt/dist/mitt.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mitt.js","sources":["../src/index.ts"],"sourcesContent":["export type EventType = string | symbol;\n\n// An event handler can take an optional event argument\n// and should not return a value\nexport type Handler = (event?: T) => void;\nexport type WildcardHandler = (type: EventType, event?: any) => void;\n\n// An array of all currently registered event handlers for a type\nexport type EventHandlerList = Array;\nexport type WildCardEventHandlerList = Array;\n\n// A map of event types and their corresponding event handlers.\nexport type EventHandlerMap = Map;\n\nexport interface Emitter {\n\tall: EventHandlerMap;\n\n\ton(type: EventType, handler: Handler): void;\n\ton(type: '*', handler: WildcardHandler): void;\n\n\toff(type: EventType, handler: Handler): void;\n\toff(type: '*', handler: WildcardHandler): void;\n\n\temit(type: EventType, event?: T): void;\n\temit(type: '*', event?: any): void;\n}\n\n/**\n * Mitt: Tiny (~200b) functional event emitter / pubsub.\n * @name mitt\n * @returns {Mitt}\n */\nexport default function mitt(all?: EventHandlerMap): Emitter {\n\tall = all || new Map();\n\n\treturn {\n\n\t\t/**\n\t\t * A Map of event names to registered handler functions.\n\t\t */\n\t\tall,\n\n\t\t/**\n\t\t * Register an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to listen for, or `\"*\"` for all events\n\t\t * @param {Function} handler Function to call in response to given event\n\t\t * @memberOf mitt\n\t\t */\n\t\ton(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tconst added = handlers && handlers.push(handler);\n\t\t\tif (!added) {\n\t\t\t\tall.set(type, [handler]);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to unregister `handler` from, or `\"*\"`\n\t\t * @param {Function} handler Handler function to remove\n\t\t * @memberOf mitt\n\t\t */\n\t\toff(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tif (handlers) {\n\t\t\t\thandlers.splice(handlers.indexOf(handler) >>> 0, 1);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Invoke all handlers for the given type.\n\t\t * If present, `\"*\"` handlers are invoked after type-matched handlers.\n\t\t *\n\t\t * Note: Manually firing \"*\" handlers is not supported.\n\t\t *\n\t\t * @param {string|symbol} type The event type to invoke\n\t\t * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler\n\t\t * @memberOf mitt\n\t\t */\n\t\temit(type: EventType, evt: T) {\n\t\t\t((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); });\n\t\t\t((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); });\n\t\t}\n\t};\n}\n"],"names":["all","Map","on","type","handler","handlers","get","push","set","off","splice","indexOf","emit","evt","slice","map"],"mappings":"wBAgC6BA,GAG5B,MAAO,CAKNA,IAPDA,EAAMA,GAAO,IAAIC,IAehBC,YAAYC,EAAiBC,GAC5B,IAAMC,EAAWL,EAAIM,IAAIH,GACXE,GAAYA,EAASE,KAAKH,IAEvCJ,EAAIQ,IAAIL,EAAM,CAACC,KAUjBK,aAAaN,EAAiBC,GAC7B,IAAMC,EAAWL,EAAIM,IAAIH,GACrBE,GACHA,EAASK,OAAOL,EAASM,QAAQP,KAAa,EAAG,IAcnDQ,cAAcT,EAAiBU,IAC5Bb,EAAIM,IAAIH,IAAS,IAAyBW,QAAQC,IAAI,SAACX,GAAcA,EAAQS,MAC7Eb,EAAIM,IAAI,MAAQ,IAAiCQ,QAAQC,IAAI,SAACX,GAAcA,EAAQD,EAAMU"} \ No newline at end of file diff --git a/remote/test/puppeteer/vendor/mitt/dist/mitt.modern.js b/remote/test/puppeteer/vendor/mitt/dist/mitt.modern.js new file mode 100644 index 0000000000000..0777f6de72093 --- /dev/null +++ b/remote/test/puppeteer/vendor/mitt/dist/mitt.modern.js @@ -0,0 +1,2 @@ +export default function(e){return{all:e=e||new Map,on(t,n){const s=e.get(t);s&&s.push(n)||e.set(t,[n])},off(t,n){const s=e.get(t);s&&s.splice(s.indexOf(n)>>>0,1)},emit(t,n){(e.get(t)||[]).slice().map(e=>{e(n)}),(e.get("*")||[]).slice().map(e=>{e(t,n)})}}} +//# sourceMappingURL=mitt.modern.js.map diff --git a/remote/test/puppeteer/vendor/mitt/dist/mitt.modern.js.map b/remote/test/puppeteer/vendor/mitt/dist/mitt.modern.js.map new file mode 100644 index 0000000000000..5f669b2d61d0b --- /dev/null +++ b/remote/test/puppeteer/vendor/mitt/dist/mitt.modern.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mitt.modern.js","sources":["../src/index.ts"],"sourcesContent":["export type EventType = string | symbol;\n\n// An event handler can take an optional event argument\n// and should not return a value\nexport type Handler = (event?: T) => void;\nexport type WildcardHandler = (type: EventType, event?: any) => void;\n\n// An array of all currently registered event handlers for a type\nexport type EventHandlerList = Array;\nexport type WildCardEventHandlerList = Array;\n\n// A map of event types and their corresponding event handlers.\nexport type EventHandlerMap = Map;\n\nexport interface Emitter {\n\tall: EventHandlerMap;\n\n\ton(type: EventType, handler: Handler): void;\n\ton(type: '*', handler: WildcardHandler): void;\n\n\toff(type: EventType, handler: Handler): void;\n\toff(type: '*', handler: WildcardHandler): void;\n\n\temit(type: EventType, event?: T): void;\n\temit(type: '*', event?: any): void;\n}\n\n/**\n * Mitt: Tiny (~200b) functional event emitter / pubsub.\n * @name mitt\n * @returns {Mitt}\n */\nexport default function mitt(all?: EventHandlerMap): Emitter {\n\tall = all || new Map();\n\n\treturn {\n\n\t\t/**\n\t\t * A Map of event names to registered handler functions.\n\t\t */\n\t\tall,\n\n\t\t/**\n\t\t * Register an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to listen for, or `\"*\"` for all events\n\t\t * @param {Function} handler Function to call in response to given event\n\t\t * @memberOf mitt\n\t\t */\n\t\ton(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tconst added = handlers && handlers.push(handler);\n\t\t\tif (!added) {\n\t\t\t\tall.set(type, [handler]);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to unregister `handler` from, or `\"*\"`\n\t\t * @param {Function} handler Handler function to remove\n\t\t * @memberOf mitt\n\t\t */\n\t\toff(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tif (handlers) {\n\t\t\t\thandlers.splice(handlers.indexOf(handler) >>> 0, 1);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Invoke all handlers for the given type.\n\t\t * If present, `\"*\"` handlers are invoked after type-matched handlers.\n\t\t *\n\t\t * Note: Manually firing \"*\" handlers is not supported.\n\t\t *\n\t\t * @param {string|symbol} type The event type to invoke\n\t\t * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler\n\t\t * @memberOf mitt\n\t\t */\n\t\temit(type: EventType, evt: T) {\n\t\t\t((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); });\n\t\t\t((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); });\n\t\t}\n\t};\n}\n"],"names":["all","Map","on","type","handler","handlers","get","push","set","off","splice","indexOf","emit","evt","slice","map"],"mappings":"wBAgC6BA,GAG5B,MAAO,CAKNA,IAPDA,EAAMA,GAAO,IAAIC,IAehBC,GAAYC,EAAiBC,GAC5B,MAAMC,EAAWL,EAAIM,IAAIH,GACXE,GAAYA,EAASE,KAAKH,IAEvCJ,EAAIQ,IAAIL,EAAM,CAACC,KAUjBK,IAAaN,EAAiBC,GAC7B,MAAMC,EAAWL,EAAIM,IAAIH,GACrBE,GACHA,EAASK,OAAOL,EAASM,QAAQP,KAAa,EAAG,IAcnDQ,KAAcT,EAAiBU,IAC5Bb,EAAIM,IAAIH,IAAS,IAAyBW,QAAQC,IAAKX,IAAcA,EAAQS,MAC7Eb,EAAIM,IAAI,MAAQ,IAAiCQ,QAAQC,IAAKX,IAAcA,EAAQD,EAAMU"} \ No newline at end of file diff --git a/remote/test/puppeteer/vendor/mitt/dist/mitt.umd.js b/remote/test/puppeteer/vendor/mitt/dist/mitt.umd.js new file mode 100644 index 0000000000000..ce0e0059aef8f --- /dev/null +++ b/remote/test/puppeteer/vendor/mitt/dist/mitt.umd.js @@ -0,0 +1,2 @@ +!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).mitt=n()}(this,function(){return function(e){return{all:e=e||new Map,on:function(n,t){var f=e.get(n);f&&f.push(t)||e.set(n,[t])},off:function(n,t){var f=e.get(n);f&&f.splice(f.indexOf(t)>>>0,1)},emit:function(n,t){(e.get(n)||[]).slice().map(function(e){e(t)}),(e.get("*")||[]).slice().map(function(e){e(n,t)})}}}}); +//# sourceMappingURL=mitt.umd.js.map diff --git a/remote/test/puppeteer/vendor/mitt/dist/mitt.umd.js.map b/remote/test/puppeteer/vendor/mitt/dist/mitt.umd.js.map new file mode 100644 index 0000000000000..642c89400653d --- /dev/null +++ b/remote/test/puppeteer/vendor/mitt/dist/mitt.umd.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mitt.umd.js","sources":["../src/index.ts"],"sourcesContent":["export type EventType = string | symbol;\n\n// An event handler can take an optional event argument\n// and should not return a value\nexport type Handler = (event?: T) => void;\nexport type WildcardHandler = (type: EventType, event?: any) => void;\n\n// An array of all currently registered event handlers for a type\nexport type EventHandlerList = Array;\nexport type WildCardEventHandlerList = Array;\n\n// A map of event types and their corresponding event handlers.\nexport type EventHandlerMap = Map;\n\nexport interface Emitter {\n\tall: EventHandlerMap;\n\n\ton(type: EventType, handler: Handler): void;\n\ton(type: '*', handler: WildcardHandler): void;\n\n\toff(type: EventType, handler: Handler): void;\n\toff(type: '*', handler: WildcardHandler): void;\n\n\temit(type: EventType, event?: T): void;\n\temit(type: '*', event?: any): void;\n}\n\n/**\n * Mitt: Tiny (~200b) functional event emitter / pubsub.\n * @name mitt\n * @returns {Mitt}\n */\nexport default function mitt(all?: EventHandlerMap): Emitter {\n\tall = all || new Map();\n\n\treturn {\n\n\t\t/**\n\t\t * A Map of event names to registered handler functions.\n\t\t */\n\t\tall,\n\n\t\t/**\n\t\t * Register an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to listen for, or `\"*\"` for all events\n\t\t * @param {Function} handler Function to call in response to given event\n\t\t * @memberOf mitt\n\t\t */\n\t\ton(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tconst added = handlers && handlers.push(handler);\n\t\t\tif (!added) {\n\t\t\t\tall.set(type, [handler]);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove an event handler for the given type.\n\t\t * @param {string|symbol} type Type of event to unregister `handler` from, or `\"*\"`\n\t\t * @param {Function} handler Handler function to remove\n\t\t * @memberOf mitt\n\t\t */\n\t\toff(type: EventType, handler: Handler) {\n\t\t\tconst handlers = all.get(type);\n\t\t\tif (handlers) {\n\t\t\t\thandlers.splice(handlers.indexOf(handler) >>> 0, 1);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Invoke all handlers for the given type.\n\t\t * If present, `\"*\"` handlers are invoked after type-matched handlers.\n\t\t *\n\t\t * Note: Manually firing \"*\" handlers is not supported.\n\t\t *\n\t\t * @param {string|symbol} type The event type to invoke\n\t\t * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler\n\t\t * @memberOf mitt\n\t\t */\n\t\temit(type: EventType, evt: T) {\n\t\t\t((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); });\n\t\t\t((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); });\n\t\t}\n\t};\n}\n"],"names":["all","Map","on","type","handler","handlers","get","push","set","off","splice","indexOf","emit","evt","slice","map"],"mappings":"6LAgC6BA,GAG5B,MAAO,CAKNA,IAPDA,EAAMA,GAAO,IAAIC,IAehBC,YAAYC,EAAiBC,GAC5B,IAAMC,EAAWL,EAAIM,IAAIH,GACXE,GAAYA,EAASE,KAAKH,IAEvCJ,EAAIQ,IAAIL,EAAM,CAACC,KAUjBK,aAAaN,EAAiBC,GAC7B,IAAMC,EAAWL,EAAIM,IAAIH,GACrBE,GACHA,EAASK,OAAOL,EAASM,QAAQP,KAAa,EAAG,IAcnDQ,cAAcT,EAAiBU,IAC5Bb,EAAIM,IAAIH,IAAS,IAAyBW,QAAQC,IAAI,SAACX,GAAcA,EAAQS,MAC7Eb,EAAIM,IAAI,MAAQ,IAAiCQ,QAAQC,IAAI,SAACX,GAAcA,EAAQD,EAAMU"} \ No newline at end of file diff --git a/remote/test/puppeteer/vendor/mitt/index.d.ts b/remote/test/puppeteer/vendor/mitt/index.d.ts new file mode 100644 index 0000000000000..55346dd9769a8 --- /dev/null +++ b/remote/test/puppeteer/vendor/mitt/index.d.ts @@ -0,0 +1,21 @@ +export declare type EventType = string | symbol; +export declare type Handler = (event?: T) => void; +export declare type WildcardHandler = (type: EventType, event?: any) => void; +export declare type EventHandlerList = Array; +export declare type WildCardEventHandlerList = Array; +export declare type EventHandlerMap = Map; +export interface Emitter { + all: EventHandlerMap; + on(type: EventType, handler: Handler): void; + on(type: '*', handler: WildcardHandler): void; + off(type: EventType, handler: Handler): void; + off(type: '*', handler: WildcardHandler): void; + emit(type: EventType, event?: T): void; + emit(type: '*', event?: any): void; +} +/** + * Mitt: Tiny (~200b) functional event emitter / pubsub. + * @name mitt + * @returns {Mitt} + */ +export default function mitt(all?: EventHandlerMap): Emitter; diff --git a/remote/test/puppeteer/vendor/mitt/package.json b/remote/test/puppeteer/vendor/mitt/package.json new file mode 100644 index 0000000000000..0105524a0d290 --- /dev/null +++ b/remote/test/puppeteer/vendor/mitt/package.json @@ -0,0 +1,141 @@ +{ + "_from": "mitt@latest", + "_id": "mitt@2.1.0", + "_inBundle": false, + "_integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==", + "_location": "/mitt", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "mitt@latest", + "name": "mitt", + "escapedName": "mitt", + "rawSpec": "latest", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/mitt/-/mitt-2.1.0.tgz", + "_shasum": "f740577c23176c6205b121b2973514eade1b2230", + "_spec": "mitt@latest", + "_where": "/Users/jacktfranklin/src/puppeteer", + "authors": [ + "Jason Miller " + ], + "bugs": { + "url": "https://github.com/developit/mitt/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "Tiny 200b functional Event Emitter / pubsub.", + "devDependencies": { + "@types/chai": "^4.2.11", + "@types/mocha": "^7.0.2", + "@types/sinon": "^9.0.4", + "@types/sinon-chai": "^3.2.4", + "@typescript-eslint/eslint-plugin": "^3.0.1", + "@typescript-eslint/parser": "^3.0.1", + "chai": "^4.2.0", + "documentation": "^13.0.0", + "eslint": "^7.1.0", + "eslint-config-developit": "^1.2.0", + "esm": "^3.2.25", + "microbundle": "^0.12.3", + "mocha": "^8.0.1", + "npm-run-all": "^4.1.5", + "rimraf": "^3.0.2", + "sinon": "^9.0.2", + "sinon-chai": "^3.5.0", + "ts-node": "^8.10.2", + "typescript": "^3.9.3" + }, + "eslintConfig": { + "extends": [ + "developit", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "sourceType": "module" + }, + "env": { + "browser": true, + "mocha": true, + "jest": false, + "es6": true + }, + "globals": { + "expect": true + }, + "rules": { + "semi": [ + 2, + "always" + ], + "jest/valid-expect": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-empty-function": 0 + } + }, + "eslintIgnore": [ + "dist", + "index.d.ts" + ], + "esmodules": "dist/mitt.modern.js", + "files": [ + "src", + "dist", + "index.d.ts" + ], + "homepage": "https://github.com/developit/mitt", + "jsnext:main": "dist/mitt.es.js", + "keywords": [ + "events", + "eventemitter", + "emitter", + "pubsub" + ], + "license": "MIT", + "main": "dist/mitt.js", + "mocha": { + "extension": [ + "ts" + ], + "require": [ + "ts-node/register", + "esm" + ], + "spec": [ + "test/*_test.ts" + ] + }, + "module": "dist/mitt.es.js", + "name": "mitt", + "repository": { + "type": "git", + "url": "git+https://github.com/developit/mitt.git" + }, + "scripts": { + "build": "npm-run-all --silent clean -p bundle -s docs", + "bundle": "microbundle", + "clean": "rimraf dist", + "docs": "documentation readme src/index.ts --section API -q --parse-extension ts", + "lint": "eslint src test --ext ts --ext js", + "mocha": "mocha test", + "release": "npm run -s build -s && npm t && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish", + "test": "npm-run-all --silent typecheck lint mocha test-types", + "test-types": "tsc test/test-types-compilation.ts --noEmit", + "typecheck": "tsc --noEmit" + }, + "source": "src/index.ts", + "typings": "index.d.ts", + "umd:main": "dist/mitt.umd.js", + "version": "2.1.0" +} diff --git a/remote/test/puppeteer/vendor/mitt/src/index.ts b/remote/test/puppeteer/vendor/mitt/src/index.ts new file mode 100644 index 0000000000000..ae85607f7cfe6 --- /dev/null +++ b/remote/test/puppeteer/vendor/mitt/src/index.ts @@ -0,0 +1,85 @@ +export type EventType = string | symbol; + +// An event handler can take an optional event argument +// and should not return a value +export type Handler = (event?: T) => void; +export type WildcardHandler = (type: EventType, event?: any) => void; + +// An array of all currently registered event handlers for a type +export type EventHandlerList = Array; +export type WildCardEventHandlerList = Array; + +// A map of event types and their corresponding event handlers. +export type EventHandlerMap = Map; + +export interface Emitter { + all: EventHandlerMap; + + on(type: EventType, handler: Handler): void; + on(type: '*', handler: WildcardHandler): void; + + off(type: EventType, handler: Handler): void; + off(type: '*', handler: WildcardHandler): void; + + emit(type: EventType, event?: T): void; + emit(type: '*', event?: any): void; +} + +/** + * Mitt: Tiny (~200b) functional event emitter / pubsub. + * @name mitt + * @returns {Mitt} + */ +export default function mitt(all?: EventHandlerMap): Emitter { + all = all || new Map(); + + return { + + /** + * A Map of event names to registered handler functions. + */ + all, + + /** + * Register an event handler for the given type. + * @param {string|symbol} type Type of event to listen for, or `"*"` for all events + * @param {Function} handler Function to call in response to given event + * @memberOf mitt + */ + on(type: EventType, handler: Handler) { + const handlers = all.get(type); + const added = handlers && handlers.push(handler); + if (!added) { + all.set(type, [handler]); + } + }, + + /** + * Remove an event handler for the given type. + * @param {string|symbol} type Type of event to unregister `handler` from, or `"*"` + * @param {Function} handler Handler function to remove + * @memberOf mitt + */ + off(type: EventType, handler: Handler) { + const handlers = all.get(type); + if (handlers) { + handlers.splice(handlers.indexOf(handler) >>> 0, 1); + } + }, + + /** + * Invoke all handlers for the given type. + * If present, `"*"` handlers are invoked after type-matched handlers. + * + * Note: Manually firing "*" handlers is not supported. + * + * @param {string|symbol} type The event type to invoke + * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler + * @memberOf mitt + */ + emit(type: EventType, evt: T) { + ((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); }); + ((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); }); + } + }; +} diff --git a/remote/test/puppeteer/vendor/tsconfig.cjs.json b/remote/test/puppeteer/vendor/tsconfig.cjs.json new file mode 100644 index 0000000000000..f73a8d57d0115 --- /dev/null +++ b/remote/test/puppeteer/vendor/tsconfig.cjs.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.base.json", + "exclude": [ + "mitt/dist" + ], + "compilerOptions": { + "composite": true, + "outDir": "../lib/cjs/vendor", + "module": "CommonJS" + } +} diff --git a/remote/test/puppeteer/vendor/tsconfig.esm.json b/remote/test/puppeteer/vendor/tsconfig.esm.json new file mode 100644 index 0000000000000..1f0bae8e3d2db --- /dev/null +++ b/remote/test/puppeteer/vendor/tsconfig.esm.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.base.json", + "exclude": [ + "mitt/dist" + ], + "compilerOptions": { + "composite": true, + "outDir": "../lib/esm/vendor", + "module": "esnext" + } +} diff --git a/remote/test/puppeteer/versions.js b/remote/test/puppeteer/versions.js new file mode 100644 index 0000000000000..5a66247a4dc8c --- /dev/null +++ b/remote/test/puppeteer/versions.js @@ -0,0 +1,37 @@ +/** + * Copyright 2020 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const versionsPerRelease = new Map([ + // This is a mapping from Chromium version => Puppeteer version. + // In Chromium roll patches, use 'NEXT' for the Puppeteer version. + ['88.0.4298.0', '5.5.0'], + ['87.0.4272.0', 'v5.4.0'], + ['86.0.4240.0', 'v5.3.0'], + ['85.0.4182.0', 'v5.2.1'], + ['84.0.4147.0', 'v5.1.0'], + ['83.0.4103.0', 'v3.1.0'], + ['81.0.4044.0', 'v3.0.0'], + ['80.0.3987.0', 'v2.1.0'], + ['79.0.3942.0', 'v2.0.0'], + ['78.0.3882.0', 'v1.20.0'], + ['77.0.3803.0', 'v1.19.0'], + ['76.0.3803.0', 'v1.17.0'], + ['75.0.3765.0', 'v1.15.0'], + ['74.0.3723.0', 'v1.13.0'], + ['73.0.3679.0', 'v1.12.2'], +]); + +module.exports = versionsPerRelease; diff --git a/remote/test/puppeteer/web-test-runner.config.js b/remote/test/puppeteer/web-test-runner.config.js new file mode 100644 index 0000000000000..8cf8ad5361dff --- /dev/null +++ b/remote/test/puppeteer/web-test-runner.config.js @@ -0,0 +1,43 @@ +/** + * Copyright 2020 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const { chromeLauncher } = require('@web/test-runner-chrome'); + +module.exports = { + files: ['test-browser/**/*.spec.js'], + browsers: [ + chromeLauncher({ + async createPage({ browser }) { + const page = await browser.newPage(); + page.evaluateOnNewDocument((wsEndpoint) => { + window.__ENV__ = { wsEndpoint }; + }, browser.wsEndpoint()); + + return page; + }, + }), + ], + plugins: [ + { + // turn expect UMD into an es module + name: 'esmify-expect', + transform(context) { + if (context.path === '/node_modules/expect/build-es5/index.js') { + return `const module = {}; const exports = {};\n${context.body};\n export default module.exports;`; + } + }, + }, + ], +};