Bug 1797744 - [puppeteer] Sync vendored puppeteer to v19.6.0. r=webdriver-reviewers,jdescottes,whimboo

Differential Revision: https://phabricator.services.mozilla.com/D167407
This commit is contained in:
Alexandra Borovova 2023-01-27 16:50:45 +00:00
Родитель 51f82934e3
Коммит 7e919228b0
319 изменённых файлов: 48062 добавлений и 8097 удалений

Просмотреть файл

@ -113,18 +113,19 @@ _OPT\.OBJ/
^remote/test/puppeteer/.*\.tsbuildinfo
^remote/test/puppeteer/\.github
^remote/test/puppeteer/\.husky
^remote/test/puppeteer/\.local-chromium/
^remote/test/puppeteer/\.local-firefox/
^remote/test/puppeteer/coverage/
^remote/test/puppeteer/.devcontainer/
^remote/test/puppeteer/docker/
^remote/test/puppeteer/docs/puppeteer-core\.api\.json
^remote/test/puppeteer/docs/puppeteer\.api\.json
^remote/test/puppeteer/experimental/
^remote/test/puppeteer/lib/
^remote/test/puppeteer/node_modules/
^remote/test/puppeteer/package-lock\.json
^remote/test/puppeteer/puppeteer.*\.tgz
^remote/test/puppeteer/packages/ng-schematics/test/build
^remote/test/puppeteer/src/generated
^remote/test/puppeteer/test/build
^remote/test/puppeteer/test/installation/puppeteer.*\.tgz
^remote/test/puppeteer/test/output-firefox
^remote/test/puppeteer/test/output-chromium
^remote/test/puppeteer/testserver/lib/

14
remote/.gitignore поставляемый
Просмотреть файл

@ -1,20 +1,20 @@
test/puppeteer/**/.wireit
test/puppeteer/**/*.tsbuildinfo
test/puppeteer/**/lib
test/puppeteer/.github
test/puppeteer/.husky
test/puppeteer/.local-chromium/
test/puppeteer/.local-firefox/
test/puppeteer/coverage/
test/puppeteer/.devcontainer/
test/puppeteer/docker/
test/puppeteer/docs/puppeteer-core.api.json
test/puppeteer/docs/puppeteer.api.json
test/puppeteer/experimental/
test/puppeteer/lib/
test/puppeteer/node_modules/
test/puppeteer/package-lock.json
test/puppeteer/puppeteer*.tgz
test/puppeteer/packages/ng-schematics/test/build
test/puppeteer/test/installation/puppeteer*.tgz
test/puppeteer/src/generated
test/puppeteer/test/build
test/puppeteer/test/**/build
test/puppeteer/test/output-firefox
test/puppeteer/test/output-chromium
test/puppeteer/testserver/lib/
test/puppeteer/utils/mochaRunner/lib/
test/puppeteer/website

Просмотреть файл

@ -11,7 +11,7 @@ process interspersed with some tips.
1. Clone the Puppeteer git repository and checkout the release tag you want
to vendor into mozilla-central.
% git checkout tags/v10.0 -b sync-v10.0
% git checkout tags/puppeteer-v10.0 -b sync-v10.0
2. Apply any recent changes in `remote/test/puppeteer` to the Puppeteer branch
created above.

Просмотреть файл

@ -299,7 +299,20 @@ class MochaOutputHandler(object):
None,
)
if expected_item_for_file is None:
expected = ["PASS"]
# if there is no expectation data for the file,
# try to find data for all tests.
expected_item_for_all_tests = next(
(
expectation
for expectation in list(self.expected)
if expectation["testIdPattern"] == ""
),
None,
)
if expected_item_for_all_tests is None:
expected = ["PASS"]
else:
expected = expected_item_for_all_tests["expectations"]
else:
expected = expected_item_for_file["expectations"]
else:
@ -392,9 +405,6 @@ class PuppeteerRunner(MozbuildObject):
before invoking npm. Overrides default preferences.
`enable_webrender`:
Boolean to indicate whether to enable WebRender compositor in Gecko.
`subset`
Indicates only a subset of tests are being run, so we should
skip the check for missing results
"""
setup()
@ -426,18 +436,26 @@ class PuppeteerRunner(MozbuildObject):
"--no-coverage",
]
env["HEADLESS"] = str(params.get("headless", False))
test_command = "test:" + product
if product == "firefox":
env["BINARY"] = binary
env["PUPPETEER_PRODUCT"] = "firefox"
env["MOZ_WEBRENDER"] = "%d" % params.get("enable_webrender", False)
test_command = "test:firefox"
elif env["HEADLESS"] == "False":
test_command = "test:chrome:headful"
else:
test_command = "test:chrome:headless"
env["PUPPETEER_CACHE_DIR"] = os.path.join(
self.topobjdir,
"_tests",
"remote",
"test",
"puppeteer",
".cache",
)
if env["HEADLESS"] == "True":
test_command = test_command + ":headless"
else:
test_command = test_command + ":headful"
command = ["run", test_command, "--"] + mocha_options
@ -464,14 +482,16 @@ class PuppeteerRunner(MozbuildObject):
expected_data = json.load(f)
else:
expected_data = []
# Filter expectation data for the selected browser,
# headless or headful mode, and the operating system.
expected_platform = platform.uname().system.lower()
if expected_platform == "windows":
expected_platform = "win32"
# Filter expectation data for the selected browser,
# headless or headful mode, and the operating system.
expectations = filter(
lambda el: product in el["parameters"]
and "webDriverBiDi" not in el["parameters"]
and (
(env["HEADLESS"] == "False" and "headless" not in el["parameters"])
or "headful" not in el["parameters"]
@ -559,13 +579,6 @@ def create_parser_puppeteer():
"debug level messages with -v, trace messages with -vv,"
"and to not truncate long trace messages with -vvv",
)
p.add_argument(
"--subset",
action="store_true",
default=False,
help="Indicate that only a subset of the tests are running, "
"so checks for missing tests should be skipped",
)
p.add_argument("tests", nargs="*")
mozlog.commandline.add_logging_group(p)
return p
@ -597,7 +610,6 @@ def puppeteer_test(
verbosity=0,
tests=None,
product="firefox",
subset=False,
**kwargs,
):
@ -656,7 +668,6 @@ def puppeteer_test(
"extra_prefs": prefs,
"product": product,
"extra_launcher_options": options,
"subset": subset,
}
puppeteer = command_context._spawn(PuppeteerRunner)
try:
@ -674,25 +685,31 @@ def install_puppeteer(command_context, product, ci):
env = {"HUSKY": "0"}
puppeteer_dir = os.path.join("remote", "test", "puppeteer")
puppeteer_dir_full_path = os.path.join(command_context.topsrcdir, puppeteer_dir)
puppeteer_test_dir = os.path.join(puppeteer_dir, "test")
if product != "chrome":
if product == "chrome":
env["PUPPETEER_CACHE_DIR"] = os.path.join(
command_context.topobjdir, "_tests", puppeteer_dir, ".cache"
)
else:
env["PUPPETEER_SKIP_DOWNLOAD"] = "1"
if not ci:
npm(
"run",
"clean",
cwd=os.path.join(command_context.topsrcdir, puppeteer_dir),
cwd=puppeteer_dir_full_path,
env=env,
exit_on_fail=False,
)
command = "ci" if ci else "install"
npm(command, cwd=os.path.join(command_context.topsrcdir, puppeteer_dir), env=env)
npm(command, cwd=puppeteer_dir_full_path, env=env)
npm(
"run",
"build:dev",
cwd=os.path.join(command_context.topsrcdir, puppeteer_dir),
"build",
cwd=os.path.join(command_context.topsrcdir, puppeteer_test_dir),
env=env,
)

Просмотреть файл

@ -8,17 +8,17 @@ lib/
# Generated files
**/*.tsbuildinfo
puppeteer.api.json
puppeteer*.tgz
*.api.json
*.tgz
yarn.lock
.docusaurus/
.cache-loader
.local-chromium/
.local-firefox/
test/output-*/
.dev_profile*
coverage/
src/generated
generated/
.eslintcache
/.cache/
# IDE Artifacts
.vscode
@ -35,8 +35,10 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Wireit
.wireit
## [END] Keep in sync with .gitignore
# ESLint ignores.
assets/
vendor/
third_party/

Просмотреть файл

@ -103,7 +103,7 @@ module.exports = {
{
name: 'mitt',
message:
'Import Mitt from the vendored location: vendor/mitt/src/index.js',
'Import `mitt` from the vendored location: third_party/mitt/index.js',
},
],
},

Просмотреть файл

@ -8,20 +8,21 @@ lib/
# Generated files
**/*.tsbuildinfo
puppeteer.api.json
puppeteer*.tgz
*.api.json
*.tgz
yarn.lock
.docusaurus/
.cache-loader
.local-chromium/
.local-firefox/
test/output-*/
.dev_profile*
coverage/
src/generated
generated/
.eslintcache
/.cache/
# IDE Artifacts
.vscode
.vscode/*
!.vscode/extensions.json
.devcontainer
# Misc
@ -34,14 +35,14 @@ src/generated
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Wireit
.wireit
## [END] Keep in sync with .gitignore
# Prettier-only ignores.
assets/
CHANGELOG.md
package-lock.json
package.json
test/assets/
vendor/
docs/
docs/api
versioned_*/

Просмотреть файл

@ -1 +1,7 @@
module.exports = require('gts/.prettierrc.json');
/**
* @type {import('prettier').Config}
*/
module.exports = {
...require('gts/.prettierrc.json'),
// proseWrap: 'always', // Uncomment this while working on Markdown documents. MAKE SURE TO COMMENT THIS BEFORE RUNNING CHECKS/FORMATS OR EVERYTHING WILL BE MODIFIED.
};

Просмотреть файл

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

Просмотреть файл

@ -1,32 +1,35 @@
# Puppeteer
<!-- [START badges] -->
[![Build status](https://github.com/puppeteer/puppeteer/workflows/CI/badge.svg)](https://github.com/puppeteer/puppeteer/actions?query=workflow%3ACI) [![npm puppeteer package](https://img.shields.io/npm/v/puppeteer.svg)](https://npmjs.org/package/puppeteer)
<!-- [END badges] -->
[![Build status](https://github.com/puppeteer/puppeteer/workflows/CI/badge.svg)](https://github.com/puppeteer/puppeteer/actions?query=workflow%3ACI)
[![npm puppeteer package](https://img.shields.io/npm/v/puppeteer.svg)](https://npmjs.org/package/puppeteer)
<img src="https://user-images.githubusercontent.com/10379601/29446482-04f7036a-841f-11e7-9872-91d1fc2ea683.png" height="200" align="right"/>
###### [API](https://pptr.dev/api) | [FAQ](https://pptr.dev/faq) | [Contributing](https://pptr.dev/contributing) | [Troubleshooting](https://pptr.dev/troubleshooting)
#### [Guides](https://pptr.dev/category/guides) | [API](https://pptr.dev/api) | [FAQ](https://pptr.dev/faq) | [Contributing](https://pptr.dev/contributing) | [Troubleshooting](https://pptr.dev/troubleshooting)
> Puppeteer is a Node 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.
> Puppeteer is a Node.js library which provides a high-level API to control
> Chrome/Chromium over the
> [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/).
> Puppeteer runs in
> [headless](https://developers.google.com/web/updates/2017/04/headless-chrome)
> mode by default, but can be configured to run in full (non-headless)
> Chrome/Chromium.
<!-- [START usecases] -->
#### What can I do?
##### What can I do?
Most things that you can do manually in the browser can be done using Puppeteer! Here are a few examples to get you started:
Most things that you can do manually in the browser can be done using Puppeteer!
Here are a few examples to get you started:
- Generate screenshots and PDFs of pages.
- Crawl a SPA (Single-Page Application) and generate pre-rendered content (i.e. "SSR" (Server-Side Rendering)).
- Crawl a SPA (Single-Page Application) and generate pre-rendered content (i.e.
"SSR" (Server-Side Rendering)).
- Automate form submission, UI testing, keyboard input, etc.
- Create an up-to-date, automated testing environment. Run your tests directly in the latest version of Chrome using the latest JavaScript and browser features.
- Capture a [timeline trace](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference) of your site to help diagnose performance issues.
- Create an automated testing environment using the latest JavaScript and
browser features.
- Capture a
[timeline trace](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference)
of your site to help diagnose performance issues.
- Test Chrome Extensions.
<!-- [END usecases] -->
<!-- [START getstarted] -->
## Getting Started
@ -36,240 +39,138 @@ To use Puppeteer in your project, run:
```bash
npm i puppeteer
# or "yarn add puppeteer"
# or `yarn add puppeteer`
# or `pnpm i puppeteer`
```
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 (customizable through [Environment Variables](#environment-variables)). For a version of Puppeteer purely for connection, see [`puppeteer-core`](#puppeteer-core).
When you install Puppeteer, it automatically downloads a recent version of
Chromium (~170MB macOS, ~282MB Linux, ~280MB Windows) that is
[guaranteed to work](https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy)
with Puppeteer. For a version of Puppeteer without installation, see
[`puppeteer-core`](#puppeteer-core).
#### Environment Variables
#### Configuration
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).
Puppeteer uses several defaults that can be customized through configuration
files.
- `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` - defines HTTP proxy settings that are used to download and run the browser.
- `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD` - do not download bundled Chromium during installation step.
- `PUPPETEER_TMP_DIR` - defines the directory to be used by Puppeteer for creating temporary files. Defaults to [`os.tmpdir()`](https://nodejs.org/api/os.html#os_os_tmpdir).
- `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_DOWNLOAD_PATH` - overwrite the path for the downloads folder. Defaults to `<root>/.local-chromium`, where `<root>` is Puppeteer's package root.
- `PUPPETEER_CHROMIUM_REVISION` - specify a certain version of Chromium you'd like Puppeteer to use. See [`puppeteer.launch`](https://pptr.dev/api/puppeteer.puppeteernode.launch) on how executable path is inferred.
- `PUPPETEER_EXECUTABLE_PATH` - specify an executable path to be used in [`puppeteer.launch`](https://pptr.dev/api/puppeteer.puppeteernode.launch).
- `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`](https://pptr.dev/api/puppeteer.puppeteernode.launch) supersedes this environment variable. The product is exposed in [`puppeteer.product`](https://pptr.dev/api/puppeteer.product)
- `PUPPETEER_EXPERIMENTAL_CHROMIUM_MAC_ARM` — specify Puppeteer download Chromium for Apple M1. On Apple M1 devices Puppeteer by default downloads the version for Intel's processor which runs via Rosetta. It works without any problems, however, with this option, you should get more efficient resource usage (CPU and RAM) that could lead to a faster execution time.
For example, to change the default cache directory Puppeteer uses to install
browsers, you can add a `.puppeteerrc.cjs` (or `puppeteer.config.cjs`) at the
root of your application with the contents
:::danger
```js
const {join} = require('path');
Puppeteer is only [guaranteed to work](https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk.
/**
* @type {import("puppeteer").Configuration}
*/
module.exports = {
// Changes the cache location for Puppeteer.
cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
};
```
:::
After adding the configuration file, you will need to remove and reinstall
`puppeteer` for it to take effect.
:::caution
See the [configuration guide](https://pptr.dev/guides/configuration) for more
information.
`PUPPETEER_*` env variables are not accounted for in [`puppeteer-core`](#puppeteer-core).
:::
#### puppeteer-core
#### `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` 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` automates several workflows using reasonable
defaults [that can be customized](https://pptr.dev/guides/configuration).
`puppeteer-core` is a _library_ to help drive anything that supports DevTools protocol. `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.
`puppeteer-core` is a _library_ to help drive anything that supports DevTools
protocol. Being a library, `puppeteer-core` is fully driven through its
programmatic interface implying no defaults are assumed and `puppeteer-core`
will not download Chromium when installed.
To sum up, the only differences between `puppeteer-core` and `puppeteer` are:
You should use `puppeteer-core` if you are
[connecting to a remote browser](https://pptr.dev/api/puppeteer.puppeteer.connect)
or [managing browsers yourself](https://pptr.dev/api/puppeteer.browserfetcher).
If you are managing browsers yourself, you will need to call
[`puppeteer.launch`](https://pptr.dev/api/puppeteer.puppeteernode.launch) with
an an explicit
[`executablePath`](https://pptr.dev/api/puppeteer.launchoptions.executablepath)
(or [`channel`](https://pptr.dev/api/puppeteer.launchoptions.channel) if it's
installed in a standard location).
- `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:
When using `puppeteer-core`, remember to change the import:
```ts
const puppeteer = require('puppeteer-core');
import puppeteer from 'puppeteer-core';
```
You will then need to call [`puppeteer.connect`](https://pptr.dev/api/puppeteer.puppeteer.connect) or [`puppeteer.launch`](https://pptr.dev/api/puppeteer.puppeteernode.launch) with an explicit `executablePath` or `channel` option.
### Usage
Puppeteer follows the latest [maintenance LTS](https://github.com/nodejs/Release#release-schedule) version of Node.
Puppeteer follows the latest
[maintenance LTS](https://github.com/nodejs/Release#release-schedule) version of
Node.
Puppeteer will be familiar to people using other browser testing frameworks. You create an instance
of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://pptr.dev/api).
Puppeteer will be familiar to people using other browser testing frameworks. You
[launch](https://pptr.dev/api/puppeteer.puppeteernode.launch)/[connect](https://pptr.dev/api/puppeteer.puppeteernode.connect)
a [browser](https://pptr.dev/api/puppeteer.browser),
[create](https://pptr.dev/api/puppeteer.browser.newpage) some
[pages](https://pptr.dev/api/puppeteer.page), and then manipulate them with
[Puppeteer's API](https://pptr.dev/api).
**Example** - navigating to https://example.com and saving a screenshot as _example.png_:
For more in-depth usage, check our [guides](https://pptr.dev/category/guides)
and [examples](https://github.com/puppeteer/puppeteer/tree/main/examples).
Save file as **example.js**
#### Example
The following example searches [developer.chrome.com](https://developer.chrome.com/) for blog posts with text "automate beyond recorder", click on the first result and print the full title of the blog post.
```ts
const puppeteer = require('puppeteer');
import puppeteer from 'puppeteer';
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'example.png'});
await browser.close();
})();
```
await page.goto('https://developer.chrome.com/');
Execute script on the command line
// Set screen size
await page.setViewport({width: 1080, height: 1024});
```bash
node example.js
```
// Type into search box
await page.type('.search-box__input', 'automate beyond recorder');
Puppeteer sets an initial page size to 800×600px, which defines the screenshot size. The page size can be customized with [`Page.setViewport()`](https://pptr.dev/api/puppeteer.page.setviewport).
// Wait and click on first result
const searchResultSelector = '.search-box__link';
await page.waitForSelector(searchResultSelector);
await page.click(searchResultSelector);
**Example** - create a PDF.
Save file as **hn.js**
```ts
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://news.ycombinator.com', {
waitUntil: 'networkidle2',
});
await page.pdf({path: 'hn.pdf', format: 'a4'});
await browser.close();
})();
```
Execute script on the command line
```bash
node hn.js
```
See [`Page.pdf`](https://pptr.dev/api/puppeteer.page.pdf) for more information about creating pdfs.
**Example** - evaluate script in the context of the page
Save file as **get-dimensions.js**
```ts
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
// Get the "viewport" of the page, as reported by the page.
const dimensions = await page.evaluate(() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio,
};
});
console.log('Dimensions:', dimensions);
await browser.close();
})();
```
Execute script on the command line
```bash
node get-dimensions.js
```
See [`Page.evaluate`](https://pptr.dev/api/puppeteer.page.evaluate) and related methods like [`Page.evaluateOnNewDocument`](https://pptr.dev/api/puppeteer.page.evaluateOnNewDocument) and [`Page.exposeFunction`](https://pptr.dev/api/puppeteer.page.exposeFunction).
<!-- [END getstarted] -->
### Running in Docker
Puppeteer offers a Docker image that includes Chromium along with the required dependencies and a pre-installed Puppeteer version. The image is available via the [GitHub Container Registry](https://github.com/puppeteer/puppeteer/pkgs/container/puppeteer). The latest image is tagged as `latest` and other tags match Puppeteer versions. For example,
```sh
docker pull ghcr.io/puppeteer/puppeteer:latest # pulls the latest
docker pull ghcr.io/puppeteer/puppeteer:16.1.0 # pulls the image that contains Puppeteer v16.1.0
```
The image is meant for running the browser in the sandbox mode and therefore, running the image requires the `SYS_ADMIN` capability. For example,
```sh
docker run -i --init --cap-add=SYS_ADMIN --rm ghcr.io/puppeteer/puppeteer:latest node -e "`cat docker/test/smoke-test.js`"
```
Replace the path to [`smoke-test.js`](https://raw.githubusercontent.com/puppeteer/puppeteer/main/docker/test/smoke-test.js) with a path to your script.
The script can import or require the `puppeteer` module because it's pre-installed inside the image.
Currently, the image includes the LTS version of Node.js. If you need to build an image based on a different base image, you can use our [`Dockerfile`](https://github.com/puppeteer/puppeteer/blob/main/docker/Dockerfile) as the starting point.
### Working with Chrome Extensions
Puppeteer can be used for testing Chrome Extensions.
:::caution
Extensions in Chrome / Chromium currently only work in non-headless mode and experimental Chrome 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`:
```ts
const puppeteer = require('puppeteer');
(async () => {
const pathToExtension = require('path').join(__dirname, 'my-extension');
const browser = await puppeteer.launch({
headless: 'chrome',
args: [
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
});
const backgroundPageTarget = await browser.waitForTarget(
target => target.type() === 'background_page'
// Localte the full title with a unique string
const textSelector = await page.waitForSelector(
'text/Customize and automate'
);
const backgroundPage = await backgroundPageTarget.page();
// Test the background page as you would any other page.
const fullTitle = await textSelector.evaluate(el => el.textContent);
// Print the full title
console.log('The title of this blog post is "%s".', fullTitle);
await browser.close();
})();
```
:::note
Chrome Manifest V3 extensions have a background ServiceWorker of type 'service_worker', instead of a page of type 'background_page'.
:::
:::note
It is not yet possible to test extension popups or content scripts.
:::
<!-- [START runtimesettings] -->
## Default runtime settings
### Default runtime settings
**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://pptr.dev/api/puppeteer.browserlaunchargumentoptions.headless) 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`](https://pptr.dev/api/puppeteer.browserlaunchargumentoptions.headless)
option when launching a browser:
```ts
const browser = await puppeteer.launch({headless: false}); // default is true
@ -277,139 +178,51 @@ const browser = await puppeteer.launch({headless: false}); // default is true
**2. Runs a bundled version of Chromium**
By default, Puppeteer downloads and uses a specific version of Chromium so its API
is guaranteed to work out of the box. To use Puppeteer with a different version of Chrome or Chromium,
pass in the executable's path when creating a `Browser` instance:
By default, Puppeteer downloads and uses a specific version of Chromium so its
API is guaranteed to work out of the box. To use Puppeteer with a different
version of Chrome or Chromium, pass in the executable's path when creating a
`Browser` instance:
```ts
const browser = await puppeteer.launch({executablePath: '/path/to/Chrome'});
```
You can also use Puppeteer with Firefox Nightly (experimental support). See [`Puppeteer.launch`](https://pptr.dev/api/puppeteer.puppeteernode.launch) for more information.
You can also use Puppeteer with Firefox Nightly (experimental support). See
[`Puppeteer.launch`](https://pptr.dev/api/puppeteer.puppeteernode.launch) for
more information.
See [`this article`](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) for a description of the differences between Chromium and Chrome. [`This article`](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users.
See
[`this article`](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/)
for a description of the differences between Chromium and Chrome.
[`This article`](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/chromium_browser_vs_google_chrome.md)
describes some differences for Linux users.
**3. Creates a fresh user profile**
Puppeteer creates its own browser user profile which it **cleans up on every run**.
Puppeteer creates its own browser user profile which it **cleans up on every
run**.
<!-- [END runtimesettings] -->
#### Using Docker
See our [Docker guide](https://pptr.dev/guides/docker).
#### Using Chrome Extensions
See our [Chrome extensions guide](https://pptr.dev/guides/chrome-extensions).
## Resources
- [API Documentation](https://pptr.dev/api)
- [Guides](https://pptr.dev/category/guides)
- [Examples](https://github.com/puppeteer/puppeteer/tree/main/examples)
- [Community list of Puppeteer resources](https://github.com/transitive-bullshit/awesome-puppeteer)
<!-- [START debugging] -->
## Debugging tips
1. Turn off headless mode - sometimes it's useful to see what the browser is
displaying. Instead of launching in headless mode, launch a full version of
the browser using `headless: false`:
```ts
const browser = await puppeteer.launch({headless: false});
```
2. Slow it down - the `slowMo` option slows down Puppeteer operations by the
specified amount of milliseconds. It's another way to help see what's going on.
```ts
const browser = await puppeteer.launch({
headless: false,
slowMo: 250, // slow down by 250ms
});
```
3. Capture console output - You can listen for the `console` event.
This is also handy when debugging code in `page.evaluate()`:
```ts
page.on('console', msg => console.log('PAGE LOG:', msg.text()));
await page.evaluate(() => console.log(`url is ${location.href}`));
```
4. Use debugger in application code browser
There are two execution context: node.js that is running test code, and the browser
running application code being tested. This lets you debug code in the
application code browser; ie code inside `evaluate()`.
- Use `{devtools: true}` when launching Puppeteer:
```ts
const browser = await puppeteer.launch({devtools: true});
```
- Change default test timeout:
jest: `jest.setTimeout(100000);`
jasmine: `jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000;`
mocha: `this.timeout(100000);` (don't forget to change test to use [function and not '=>'](https://stackoverflow.com/a/23492442))
- Add an evaluate statement with `debugger` inside / add `debugger` to an existing evaluate statement:
```ts
await page.evaluate(() => {
debugger;
});
```
The test will now stop executing in the above evaluate statement, and chromium will stop in debug mode.
5. Use debugger in node.js
This will let you debug test code. For example, you can step over `await page.click()` in the node.js script and see the click happen in the application code browser.
Note that you won't be able to run `await page.click()` in
DevTools console due to this [Chromium bug](https://bugs.chromium.org/p/chromium/issues/detail?id=833928). So if
you want to try something out, you have to add it to your test file.
- Add `debugger;` to your test, eg:
```ts
debugger;
await page.click('a[target=_blank]');
```
- Set `headless` to `false`
- Run `node --inspect-brk`, eg `node --inspect-brk node_modules/.bin/jest tests`
- In Chrome open `chrome://inspect/#devices` and click `inspect`
- In the newly opened test browser, type `F8` to resume test execution
- Now your `debugger` will be hit and you can debug in the test browser
6. Enable verbose logging - internal DevTools protocol traffic
will be logged via the [`debug`](https://github.com/visionmedia/debug) module under the `puppeteer` namespace.
# Basic verbose logging
env DEBUG="puppeteer:*" node script.js
# Protocol traffic can be rather noisy. This example filters out all Network domain messages
env DEBUG="puppeteer:*" env DEBUG_COLORS=true node script.js 2>&1 | grep -v '"Network'
7. Debug your Puppeteer (node) code easily, using [ndb](https://github.com/GoogleChromeLabs/ndb)
- `npm install -g ndb` (or even better, use [npx](https://github.com/zkat/npx)!)
- add a `debugger` to your Puppeteer (node) code
- add `ndb` (or `npx ndb`) before your test command. For example:
`ndb jest` or `ndb mocha` (or `npx ndb jest` / `npx ndb mocha`)
- debug your test inside chromium like a boss!
<!-- [END debugging] -->
## Contributing
Check out our [contributing guide](https://pptr.dev/contributing) to get an overview of Puppeteer development.
Check out our [contributing guide](https://pptr.dev/contributing) to get an
overview of Puppeteer development.
## FAQ
Our [FAQ](https://pptr.dev/faq) has migrated to [our site](https://pptr.dev/faq).
Our [FAQ](https://pptr.dev/faq) has migrated to
[our site](https://pptr.dev/faq).

Просмотреть файл

@ -0,0 +1,7 @@
# Security Policy
The Puppeteer project takes security very seriously. Please use Chromium's process to report security issues.
## Reporting a Vulnerability
See https://www.chromium.org/Home/chromium-security/reporting-security-bugs/

Просмотреть файл

@ -13,11 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// See https://github.com/conventional-changelog/commitlint/blob/master/docs/reference-rules.md
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'body-max-line-length': [0, 'always', 100],
'footer-max-line-length': [0, 'always', 100],
'subject-case': [0, 'never'],
// Override. The subject may be the name of a class.
'subject-case': [0],
// Override. Most UIs wrap the body.
'body-max-line-length': [0],
// Override. Most UIs wrap the footer.
'footer-max-line-length': [0],
},
};

Просмотреть файл

@ -1,16 +0,0 @@
# Compatibility layer
This directory provides an additional compatibility layer between ES modules and CommonJS.
## Why?
Both `./cjs/compat.ts` and `./esm/compat.ts` are written as ES modules, but `./cjs/compat.ts` can additionally use NodeJS CommonJS globals such as `__dirname` and `require` while these are disabled in ES module mode. For more information, see [Differences between ES modules and CommonJS](https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs).
## Adding exports
In order to add exports, two things need to be done:
- The exports must be declared in `src/compat.ts`.
- The exports must be realized in `./cjs/compat.ts` and `./esm/compat.ts`.
In the event `compat.ts` becomes too large, you can place declarations in another file. Just make sure `./cjs`, `./esm`, and `src` have the same structure.

Просмотреть файл

@ -1,19 +0,0 @@
import {dirname} from 'path';
/**
* @internal
*/
let puppeteerDirname: string;
try {
// In some environments, like esbuild, this will throw an error.
// We suppress the error since the bundled binary is not expected
// to be used or installed in this case and, therefore, the
// root directory does not have to be known.
puppeteerDirname = dirname(require.resolve('./compat'));
} catch (error) {
// Fallback to __dirname.
puppeteerDirname = __dirname;
}
export {puppeteerDirname};

Просмотреть файл

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

Просмотреть файл

@ -1,22 +0,0 @@
import {createRequire} from 'module';
import {dirname} from 'path';
import {fileURLToPath} from 'url';
const require = createRequire(import.meta.url);
/**
* @internal
*/
let puppeteerDirname: string;
try {
// In some environments, like esbuild, this will throw an error.
// We suppress the error since the bundled binary is not expected
// to be used or installed in this case and, therefore, the
// root directory does not have to be known.
puppeteerDirname = dirname(require.resolve('./compat'));
} catch (error) {
puppeteerDirname = dirname(fileURLToPath(import.meta.url));
}
export {puppeteerDirname};

Просмотреть файл

@ -1,8 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "../../lib/esm/puppeteer",
"module": "esnext"
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -39,3 +39,4 @@ More complex and use case driven examples can be found at [github.com/GoogleChro
## Services
- [Checkly](https://checklyhq.com) - Monitoring SaaS that uses Puppeteer to check availability and correctness of web pages and apps.
- [Doppio](https://doppio.sh) - SaaS API to create screenshots or PDFs from HTML/CSS/JS

Просмотреть файл

@ -34,7 +34,7 @@ const firefoxOptions = {
await page.goto('https://news.ycombinator.com/');
// Extract articles from the page.
const resultsSelector = '.titlelink';
const resultsSelector = '.titleline > a';
const links = await page.evaluate(resultsSelector => {
const anchors = Array.from(document.querySelectorAll(resultsSelector));
return anchors.map(anchor => {

Просмотреть файл

@ -1,89 +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.
*/
/**
* This file is part of public API.
*
* By default, the `puppeteer` package runs this script during the installation
* process unless one of the env flags is provided.
* `puppeteer-core` package doesn't include this step at all. However, it's
* still possible to install a supported browser using this script when
* necessary.
*/
const compileTypeScriptIfRequired = require('./typescript-if-required.js');
async function download() {
await compileTypeScriptIfRequired();
// need to ensure TS is compiled before loading the installer
const {
downloadBrowser,
logPolitely,
} = require('./lib/cjs/puppeteer/node/install.js');
if (process.env.PUPPETEER_SKIP_DOWNLOAD) {
logPolitely(
'**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" environment variable was found.'
);
return;
}
if (
process.env.NPM_CONFIG_PUPPETEER_SKIP_DOWNLOAD ||
process.env.npm_config_puppeteer_skip_download
) {
logPolitely(
'**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" was set in npm config.'
);
return;
}
if (
process.env.NPM_PACKAGE_CONFIG_PUPPETEER_SKIP_DOWNLOAD ||
process.env.npm_package_config_puppeteer_skip_download
) {
logPolitely(
'**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" was set in project config.'
);
return;
}
if (process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD) {
logPolitely(
'**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" environment variable was found.'
);
return;
}
if (
process.env.NPM_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD ||
process.env.npm_config_puppeteer_skip_chromium_download
) {
logPolitely(
'**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in npm config.'
);
return;
}
if (
process.env.NPM_PACKAGE_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD ||
process.env.npm_package_config_puppeteer_skip_chromium_download
) {
logPolitely(
'**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in project config.'
);
return;
}
downloadBrowser();
}
download();

Просмотреть файл

@ -5,6 +5,6 @@ origin:
description: Headless Chrome Node API
license: Apache-2.0
name: puppeteer
release: 7d6927209e5d557891bd618ddb01d54bc3566307
release: e13e9647fc0d917da94af8851a09ed318fb0e07c
url: /Users/alexandraborovova/Projects/puppeteer
schema: 1

6622
remote/test/puppeteer/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,145 +1,115 @@
{
"name": "puppeteer",
"version": "18.0.0",
"description": "A high-level API to control headless Chrome over the DevTools Protocol",
"keywords": [
"puppeteer",
"chrome",
"headless",
"automation"
],
"type": "commonjs",
"main": "./lib/cjs/puppeteer/puppeteer.js",
"exports": {
".": {
"types": "./lib/types.d.ts",
"import": "./lib/esm/puppeteer/puppeteer.js",
"require": "./lib/cjs/puppeteer/puppeteer.js"
},
"./*": {
"import": "./*",
"require": "./*"
}
},
"types": "lib/types.d.ts",
"repository": "github:puppeteer/puppeteer",
"engines": {
"node": ">=14.1.0"
"name": "puppeteer-repo",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/puppeteer/puppeteer"
},
"scripts": {
"test": "cross-env MOZ_WEBRENDER=0 PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT=20000 node utils/mochaRunner/lib/main.js",
"test:types": "tsd",
"test:install": "scripts/test-install.sh",
"test:firefox": "npm run test -- --test-suite firefox-headless",
"test:chrome": "run-s test:chrome:*",
"test:chrome:headless": "npm run test -- --test-suite chrome-headless",
"test:chrome:headless-chrome": "npm run test -- --test-suite chrome-new-headless",
"test:chrome:headful": "npm run test -- --test-suite chrome-headful",
"prepublishOnly": "npm run build",
"prepare": "node typescript-if-required.js && husky install",
"lint": "run-s lint:prettier lint:eslint",
"lint:prettier": "prettier --check .",
"lint:eslint": "([ \"$CI\" = true ] && eslint --ext js --ext ts --quiet -f codeframe . || eslint --ext js --ext ts .)",
"install": "node install.js",
"generate:sources": "tsx utils/generate_sources.ts",
"generate:artifacts": "tsx utils/generate_artifacts.ts",
"generate:markdown": "tsx utils/generate_docs.ts",
"format": "run-s format:*",
"format:prettier": "prettier --write .",
"format:eslint": "eslint --ext js --ext ts --fix .",
"docs": "run-s build generate:markdown",
"debug": "npm run build:dev && mocha --inspect-brk",
"bisect": "tsx tools/bisect.ts",
"build": "npm run build --workspaces --if-present",
"check:pinned-deps": "tsx tools/ensure-pinned-deps",
"check": "npm run check --workspaces --if-present && run-p check:*",
"clean": "npm run clean --workspaces --if-present && rimraf **/.wireit",
"commitlint": "commitlint --from=HEAD~1",
"clean": "rimraf lib && rimraf test/build",
"check": "run-p check:*",
"check:protocol-revision": "tsx scripts/ensure-correct-devtools-protocol-package",
"check:pinned-deps": "tsx scripts/ensure-pinned-deps",
"build": "npm run build:prod",
"build:dev": "run-s generate:sources build:tsc:dev generate:artifacts",
"build:prod": "run-s generate:sources build:tsc:prod generate:artifacts",
"build:tsc:dev": "tsc -b test",
"build:tsc:prod": "tsc -b tsconfig.lib.json"
},
"files": [
"lib",
"install.js",
"typescript-if-required.js",
"!**/*.tsbuildinfo"
],
"author": "The Chromium Authors",
"license": "Apache-2.0",
"dependencies": {
"cross-fetch": "3.1.5",
"debug": "4.3.4",
"devtools-protocol": "0.0.1036444",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"progress": "2.0.3",
"proxy-from-env": "1.1.0",
"rimraf": "3.0.2",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3",
"ws": "8.8.1"
"debug": "mocha --inspect-brk",
"docs": "run-s build generate:markdown",
"format:eslint": "eslint --ext js --ext ts --fix .",
"format:prettier": "prettier --write .",
"format": "run-s format:*",
"generate:markdown": "tsx tools/generate_docs.ts",
"lint:eslint": "([ \"$CI\" = true ] && eslint --ext js --ext ts --quiet -f codeframe . || eslint --ext js --ext ts .)",
"lint:prettier": "prettier --check .",
"lint": "run-s lint:prettier lint:eslint",
"postinstall": "npm run postinstall --workspaces --if-present",
"prepare": "husky install",
"test-install": "npm run test --workspace @puppeteer-test/installation",
"test-types": "tsd -t packages/puppeteer",
"test:chrome:headful": "npm test -- --test-suite chrome-headful",
"test:chrome:headless-chrome": "npm test -- --test-suite chrome-new-headless",
"test:chrome:headless": "npm test -- --test-suite chrome-headless",
"test:chrome:bidi": "npm test -- --test-suite chrome-bidi",
"test:chrome": "run-s test:chrome:*",
"test:firefox:bidi": "npm test -- --test-suite firefox-bidi",
"test:firefox:headful": "npm test -- --test-suite firefox-headful",
"test:firefox:headless": "npm test -- --test-suite firefox-headless",
"test:firefox": "run-s test:firefox:*",
"test": "cross-env PUPPETEER_DEFERRED_PROMISE_DEBUG_TIMEOUT=20000 node tools/mochaRunner/lib/main.js"
},
"devDependencies": {
"@commitlint/cli": "17.0.3",
"@commitlint/config-conventional": "17.0.3",
"@microsoft/api-documenter": "7.19.4",
"@microsoft/api-extractor": "7.29.2",
"@microsoft/api-extractor-model": "7.23.0",
"@actions/core": "1.10.0",
"@commitlint/cli": "17.3.0",
"@commitlint/config-conventional": "17.3.0",
"@microsoft/api-documenter": "7.19.26",
"@microsoft/api-extractor": "7.33.7",
"@microsoft/api-extractor-model": "7.25.3",
"@pptr/testserver": "file:packages/testserver",
"@rollup/plugin-commonjs": "24.0.0",
"@rollup/plugin-node-resolve": "15.0.1",
"@types/debug": "4.1.7",
"@types/diff": "5.0.2",
"@types/glob": "7.2.0",
"@types/glob": "8.0.0",
"@types/mime": "3.0.1",
"@types/mocha": "9.1.1",
"@types/node": "18.7.1",
"@types/mocha": "10.0.1",
"@types/node": "18.11.17",
"@types/pixelmatch": "5.2.4",
"@types/pngjs": "6.0.1",
"@types/progress": "2.0.5",
"@types/proxy-from-env": "1.0.1",
"@types/rimraf": "3.0.2",
"@types/semver": "7.3.11",
"@types/semver": "7.3.13",
"@types/sinon": "10.0.13",
"@types/tar-fs": "2.0.1",
"@types/unbzip2-stream": "1.4.0",
"@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "5.33.0",
"@typescript-eslint/parser": "5.33.0",
"@typescript-eslint/eslint-plugin": "5.46.1",
"@typescript-eslint/parser": "5.46.1",
"c8": "7.12.0",
"chromium-bidi": "0.4.3",
"commonmark": "0.30.0",
"cross-env": "7.0.3",
"diff": "5.1.0",
"esbuild": "0.15.5",
"eslint": "8.21.0",
"esbuild": "0.16.9",
"eslint": "8.30.0",
"eslint-config-prettier": "8.5.0",
"eslint-formatter-codeframe": "7.32.1",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-local": "1.0.0",
"eslint-plugin-mocha": "10.1.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-tsdoc": "0.2.16",
"eslint-plugin-tsdoc": "0.2.17",
"eslint-plugin-unused-imports": "2.0.0",
"esprima": "4.0.1",
"expect": "25.2.7",
"glob": "8.0.3",
"glob": "8.1.0",
"gts": "4.0.0",
"husky": "8.0.1",
"husky": "8.0.2",
"jpeg-js": "0.4.4",
"mime": "3.0.0",
"minimist": "1.2.6",
"mocha": "10.0.0",
"minimist": "1.2.7",
"mitt": "3.0.0",
"mocha": "10.2.0",
"ncp": "2.0.0",
"npm-run-all": "4.1.5",
"pixelmatch": "5.3.0",
"pngjs": "6.0.0",
"prettier": "2.7.1",
"semver": "7.3.7",
"sinon": "14.0.0",
"prettier": "2.8.1",
"puppeteer": "file:packages/puppeteer",
"rollup": "2.79.1",
"rollup-plugin-dts": "4.2.2",
"semver": "7.3.8",
"sinon": "15.0.1",
"source-map-support": "0.5.21",
"text-diff": "1.0.1",
"tsd": "0.22.0",
"tsx": "3.8.2",
"typescript": "4.7.4",
"zod": "3.18.0"
}
"tsd": "0.25.0",
"tsx": "3.12.1",
"typescript": "4.9.4",
"wireit": "0.9.2",
"zod": "3.20.2"
},
"workspaces": [
"packages/*",
"test",
"test/installation"
]
}

Просмотреть файл

@ -0,0 +1,2 @@
# Ignore File that will be copied to Angular
/files/

22
remote/test/puppeteer/packages/ng-schematics/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,22 @@
# Outputs
src/**/*.js
src/**/*.js.map
src/**/*.d.ts
# Keep files that serve as template
!src/**/files/**/*
# IDEs
.idea/
jsconfig.json
.vscode/
# Misc
node_modules/
npm-debug.log*
yarn-error.log*
# Mac OSX Finder files.
**/.DS_Store
.DS_Store

Просмотреть файл

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

Просмотреть файл

@ -0,0 +1,8 @@
# Changelog
## 0.1.0 (2022-11-23)
### Features
* **ng-schematics:** Release @puppeteer/ng-schematics ([#9244](https://github.com/puppeteer/puppeteer/issues/9244)) ([be33929](https://github.com/puppeteer/puppeteer/commit/be33929770e473992ad49029e6d038d36591e108))

Просмотреть файл

@ -0,0 +1,51 @@
# Puppeteer Angular Schematic
Adds Puppeteer-based e2e tests to your Angular project.
## Usage
Run the command below in an Angular CLI app directory and follow the prompts.
_Note this will add the schematic as a dependency to your project._
```bash
ng add @puppeteer/ng-schematics
```
Or you can use the same command followed by the [options](#options) below.
Currently, this schematic supports the following test frameworks:
- **Jasmine** [https://jasmine.github.io/]
- **Jest** [https://jestjs.io/]
- **Mocha** [https://mochajs.org/]
- **Node Test Runner** _(Experimental)_ [https://nodejs.org/api/test.html]
With the schematics installed you can run E2E tests:
```bash
ng e2e
```
> Note: Command spawns it's own server on the same port `ng serve` does.
## Options
When adding schematics to your project you can to provide following options:
| Option | Description | Value | Required |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -------- |
| `--isDefaultTester` | When true, replaces default `ng e2e` command. | `boolean` | `true` |
| `--exportConfig` | When true, creates an empty [Puppeteer configuration](https://pptr.dev/guides/configuration) file. (`.puppeteerrc.cjs`) | `boolean` | `true` |
| `--testingFramework` | The testing framework to install along side Puppeteer. | `"jasmine"`, `"jest"`, `"mocha"`, `"node"` | `true` |
## Contributing
Check out our [contributing guide](https://pptr.dev/contributing) to get an overview of what you need to develop in the Puppeteer repo.
### Unit Testing
The schematics utilize `@angular-devkit/schematics/testing` for verifying correct file creation and `package.json` updates. To execute the test suit:
```bash
npm run test
```

Просмотреть файл

@ -0,0 +1,70 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const fs = require('fs/promises');
const path = require('path');
/**
*
* @param {String} directory
* @param {String[]} files
*/
async function findSchemaFiles(directory, files = []) {
const items = await fs.readdir(directory);
const promises = [];
// Match any listing that has no *.* format
// Ignore files folder
const regEx = /^.*\.[^\s]*$/;
items.forEach(item => {
if (!item.match(regEx)) {
promises.push(findSchemaFiles(`${directory}/${item}`, files));
} else if (item.endsWith('.json') || directory.includes('files')) {
files.push(`${directory}/${item}`);
}
});
await Promise.all(promises);
return files;
}
async function copySchemaFiles() {
const srcDir = './src';
const outputDir = './lib';
const files = await findSchemaFiles(srcDir);
const moves = files.map(file => {
const to = file.replace(srcDir, outputDir);
return {from: file, to};
});
// Because fs.cp is Experimental (recursive support)
// We need to create directories first and copy the files
await Promise.all(
moves.map(({to}) => {
const dir = path.dirname(to);
return fs.mkdir(dir, {recursive: true});
})
);
await Promise.all(
moves.map(({from, to}) => {
return fs.copyFile(from, to);
})
);
}
copySchemaFiles();

1098
remote/test/puppeteer/packages/ng-schematics/package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,86 @@
{
"name": "@puppeteer/ng-schematics",
"version": "0.1.0",
"description": "Puppeteer Angular schematics",
"scripts": {
"dev": "npm run build --watch",
"dev:test": "npm run test --watch",
"copy": "wireit",
"build": "wireit",
"clean": "tsc --build --clean && rimraf lib",
"clean:test": "rimraf test/build",
"test": "wireit"
},
"wireit": {
"copy": {
"clean": "if-file-deleted",
"command": "node copySchemaFiles.js",
"files": [
"src/**/files/**",
"src/**/*.json"
],
"output": [
"lib/**/files/**",
"lib/**/*.json"
],
"dependencies": [
"clean"
]
},
"build": {
"command": "tsc -b",
"files": [
"src/**/*.ts",
"!src/**/files",
"!src/**/*.json"
],
"output": [
"lib/**",
"!lib/**/files",
"!lib/**/*.json"
],
"dependencies": [
"copy"
]
},
"test": {
"command": "mocha",
"dependencies": [
"clean:test",
"build"
]
}
},
"keywords": [
"angular",
"puppeteer",
"schematics"
],
"repository": {
"type": "git",
"url": "https://github.com/puppeteer/puppeteer/tree/main/packages/ng-schematics"
},
"author": "The Chromium Authors",
"license": "Apache-2.0",
"engines": {
"node": ">=14.1.0"
},
"dependencies": {
"@angular-devkit/architect": "^0.1501.2",
"@angular-devkit/core": "^15.1.2",
"@angular-devkit/schematics": "^15.1.2"
},
"devDependencies": {
"@types/node": "^14.15.0",
"@schematics/angular": "^14.2.8"
},
"files": [
"lib",
"!*.tsbuildinfo"
],
"ng-add": {
"save": "devDependencies"
},
"schematics": "./lib/schematics/collection.json",
"builders": "./lib/builders/builders.json"
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"$schema": "../../../../node_modules/@angular-devkit/architect/src/builders-schema.json",
"builders": {
"puppeteer": {
"implementation": "./puppeteer",
"schema": "./puppeteer/schema.json",
"description": "Run e2e test with Puppeteer"
}
}
}

Просмотреть файл

@ -0,0 +1,137 @@
import {
createBuilder,
BuilderContext,
BuilderOutput,
targetFromTargetString,
BuilderRun,
} from '@angular-devkit/architect';
import {JsonObject} from '@angular-devkit/core';
import {spawn} from 'child_process';
import {PuppeteerBuilderOptions} from './types.js';
const terminalStyles = {
blue: '\u001b[34m',
green: '\u001b[32m',
bold: '\u001b[1m',
reverse: '\u001b[7m',
clear: '\u001b[0m',
};
function getError(executable: string, args: string[]) {
return (
`Puppeteer E2E tests failed!` +
'\n' +
`Error running '${executable}' with arguments '${args.join(' ')}'.` +
`\n` +
'Please look at the output above to determine the issue!'
);
}
function getExecutable(command: string[]) {
const executable = command.shift()!;
const error = getError(executable, command);
if (executable === 'node') {
return {
executable: executable,
args: command,
error,
};
}
return {
executable: `./node_modules/.bin/${executable}`,
args: command,
error,
};
}
async function executeCommand(context: BuilderContext, command: string[]) {
await new Promise((resolve, reject) => {
context.logger.debug(`Trying to execute command - ${command.join(' ')}.`);
const {executable, args, error} = getExecutable(command);
const child = spawn(executable, args, {
cwd: context.workspaceRoot,
stdio: 'inherit',
});
child.on('error', message => {
console.log(message);
reject(error);
});
child.on('exit', code => {
if (code === 0) {
resolve(true);
} else {
reject(error);
}
});
});
}
function message(
message: string,
context: BuilderContext,
type: 'info' | 'success' = 'info'
): void {
const color = type === 'info' ? terminalStyles.blue : terminalStyles.green;
context.logger.info(
`${terminalStyles.bold}${terminalStyles.reverse}${color}${message}${terminalStyles.clear}`
);
}
async function startServer(
options: PuppeteerBuilderOptions,
context: BuilderContext
): Promise<BuilderRun> {
context.logger.debug('Trying to start server.');
const target = targetFromTargetString(options.devServerTarget);
const defaultServerOptions = await context.getTargetOptions(target);
const overrides = {
watch: false,
host: defaultServerOptions['host'],
port: defaultServerOptions['port'],
} as JsonObject;
message('Spawning test server...\n', context);
const server = await context.scheduleTarget(target, overrides);
const result = await server.result;
if (!result.success) {
throw new Error('Failed to spawn server! Stopping tests...');
}
return server;
}
async function executeE2ETest(
options: PuppeteerBuilderOptions,
context: BuilderContext
): Promise<BuilderOutput> {
let server: BuilderRun | null = null;
try {
server = await startServer(options, context);
message('\nRunning tests...\n', context);
for (const command of options.commands) {
await executeCommand(context, command);
}
message('\nTest ran successfully!', context, 'success');
return {success: true};
} catch (error) {
if (error instanceof Error) {
return {success: false, error: error.message};
}
return {success: false, error: error as any};
} finally {
if (server) {
await server.stop();
}
}
}
export default createBuilder<PuppeteerBuilderOptions>(executeE2ETest) as any;

Просмотреть файл

@ -0,0 +1,22 @@
{
"title": "Puppeteer",
"description": "Options for Puppeteer Angular Schematics",
"type": "object",
"properties": {
"commands": {
"type": "array",
"items": {
"type": "array",
"item": {
"type": "string"
}
},
"description": "Commands to execute in the repo. Commands prefixed with `./node_modules/bin` (Exception: 'node')."
},
"devServerTarget": {
"type": "string",
"description": "Angular target that spawns the server."
}
},
"additionalProperties": true
}

Просмотреть файл

@ -0,0 +1,24 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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,
* 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 {JsonObject} from '@angular-devkit/core';
type Command = [string, ...string[]];
export interface PuppeteerBuilderOptions extends JsonObject {
commands: Command[];
devServerTarget: string;
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"$schema": "../../../../node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"ng-add": {
"description": "Add Puppeteer to an Angular project",
"factory": "./ng-add/index#ngAdd",
"schema": "./ng-add/schema.json"
}
}
}

Просмотреть файл

@ -0,0 +1,4 @@
/**
* @type {import("puppeteer").Configuration}
*/
module.exports = {};

Просмотреть файл

@ -0,0 +1,59 @@
import * as puppeteer from 'puppeteer';
<% if(testingFramework == 'node') { %>
import {
describe,
it,
before,
beforeEach,
after,
afterEach,
} from 'node:test';
<% } %><% if(testingFramework == 'mocha' || testingFramework == 'node') { %>
import * as assert from 'assert';
<% } %>
describe('App test', function () {
let browser: puppeteer.Browser;
let page: puppeteer.Page;
<% if(testingFramework == 'jasmine' || testingFramework == 'jest') { %>
beforeAll(async () => {
browser = await puppeteer.launch();
});
<% } %><% if(testingFramework == 'mocha' || testingFramework == 'node') { %>
before(async () => {
browser = await puppeteer.launch();
});
<% } %>
beforeEach(async () => {
page = await browser.newPage();
await page.goto('<%= baseUrl %>');
});
afterEach(async () => {
await page.close();
});
<% if(testingFramework == 'jasmine' || testingFramework == 'jest') { %>
afterAll(async () => {
await browser.close();
});
<% } %><% if(testingFramework == 'mocha' || testingFramework == 'node') { %>
after(async () => {
await browser.close();
});
<% } %>
it('is running', async function () {
const element = await page.waitForSelector(
'text/<%= project %> app is running!'
);
<% if(testingFramework == 'jasmine' || testingFramework == 'jest') { %>
expect(element).not.toBeNull();
<% } %><% if(testingFramework == 'mocha' || testingFramework == 'node') { %>
assert.ok(element);
<% } %>
});
});

Просмотреть файл

@ -0,0 +1,15 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.json",
"compilerOptions": {
<% if(testingFramework == 'jest') { %>
"esModuleInterop": true,
<% } %><% if(testingFramework == 'node') { %>
"module": "CommonJS",
"rootDir": "tests/",
"outDir": "test/",
<% } %>
"types": ["<%= testingFramework %>"]
},
"include": ["tests/**/*.e2e.ts"]
}

Просмотреть файл

@ -0,0 +1,4 @@
require('@babel/register')({
extensions: ['.js', '.ts'],
presets: ['@babel/preset-env', '@babel/preset-typescript'],
});

Просмотреть файл

@ -0,0 +1,9 @@
{
"spec_dir": "e2e",
"spec_files": ["**/*[eE]2[eE].ts"],
"helpers": ["helpers/babel.js", "helpers/**/*.{js|ts}"],
"env": {
"stopSpecOnExpectationFailure": false,
"random": true
}
}

Просмотреть файл

@ -0,0 +1,11 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
testMatch: ['<rootDir>/tests/**/?(*.)+(e2e).[tj]s?(x)'],
preset: 'ts-jest',
testEnvironment: 'node',
};

Просмотреть файл

@ -0,0 +1,4 @@
module.exports = {
file: ['e2e/babel.js'],
spec: './e2e/tests/**/*.e2e.ts',
};

Просмотреть файл

@ -0,0 +1,4 @@
require('@babel/register')({
extensions: ['.js', '.ts'],
presets: ['@babel/preset-env', '@babel/preset-typescript'],
});

Просмотреть файл

@ -0,0 +1,3 @@
# Compiled e2e tests output Node auto resolves files in folders named 'test'
test/

Просмотреть файл

@ -0,0 +1,128 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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,
* 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 {chain, Rule, SchematicContext, Tree} from '@angular-devkit/schematics';
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
import {concatMap, map, scan} from 'rxjs/operators';
import {of} from 'rxjs';
import {
addBaseFiles,
addFrameworkFiles,
getNgCommandName,
} from '../utils/files.js';
import {
addPackageJsonDependencies,
addPackageJsonScripts,
getDependenciesFromOptions,
getPackageLatestNpmVersion,
DependencyType,
type NodePackage,
updateAngularJsonScripts,
} from '../utils/packages.js';
import {type SchematicsOptions} from '../utils/types.js';
import {getAngularConfig} from '../utils/json.js';
// You don't have to export the function as default. You can also have more than one rule
// factory per file.
export function ngAdd(options: SchematicsOptions): Rule {
return (tree: Tree, context: SchematicContext) => {
return chain([
addDependencies(options),
addPuppeteerFiles(options),
addOtherFiles(options),
updateScripts(options),
updateAngularConfig(options),
])(tree, context);
};
}
function addDependencies(options: SchematicsOptions): Rule {
return (tree: Tree, context: SchematicContext) => {
context.logger.debug('Adding dependencies to "package.json"');
const dependencies = getDependenciesFromOptions(options);
return of(...dependencies).pipe(
concatMap((packageName: string) => {
return getPackageLatestNpmVersion(packageName);
}),
scan((array, nodePackage) => {
array.push(nodePackage);
return array;
}, [] as NodePackage[]),
map(packages => {
context.logger.debug('Updating dependencies...');
addPackageJsonDependencies(tree, packages, DependencyType.Dev);
context.addTask(new NodePackageInstallTask());
return tree;
})
);
};
}
function updateScripts(options: SchematicsOptions): Rule {
return (tree: Tree, context: SchematicContext): Tree => {
context.logger.debug('Updating "package.json" scripts');
const angularJson = getAngularConfig(tree);
const projects = Object.keys(angularJson['projects']);
if (projects.length === 1) {
const name = getNgCommandName(options);
const prefix = options.isDefaultTester ? '' : `run ${projects[0]}:`;
return addPackageJsonScripts(tree, [
{
name,
script: `ng ${prefix}${name}`,
},
]);
}
return tree;
};
}
function addPuppeteerFiles(options: SchematicsOptions): Rule {
return (tree: Tree, context: SchematicContext) => {
context.logger.debug('Adding Puppeteer base files.');
const {projects} = getAngularConfig(tree);
return addBaseFiles(tree, context, {
projects,
options,
});
};
}
function addOtherFiles(options: SchematicsOptions): Rule {
return (tree: Tree, context: SchematicContext) => {
context.logger.debug('Adding Puppeteer additional files.');
const {projects} = getAngularConfig(tree);
return addFrameworkFiles(tree, context, {
projects,
options,
});
};
}
function updateAngularConfig(options: SchematicsOptions): Rule {
return (tree: Tree, context: SchematicContext): Tree => {
context.logger.debug('Updating "angular.json".');
return updateAngularJsonScripts(tree, options);
};
}

Просмотреть файл

@ -0,0 +1,49 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "Puppeteer",
"title": "Puppeteer Install Schema",
"type": "object",
"properties": {
"isDefaultTester": {
"description": "",
"type": "boolean",
"default": true,
"x-prompt": "Use Puppeteer as default `ng e2e` command?"
},
"exportConfig": {
"description": "",
"type": "boolean",
"default": false,
"x-prompt": "Export default Puppeteer config file?"
},
"testingFramework": {
"description": "",
"type": "string",
"enum": ["jasmine", "jest", "mocha", "node"],
"default": "jasmine",
"x-prompt": {
"message": "With what Testing Library do you wish to integrate?",
"type": "list",
"items": [
{
"value": "jasmine",
"label": "Use Jasmine [https://jasmine.github.io/]"
},
{
"value": "jest",
"label": "Use Jest [https://jestjs.io/]"
},
{
"value": "mocha",
"label": "Use Mocha [https://mochajs.org/]"
},
{
"value": "node",
"label": "Use Node Test Runner (Experimental: Node v18) [https://nodejs.org/api/test.html]"
}
]
}
}
},
"required": []
}

Просмотреть файл

@ -0,0 +1,160 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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,
* 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 {getSystemPath, normalize, strings} from '@angular-devkit/core';
import {
SchematicContext,
Tree,
apply,
applyTemplates,
chain,
filter,
mergeWith,
move,
url,
} from '@angular-devkit/schematics';
import {relative, resolve} from 'path';
import {SchematicsOptions, TestingFramework} from './types.js';
export interface FilesOptions {
projects: any;
options: SchematicsOptions;
applyPath: string;
relativeToWorkspacePath: string;
movePath?: string;
filterPredicate?: (path: string) => boolean;
}
const PUPPETEER_CONFIG_TEMPLATE = '.puppeteerrc.cjs.template';
export function addFiles(
tree: Tree,
context: SchematicContext,
{
projects,
options,
applyPath,
movePath,
relativeToWorkspacePath,
filterPredicate,
}: FilesOptions
): any {
return chain(
Object.keys(projects).map(name => {
const project = projects[name];
const projectPath = resolve(getSystemPath(normalize(project.root)));
const workspacePath = resolve(getSystemPath(normalize('')));
const relativeToWorkspace = relative(
`${projectPath}${relativeToWorkspacePath}`,
workspacePath
);
const baseUrl = getProjectBaseUrl(project);
return mergeWith(
apply(url(applyPath), [
filter(
filterPredicate ??
(() => {
return true;
})
),
move(movePath ? `${project.root}${movePath}` : project.root),
applyTemplates({
...options,
...strings,
root: project.root ? `${project.root}/` : project.root,
baseUrl,
project: name,
relativeToWorkspace,
}),
])
);
})
)(tree, context);
}
function getProjectBaseUrl(project: any): string {
let options = {protocol: 'http', port: 4200, host: 'localhost'};
if (project.architect?.serve?.options) {
const projectOptions = project.architect?.serve?.options;
options = {...options, ...projectOptions};
options.protocol = projectOptions.ssl ? 'https' : 'http';
}
return `${options.protocol}://${options.host}:${options.port}`;
}
export function addBaseFiles(
tree: Tree,
context: SchematicContext,
filesOptions: Omit<FilesOptions, 'applyPath' | 'relativeToWorkspacePath'>
): any {
const options: FilesOptions = {
...filesOptions,
applyPath: './files/base',
relativeToWorkspacePath: `/`,
filterPredicate: path => {
return path.includes(PUPPETEER_CONFIG_TEMPLATE) &&
!filesOptions.options.exportConfig
? false
: true;
},
};
return addFiles(tree, context, options);
}
export function addFrameworkFiles(
tree: Tree,
context: SchematicContext,
filesOptions: Omit<FilesOptions, 'applyPath' | 'relativeToWorkspacePath'>
): any {
const testingFramework = filesOptions.options.testingFramework;
const options: FilesOptions = {
...filesOptions,
applyPath: `./files/${testingFramework}`,
relativeToWorkspacePath: `/`,
};
return addFiles(tree, context, options);
}
export function getScriptFromOptions(options: SchematicsOptions): string[][] {
switch (options.testingFramework) {
case TestingFramework.Jasmine:
return [[`jasmine`, '--config=./e2e/support/jasmine.json']];
case TestingFramework.Jest:
return [[`jest`, '-c', 'e2e/jest.config.js']];
case TestingFramework.Mocha:
return [[`mocha`, '--config=./e2e/.mocharc.js']];
case TestingFramework.Node:
return [
[`tsc`, '-p', 'e2e/tsconfig.json'],
['node', '--test', 'e2e/'],
];
}
}
export function getNgCommandName(options: SchematicsOptions): string {
if (options.isDefaultTester) {
return 'e2e';
}
return 'puppeteer';
}

Просмотреть файл

@ -0,0 +1,38 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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,
* 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 {SchematicsException, Tree} from '@angular-devkit/schematics';
export function getJsonFileAsObject(
tree: Tree,
path: string
): Record<string, any> {
try {
const buffer = tree.read(path) as Buffer;
const content = buffer.toString();
return JSON.parse(content);
} catch {
throw new SchematicsException(`Unable to retrieve file at ${path}.`);
}
}
export function getObjectAsJson(object: Record<string, any>): string {
return JSON.stringify(object, null, 2);
}
export function getAngularConfig(tree: Tree): Record<string, any> {
return getJsonFileAsObject(tree, './angular.json');
}

Просмотреть файл

@ -0,0 +1,202 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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,
* 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 {Tree} from '@angular-devkit/schematics';
import {get} from 'https';
import {SchematicsOptions, TestingFramework} from './types.js';
import {
getAngularConfig,
getJsonFileAsObject,
getObjectAsJson,
} from './json.js';
import {getNgCommandName, getScriptFromOptions} from './files.js';
export interface NodePackage {
name: string;
version: string;
}
export interface NodeScripts {
name: string;
script: string;
}
export enum DependencyType {
Default = 'dependencies',
Dev = 'devDependencies',
Peer = 'peerDependencies',
Optional = 'optionalDependencies',
}
export function getPackageLatestNpmVersion(name: string): Promise<NodePackage> {
return new Promise(resolve => {
let version = 'latest';
return get(`https://registry.npmjs.org/${name}`, res => {
let data = '';
res.on('data', (chunk: any) => {
data += chunk;
});
res.on('end', () => {
try {
const response = JSON.parse(data);
version = response?.['dist-tags']?.latest ?? version;
} catch {
} finally {
resolve({
name,
version,
});
}
});
}).on('error', () => {
resolve({
name,
version,
});
});
});
}
function updateJsonValues(
json: Record<string, any>,
target: string,
updates: Array<{name: string; value: any}>,
overwrite = false
) {
updates.forEach(({name, value}) => {
if (!json[target][name] || overwrite) {
json[target] = {
...json[target],
[name]: value,
};
}
});
}
export function addPackageJsonDependencies(
tree: Tree,
packages: NodePackage[],
type: DependencyType,
overwrite?: boolean,
fileLocation = './package.json'
): Tree {
const packageJson = getJsonFileAsObject(tree, fileLocation);
updateJsonValues(
packageJson,
type,
packages.map(({name, version}) => {
return {name, value: version};
}),
overwrite
);
tree.overwrite(fileLocation, getObjectAsJson(packageJson));
return tree;
}
export function getDependenciesFromOptions(
options: SchematicsOptions
): string[] {
const dependencies = ['puppeteer'];
const babelPackages = [
'@babel/core',
'@babel/register',
'@babel/preset-env',
'@babel/preset-typescript',
];
switch (options.testingFramework) {
case TestingFramework.Jasmine:
dependencies.push('jasmine', ...babelPackages);
break;
case TestingFramework.Jest:
dependencies.push('jest', '@types/jest', 'ts-jest');
break;
case TestingFramework.Mocha:
dependencies.push('mocha', '@types/mocha', ...babelPackages);
break;
case TestingFramework.Node:
dependencies.push('@types/node');
break;
}
return dependencies;
}
export function addPackageJsonScripts(
tree: Tree,
scripts: NodeScripts[],
overwrite?: boolean,
fileLocation = './package.json'
): Tree {
const packageJson = getJsonFileAsObject(tree, fileLocation);
updateJsonValues(
packageJson,
'scripts',
scripts.map(({name, script}) => {
return {name, value: script};
}),
overwrite
);
tree.overwrite(fileLocation, getObjectAsJson(packageJson));
return tree;
}
export function updateAngularJsonScripts(
tree: Tree,
options: SchematicsOptions,
overwrite = true
): Tree {
const angularJson = getAngularConfig(tree);
const commands = getScriptFromOptions(options);
const name = getNgCommandName(options);
Object.keys(angularJson['projects']).forEach(project => {
const e2eScript = [
{
name,
value: {
builder: '@puppeteer/ng-schematics:puppeteer',
options: {
commands,
devServerTarget: `${project}:serve`,
},
configurations: {
production: {
devServerTarget: `${project}:serve:production`,
},
},
},
},
];
updateJsonValues(
angularJson['projects'][project],
'architect',
e2eScript,
overwrite
);
});
tree.overwrite('./angular.json', getObjectAsJson(angularJson));
return tree;
}

Просмотреть файл

@ -0,0 +1,28 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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,
* 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.
*/
export enum TestingFramework {
Jasmine = 'jasmine',
Jest = 'jest',
Mocha = 'mocha',
Node = 'node',
}
export interface SchematicsOptions {
isDefaultTester: boolean;
exportConfig: boolean;
testingFramework: TestingFramework;
}

Просмотреть файл

@ -0,0 +1,211 @@
import expect from 'expect';
import sinon from 'sinon';
import https from 'https';
import {join} from 'path';
import {
SchematicTestRunner,
UnitTestTree,
} from '@angular-devkit/schematics/testing/schematic-test-runner';
import {JsonObject} from '@angular-devkit/core';
const WORKSPACE_OPTIONS = {
name: 'workspace',
newProjectRoot: 'projects',
version: '14.0.0',
};
const APPLICATION_OPTIONS = {
name: 'sandbox',
};
function getProjectFile(file: string): string {
return `/${WORKSPACE_OPTIONS.newProjectRoot}/${APPLICATION_OPTIONS.name}/${file}`;
}
function getAngularJsonScripts(
tree: UnitTestTree,
isDefault = true
): {
builder: string;
configurations: Record<string, any>;
options: Record<string, any>;
} {
const angularJson = tree.readJson('angular.json') as any;
const e2eScript = isDefault ? 'e2e' : 'puppeteer';
return angularJson['projects']?.[APPLICATION_OPTIONS.name]?.['architect'][
e2eScript
];
}
function getPackageJson(tree: UnitTestTree): {
scripts: Record<string, string>;
devDependencies: string[];
} {
const packageJson = tree.readJson('package.json') as JsonObject;
return {
scripts: packageJson['scripts'] as any,
devDependencies: Object.keys(
packageJson['devDependencies'] as Record<string, string>
),
};
}
async function buildTestingTree(userOptions?: Record<string, any>) {
const runner = new SchematicTestRunner(
'schematics',
join(__dirname, '../../lib/schematics/collection.json')
);
const options = {
isDefaultTester: true,
exportConfig: false,
testingFramework: 'jasmine',
...userOptions,
};
let workingTree: UnitTestTree;
// Build workspace
workingTree = await runner
.runExternalSchematicAsync(
'@schematics/angular',
'workspace',
WORKSPACE_OPTIONS
)
.toPromise();
// Build dummy application
workingTree = await runner
.runExternalSchematicAsync(
'@schematics/angular',
'application',
APPLICATION_OPTIONS,
workingTree
)
.toPromise();
return await runner
.runSchematicAsync('ng-add', options, workingTree)
.toPromise();
}
describe('@puppeteer/ng-schematics: ng-add', () => {
// Stop outgoing Request for version fetching
before(() => {
const httpsGetStub = sinon.stub(https, 'get');
httpsGetStub.returns({
on: (_: any, callback: () => void) => {
callback();
},
} as any);
});
after(() => {
sinon.restore();
});
it('should create base files and update to "package.json"', async () => {
const tree = await buildTestingTree();
const {devDependencies, scripts} = getPackageJson(tree);
const {builder, configurations} = getAngularJsonScripts(tree);
expect(tree.files).toContain(getProjectFile('e2e/tsconfig.json'));
expect(tree.files).toContain(getProjectFile('e2e/tests/app.e2e.ts'));
expect(devDependencies).toContain('puppeteer');
expect(scripts['e2e']).toBe('ng e2e');
expect(builder).toBe('@puppeteer/ng-schematics:puppeteer');
expect(configurations).toEqual({
production: {
devServerTarget: 'sandbox:serve:production',
},
});
});
it('should update create proper "ng" command for non default tester', async () => {
const tree = await buildTestingTree({
isDefaultTester: false,
});
const {scripts} = getPackageJson(tree);
const {builder} = getAngularJsonScripts(tree, false);
expect(scripts['puppeteer']).toBe('ng run sandbox:puppeteer');
expect(builder).toBe('@puppeteer/ng-schematics:puppeteer');
});
it('should create Puppeteer config', async () => {
const {files} = await buildTestingTree({
exportConfig: true,
});
expect(files).toContain(getProjectFile('.puppeteerrc.cjs'));
});
it('should not create Puppeteer config', async () => {
const {files} = await buildTestingTree({
exportConfig: false,
});
expect(files).not.toContain(getProjectFile('.puppeteerrc.cjs'));
});
it('should create Jasmine files and update "package.json"', async () => {
const tree = await buildTestingTree({
testingFramework: 'jasmine',
});
const {devDependencies} = getPackageJson(tree);
const {options} = getAngularJsonScripts(tree);
expect(tree.files).toContain(getProjectFile('e2e/support/jasmine.json'));
expect(tree.files).toContain(getProjectFile('e2e/helpers/babel.js'));
expect(devDependencies).toContain('jasmine');
expect(devDependencies).toContain('@babel/core');
expect(devDependencies).toContain('@babel/register');
expect(devDependencies).toContain('@babel/preset-typescript');
expect(options['commands']).toEqual([
[`jasmine`, '--config=./e2e/support/jasmine.json'],
]);
});
it('should create Jest files and update "package.json"', async () => {
const tree = await buildTestingTree({
testingFramework: 'jest',
});
const {devDependencies} = getPackageJson(tree);
const {options} = getAngularJsonScripts(tree);
expect(tree.files).toContain(getProjectFile('e2e/jest.config.js'));
expect(devDependencies).toContain('jest');
expect(devDependencies).toContain('@types/jest');
expect(devDependencies).toContain('ts-jest');
expect(options['commands']).toEqual([[`jest`, '-c', 'e2e/jest.config.js']]);
});
it('should create Mocha files and update "package.json"', async () => {
const tree = await buildTestingTree({
testingFramework: 'mocha',
});
const {devDependencies} = getPackageJson(tree);
const {options} = getAngularJsonScripts(tree);
expect(tree.files).toContain(getProjectFile('e2e/.mocharc.js'));
expect(tree.files).toContain(getProjectFile('e2e/babel.js'));
expect(devDependencies).toContain('mocha');
expect(devDependencies).toContain('@types/mocha');
expect(devDependencies).toContain('@babel/core');
expect(devDependencies).toContain('@babel/register');
expect(devDependencies).toContain('@babel/preset-typescript');
expect(options['commands']).toEqual([
[`mocha`, '--config=./e2e/.mocharc.js'],
]);
});
it('should create Node files"', async () => {
const tree = await buildTestingTree({
testingFramework: 'node',
});
const {options} = getAngularJsonScripts(tree);
expect(tree.files).toContain(getProjectFile('e2e/.gitignore'));
expect(options['commands']).toEqual([
[`tsc`, '-p', 'e2e/tsconfig.json'],
['node', '--test', 'e2e/'],
]);
});
});

Просмотреть файл

@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "tsconfig",
"lib": ["ES2018"],
"module": "CommonJS",
"noEmitOnError": true,
"rootDir": "src/",
"outDir": "lib/",
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"sourceMap": true,
"types": ["node"],
"target": "ES6"
},
"include": ["src/**/*"],
"exclude": ["src/**/files/**/*"],
"references": [{"path": "./tsconfig.spec.json"}]
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "test/src/",
"outDir": "test/build/",
"types": ["node", "mocha"]
},
"include": ["test/src/**/*"],
"exclude": ["test/build/**/*"]
}

1
remote/test/puppeteer/packages/puppeteer-core/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
README.md

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,6 +1,6 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"mainEntryPointFilePath": "<projectFolder>/lib/esm/puppeteer/types.d.ts",
"mainEntryPointFilePath": "<projectFolder>/lib/esm/puppeteer/puppeteer-core.d.ts",
"bundledPackages": [],
"apiReport": {
@ -9,7 +9,7 @@
"docModel": {
"enabled": true,
"apiJsonFilePath": "<projectFolder>/docs/<unscopedPackageName>.api.json"
"apiJsonFilePath": "<projectFolder>/../../docs/<unscopedPackageName>.api.json"
},
"dtsRollup": {

Просмотреть файл

@ -0,0 +1,169 @@
{
"name": "puppeteer-core",
"version": "19.6.0",
"description": "A high-level API to control headless Chrome over the DevTools Protocol",
"keywords": [
"puppeteer",
"chrome",
"headless",
"automation"
],
"type": "commonjs",
"main": "./lib/cjs/puppeteer/puppeteer-core.js",
"types": "./lib/types.d.ts",
"exports": {
".": {
"types": "./lib/types.d.ts",
"import": "./lib/esm/puppeteer/puppeteer-core.js",
"require": "./lib/cjs/puppeteer/puppeteer-core.js"
},
"./internal/*": {
"import": "./lib/esm/puppeteer/*",
"require": "./lib/cjs/puppeteer/*"
},
"./*": {
"import": "./*",
"require": "./*"
}
},
"repository": {
"type": "git",
"url": "https://github.com/puppeteer/puppeteer/tree/main/packages/puppeteer-core"
},
"engines": {
"node": ">=14.1.0"
},
"scripts": {
"build:third_party": "wireit",
"build:tsc": "wireit",
"build:types": "wireit",
"build": "wireit",
"check": "tsx tools/ensure-correct-devtools-protocol-package",
"format:types": "wireit",
"generate:package-json": "wireit",
"generate:sources": "wireit",
"prepack": "wireit",
"clean": "tsc -b --clean && rimraf lib src/generated",
"clean:third_party": "wireit"
},
"wireit": {
"prepack": {
"command": "cp ../../README.md README.md",
"files": [
"../../README.md"
],
"output": [
"README.md"
]
},
"build": {
"dependencies": [
"build:third_party",
"format:types",
"generate:package-json"
]
},
"generate:sources": {
"command": "tsx tools/generate_sources.ts",
"files": [
"tools/generate_sources.ts",
"src/templates/**"
],
"output": [
"src/generated/**"
]
},
"clean:third_party": {
"command": "rimraf lib/esm/third_party lib/cjs/third_party"
},
"build:third_party": {
"command": "rollup --config rollup.third_party.config.js",
"dependencies": [
"build:tsc"
],
"clean": false,
"files": [
"lib/esm/third_party/**",
"lib/cjs/third_party/**"
],
"output": [
"lib/esm/third_party/**",
"lib/cjs/third_party/**"
]
},
"generate:package-json": {
"command": "tsx ../../tools/generate_module_package_json.ts lib/esm/package.json",
"clean": "if-file-deleted",
"dependencies": [
"build:tsc"
],
"output": [
"lib/esm/package.json"
]
},
"build:types": {
"command": "api-extractor run --local",
"dependencies": [
"build:tsc"
],
"files": [
"tsconfig.json",
"api-extractor.json",
"lib/esm/puppeteer/types.d.ts"
],
"output": [
"lib/types.d.ts"
]
},
"format:types": {
"command": "eslint --cache-location .eslintcache --cache --ext=ts --no-ignore --no-eslintrc -c=../../.eslintrc.types.cjs --fix lib/types.d.ts",
"dependencies": [
"build:types"
],
"clean": false,
"files": [
"lib/types.d.ts",
"../../.eslintrc.types.cjs"
],
"output": [
"lib/types.d.ts"
]
},
"build:tsc": {
"command": "tsc -b",
"clean": "if-file-deleted",
"dependencies": [
"clean:third_party",
"generate:sources"
],
"files": [
"src/**",
"compat/**",
"third_party/**",
"**/tsconfig.*.json"
],
"output": [
"lib/esm/**",
"lib/cjs/**"
]
}
},
"files": [
"lib",
"!*.tsbuildinfo"
],
"author": "The Chromium Authors",
"license": "Apache-2.0",
"dependencies": {
"cross-fetch": "3.1.5",
"debug": "4.3.4",
"devtools-protocol": "0.0.1082910",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"proxy-from-env": "1.1.0",
"rimraf": "3.0.2",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3",
"ws": "8.11.0"
}
}

Просмотреть файл

@ -0,0 +1,41 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import glob from 'glob';
import dts from 'rollup-plugin-dts';
import {nodeResolve} from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
export default ['cjs', 'esm'].flatMap(outputType => {
const configs = [];
// Note we don't use path.join here. We cannot since `glob` does not support
// the backslash path separator.
const thirdPartyPath = `lib/${outputType}/third_party`;
for (const jsFile of glob.sync(`${thirdPartyPath}/**/*.js`)) {
configs.push({
input: jsFile,
output: {file: jsFile, format: outputType},
plugins: [commonjs(), nodeResolve()],
});
}
for (const typesFile of glob.sync(`${thirdPartyPath}/**/*.d.ts`)) {
configs.push({
input: typesFile,
output: {file: typesFile, format: outputType},
plugins: [dts({respectExternal: true})],
});
}
return configs;
});

Просмотреть файл

@ -19,8 +19,9 @@
import {ChildProcess} from 'child_process';
import {Protocol} from 'devtools-protocol';
import {EventEmitter} from '../common/EventEmitter.js';
import type {Page} from '../common/Page.js'; // TODO: move to ./api
import type {Page} from './Page.js'; // TODO: move to ./api
import type {Target} from '../common/Target.js'; // TODO: move to ./api
import type {BrowserContext} from './BrowserContext.js';
/**
* BrowserContext options.
@ -181,7 +182,7 @@ export const enum BrowserEmittedEvents {
* An example of using a {@link Browser} to create a {@link Page}:
*
* ```ts
* const puppeteer = require('puppeteer');
* import puppeteer from 'puppeteer';
*
* (async () => {
* const browser = await puppeteer.launch();
@ -195,7 +196,7 @@ export const enum BrowserEmittedEvents {
* An example of disconnecting from and reconnecting to a {@link Browser}:
*
* ```ts
* const puppeteer = require('puppeteer');
* import puppeteer from 'puppeteer';
*
* (async () => {
* const browser = await puppeteer.launch();
@ -466,163 +467,3 @@ export const enum BrowserContextEmittedEvents {
*/
TargetDestroyed = 'targetdestroyed',
}
/**
* 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 {@link Browser.newPage | Browser.newPage} creates a page
* in the default browser context.
*
* @remarks
*
* The Browser class extends from Puppeteer's {@link EventEmitter} class and
* will emit various events which are documented in the
* {@link BrowserContextEmittedEvents} enum.
*
* 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
* {@link Browser.createIncognitoBrowserContext | Browser.createIncognitoBrowserContext}
* method. "Incognito" browser contexts don't write any browsing data to disk.
*
* @example
*
* ```ts
* // 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();
* ```
*
* @public
*/
export class BrowserContext extends EventEmitter {
/**
* @internal
*/
constructor() {
super();
}
/**
* An array of all active targets inside the browser context.
*/
targets(): Target[] {
throw new Error('Not implemented');
}
/**
* This searches for a target in this specific browser context.
*
* @example
* An example of finding a target for a page opened via `window.open`:
*
* ```ts
* await page.evaluate(() => window.open('https://www.example.com/'));
* const newWindowTarget = await browserContext.waitForTarget(
* target => target.url() === 'https://www.example.com/'
* );
* ```
*
* @param predicate - A function to be run for every target
* @param options - An object of options. Accepts a timout,
* which is the maximum wait time in milliseconds.
* Pass `0` to disable the timeout. Defaults to 30 seconds.
* @returns Promise which resolves to the first target found
* that matches the `predicate` function.
*/
waitForTarget(
predicate: (x: Target) => boolean | Promise<boolean>,
options?: {timeout?: number}
): Promise<Target>;
waitForTarget(): Promise<Target> {
throw new Error('Not implemented');
}
/**
* An array of all pages inside the browser context.
*
* @returns 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 {@link Target.page | the target page}.
*/
pages(): Promise<Page[]> {
throw new Error('Not implemented');
}
/**
* Returns whether BrowserContext is incognito.
* The default browser context is the only non-incognito browser context.
*
* @remarks
* The default browser context cannot be closed.
*/
isIncognito(): boolean {
throw new Error('Not implemented');
}
/**
* @example
*
* ```ts
* const context = browser.defaultBrowserContext();
* await context.overridePermissions('https://html5demos.com', [
* 'geolocation',
* ]);
* ```
*
* @param origin - The origin to grant permissions to, e.g. "https://example.com".
* @param permissions - An array of permissions to grant.
* All permissions that are not listed here will be automatically denied.
*/
overridePermissions(origin: string, permissions: Permission[]): Promise<void>;
overridePermissions(): Promise<void> {
throw new Error('Not implemented');
}
/**
* Clears all permission overrides for the browser context.
*
* @example
*
* ```ts
* const context = browser.defaultBrowserContext();
* context.overridePermissions('https://example.com', ['clipboard-read']);
* // do stuff ..
* context.clearPermissionOverrides();
* ```
*/
clearPermissionOverrides(): Promise<void> {
throw new Error('Not implemented');
}
/**
* Creates a new page in the browser context.
*/
newPage(): Promise<Page> {
throw new Error('Not implemented');
}
/**
* The browser this browser context belongs to.
*/
browser(): Browser {
throw new Error('Not implemented');
}
/**
* Closes the browser context. All the targets that belong to the browser context
* will be closed.
*
* @remarks
* Only incognito browser contexts can be closed.
*/
close(): Promise<void> {
throw new Error('Not implemented');
}
}

Просмотреть файл

@ -0,0 +1,185 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {EventEmitter} from '../common/EventEmitter.js';
import {Page} from './Page.js';
import {Target} from '../common/Target.js';
import type {Permission, Browser} from './Browser.js';
/**
* 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 {@link Browser.newPage | Browser.newPage} creates a page
* in the default browser context.
*
* @remarks
*
* The Browser class extends from Puppeteer's {@link EventEmitter} class and
* will emit various events which are documented in the
* {@link BrowserContextEmittedEvents} enum.
*
* 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
* {@link Browser.createIncognitoBrowserContext | Browser.createIncognitoBrowserContext}
* method. "Incognito" browser contexts don't write any browsing data to disk.
*
* @example
*
* ```ts
* // 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();
* ```
*
* @public
*/
export class BrowserContext extends EventEmitter {
/**
* @internal
*/
constructor() {
super();
}
/**
* An array of all active targets inside the browser context.
*/
targets(): Target[] {
throw new Error('Not implemented');
}
/**
* This searches for a target in this specific browser context.
*
* @example
* An example of finding a target for a page opened via `window.open`:
*
* ```ts
* await page.evaluate(() => window.open('https://www.example.com/'));
* const newWindowTarget = await browserContext.waitForTarget(
* target => target.url() === 'https://www.example.com/'
* );
* ```
*
* @param predicate - A function to be run for every target
* @param options - An object of options. Accepts a timeout,
* which is the maximum wait time in milliseconds.
* Pass `0` to disable the timeout. Defaults to 30 seconds.
* @returns Promise which resolves to the first target found
* that matches the `predicate` function.
*/
waitForTarget(
predicate: (x: Target) => boolean | Promise<boolean>,
options?: {timeout?: number}
): Promise<Target>;
waitForTarget(): Promise<Target> {
throw new Error('Not implemented');
}
/**
* An array of all pages inside the browser context.
*
* @returns 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 {@link Target.page | the target page}.
*/
pages(): Promise<Page[]> {
throw new Error('Not implemented');
}
/**
* Returns whether BrowserContext is incognito.
* The default browser context is the only non-incognito browser context.
*
* @remarks
* The default browser context cannot be closed.
*/
isIncognito(): boolean {
throw new Error('Not implemented');
}
/**
* @example
*
* ```ts
* const context = browser.defaultBrowserContext();
* await context.overridePermissions('https://html5demos.com', [
* 'geolocation',
* ]);
* ```
*
* @param origin - The origin to grant permissions to, e.g. "https://example.com".
* @param permissions - An array of permissions to grant.
* All permissions that are not listed here will be automatically denied.
*/
overridePermissions(origin: string, permissions: Permission[]): Promise<void>;
overridePermissions(): Promise<void> {
throw new Error('Not implemented');
}
/**
* Clears all permission overrides for the browser context.
*
* @example
*
* ```ts
* const context = browser.defaultBrowserContext();
* context.overridePermissions('https://example.com', ['clipboard-read']);
* // do stuff ..
* context.clearPermissionOverrides();
* ```
*/
clearPermissionOverrides(): Promise<void> {
throw new Error('Not implemented');
}
/**
* Creates a new page in the browser context.
*/
newPage(): Promise<Page> {
throw new Error('Not implemented');
}
/**
* The browser this browser context belongs to.
*/
browser(): Browser {
throw new Error('Not implemented');
}
/**
* Closes the browser context. All the targets that belong to the browser context
* will be closed.
*
* @remarks
* Only incognito browser contexts can be closed.
*/
close(): Promise<void> {
throw new Error('Not implemented');
}
get id(): string | undefined {
return undefined;
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

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

Просмотреть файл

@ -564,7 +564,10 @@ class AXNode {
}
for (const node of nodeById.values()) {
for (const childId of node.payload.childIds || []) {
node.children.push(nodeById.get(childId)!);
const child = nodeById.get(childId);
if (child) {
node.children.push(child);
}
}
}
return nodeById.values().next().value;

Просмотреть файл

@ -15,12 +15,14 @@
*/
import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {ElementHandle} from './ElementHandle.js';
import {Frame} from './Frame.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorld.js';
import {PuppeteerQueryHandler} from './QueryHandler.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import type {ElementHandle} from './ElementHandle.js';
import type {PuppeteerQueryHandler} from './QueryHandler.js';
import type {Frame} from './Frame.js';
async function queryAXTree(
client: CDPSession,
@ -115,7 +117,7 @@ const waitFor: PuppeteerQueryHandler['waitFor'] = async (
) => {
let frame: Frame;
let element: ElementHandle<Node> | undefined;
if (elementOrFrame instanceof Frame) {
if ('isOOPFrame' in elementOrFrame) {
frame = elementOrFrame;
} else {
frame = elementOrFrame.frame;
@ -146,16 +148,18 @@ const waitFor: PuppeteerQueryHandler['waitFor'] = async (
element,
selector,
options,
new Set([ariaQuerySelector])
new Map([['ariaQuerySelector', ariaQuerySelector]])
);
if (element) {
await element.dispose();
}
if (!(result instanceof ElementHandle)) {
const handle = result?.asElement();
if (!handle) {
await result?.dispose();
return null;
}
return result.frame.worlds[MAIN_WORLD].transferHandle(result);
return handle.frame.worlds[MAIN_WORLD].transferHandle(handle);
};
const queryAll: PuppeteerQueryHandler['queryAll'] = async (

Просмотреть файл

@ -19,7 +19,7 @@ import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js';
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js';
import {waitWithTimeout} from './util.js';
import {Page} from './Page.js';
import {Page} from '../api/Page.js';
import {Viewport} from './PuppeteerViewport.js';
import {Target} from './Target.js';
import {TaskQueue} from './TaskQueue.js';
@ -28,7 +28,6 @@ import {ChromeTargetManager} from './ChromeTargetManager.js';
import {FirefoxTargetManager} from './FirefoxTargetManager.js';
import {
Browser as BrowserBase,
BrowserContext,
BrowserCloseCallback,
TargetFilterCallback,
IsPageTargetCallback,
@ -39,6 +38,7 @@ import {
WaitForTargetOptions,
Permission,
} from '../api/Browser.js';
import {BrowserContext} from '../api/BrowserContext.js';
/**
* @internal
@ -596,6 +596,10 @@ export class CDPBrowserContext extends BrowserContext {
this.#id = contextId;
}
override get id(): string | undefined {
return this.#id;
}
/**
* An array of all active targets inside the browser context.
*/
@ -619,7 +623,7 @@ export class CDPBrowserContext extends BrowserContext {
* ```
*
* @param predicate - A function to be run for every target
* @param options - An object of options. Accepts a timout,
* @param options - An object of options. Accepts a timeout,
* which is the maximum wait time in milliseconds.
* Pass `0` to disable the timeout. Defaults to 30 seconds.
* @returns Promise which resolves to the first target found

Просмотреть файл

@ -24,6 +24,8 @@ import {Connection} from './Connection.js';
import {ConnectionTransport} from './ConnectionTransport.js';
import {getFetch} from './fetch.js';
import {Viewport} from './PuppeteerViewport.js';
import type {ConnectOptions} from './Puppeteer.js';
/**
* Generic browser options that can be passed when launching any browser or when
* connecting to an existing browser instance.
@ -61,7 +63,7 @@ export interface BrowserConnectOptions {
const getWebSocketTransportClass = async () => {
return isNode
? (await import('../node/NodeWebSocketTransport.js')).NodeWebSocketTransport
? (await import('./NodeWebSocketTransport.js')).NodeWebSocketTransport
: (await import('./BrowserWebSocketTransport.js'))
.BrowserWebSocketTransport;
};
@ -73,11 +75,7 @@ const getWebSocketTransportClass = async () => {
* @internal
*/
export async function _connectToCDPBrowser(
options: BrowserConnectOptions & {
browserWSEndpoint?: string;
browserURL?: string;
transport?: ConnectionTransport;
}
options: BrowserConnectOptions & ConnectOptions
): Promise<CDPBrowser> {
const {
browserWSEndpoint,
@ -85,6 +83,7 @@ export async function _connectToCDPBrowser(
ignoreHTTPSErrors = false,
defaultViewport = {width: 800, height: 600},
transport,
headers = {},
slowMo = 0,
targetFilter,
_isPageTarget: isPageTarget,
@ -102,7 +101,7 @@ export async function _connectToCDPBrowser(
} else if (browserWSEndpoint) {
const WebSocketClass = await getWebSocketTransportClass();
const connectionTransport: ConnectionTransport =
await WebSocketClass.create(browserWSEndpoint);
await WebSocketClass.create(browserWSEndpoint, headers);
connection = new Connection(browserWSEndpoint, connectionTransport, slowMo);
} else if (browserURL) {
const connectionURL = await getWSEndpoint(browserURL);

Просмотреть файл

@ -14,7 +14,7 @@
* limitations under the License.
*/
import Protocol from 'devtools-protocol';
import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js';
import {CDPSession, Connection} from './Connection.js';
import {EventEmitter} from './EventEmitter.js';
@ -231,13 +231,6 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
const target = this.#targetFactory(event.targetInfo, undefined);
this.#attachedTargetsByTargetId.set(event.targetInfo.targetId, target);
}
if (event.targetInfo.type === 'shared_worker') {
// Special case (https://crbug.com/1338156): currently, shared_workers
// don't get auto-attached. This should be removed once the auto-attach
// works.
await this.#connection._createSession(event.targetInfo, true);
}
};
#onTargetDestroyed = (event: Protocol.Target.TargetDestroyedEvent) => {
@ -360,7 +353,7 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
// present in #attachedTargetsBySessionId.
assert(this.#attachedTargetsBySessionId.has(parentSession.id()));
}
await interceptor(
interceptor(
target,
parentSession instanceof Connection
? null

Просмотреть файл

@ -0,0 +1,135 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Product} from './Product.js';
/**
* Defines experiment options for Puppeteer.
*
* See individual properties for more information.
*
* @public
*/
export interface ExperimentsConfiguration {
/**
* Require Puppeteer to download Chromium for Apple M1.
*
* On Apple M1 devices Puppeteer by default downloads the version for
* Intel's processor which runs via Rosetta. It works without any problems,
* however, with this option, you should get more efficient resource usage
* (CPU and RAM) that could lead to a faster execution time.
*
* Can be overridden by `PUPPETEER_EXPERIMENTAL_CHROMIUM_MAC_ARM`.
*
* @defaultValue `false`
*/
macArmChromiumEnabled?: boolean;
}
/**
* Defines options to configure Puppeteer's behavior during installation and
* runtime.
*
* See individual properties for more information.
*
* @public
*/
export interface Configuration {
/**
* Specifies a certain version of the browser you'd like Puppeteer to use.
*
* Can be overridden by `PUPPETEER_BROWSER_REVISION`.
*
* See {@link PuppeteerNode.launch | puppeteer.launch} on how executable path
* is inferred.
*
* @defaultValue A compatible-revision of the browser.
*/
browserRevision?: string;
/**
* Defines the directory to be used by Puppeteer for caching.
*
* Can be overridden by `PUPPETEER_CACHE_DIR`.
*
* @defaultValue `path.join(os.homedir(), '.cache', 'puppeteer')`
*/
cacheDirectory?: string;
/**
* Specifies the URL prefix that is used to download Chromium.
*
* Can be overridden by `PUPPETEER_DOWNLOAD_HOST`.
*
* @remarks
* This must include the protocol and may even need a path prefix.
*
* @defaultValue Either https://storage.googleapis.com or
* https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central,
* depending on the product.
*/
downloadHost?: string;
/**
* Specifies the path for the downloads folder.
*
* Can be overridden by `PUPPETEER_DOWNLOAD_PATH`.
*
* @defaultValue `<cache>/<product>` where `<cache>` is Puppeteer's cache
* directory and `<product>` is the name of the browser.
*/
downloadPath?: string;
/**
* Specifies an executable path to be used in
* {@link PuppeteerNode.launch | puppeteer.launch}.
*
* Can be overridden by `PUPPETEER_EXECUTABLE_PATH`.
*
* @defaultValue Auto-computed.
*/
executablePath?: string;
/**
* Specifies which browser you'd like Puppeteer to use.
*
* Can be overridden by `PUPPETEER_PRODUCT`.
*
* @defaultValue `'chrome'`
*/
defaultProduct?: Product;
/**
* Defines the directory to be used by Puppeteer for creating temporary files.
*
* Can be overridden by `PUPPETEER_TMP_DIR`.
*
* @defaultValue `os.tmpdir()`
*/
temporaryDirectory?: string;
/**
* Tells Puppeteer to not download during installation.
*
* Can be overridden by `PUPPETEER_SKIP_DOWNLOAD`.
*/
skipDownload?: boolean;
/**
* Tells Puppeteer to log at the given level.
*
* At the moment, any option silences logging.
*
* @defaultValue `undefined`
*/
logLevel?: 'silent' | 'error' | 'warn';
/**
* Defines experimental options for Puppeteer.
*/
experiments?: ExperimentsConfiguration;
}

Просмотреть файл

@ -74,6 +74,12 @@ export interface JSCoverageOptions {
* Whether the result includes raw V8 script coverage entries.
*/
includeRawScriptCoverage?: boolean;
/**
* Whether to collect coverage information at the block level.
* If true, coverage will be collected at the block level (this is the default).
* If false, coverage will be collected at the function level.
*/
useBlockCoverage?: boolean;
}
/**
@ -135,7 +141,8 @@ export class Coverage {
/**
* @param options - Set of configurable options for coverage defaults to
* `resetOnNavigation : true, reportAnonymousScripts : false`
* `resetOnNavigation : true, reportAnonymousScripts : false,`
* `includeRawScriptCoverage : false, useBlockCoverage : true`
* @returns Promise that resolves when coverage is started.
*
* @remarks
@ -204,6 +211,7 @@ export class JSCoverage {
resetOnNavigation?: boolean;
reportAnonymousScripts?: boolean;
includeRawScriptCoverage?: boolean;
useBlockCoverage?: boolean;
} = {}
): Promise<void> {
assert(!this.#enabled, 'JSCoverage is already enabled');
@ -211,6 +219,7 @@ export class JSCoverage {
resetOnNavigation = true,
reportAnonymousScripts = false,
includeRawScriptCoverage = false,
useBlockCoverage = true,
} = options;
this.#resetOnNavigation = resetOnNavigation;
this.#reportAnonymousScripts = reportAnonymousScripts;
@ -234,7 +243,7 @@ export class JSCoverage {
this.#client.send('Profiler.enable'),
this.#client.send('Profiler.startPreciseCoverage', {
callCount: this.#includeRawScriptCoverage,
detailed: true,
detailed: useBlockCoverage,
}),
this.#client.send('Debugger.enable'),
this.#client.send('Debugger.setSkipAllPauses', {skip: true}),

Просмотреть файл

@ -76,6 +76,9 @@ export async function importDebug(): Promise<typeof import('debug')> {
export const debug = (prefix: string): ((...args: unknown[]) => void) => {
if (isNode) {
return async (...logArgs: unknown[]) => {
if (captureLogs) {
capturedLogs.push(prefix + logArgs);
}
(await importDebug())(prefix)(logArgs);
};
}
@ -107,3 +110,27 @@ export const debug = (prefix: string): ((...args: unknown[]) => void) => {
console.log(`${prefix}:`, ...logArgs);
};
};
/**
* @internal
*/
let capturedLogs: string[] = [];
/**
* @internal
*/
let captureLogs = false;
/**
* @internal
*/
export function setLogCapture(value: boolean): void {
capturedLogs = [];
captureLogs = value;
}
/**
* @internal
*/
export function getCapturedLogs(): string[] {
return capturedLogs;
}

Просмотреть файл

@ -14,23 +14,17 @@
* limitations under the License.
*/
import {Viewport} from './PuppeteerViewport.js';
/**
* @public
*/
export interface Device {
name: string;
userAgent: string;
viewport: {
width: number;
height: number;
deviceScaleFactor: number;
isMobile: boolean;
hasTouch: boolean;
isLandscape: boolean;
};
viewport: Viewport;
}
const deviceArray: Device[] = [
const knownDevices = [
{
name: 'Blackberry PlayBook',
userAgent:
@ -1526,23 +1520,25 @@ const deviceArray: Device[] = [
isLandscape: true,
},
},
];
] as const;
const knownDevicesByName = {} as Record<
typeof knownDevices[number]['name'],
Device
>;
for (const device of knownDevices) {
knownDevicesByName[device.name] = device;
}
/**
* @public
*/
export type DevicesMap = {
[name: string]: Device;
};
/**
* A list of devices to be used with `page.emulate(options)`. Actual list of devices can be found in {@link https://github.com/puppeteer/puppeteer/blob/main/src/common/DeviceDescriptors.ts | src/common/DeviceDescriptors.ts}.
* A list of devices to be used with {@link Page.emulate}.
*
* @example
*
* ```ts
* const puppeteer = require('puppeteer');
* const iPhone = puppeteer.devices['iPhone 6'];
* import {KnownDevices} from 'puppeteer';
* const iPhone = KnownDevices['iPhone 6'];
*
* (async () => {
* const browser = await puppeteer.launch();
@ -1556,10 +1552,11 @@ export type DevicesMap = {
*
* @public
*/
const devices: DevicesMap = {};
export const KnownDevices = Object.freeze(knownDevicesByName);
for (const device of deviceArray) {
devices[device.name] = device;
}
export {devices};
/**
* @deprecated Import {@link KnownDevices}
*
* @public
*/
export const devices = KnownDevices;

Просмотреть файл

@ -26,7 +26,7 @@ import {Protocol} from 'devtools-protocol';
* @example
*
* ```ts
* const puppeteer = require('puppeteer');
* import puppeteer from 'puppeteer';
*
* (async () => {
* const browser = await puppeteer.launch();

Просмотреть файл

@ -29,11 +29,12 @@ import {
Point,
PressOptions,
} from './JSHandle.js';
import {Page, ScreenshotOptions} from './Page.js';
import {Page, ScreenshotOptions} from '../api/Page.js';
import {getQueryHandlerAndSelector} from './QueryHandler.js';
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
import {ElementFor, EvaluateFunc, HandleFor, NodeFor} from './types.js';
import {KeyInput} from './USKeyboardLayout.js';
import {debugError, isString} from './util.js';
import {CDPPage} from './Page.js';
const applyOffsetsToQuad = (
quad: Point[],
@ -52,7 +53,7 @@ const applyOffsetsToQuad = (
* ElementHandles can be created with the {@link Page.$} method.
*
* ```ts
* const puppeteer = require('puppeteer');
* import puppeteer from 'puppeteer';
*
* (async () => {
* const browser = await puppeteer.launch();
@ -236,8 +237,8 @@ export class ElementHandle<
Selector extends string,
Params extends unknown[],
Func extends EvaluateFunc<
[Array<NodeFor<Selector>>, ...Params]
> = EvaluateFunc<[Array<NodeFor<Selector>>, ...Params]>
[HandleFor<Array<NodeFor<Selector>>>, ...Params]
> = EvaluateFunc<[HandleFor<Array<NodeFor<Selector>>>, ...Params]>
>(
selector: Selector,
pageFunction: Func | string,
@ -253,9 +254,9 @@ export class ElementHandle<
this,
updatedSelector
)) as Array<HandleFor<NodeFor<Selector>>>;
const elements = await this.evaluateHandle((_, ...elements) => {
const elements = (await this.evaluateHandle((_, ...elements) => {
return elements;
}, ...handles);
}, ...handles)) as JSHandle<Array<NodeFor<Selector>>>;
const [result] = await Promise.all([
elements.evaluate(pageFunction, ...args),
...handles.map(handle => {
@ -269,7 +270,12 @@ export class ElementHandle<
/**
* @deprecated Use {@link ElementHandle.$$} with the `xpath` prefix.
*
* Example: `await elementHandle.$$('xpath/' + xpathExpression)`
*
* The method evaluates the XPath expression relative to the elementHandle.
* If `xpath` starts with `//` instead of `.//`, the dot will be appended
* automatically.
*
* If there are no such elements, the method will resolve to an empty array.
* @param expression - Expression to {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate | evaluate}
*/
@ -290,7 +296,7 @@ export class ElementHandle<
* @example
*
* ```ts
* const puppeteer = require('puppeteer');
* import puppeteer from 'puppeteer';
*
* (async () => {
* const browser = await puppeteer.launch();
@ -335,6 +341,10 @@ export class ElementHandle<
* @deprecated Use {@link ElementHandle.waitForSelector} with the `xpath`
* prefix.
*
* Example: `await elementHandle.waitForSelector('xpath/' + xpathExpression)`
*
* The method evaluates the XPath expression relative to the elementHandle.
*
* Wait for the `xpath` within the element. If at the moment of calling the
* method the `xpath` already exists, the method will return immediately. If
* the `xpath` doesn't appear after the `timeout` milliseconds of waiting, the
@ -343,10 +353,10 @@ export class ElementHandle<
* If `xpath` starts with `//` instead of `.//`, the dot will be appended
* automatically.
*
* This method works across navigation
* This method works across navigation.
*
* ```ts
* const puppeteer = require('puppeteer');
* import puppeteer from 'puppeteer';
* (async () => {
* const browser = await puppeteer.launch();
* const page = await browser.newPage();
@ -402,6 +412,37 @@ export class ElementHandle<
return this.waitForSelector(`xpath/${xpath}`, options);
}
/**
* Converts the current handle to the given element type.
*
* @example
*
* ```ts
* const element: ElementHandle<Element> = await page.$(
* '.class-name-of-anchor'
* );
* // DO NOT DISPOSE `element`, this will be always be the same handle.
* const anchor: ElementHandle<HTMLAnchorElement> = await element.toElement(
* 'a'
* );
* ```
*
* @param tagName - The tag name of the desired element type.
* @throws An error if the handle does not match. **The handle will not be
* automatically disposed.**
*/
async toElement<
K extends keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap
>(tagName: K): Promise<HandleFor<ElementFor<K>>> {
const isMatchingTagName = await this.evaluate((node, tagName) => {
return node.nodeName === tagName.toUpperCase();
}, tagName);
if (!isMatchingTagName) {
throw new Error(`Element is not a(n) \`${tagName}\` element`);
}
return this as unknown as HandleFor<ElementFor<K>>;
}
override asElement(): ElementHandle<ElementType> | null {
return this;
}
@ -510,7 +551,7 @@ export class ElementHandle<
objectId: this.remoteObject().objectId,
})
.catch(debugError),
this.#page._client().send('Page.getLayoutMetrics'),
(this.#page as CDPPage)._client().send('Page.getLayoutMetrics'),
]);
if (!result || !result.quads.length) {
throw new Error('Node is either not clickable or not an HTMLElement');

Просмотреть файл

@ -15,9 +15,14 @@
*/
/**
* @deprecated Do not use.
*
* @public
*/
export class CustomError extends Error {
/**
* @internal
*/
constructor(message?: string) {
super(message);
this.name = this.constructor.name;
@ -43,11 +48,39 @@ export class TimeoutError extends CustomError {}
* @public
*/
export class ProtocolError extends CustomError {
public code?: number;
public originalMessage = '';
#code?: number;
#originalMessage = '';
/**
* @internal
*/
set code(code: number | undefined) {
this.#code = code;
}
/**
* @public
*/
get code(): number | undefined {
return this.#code;
}
/**
* @internal
*/
set originalMessage(originalMessage: string) {
this.#originalMessage = originalMessage;
}
/**
* @public
*/
get originalMessage(): string {
return this.#originalMessage;
}
}
/**
* @deprecated Do not use.
*
* @public
*/
export interface PuppeteerErrors {
@ -56,6 +89,8 @@ export interface PuppeteerErrors {
}
/**
* @deprecated Import error classes directly.
*
* Puppeteer methods might throw errors if they are unable to fulfill a request.
* For example, `page.waitForSelector(selector[, options])` might fail if the
* selector doesn't match any nodes during the given timeframe.
@ -70,7 +105,7 @@ export interface PuppeteerErrors {
* try {
* await page.waitForSelector('.foo');
* } catch (e) {
* if (e instanceof puppeteer.errors.TimeoutError) {
* if (e instanceof TimeoutError) {
* // Do something if this is a timeout.
* }
* }

Просмотреть файл

@ -14,16 +14,16 @@
* limitations under the License.
*/
import mitt, {
Emitter,
EventType,
Handler,
} from '../../vendor/mitt/src/index.js';
import mitt, {Emitter, EventHandlerMap} from '../../third_party/mitt/index.js';
/**
* @public
*/
export {EventType, Handler};
export type EventType = string | symbol;
/**
* @public
*/
export type Handler<T = unknown> = (event: T) => void;
/**
* @public
@ -57,8 +57,8 @@ export interface CommonEventEmitter {
* @public
*/
export class EventEmitter implements CommonEventEmitter {
private emitter: Emitter;
private eventsMap = new Map<EventType, Handler[]>();
private emitter: Emitter<Record<string | symbol, any>>;
private eventsMap: EventHandlerMap<Record<string | symbol, any>> = new Map();
/**
* @internal
@ -73,7 +73,7 @@ export class EventEmitter implements CommonEventEmitter {
* @param handler - the function to be called when the event occurs.
* @returns `this` to enable you to chain method calls.
*/
on(event: EventType, handler: Handler): EventEmitter {
on(event: EventType, handler: Handler<any>): EventEmitter {
this.emitter.on(event, handler);
return this;
}
@ -84,7 +84,7 @@ export class EventEmitter implements CommonEventEmitter {
* @param handler - the function that should be removed.
* @returns `this` to enable you to chain method calls.
*/
off(event: EventType, handler: Handler): EventEmitter {
off(event: EventType, handler: Handler<any>): EventEmitter {
this.emitter.off(event, handler);
return this;
}
@ -93,7 +93,7 @@ export class EventEmitter implements CommonEventEmitter {
* Remove an event listener.
* @deprecated please use {@link EventEmitter.off} instead.
*/
removeListener(event: EventType, handler: Handler): EventEmitter {
removeListener(event: EventType, handler: Handler<any>): EventEmitter {
this.off(event, handler);
return this;
}
@ -102,7 +102,7 @@ export class EventEmitter implements CommonEventEmitter {
* Add an event listener.
* @deprecated please use {@link EventEmitter.on} instead.
*/
addListener(event: EventType, handler: Handler): EventEmitter {
addListener(event: EventType, handler: Handler<any>): EventEmitter {
this.on(event, handler);
return this;
}
@ -125,8 +125,8 @@ export class EventEmitter implements CommonEventEmitter {
* @param handler - the handler function to run when the event occurs
* @returns `this` to enable you to chain method calls.
*/
once(event: EventType, handler: Handler): EventEmitter {
const onceHandler: Handler = eventData => {
once(event: EventType, handler: Handler<any>): EventEmitter {
const onceHandler: Handler<any> = eventData => {
handler(eventData);
this.off(event, onceHandler);
};

Просмотреть файл

@ -14,7 +14,7 @@
* limitations under the License.
*/
import Protocol from 'devtools-protocol';
import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js';
import {CDPSession, Connection} from './Connection.js';
import {Target} from './Target.js';

Просмотреть файл

@ -26,12 +26,11 @@ import {MouseButton} from './Input.js';
import {
IsolatedWorld,
IsolatedWorldChart,
MAIN_WORLD,
PUPPETEER_WORLD,
WaitForSelectorOptions,
} from './IsolatedWorld.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
import {Page} from './Page.js';
import {Page} from '../api/Page.js';
import {getQueryHandlerAndSelector} from './QueryHandler.js';
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
import {importFS} from './util.js';
@ -128,7 +127,7 @@ export interface FrameAddStyleTagOptions {
* An example of dumping frame tree:
*
* ```ts
* const puppeteer = require('puppeteer');
* import puppeteer from 'puppeteer';
*
* (async () => {
* const browser = await puppeteer.launch();
@ -290,12 +289,16 @@ export class Frame {
url: string,
options: {
referer?: string;
referrerPolicy?: string;
timeout?: number;
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
} = {}
): Promise<HTTPResponse | null> {
const {
referer = this._frameManager.networkManager.extraHTTPHeaders()['referer'],
referrerPolicy = this._frameManager.networkManager.extraHTTPHeaders()[
'referer-policy'
],
waitUntil = ['load'],
timeout = this._frameManager.timeoutSettings.navigationTimeout(),
} = options;
@ -308,7 +311,13 @@ export class Frame {
timeout
);
let error = await Promise.race([
navigate(this.#client, url, referer, this._id),
navigate(
this.#client,
url,
referer,
referrerPolicy as Protocol.Page.ReferrerPolicy,
this._id
),
watcher.timeoutOrTerminationPromise(),
]);
if (!error) {
@ -333,6 +342,7 @@ export class Frame {
client: CDPSession,
url: string,
referrer: string | undefined,
referrerPolicy: Protocol.Page.ReferrerPolicy | undefined,
frameId: string
): Promise<Error | null> {
try {
@ -340,6 +350,7 @@ export class Frame {
url,
referrer,
frameId,
referrerPolicy,
});
ensureNewDocumentNavigation = !!response.loaderId;
return response.errorText
@ -551,7 +562,11 @@ export class Frame {
/**
* @deprecated Use {@link Frame.$$} with the `xpath` prefix.
*
* Example: `await frame.$$('xpath/' + xpathExpression)`
*
* This method evaluates the given XPath expression and returns the results.
* If `xpath` starts with `//` instead of `.//`, the dot will be appended
* automatically.
* @param expression - the XPath expression to evaluate.
*/
async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
@ -566,7 +581,7 @@ export class Frame {
* @example
*
* ```ts
* const puppeteer = require('puppeteer');
* import puppeteer from 'puppeteer';
*
* (async () => {
* const browser = await puppeteer.launch();
@ -610,6 +625,12 @@ export class Frame {
/**
* @deprecated Use {@link Frame.waitForSelector} with the `xpath` prefix.
*
* Example: `await frame.waitForSelector('xpath/' + xpathExpression)`
*
* The method evaluates the XPath expression relative to the Frame.
* If `xpath` starts with `//` instead of `.//`, the dot will be appended
* automatically.
*
* Wait for the `xpath` to appear in page. If at the moment of calling the
* method the `xpath` already exists, the method will return immediately. If
* the xpath doesn't appear after the `timeout` milliseconds of waiting, the
@ -620,7 +641,7 @@ export class Frame {
* an XPath.
*
* @param xpath - the XPath expression to wait for.
* @param options - options to configure the visiblity of the element and how
* @param options - options to configure the visibility of the element and how
* long to wait before timing out.
*/
async waitForXPath(
@ -638,7 +659,7 @@ export class Frame {
* The `waitForFunction` can be used to observe viewport size change:
*
* ```ts
* const puppeteer = require('puppeteer');
* import puppeteer from 'puppeteer';
*
* (async () => {
* . const browser = await puppeteer.launch();
@ -1020,7 +1041,7 @@ export class Frame {
}
/**
* @deprecated Use `new Promise(r => setTimeout(r, milliseconds));`.
* @deprecated Replace with `new Promise(r => setTimeout(r, milliseconds));`.
*
* Causes your script to wait for the given number of milliseconds.
*

Просмотреть файл

@ -22,9 +22,10 @@ import {EventEmitter} from './EventEmitter.js';
import {EVALUATION_SCRIPT_URL, ExecutionContext} from './ExecutionContext.js';
import {Frame} from './Frame.js';
import {FrameTree} from './FrameTree.js';
import {IsolatedWorld, MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorld.js';
import {IsolatedWorld} from './IsolatedWorld.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {NetworkManager} from './NetworkManager.js';
import {Page} from './Page.js';
import {Page} from '../api/Page.js';
import {Target} from './Target.js';
import {TimeoutSettings} from './TimeoutSettings.js';
import {debugError} from './util.js';
@ -67,6 +68,13 @@ export class FrameManager extends EventEmitter {
*/
_frameTree = new FrameTree();
/**
* Set of frame IDs stored to indicate if a frame has received a
* frameNavigated event so that frame tree responses could be ignored as the
* frameNavigated event usually contains the latest information.
*/
#frameNavigatedReceived = new Set<string>();
get timeoutSettings(): TimeoutSettings {
return this.#timeoutSettings;
}
@ -98,6 +106,7 @@ export class FrameManager extends EventEmitter {
this.#onFrameAttached(session, event.frameId, event.parentFrameId);
});
session.on('Page.frameNavigated', event => {
this.#frameNavigatedReceived.add(event.frame.id);
this.#onFrameNavigated(event.frame);
});
session.on('Page.navigatedWithinDocument', event => {
@ -202,15 +211,6 @@ export class FrameManager extends EventEmitter {
this.initialize(target._session());
}
onDetachedFromTarget(target: Target): void {
const frame = this.frame(target._targetId);
if (frame && frame.isOOPFrame()) {
// When an OOP iframe is removed from the page, it
// will only get a Target.detachedFromTarget event.
this.#removeFramesRecursively(frame);
}
}
#onLifecycleEvent(event: Protocol.Page.LifecycleEventEvent): void {
const frame = this.frame(event.frameId);
if (!frame) {
@ -248,7 +248,12 @@ export class FrameManager extends EventEmitter {
frameTree.frame.parentId
);
}
this.#onFrameNavigated(frameTree.frame);
if (!this.#frameNavigatedReceived.has(frameTree.frame.id)) {
this.#onFrameNavigated(frameTree.frame);
} else {
this.#frameNavigatedReceived.delete(frameTree.frame.id);
}
if (!frameTree.childFrames) {
return;
}
@ -383,8 +388,7 @@ export class FrameManager extends EventEmitter {
if (frame._client() !== session) {
return;
}
if (contextPayload.auxData && !!contextPayload.auxData['isDefault']) {
if (contextPayload.auxData && contextPayload.auxData['isDefault']) {
world = frame.worlds[MAIN_WORLD];
} else if (
contextPayload.name === UTILITY_WORLD_NAME &&

Просмотреть файл

@ -35,7 +35,7 @@ type KeyDescription = Required<
* {@link Keyboard.up}, and {@link Keyboard.sendCharacter}
* to manually fire events as if they were generated from a real keyboard.
*
* On MacOS, keyboard shortcuts like `⌘ A` -\> Select All do not work.
* On macOS, keyboard shortcuts like `⌘ A` -\> Select All do not work.
* See {@link https://github.com/puppeteer/puppeteer/issues/1313 | #1313}.
*
* @example
@ -104,11 +104,16 @@ export class Keyboard {
* See {@link KeyInput} for a list of all key names.
*
* @param options - An object of options. Accepts text which, if specified,
* generates an input event with this text.
* generates an input event with this text. Accepts commands which, if specified,
* is the commands of keyboard shortcuts,
* see {@link https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/editing/commands/editor_command_names.h | Chromium Source Code} for valid command names.
*/
async down(
key: KeyInput,
options: {text?: string} = {text: undefined}
options: {text?: string; commands?: string[]} = {
text: undefined,
commands: [],
}
): Promise<void> {
const description = this.#keyDescriptionForString(key);
@ -128,6 +133,7 @@ export class Keyboard {
autoRepeat,
location: description.location,
isKeypad: description.location === 3,
commands: options.commands,
});
}
@ -304,11 +310,13 @@ export class Keyboard {
* @param options - An object of options. Accepts text which, if specified,
* generates an input event with this text. Accepts delay which,
* if specified, is the time to wait between `keydown` and `keyup` in milliseconds.
* Defaults to 0.
* Defaults to 0. Accepts commands which, if specified,
* is the commands of keyboard shortcuts,
* see {@link https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/editing/commands/editor_command_names.h | Chromium Source Code} for valid command names.
*/
async press(
key: KeyInput,
options: {delay?: number; text?: string} = {}
options: {delay?: number; text?: string; commands?: string[]} = {}
): Promise<void> {
const {delay = null} = options;
await this.down(key, options);
@ -615,7 +623,7 @@ export class Mouse {
/**
* Performs a drag, dragenter, dragover, and drop in sequence.
* @param target - point to drag from
* @param start - point to drag from
* @param target - point to drop on
* @param options - An object of options. Accepts delay which,
* if specified, is the time to wait between `dragover` and `drop` in milliseconds.

Просмотреть файл

@ -16,12 +16,10 @@
import {Protocol} from 'devtools-protocol';
import {source as injectedSource} from '../generated/injected.js';
import type PuppeteerUtil from '../injected/injected.js';
import {assert} from '../util/assert.js';
import {createDeferredPromise} from '../util/DeferredPromise.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {CDPSession} from './Connection.js';
import {ElementHandle} from './ElementHandle.js';
import {ExecutionContext} from './ExecutionContext.js';
import {Frame} from './Frame.js';
import {FrameManager} from './FrameManager.js';
@ -30,9 +28,13 @@ import {JSHandle} from './JSHandle.js';
import {LazyArg} from './LazyArg.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
import {TimeoutSettings} from './TimeoutSettings.js';
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
import {EvaluateFunc, HandleFor, InnerLazyParams, NodeFor} from './types.js';
import {createJSHandle, debugError, pageBindingInitString} from './util.js';
import {TaskManager, WaitTask} from './WaitTask.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import type PuppeteerUtil from '../injected/injected.js';
import type {ElementHandle} from './ElementHandle.js';
/**
* @public
@ -70,20 +72,6 @@ export interface PageBinding {
pptrFunction: Function;
}
/**
* A unique key for {@link IsolatedWorldChart} to denote the default world.
* Execution contexts are automatically created in the default world.
*
* @internal
*/
export const MAIN_WORLD = Symbol('mainWorld');
/**
* A unique key for {@link IsolatedWorldChart} to denote the puppeteer world.
* This world contains all puppeteer-internal bindings/code.
*
* @internal
*/
export const PUPPETEER_WORLD = Symbol('puppeteerWorld');
/**
* @internal
*/
@ -486,7 +474,7 @@ export class IsolatedWorld {
);
} catch (error) {
// The WaitTask may already have been resolved by timing out, or the
// exection context may have been destroyed.
// execution context may have been destroyed.
// In both caes, the promises above are rejected with a protocol error.
// We can safely ignores these, as the WaitTask is re-installed in
// the next execution context if needed.
@ -502,7 +490,7 @@ export class IsolatedWorld {
root: ElementHandle<Node> | undefined,
selector: string,
options: WaitForSelectorOptions,
bindings = new Set<(...args: never[]) => unknown>()
bindings = new Map<string, (...args: never[]) => unknown>()
): Promise<JSHandle<unknown> | null> {
const {
visible: waitForVisible = false,
@ -559,14 +547,16 @@ export class IsolatedWorld {
waitForFunction<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
Func extends EvaluateFunc<InnerLazyParams<Params>> = EvaluateFunc<
InnerLazyParams<Params>
>
>(
pageFunction: Func | string,
options: {
polling?: 'raf' | 'mutation' | number;
timeout?: number;
root?: ElementHandle<Node>;
bindings?: Set<(...args: never[]) => unknown>;
bindings?: Map<string, (...args: never[]) => unknown>;
} = {},
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {

Просмотреть файл

@ -0,0 +1,30 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A unique key for {@link IsolatedWorldChart} to denote the default world.
* Execution contexts are automatically created in the default world.
*
* @internal
*/
export const MAIN_WORLD = Symbol('mainWorld');
/**
* A unique key for {@link IsolatedWorldChart} to denote the puppeteer world.
* This world contains all puppeteer-internal bindings/code.
*
* @internal
*/
export const PUPPETEER_WORLD = Symbol('puppeteerWorld');

Просмотреть файл

@ -167,7 +167,7 @@ export class JSHandle<T = unknown> {
propertyName: HandleOr<K>
): Promise<HandleFor<T[K]>> {
return this.evaluateHandle((object, propertyName) => {
return object[propertyName];
return object[propertyName as K];
}, propertyName);
}

Просмотреть файл

@ -168,6 +168,11 @@ export class LifecycleWatcher {
NetworkManagerEmittedEvents.Response,
this.#onResponse.bind(this)
),
addEventListener(
this.#frameManager.networkManager,
NetworkManagerEmittedEvents.RequestFailed,
this.#onRequestFailed.bind(this)
),
];
this.#timeoutPromise = this.#createTimeoutPromise();
@ -189,6 +194,13 @@ export class LifecycleWatcher {
}
}
#onRequestFailed(request: HTTPRequest): void {
if (this.#navigationRequest?._requestId !== request._requestId) {
return;
}
this.#navigationResponseReceived?.resolve();
}
#onResponse(response: HTTPResponse): void {
if (this.#navigationRequest?._requestId !== response.request()._requestId) {
return;

Просмотреть файл

@ -21,7 +21,10 @@ import {packageVersion} from '../generated/version.js';
* @internal
*/
export class NodeWebSocketTransport implements ConnectionTransport {
static create(url: string): Promise<NodeWebSocketTransport> {
static create(
url: string,
headers?: Record<string, string>
): Promise<NodeWebSocketTransport> {
return new Promise((resolve, reject) => {
const ws = new NodeWebSocket(url, [], {
followRedirects: true,
@ -29,6 +32,7 @@ export class NodeWebSocketTransport implements ConnectionTransport {
maxPayload: 256 * 1024 * 1024, // 256Mb
headers: {
'User-Agent': `Puppeteer ${packageVersion}`,
...headers,
},
});

Просмотреть файл

@ -167,7 +167,7 @@ export interface PDFOptions {
*/
omitBackground?: boolean;
/**
* Timeout in milliseconds
* Timeout in milliseconds. Pass `0` to disable timeout.
* @defaultValue 30000
*/
timeout?: number;

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше