Merge branch 'microsoft:main' into cnm_dev
This commit is contained in:
commit
40a7985224
2
.github/workflows/create_test_report.yml
vendored
2
.github/workflows/create_test_report.yml
vendored
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
|
||||
- name: Merge reports
|
||||
run: |
|
||||
npx playwright merge-reports --reporter=html,packages/playwright/lib/reporters/markdown.js ./all-blob-reports
|
||||
npx playwright merge-reports --config .github/workflows/merge.config.ts ./all-blob-reports
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
|
||||
|
|
|
|||
2
.github/workflows/infra.yml
vendored
2
.github/workflows/infra.yml
vendored
|
|
@ -35,7 +35,7 @@ jobs:
|
|||
exit 1
|
||||
fi
|
||||
- name: Audit prod NPM dependencies
|
||||
run: npm audit --omit dev
|
||||
run: node utils/check_audit.js
|
||||
lint-snippets:
|
||||
name: "Lint snippets"
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
4
.github/workflows/merge.config.ts
vendored
Normal file
4
.github/workflows/merge.config.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
testDir: '../../tests',
|
||||
reporter: [[require.resolve('../../packages/playwright/lib/reporters/markdown')], ['html']]
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# 🎭 Playwright
|
||||
|
||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop --> [](https://aka.ms/playwright/discord)
|
||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop --> [](https://aka.ms/playwright/discord)
|
||||
|
||||
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
|
|||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium <!-- GEN:chromium-version -->134.0.6998.15<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Chromium <!-- GEN:chromium-version -->134.0.6998.23<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->18.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Firefox <!-- GEN:firefox-version -->135.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
|
|
|
|||
|
|
@ -2035,9 +2035,9 @@ Triggers a `change` and `input` event once all the provided options have been se
|
|||
|
||||
```html
|
||||
<select multiple>
|
||||
<option value="red">Red</div>
|
||||
<option value="green">Green</div>
|
||||
<option value="blue">Blue</div>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="blue">Blue</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
|
|
@ -2332,7 +2332,7 @@ This method expects [Locator] to point to an
|
|||
## async method: Locator.tap
|
||||
* since: v1.14
|
||||
|
||||
Perform a tap gesture on the element matching the locator.
|
||||
Perform a tap gesture on the element matching the locator. For examples of emulating other gestures by manually dispatching touch events, see the [emulating legacy touch events](../touch-events.md) page.
|
||||
|
||||
**Details**
|
||||
|
||||
|
|
@ -2478,6 +2478,18 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Locator.uncheck.trial = %%-input-trial-%%
|
||||
* since: v1.14
|
||||
|
||||
## method: Locator.visible
|
||||
* since: v1.51
|
||||
- returns: <[Locator]>
|
||||
|
||||
Returns a locator that only matches [visible](../actionability.md#visible) elements.
|
||||
|
||||
### option: Locator.visible.visible
|
||||
* since: v1.51
|
||||
- `visible` <[boolean]>
|
||||
|
||||
Whether to match visible or invisible elements.
|
||||
|
||||
## async method: Locator.waitFor
|
||||
* since: v1.16
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the
|
||||
touchscreen can only be used in browser contexts that have been initialized with `hasTouch` set to true.
|
||||
|
||||
This class is limited to emulating tap gestures. For examples of other gestures simulated by manually dispatching touch events, see the [emulating legacy touch events](../touch-events.md) page.
|
||||
|
||||
## async method: Touchscreen.tap
|
||||
* since: v1.8
|
||||
|
||||
|
|
|
|||
|
|
@ -1229,6 +1229,7 @@ Specify screenshot type, defaults to `png`.
|
|||
|
||||
Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with
|
||||
a pink box `#FF00FF` (customized by [`option: maskColor`]) that completely covers its bounding box.
|
||||
The mask is also applied to invisible elements, see [Matching only visible elements](../locators.md#matching-only-visible-elements) to disable that.
|
||||
|
||||
## screenshot-option-mask-color
|
||||
* since: v1.35
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ Create `tests/auth.setup.ts` that will prepare authenticated browser state for a
|
|||
|
||||
```js title="tests/auth.setup.ts"
|
||||
import { test as setup, expect } from '@playwright/test';
|
||||
import * as path from 'path';
|
||||
import path from 'path';
|
||||
|
||||
const authFile = path.join(__dirname, '../playwright/.auth/user.json');
|
||||
|
||||
|
|
@ -143,8 +143,8 @@ Create `playwright/fixtures.ts` file that will [override `storageState` fixture]
|
|||
|
||||
```js title="playwright/fixtures.ts"
|
||||
import { test as baseTest, expect } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export * from '@playwright/test';
|
||||
export const test = baseTest.extend<{}, { workerStorageState: string }>({
|
||||
|
|
@ -348,8 +348,8 @@ Alternatively, in a [worker fixture](#moderate-one-account-per-parallel-worker):
|
|||
|
||||
```js title="playwright/fixtures.ts"
|
||||
import { test as baseTest, request } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export * from '@playwright/test';
|
||||
export const test = baseTest.extend<{}, { workerStorageState: string }>({
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ First, add fixtures that will load the extension:
|
|||
|
||||
```js title="fixtures.ts"
|
||||
import { test as base, chromium, type BrowserContext } from '@playwright/test';
|
||||
import * as path from 'path';
|
||||
import path from 'path';
|
||||
|
||||
export const test = base.extend<{
|
||||
context: BrowserContext;
|
||||
|
|
|
|||
|
|
@ -389,7 +389,7 @@ Next, add init script to the page.
|
|||
|
||||
```js
|
||||
import { test, expect } from '@playwright/test';
|
||||
import * as path from 'path';
|
||||
import path from 'path';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Add script for every test in the beforeEach hook.
|
||||
|
|
|
|||
|
|
@ -1310,19 +1310,19 @@ Consider a page with two buttons, the first invisible and the second [visible](.
|
|||
* This will only find a second button, because it is visible, and then click it.
|
||||
|
||||
```js
|
||||
await page.locator('button').locator('visible=true').click();
|
||||
await page.locator('button').visible().click();
|
||||
```
|
||||
```java
|
||||
page.locator("button").locator("visible=true").click();
|
||||
page.locator("button").visible().click();
|
||||
```
|
||||
```python async
|
||||
await page.locator("button").locator("visible=true").click()
|
||||
await page.locator("button").visible().click()
|
||||
```
|
||||
```python sync
|
||||
page.locator("button").locator("visible=true").click()
|
||||
page.locator("button").visible().click()
|
||||
```
|
||||
```csharp
|
||||
await page.Locator("button").Locator("visible=true").ClickAsync();
|
||||
await page.Locator("button").Visible().ClickAsync();
|
||||
```
|
||||
|
||||
## Lists
|
||||
|
|
|
|||
|
|
@ -708,9 +708,13 @@ Playwright uses simplified glob patterns for URL matching in network interceptio
|
|||
- A double `**` matches any characters including `/`
|
||||
1. Question mark `?` matches any single character except `/`
|
||||
1. Curly braces `{}` can be used to match a list of options separated by commas `,`
|
||||
1. Square brackets `[]` can be used to match a set of characters
|
||||
1. Backslash `\` can be used to escape any of special characters (note to escape backslash itself as `\\`)
|
||||
|
||||
Examples:
|
||||
- `https://example.com/*.js` matches `https://example.com/file.js` but not `https://example.com/path/file.js`
|
||||
- `https://example.com/\\?page=1` matches `https://example.com/?page=1` but not `https://example.com`
|
||||
- `**/v[0-9]*` matches `https://example.com/v1/` but not `https://example.com/vote/`
|
||||
- `**/*.js` matches both `https://example.com/file.js` and `https://example.com/path/file.js`
|
||||
- `**/*.{png,jpg,jpeg}` matches all image requests
|
||||
|
||||
|
|
|
|||
|
|
@ -824,9 +824,9 @@ This version was also tested against the following stable channels:
|
|||
|
||||
```html
|
||||
<select multiple>
|
||||
<option value="red">Red</div>
|
||||
<option value="green">Green</div>
|
||||
<option value="blue">Blue</div>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="blue">Blue</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -888,9 +888,9 @@ This version was also tested against the following stable channels:
|
|||
|
||||
```html
|
||||
<select multiple>
|
||||
<option value="red">Red</div>
|
||||
<option value="green">Green</div>
|
||||
<option value="blue">Blue</div>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="blue">Blue</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1498,9 +1498,9 @@ This version was also tested against the following stable channels:
|
|||
|
||||
```html
|
||||
<select multiple>
|
||||
<option value="red">Red</div>
|
||||
<option value="green">Green</div>
|
||||
<option value="blue">Blue</div>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="blue">Blue</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -800,9 +800,9 @@ This version was also tested against the following stable channels:
|
|||
|
||||
```html
|
||||
<select multiple>
|
||||
<option value="red">Red</div>
|
||||
<option value="green">Green</div>
|
||||
<option value="blue">Blue</div>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="blue">Blue</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ export default defineConfig({
|
|||
|
||||
Metadata contains key-value pairs to be included in the report. For example, HTML report will display it as key-value pairs, and JSON report will include metadata serialized as json.
|
||||
|
||||
See also [`property: TestConfig.populateGitInfo`] that populates metadata.
|
||||
Providing `'git.commit.info': {}` property will populate it with the git commit details. This is useful for CI/CD environments.
|
||||
|
||||
**Usage**
|
||||
|
||||
|
|
@ -291,7 +291,7 @@ Here is an example that uses [`method: TestInfo.outputPath`] to create a tempora
|
|||
|
||||
```js
|
||||
import { test, expect } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
import fs from 'fs';
|
||||
|
||||
test('example test', async ({}, testInfo) => {
|
||||
const file = testInfo.outputPath('temporary-file.txt');
|
||||
|
|
@ -326,26 +326,6 @@ This path will serve as the base directory for each test file snapshot directory
|
|||
## property: TestConfig.snapshotPathTemplate = %%-test-config-snapshot-path-template-%%
|
||||
* since: v1.28
|
||||
|
||||
## property: TestConfig.populateGitInfo
|
||||
* since: v1.51
|
||||
- type: ?<[boolean]>
|
||||
|
||||
Whether to populate `'git.commit.info'` field of the [`property: TestConfig.metadata`] with Git commit info and CI/CD information.
|
||||
|
||||
This information will appear in the HTML and JSON reports and is available in the Reporter API.
|
||||
|
||||
On Github Actions, this feature is enabled by default.
|
||||
|
||||
**Usage**
|
||||
|
||||
```js title="playwright.config.ts"
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
populateGitInfo: !!process.env.CI,
|
||||
});
|
||||
```
|
||||
|
||||
## property: TestConfig.preserveOutput
|
||||
* since: v1.10
|
||||
- type: ?<[PreserveOutput]<"always"|"never"|"failures-only">>
|
||||
|
|
@ -680,7 +660,7 @@ import { defineConfig } from '@playwright/test';
|
|||
export default defineConfig({
|
||||
webServer: {
|
||||
command: 'npm run start',
|
||||
url: 'http://127.0.0.1:3000',
|
||||
url: 'http://localhost:3000',
|
||||
timeout: 120 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
|
|
@ -709,19 +689,19 @@ export default defineConfig({
|
|||
webServer: [
|
||||
{
|
||||
command: 'npm run start',
|
||||
url: 'http://127.0.0.1:3000',
|
||||
url: 'http://localhost:3000',
|
||||
timeout: 120 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
{
|
||||
command: 'npm run backend',
|
||||
url: 'http://127.0.0.1:3333',
|
||||
url: 'http://localhost:3333',
|
||||
timeout: 120 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
}
|
||||
],
|
||||
use: {
|
||||
baseURL: 'http://127.0.0.1:3000',
|
||||
baseURL: 'http://localhost:3000',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ Returns a path inside the [`property: TestInfo.outputDir`] where the test can sa
|
|||
|
||||
```js
|
||||
import { test, expect } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
import fs from 'fs';
|
||||
|
||||
test('example test', async ({}, testInfo) => {
|
||||
const file = testInfo.outputPath('dir', 'temporary-file.txt');
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ Here is an example that uses [`method: TestInfo.outputPath`] to create a tempora
|
|||
|
||||
```js
|
||||
import { test, expect } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
import fs from 'fs';
|
||||
|
||||
test('example test', async ({}, testInfo) => {
|
||||
const file = testInfo.outputPath('temporary-file.txt');
|
||||
|
|
|
|||
|
|
@ -853,6 +853,14 @@ export default defineConfig({
|
|||
});
|
||||
```
|
||||
|
||||
### How do I use CSS imports?
|
||||
|
||||
If you have a component that imports CSS, Vite will handle it automatically. You can also use CSS pre-processors such as Sass, Less, or Stylus, and Vite will handle them as well without any additional configuration. However, corresponding CSS pre-processor needs to be installed.
|
||||
|
||||
Vite has a hard requirement that all CSS Modules are named `*.module.[css extension]`. If you have a custom build config for your project normally and have imports of the form `import styles from 'styles.css'` you must rename your files to properly indicate they are to be treated as modules. You could also write a Vite plugin to handle this for you.
|
||||
|
||||
Check [Vite documentation](https://vite.dev/guide/features#css) for more details.
|
||||
|
||||
### How can I test components that uses Pinia?
|
||||
|
||||
Pinia needs to be initialized in `playwright/index.{js,ts,jsx,tsx}`. If you do this inside a `beforeMount` hook, the `initialState` can be overwritten on a per-test basis:
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export default defineConfig({
|
|||
|
||||
use: {
|
||||
// Base URL to use in actions like `await page.goto('/')`.
|
||||
baseURL: 'http://127.0.0.1:3000',
|
||||
baseURL: 'http://localhost:3000',
|
||||
|
||||
// Collect trace when retrying the failed test.
|
||||
trace: 'on-first-retry',
|
||||
|
|
@ -50,7 +50,7 @@ export default defineConfig({
|
|||
// Run your local dev server before starting the tests.
|
||||
webServer: {
|
||||
command: 'npm run start',
|
||||
url: 'http://127.0.0.1:3000',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -408,7 +408,7 @@ Here is an example fixture that automatically attaches debug logs when the test
|
|||
|
||||
```js title="my-test.ts"
|
||||
import debug from 'debug';
|
||||
import * as fs from 'fs';
|
||||
import fs from 'fs';
|
||||
import { test as base } from '@playwright/test';
|
||||
|
||||
export const test = base.extend<{ saveLogs: void }>({
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ To make environment variables easier to manage, consider something like `.env` f
|
|||
```js title="playwright.config.ts"
|
||||
import { defineConfig } from '@playwright/test';
|
||||
import dotenv from 'dotenv';
|
||||
import * as path from 'path';
|
||||
import path from 'path';
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||
|
|
@ -309,8 +309,8 @@ See for example this CSV file, in our example `input.csv`:
|
|||
Based on this we'll generate some tests by using the [csv-parse](https://www.npmjs.com/package/csv-parse) library from NPM:
|
||||
|
||||
```js title="test.spec.ts"
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { test } from '@playwright/test';
|
||||
import { parse } from 'csv-parse/sync';
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { defineConfig } from '@playwright/test';
|
|||
export default defineConfig({
|
||||
use: {
|
||||
// Base URL to use in actions like `await page.goto('/')`.
|
||||
baseURL: 'http://127.0.0.1:3000',
|
||||
baseURL: 'http://localhost:3000',
|
||||
|
||||
// Populates context with given storage state.
|
||||
storageState: 'state.json',
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export default defineConfig({
|
|||
// Run your local dev server before starting the tests
|
||||
webServer: {
|
||||
command: 'npm run start',
|
||||
url: 'http://127.0.0.1:3000',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
stdout: 'ignore',
|
||||
stderr: 'pipe',
|
||||
|
|
@ -52,7 +52,7 @@ export default defineConfig({
|
|||
// Run your local dev server before starting the tests
|
||||
webServer: {
|
||||
command: 'npm run start',
|
||||
url: 'http://127.0.0.1:3000',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120 * 1000,
|
||||
},
|
||||
|
|
@ -63,7 +63,7 @@ export default defineConfig({
|
|||
|
||||
It is also recommended to specify the `baseURL` in the `use: {}` section of your config, so that tests can use relative urls and you don't have to specify the full URL over and over again.
|
||||
|
||||
When using [`method: Page.goto`], [`method: Page.route`], [`method: Page.waitForURL`], [`method: Page.waitForRequest`], or [`method: Page.waitForResponse`] it takes the base URL in consideration by using the [`URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor for building the corresponding URL. For Example, by setting the baseURL to `http://127.0.0.1:3000` and navigating to `/login` in your tests, Playwright will run the test using `http://127.0.0.1:3000/login`.
|
||||
When using [`method: Page.goto`], [`method: Page.route`], [`method: Page.waitForURL`], [`method: Page.waitForRequest`], or [`method: Page.waitForResponse`] it takes the base URL in consideration by using the [`URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor for building the corresponding URL. For Example, by setting the baseURL to `http://localhost:3000` and navigating to `/login` in your tests, Playwright will run the test using `http://localhost:3000/login`.
|
||||
|
||||
```js title="playwright.config.ts"
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
|
@ -74,11 +74,11 @@ export default defineConfig({
|
|||
// Run your local dev server before starting the tests
|
||||
webServer: {
|
||||
command: 'npm run start',
|
||||
url: 'http://127.0.0.1:3000',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
use: {
|
||||
baseURL: 'http://127.0.0.1:3000',
|
||||
baseURL: 'http://localhost:3000',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
|
@ -89,7 +89,7 @@ Now you can use a relative path when navigating the page:
|
|||
import { test } from '@playwright/test';
|
||||
|
||||
test('test', async ({ page }) => {
|
||||
// This will navigate to http://127.0.0.1:3000/login
|
||||
// This will navigate to http://localhost:3000/login
|
||||
await page.goto('./login');
|
||||
});
|
||||
```
|
||||
|
|
@ -106,19 +106,19 @@ export default defineConfig({
|
|||
webServer: [
|
||||
{
|
||||
command: 'npm run start',
|
||||
url: 'http://127.0.0.1:3000',
|
||||
url: 'http://localhost:3000',
|
||||
timeout: 120 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
{
|
||||
command: 'npm run backend',
|
||||
url: 'http://127.0.0.1:3333',
|
||||
url: 'http://localhost:3333',
|
||||
timeout: 120 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
}
|
||||
],
|
||||
use: {
|
||||
baseURL: 'http://127.0.0.1:3000',
|
||||
baseURL: 'http://localhost:3000',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,19 +1,13 @@
|
|||
---
|
||||
id: touch-events
|
||||
title: "Emulating touch events"
|
||||
title: "Emulating legacy touch events"
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Mobile web sites may listen to [touch events](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events) and react to user touch gestures such as swipe, pinch, tap etc. To test this functionality you can manually generate [TouchEvent]s in the page context using [`method: Locator.evaluate`].
|
||||
Web applications that handle [touch events](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events) to respond to gestures like swipe, pinch, and tap can be tested by manually dispatching [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent)s to the page. The examples below demonstrate how to use [`method: Locator.dispatchEvent`] and pass [Touch](https://developer.mozilla.org/en-US/docs/Web/API/Touch) points as arguments.
|
||||
|
||||
If your web application relies on [pointer events](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events) instead of touch events, you can use [`method: Locator.click`] and raw [`Mouse`] events to simulate a single-finger touch, and this will trigger all the same pointer events.
|
||||
|
||||
### Dispatching TouchEvent
|
||||
|
||||
You can dispatch touch events to the page using [`method: Locator.dispatchEvent`]. [Touch](https://developer.mozilla.org/en-US/docs/Web/API/Touch) points can be passed as arguments, see examples below.
|
||||
|
||||
#### Emulating pan gesture
|
||||
### Emulating pan gesture
|
||||
|
||||
In the example below, we emulate pan gesture that is expected to move the map. The app under test only uses `clientX/clientY` coordinates of the touch point, so we initialize just that. In a more complex scenario you may need to also set `pageX/pageY/screenX/screenY`, if your app needs them.
|
||||
|
||||
|
|
@ -69,7 +63,7 @@ test(`pan gesture to move the map`, async ({ page }) => {
|
|||
});
|
||||
```
|
||||
|
||||
#### Emulating pinch gesture
|
||||
### Emulating pinch gesture
|
||||
|
||||
In the example below, we emulate pinch gesture, i.e. two touch points moving closer to each other. It is expected to zoom out the map. The app under test only uses `clientX/clientY` coordinates of touch points, so we initialize just that. In a more complex scenario you may need to also set `pageX/pageY/screenX/screenY`, if your app needs them.
|
||||
|
||||
|
|
|
|||
|
|
@ -72,9 +72,9 @@ Using the following, Playwright will run your WebView2 application as a sub-proc
|
|||
|
||||
```js title="webView2Test.ts"
|
||||
import { test as base } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import childProcess from 'child_process';
|
||||
|
||||
const EXECUTABLE_PATH = path.join(
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ const noBooleanCompareRules = {
|
|||
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 2,
|
||||
};
|
||||
|
||||
const noRestrictedGlobalsRules = {
|
||||
const noWebGlobalsRules = {
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
{ 'name': 'window' },
|
||||
|
|
@ -186,6 +186,13 @@ const noRestrictedGlobalsRules = {
|
|||
],
|
||||
};
|
||||
|
||||
const noNodeGlobalsRules = {
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
{ 'name': 'process' },
|
||||
],
|
||||
};
|
||||
|
||||
const importOrderRules = {
|
||||
'import/order': [2, {
|
||||
'groups': ['builtin', 'external', 'internal', ['parent', 'sibling'], 'index', 'type'],
|
||||
|
|
@ -249,7 +256,19 @@ export default [{
|
|||
files: ['packages/playwright-core/src/server/injected/**/*.ts'],
|
||||
languageOptions: languageOptionsWithTsConfig,
|
||||
rules: {
|
||||
...noRestrictedGlobalsRules,
|
||||
...noWebGlobalsRules,
|
||||
...noFloatingPromisesRules,
|
||||
...noBooleanCompareRules,
|
||||
}
|
||||
}, {
|
||||
files: [
|
||||
'packages/playwright-core/src/client/**/*.ts',
|
||||
'packages/playwright-core/src/protocol/**/*.ts',
|
||||
'packages/playwright-core/src/utils/**/*.ts',
|
||||
],
|
||||
languageOptions: languageOptionsWithTsConfig,
|
||||
rules: {
|
||||
...noNodeGlobalsRules,
|
||||
...noFloatingPromisesRules,
|
||||
...noBooleanCompareRules,
|
||||
}
|
||||
|
|
|
|||
3796
package-lock.json
generated
3796
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -105,9 +105,9 @@
|
|||
"react-dom": "^18.1.0",
|
||||
"ssim.js": "^3.5.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^5.4.14",
|
||||
"vite": "^6.1.0",
|
||||
"ws": "^8.17.1",
|
||||
"xml2js": "^0.5.0",
|
||||
"yaml": "^2.6.0"
|
||||
"yaml": "2.6.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import type { Plugin, UserConfig } from 'vite';
|
||||
|
||||
export function bundle(): Plugin {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@
|
|||
*/
|
||||
|
||||
import { devices, defineConfig } from '@playwright/experimental-ct-react';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import path from 'path';
|
||||
import url from 'url';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: 'src',
|
||||
|
|
|
|||
|
|
@ -267,6 +267,26 @@ article, aside, details, figcaption, figure, footer, header, main, menu, nav, se
|
|||
flex: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
flex: none;
|
||||
height: 24px;
|
||||
border: 1px solid var(--color-btn-border);
|
||||
outline: none;
|
||||
color: var(--color-btn-text);
|
||||
background: var(--color-btn-bg);
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.button:not(:disabled):hover {
|
||||
border-color: var(--color-btn-hover-border);
|
||||
background-color: var(--color-btn-hover-bg);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.subnav-item, .form-control {
|
||||
border-radius: 0 !important;
|
||||
|
|
|
|||
|
|
@ -37,10 +37,6 @@
|
|||
line-height: 24px;
|
||||
}
|
||||
|
||||
.metadata-section {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.metadata-properties {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -57,9 +53,8 @@
|
|||
border-bottom: 1px solid var(--color-border-default);
|
||||
}
|
||||
|
||||
.git-commit-info a {
|
||||
.metadata-view a {
|
||||
color: var(--color-fg-default);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.copyable-property {
|
||||
|
|
|
|||
|
|
@ -87,12 +87,12 @@ const InnerMetadataView = () => {
|
|||
<GitCommitInfoView info={gitCommitInfo}/>
|
||||
{entries.length > 0 && <div className='metadata-separator' />}
|
||||
</>}
|
||||
<div className='metadata-section metadata-properties'>
|
||||
<div className='metadata-section metadata-properties' role='list'>
|
||||
{entries.map(([propertyName, value]) => {
|
||||
const valueString = typeof value !== 'object' || value === null || value === undefined ? String(value) : JSON.stringify(value);
|
||||
const trimmedValue = valueString.length > 1000 ? valueString.slice(0, 1000) + '\u2026' : valueString;
|
||||
return (
|
||||
<div key={propertyName} className='copyable-property'>
|
||||
<div key={propertyName} className='copyable-property' role='listitem'>
|
||||
<CopyToClipboardContainer value={valueString}>
|
||||
<span style={{ fontWeight: 'bold' }} title={propertyName}>{propertyName}</span>
|
||||
: <span title={trimmedValue}>{linkifyText(trimmedValue)}</span>
|
||||
|
|
@ -105,47 +105,38 @@ const InnerMetadataView = () => {
|
|||
};
|
||||
|
||||
const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => {
|
||||
const email = info['revision.email'] ? ` <${info['revision.email']}>` : '';
|
||||
const author = `${info['revision.author'] || ''}${email}`;
|
||||
const email = info.revision?.email ? ` <${info.revision?.email}>` : '';
|
||||
const author = `${info.revision?.author || ''}${email}`;
|
||||
|
||||
let subject = info['revision.subject'] || '';
|
||||
let link = info['revision.link'];
|
||||
let shortSubject = info['revision.id']?.slice(0, 7) || 'unknown';
|
||||
let subject = info.revision?.subject || '';
|
||||
let link = info.revision?.link;
|
||||
|
||||
if (info['pull.link'] && info['pull.title']) {
|
||||
subject = info['pull.title'];
|
||||
link = info['pull.link'];
|
||||
shortSubject = link ? 'Pull Request' : '';
|
||||
if (info.pull_request?.link && info.pull_request?.title) {
|
||||
subject = info.pull_request?.title;
|
||||
link = info.pull_request?.link;
|
||||
}
|
||||
|
||||
const shortTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(info['revision.timestamp']);
|
||||
const longTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).format(info['revision.timestamp']);
|
||||
return <div className='hbox git-commit-info metadata-section'>
|
||||
<div className='vbox metadata-properties'>
|
||||
<div>
|
||||
{link ? (
|
||||
<a href={link} target='_blank' rel='noopener noreferrer' title={subject}>
|
||||
{subject}
|
||||
</a>
|
||||
) : <span title={subject}>
|
||||
const shortTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(info.revision?.timestamp);
|
||||
const longTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).format(info.revision?.timestamp);
|
||||
return <div className='metadata-section' role='list'>
|
||||
<div role='listitem'>
|
||||
{link ? (
|
||||
<a href={link} target='_blank' rel='noopener noreferrer' title={subject}>
|
||||
{subject}
|
||||
</span>}
|
||||
</div>
|
||||
<div className='hbox'>
|
||||
<span className='mr-1'>{author}</span>
|
||||
<span title={longTimestamp}> on {shortTimestamp}</span>
|
||||
{info['ci.link'] && (
|
||||
<>
|
||||
<span className='mx-2'>·</span>
|
||||
<a href={info['ci.link']} target='_blank' rel='noopener noreferrer' title='CI/CD logs'>Logs</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
) : <span title={subject}>
|
||||
{subject}
|
||||
</span>}
|
||||
</div>
|
||||
<div role='listitem' className='hbox'>
|
||||
<span className='mr-1'>{author}</span>
|
||||
<span title={longTimestamp}> on {shortTimestamp}</span>
|
||||
{info.ci?.link && (
|
||||
<>
|
||||
<span className='mx-2'>·</span>
|
||||
<a href={info.ci?.link} target='_blank' rel='noopener noreferrer' title='CI/CD logs'>Logs</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{link ? (
|
||||
<a href={link} target='_blank' rel='noopener noreferrer' title='View commit details'>
|
||||
{shortSubject}
|
||||
</a>
|
||||
) : !!shortSubject && <span>{shortSubject}</span>}
|
||||
</div>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,29 +34,3 @@
|
|||
.test-error-text {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.prompt-button {
|
||||
flex: none;
|
||||
height: 24px;
|
||||
width: 80px;
|
||||
border: 1px solid var(--color-btn-border);
|
||||
outline: none;
|
||||
color: var(--color-btn-text);
|
||||
background: var(--color-btn-bg);
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.prompt-button svg {
|
||||
color: var(--color-fg-subtle);
|
||||
}
|
||||
|
||||
.prompt-button:not(:disabled):hover {
|
||||
border-color: var(--color-btn-hover-border);
|
||||
background-color: var(--color-btn-hover-bg);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
import { ansi2html } from '@web/ansi2html';
|
||||
import * as React from 'react';
|
||||
import './testErrorView.css';
|
||||
import * as icons from './icons';
|
||||
import type { ImageDiff } from '@web/shared/imageDiffView';
|
||||
import { ImageDiffView } from '@web/shared/imageDiffView';
|
||||
import type { TestResult } from './types';
|
||||
|
|
@ -27,7 +26,7 @@ import { useGitCommitInfo } from './metadataView';
|
|||
export const TestErrorView: React.FC<{ error: string; testId?: string; result?: TestResult }> = ({ error, testId, result }) => {
|
||||
return (
|
||||
<CodeSnippet code={error} testId={testId}>
|
||||
<div style={{ float: 'right', padding: '5px' }}>
|
||||
<div style={{ float: 'right', margin: 10 }}>
|
||||
<PromptButton error={error} result={result} />
|
||||
</div>
|
||||
</CodeSnippet>
|
||||
|
|
@ -51,14 +50,15 @@ const PromptButton: React.FC<{
|
|||
const gitCommitInfo = useGitCommitInfo();
|
||||
const prompt = React.useMemo(() => fixTestPrompt(
|
||||
error,
|
||||
gitCommitInfo?.['pull.diff'] ?? gitCommitInfo?.['revision.diff'],
|
||||
gitCommitInfo?.pull_request?.diff ?? gitCommitInfo?.revision?.diff,
|
||||
result?.attachments.find(a => a.name === 'pageSnapshot')?.body
|
||||
), [gitCommitInfo, result, error]);
|
||||
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
|
||||
return <button
|
||||
className='prompt-button'
|
||||
className='button'
|
||||
style={{ minWidth: 100 }}
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(prompt);
|
||||
setCopied(true);
|
||||
|
|
@ -66,7 +66,7 @@ const PromptButton: React.FC<{
|
|||
setCopied(false);
|
||||
}, 3000);
|
||||
}}>
|
||||
{copied ? <span className='prompt-button-copied'>Copied <icons.copy/></span> : 'Fix with AI'}
|
||||
{copied ? 'Copied' : 'Copy as Prompt'}
|
||||
</button>;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { bundle } from './bundle';
|
||||
import * as path from 'path';
|
||||
import path from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
|
|
|
|||
25
packages/playwright-client/index.d.ts
vendored
Normal file
25
packages/playwright-client/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Browser } from './types/types';
|
||||
|
||||
export * from './types/types';
|
||||
|
||||
export type Options = {
|
||||
headless?: boolean;
|
||||
};
|
||||
|
||||
export const connect: (wsEndpoint: string, browserName: string, options: Options) => Promise<Browser>;
|
||||
17
packages/playwright-client/index.js
Normal file
17
packages/playwright-client/index.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports = require('./lib/index');
|
||||
17
packages/playwright-client/index.mjs
Normal file
17
packages/playwright-client/index.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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 { connect } from './index.js';
|
||||
34
packages/playwright-client/package.json
Normal file
34
packages/playwright-client/package.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "@playwright/client",
|
||||
"private": true,
|
||||
"version": "1.51.0-next",
|
||||
"description": "A thin client for Playwright",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/microsoft/playwright.git"
|
||||
},
|
||||
"homepage": "https://playwright.dev",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.mjs",
|
||||
"require": "./index.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "esbuild ./src/index.ts --outdir=lib --format=cjs --bundle --platform=node --target=ES2019",
|
||||
"watch": "esbuild ./src/index.ts --outdir=lib --format=cjs --bundle --platform=node --target=ES2019 --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.51.0-next"
|
||||
}
|
||||
}
|
||||
40
packages/playwright-client/src/index.ts
Normal file
40
packages/playwright-client/src/index.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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 { Connection } from '../../playwright-core/src/client/connection';
|
||||
import { webPlatform } from './webPlatform';
|
||||
|
||||
import type { Browser } from '../../playwright-core/src/client/browser';
|
||||
|
||||
export type Options = {
|
||||
headless?: boolean;
|
||||
};
|
||||
|
||||
export async function connect(wsEndpoint: string, browserName: string, options: Options): Promise<Browser> {
|
||||
const ws = new WebSocket(`${wsEndpoint}?browser=${browserName}&launch-options=${JSON.stringify(options)}`);
|
||||
await new Promise((f, r) => {
|
||||
ws.addEventListener('open', f);
|
||||
ws.addEventListener('error', r);
|
||||
});
|
||||
|
||||
const connection = new Connection(webPlatform);
|
||||
connection.onmessage = message => ws.send(JSON.stringify(message));
|
||||
ws.addEventListener('message', message => connection.dispatch(JSON.parse(message.data)));
|
||||
ws.addEventListener('close', () => connection.close());
|
||||
|
||||
const playwright = await connection.initializePlaywright();
|
||||
return playwright._preLaunchedBrowser();
|
||||
}
|
||||
48
packages/playwright-client/src/webPlatform.ts
Normal file
48
packages/playwright-client/src/webPlatform.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/* eslint-disable no-console */
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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 { emptyPlatform } from '../../playwright-core/src/client/platform';
|
||||
|
||||
import type { Platform } from '../../playwright-core/src/client/platform';
|
||||
|
||||
export const webPlatform: Platform = {
|
||||
...emptyPlatform,
|
||||
|
||||
name: 'web',
|
||||
|
||||
boxedStackPrefixes: () => [],
|
||||
|
||||
calculateSha1: async (text: string) => {
|
||||
const bytes = new TextEncoder().encode(text);
|
||||
const hashBuffer = await window.crypto.subtle.digest('SHA-1', bytes);
|
||||
return Array.from(new Uint8Array(hashBuffer), b => b.toString(16).padStart(2, '0')).join('');
|
||||
},
|
||||
|
||||
createGuid: () => {
|
||||
return Array.from(window.crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join('');
|
||||
},
|
||||
|
||||
isLogEnabled(name: 'api' | 'channel') {
|
||||
return true;
|
||||
},
|
||||
|
||||
log(name: 'api' | 'channel', message: string | Error | object) {
|
||||
console.debug(name, message);
|
||||
},
|
||||
|
||||
showInternalStackFrames: () => true,
|
||||
};
|
||||
22992
packages/playwright-client/types/types.d.ts
vendored
Normal file
22992
packages/playwright-client/types/types.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -3,27 +3,27 @@
|
|||
"browsers": [
|
||||
{
|
||||
"name": "chromium",
|
||||
"revision": "1159",
|
||||
"revision": "1160",
|
||||
"installByDefault": true,
|
||||
"browserVersion": "134.0.6998.15"
|
||||
"browserVersion": "134.0.6998.23"
|
||||
},
|
||||
{
|
||||
"name": "chromium-headless-shell",
|
||||
"revision": "1159",
|
||||
"revision": "1160",
|
||||
"installByDefault": true,
|
||||
"browserVersion": "134.0.6998.15"
|
||||
"browserVersion": "134.0.6998.23"
|
||||
},
|
||||
{
|
||||
"name": "chromium-tip-of-tree",
|
||||
"revision": "1303",
|
||||
"revision": "1304",
|
||||
"installByDefault": false,
|
||||
"browserVersion": "135.0.7015.0"
|
||||
"browserVersion": "135.0.7021.0"
|
||||
},
|
||||
{
|
||||
"name": "chromium-tip-of-tree-headless-shell",
|
||||
"revision": "1303",
|
||||
"revision": "1304",
|
||||
"installByDefault": false,
|
||||
"browserVersion": "135.0.7015.0"
|
||||
"browserVersion": "135.0.7021.0"
|
||||
},
|
||||
{
|
||||
"name": "firefox",
|
||||
|
|
@ -33,13 +33,13 @@
|
|||
},
|
||||
{
|
||||
"name": "firefox-beta",
|
||||
"revision": "1470",
|
||||
"revision": "1471",
|
||||
"installByDefault": false,
|
||||
"browserVersion": "135.0b10"
|
||||
"browserVersion": "136.0b4"
|
||||
},
|
||||
{
|
||||
"name": "webkit",
|
||||
"revision": "2137",
|
||||
"revision": "2140",
|
||||
"installByDefault": true,
|
||||
"revisionOverrides": {
|
||||
"debian11-x64": "2105",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import * as fs from 'fs';
|
||||
import fs from 'fs';
|
||||
|
||||
import * as playwright from '../..';
|
||||
import { PipeTransport } from '../server/utils/pipeTransport';
|
||||
|
|
|
|||
|
|
@ -16,16 +16,16 @@
|
|||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import * as playwright from '../..';
|
||||
import { launchBrowserServer, printApiJson, runDriver, runServer } from './driver';
|
||||
import { registry, writeDockerVersion } from '../server';
|
||||
import { gracefullyProcessExitDoNotHang } from '../utils';
|
||||
import { gracefullyProcessExitDoNotHang, isLikelyNpxGlobal } from '../utils';
|
||||
import { runTraceInBrowser, runTraceViewerApp } from '../server/trace/viewer/traceViewer';
|
||||
import { assert, getPackageManagerExecCommand, isLikelyNpxGlobal } from '../utils';
|
||||
import { assert, getPackageManagerExecCommand } from '../utils';
|
||||
import { wrapInASCIIBox } from '../server/utils/ascii';
|
||||
import { dotenv, program } from '../utilsBundle';
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
|
|||
|
||||
setDefaultTimeout(timeout: number) {
|
||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||
this._channel.setDefaultTimeoutNoReply({ timeout });
|
||||
this._wrapApiCall(async () => {
|
||||
await this._channel.setDefaultTimeoutNoReply({ timeout });
|
||||
}, true).catch(() => {});
|
||||
}
|
||||
|
||||
async devices(options: { port?: number } = {}): Promise<AndroidDevice[]> {
|
||||
|
|
@ -133,7 +135,9 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
|
|||
|
||||
setDefaultTimeout(timeout: number) {
|
||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||
this._channel.setDefaultTimeoutNoReply({ timeout });
|
||||
this._wrapApiCall(async () => {
|
||||
await this._channel.setDefaultTimeoutNoReply({ timeout });
|
||||
}, true).catch(() => {});
|
||||
}
|
||||
|
||||
serial(): string {
|
||||
|
|
@ -393,7 +397,7 @@ export class AndroidWebView extends EventEmitter implements api.AndroidWebView {
|
|||
private _pagePromise: Promise<Page> | undefined;
|
||||
|
||||
constructor(device: AndroidDevice, data: channels.AndroidWebView) {
|
||||
super();
|
||||
super(device._platform);
|
||||
this._device = device;
|
||||
this._data = data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -246,15 +246,15 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
|||
setDefaultNavigationTimeout(timeout: number | undefined) {
|
||||
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||
this._wrapApiCall(async () => {
|
||||
this._channel.setDefaultNavigationTimeoutNoReply({ timeout }).catch(() => {});
|
||||
}, true);
|
||||
await this._channel.setDefaultNavigationTimeoutNoReply({ timeout });
|
||||
}, true).catch(() => {});
|
||||
}
|
||||
|
||||
setDefaultTimeout(timeout: number | undefined) {
|
||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||
this._wrapApiCall(async () => {
|
||||
this._channel.setDefaultTimeoutNoReply({ timeout }).catch(() => {});
|
||||
}, true);
|
||||
await this._channel.setDefaultTimeoutNoReply({ timeout });
|
||||
}, true).catch(() => {});
|
||||
}
|
||||
|
||||
browser(): Browser | null {
|
||||
|
|
@ -559,7 +559,7 @@ export async function prepareBrowserContextParams(platform: Platform, options: B
|
|||
};
|
||||
}
|
||||
if (contextParams.recordVideo && contextParams.recordVideo.dir)
|
||||
contextParams.recordVideo.dir = platform.path().resolve(process.cwd(), contextParams.recordVideo.dir);
|
||||
contextParams.recordVideo.dir = platform.path().resolve(contextParams.recordVideo.dir);
|
||||
return contextParams;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,21 +38,20 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
|||
readonly _channel: T;
|
||||
readonly _initializer: channels.InitializerTraits<T>;
|
||||
_logger: Logger | undefined;
|
||||
readonly _platform: Platform;
|
||||
readonly _instrumentation: ClientInstrumentation;
|
||||
private _eventToSubscriptionMapping: Map<string, string> = new Map();
|
||||
private _isInternalType = false;
|
||||
_wasCollected: boolean = false;
|
||||
|
||||
constructor(parent: ChannelOwner | Connection, type: string, guid: string, initializer: channels.InitializerTraits<T>) {
|
||||
super();
|
||||
const connection = parent instanceof ChannelOwner ? parent._connection : parent;
|
||||
super(connection._platform);
|
||||
this.setMaxListeners(0);
|
||||
this._connection = parent instanceof ChannelOwner ? parent._connection : parent;
|
||||
this._connection = connection;
|
||||
this._type = type;
|
||||
this._guid = guid;
|
||||
this._parent = parent instanceof ChannelOwner ? parent : undefined;
|
||||
this._instrumentation = this._connection._instrumentation;
|
||||
this._platform = this._connection.platform;
|
||||
|
||||
this._connection._objects.set(guid, this);
|
||||
if (this._parent) {
|
||||
|
|
@ -60,7 +59,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
|||
this._logger = this._parent._logger;
|
||||
}
|
||||
|
||||
this._channel = this._createChannel(new EventEmitter());
|
||||
this._channel = this._createChannel(new EventEmitter(connection._platform));
|
||||
this._initializer = initializer;
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +141,14 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
|||
};
|
||||
}
|
||||
|
||||
private _validatorToWireContext(): ValidatorContext {
|
||||
return {
|
||||
tChannelImpl: tChannelImplToWire,
|
||||
binary: this._connection.rawBuffers() ? 'buffer' : 'toBase64',
|
||||
isUnderTest: () => this._platform.isUnderTest(),
|
||||
};
|
||||
}
|
||||
|
||||
private _createChannel(base: Object): T {
|
||||
const channel = new Proxy(base, {
|
||||
get: (obj: any, prop: string | symbol) => {
|
||||
|
|
@ -150,7 +157,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
|||
if (validator) {
|
||||
return async (params: any) => {
|
||||
return await this._wrapApiCall(async apiZone => {
|
||||
const validatedParams = validator(params, '', { tChannelImpl: tChannelImplToWire, binary: this._connection.rawBuffers() ? 'buffer' : 'toBase64' });
|
||||
const validatedParams = validator(params, '', this._validatorToWireContext());
|
||||
if (!apiZone.isInternal && !apiZone.reported) {
|
||||
// Reporting/tracing/logging this api call for the first time.
|
||||
apiZone.params = params;
|
||||
|
|
@ -192,7 +199,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
|||
}
|
||||
return result;
|
||||
} catch (e) {
|
||||
const innerError = ((process.env.PWDEBUGIMPL || this._platform.isUnderTest()) && e.stack) ? '\n<inner error>\n' + e.stack : '';
|
||||
const innerError = ((this._platform.showInternalStackFrames() || this._platform.isUnderTest()) && e.stack) ? '\n<inner error>\n' + e.stack : '';
|
||||
if (apiZone.apiName && !apiZone.apiName.includes('<anonymous>'))
|
||||
e.message = apiZone.apiName + ': ' + e.message;
|
||||
const stackFrames = '\n' + stringifyStackFrames(stackTrace.frames).join('\n') + innerError;
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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 { Connection } from './connection';
|
||||
import { setPlatformForSelectors } from './selectors';
|
||||
import { setPlatformForEventEmitter } from './eventEmitter';
|
||||
import { setIsUnderTestForValidator } from '../protocol/validatorPrimitives';
|
||||
|
||||
import type { Platform } from './platform';
|
||||
|
||||
export function createConnectionFactory(platform: Platform): () => Connection {
|
||||
setPlatformForSelectors(platform);
|
||||
setPlatformForEventEmitter(platform);
|
||||
setIsUnderTestForValidator(() => platform.isUnderTest());
|
||||
return () => new Connection(platform);
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ export function captureLibraryStackTrace(platform: Platform): { frames: StackFra
|
|||
isPlaywrightLibrary: boolean;
|
||||
};
|
||||
let parsedFrames = stack.map(line => {
|
||||
const frame = parseStackFrame(line, platform.pathSeparator);
|
||||
const frame = parseStackFrame(line, platform.pathSeparator, platform.showInternalStackFrames());
|
||||
if (!frame || !frame.file)
|
||||
return null;
|
||||
const isPlaywrightLibrary = !!platform.coreDir && frame.file.startsWith(platform.coreDir);
|
||||
|
|
@ -62,10 +62,8 @@ export function captureLibraryStackTrace(platform: Platform): { frames: StackFra
|
|||
}
|
||||
|
||||
// This is for the inspector so that it did not include the test runner stack frames.
|
||||
const filterPrefixes = platform.coreDir ? [platform.coreDir, ...platform.boxedStackPrefixes()] : platform.boxedStackPrefixes();
|
||||
const filterPrefixes = platform.boxedStackPrefixes();
|
||||
parsedFrames = parsedFrames.filter(f => {
|
||||
if (process.env.PWDEBUGIMPL)
|
||||
return true;
|
||||
if (filterPrefixes.some(prefix => f.frame.file.startsWith(prefix)))
|
||||
return false;
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -78,15 +78,13 @@ export class Connection extends EventEmitter {
|
|||
toImpl: ((client: ChannelOwner) => any) | undefined;
|
||||
private _tracingCount = 0;
|
||||
readonly _instrumentation: ClientInstrumentation;
|
||||
readonly platform: Platform;
|
||||
// Used from @playwright/test fixtures -> TODO remove?
|
||||
readonly headers: HeadersArray;
|
||||
|
||||
constructor(platform: Platform, localUtils?: LocalUtils, instrumentation?: ClientInstrumentation, headers: HeadersArray = []) {
|
||||
super();
|
||||
super(platform);
|
||||
this._instrumentation = instrumentation || createInstrumentation();
|
||||
this._localUtils = localUtils;
|
||||
this.platform = platform;
|
||||
this._rootObject = new Root(this);
|
||||
this.headers = headers;
|
||||
}
|
||||
|
|
@ -136,9 +134,9 @@ export class Connection extends EventEmitter {
|
|||
const type = object._type;
|
||||
const id = ++this._lastId;
|
||||
const message = { id, guid, method, params };
|
||||
if (this.platform.isLogEnabled('channel')) {
|
||||
if (this._platform.isLogEnabled('channel')) {
|
||||
// Do not include metadata in debug logs to avoid noise.
|
||||
this.platform.log('channel', 'SEND> ' + JSON.stringify(message));
|
||||
this._platform.log('channel', 'SEND> ' + JSON.stringify(message));
|
||||
}
|
||||
const location = frames[0] ? { file: frames[0].file, line: frames[0].line, column: frames[0].column } : undefined;
|
||||
const metadata: channels.Metadata = { apiName, location, internal: !apiName, stepId };
|
||||
|
|
@ -146,35 +144,43 @@ export class Connection extends EventEmitter {
|
|||
this._localUtils?.addStackToTracingNoReply({ callData: { stack: frames, id } }).catch(() => {});
|
||||
// We need to exit zones before calling into the server, otherwise
|
||||
// when we receive events from the server, we would be in an API zone.
|
||||
this.platform.zones.empty.run(() => this.onmessage({ ...message, metadata }));
|
||||
this._platform.zones.empty.run(() => this.onmessage({ ...message, metadata }));
|
||||
return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject, apiName, type, method }));
|
||||
}
|
||||
|
||||
private _validatorFromWireContext(): ValidatorContext {
|
||||
return {
|
||||
tChannelImpl: this._tChannelImplFromWire.bind(this),
|
||||
binary: this._rawBuffers ? 'buffer' : 'fromBase64',
|
||||
isUnderTest: () => this._platform.isUnderTest(),
|
||||
};
|
||||
}
|
||||
|
||||
dispatch(message: object) {
|
||||
if (this._closedError)
|
||||
return;
|
||||
|
||||
const { id, guid, method, params, result, error, log } = message as any;
|
||||
if (id) {
|
||||
if (this.platform.isLogEnabled('channel'))
|
||||
this.platform.log('channel', '<RECV ' + JSON.stringify(message));
|
||||
if (this._platform.isLogEnabled('channel'))
|
||||
this._platform.log('channel', '<RECV ' + JSON.stringify(message));
|
||||
const callback = this._callbacks.get(id);
|
||||
if (!callback)
|
||||
throw new Error(`Cannot find command to respond: ${id}`);
|
||||
this._callbacks.delete(id);
|
||||
if (error && !result) {
|
||||
const parsedError = parseError(error);
|
||||
rewriteErrorMessage(parsedError, parsedError.message + formatCallLog(this.platform, log));
|
||||
rewriteErrorMessage(parsedError, parsedError.message + formatCallLog(this._platform, log));
|
||||
callback.reject(parsedError);
|
||||
} else {
|
||||
const validator = findValidator(callback.type, callback.method, 'Result');
|
||||
callback.resolve(validator(result, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this._rawBuffers ? 'buffer' : 'fromBase64' }));
|
||||
callback.resolve(validator(result, '', this._validatorFromWireContext()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.platform.isLogEnabled('channel'))
|
||||
this.platform.log('channel', '<EVENT ' + JSON.stringify(message));
|
||||
if (this._platform.isLogEnabled('channel'))
|
||||
this._platform.log('channel', '<EVENT ' + JSON.stringify(message));
|
||||
if (method === '__create__') {
|
||||
this._createRemoteObject(guid, params.type, params.guid, params.initializer);
|
||||
return;
|
||||
|
|
@ -198,7 +204,7 @@ export class Connection extends EventEmitter {
|
|||
}
|
||||
|
||||
const validator = findValidator(object._type, method, 'Event');
|
||||
(object._channel as any).emit(method, validator(params, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this._rawBuffers ? 'buffer' : 'fromBase64' }));
|
||||
(object._channel as any).emit(method, validator(params, '', this._validatorFromWireContext()));
|
||||
}
|
||||
|
||||
close(cause?: string) {
|
||||
|
|
@ -229,7 +235,7 @@ export class Connection extends EventEmitter {
|
|||
throw new Error(`Cannot find parent object ${parentGuid} to create ${guid}`);
|
||||
let result: ChannelOwner<any>;
|
||||
const validator = findValidator(type, '', 'Initializer');
|
||||
initializer = validator(initializer, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this._rawBuffers ? 'buffer' : 'fromBase64' });
|
||||
initializer = validator(initializer, '', this._validatorFromWireContext());
|
||||
switch (type) {
|
||||
case 'Android':
|
||||
result = new Android(parent, type, guid, initializer);
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export class Electron extends ChannelOwner<channels.ElectronChannel> implements
|
|||
async launch(options: ElectronOptions = {}): Promise<ElectronApplication> {
|
||||
const params: channels.ElectronLaunchParams = {
|
||||
...await prepareBrowserContextParams(this._platform, options),
|
||||
env: envObjectToArray(options.env ? options.env : process.env),
|
||||
env: envObjectToArray(options.env ? options.env : this._platform.env),
|
||||
tracesDir: options.tracesDir,
|
||||
};
|
||||
const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@
|
|||
* USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { emptyPlatform } from './platform';
|
||||
|
||||
import type { EventEmitter as EventEmitterType } from 'events';
|
||||
import type { Platform } from './platform';
|
||||
|
||||
|
|
@ -31,12 +29,6 @@ type EventType = string | symbol;
|
|||
type Listener = (...args: any[]) => any;
|
||||
type EventMap = Record<EventType, Listener | Listener[]>;
|
||||
|
||||
let platform = emptyPlatform;
|
||||
|
||||
export function setPlatformForEventEmitter(p: Platform) {
|
||||
platform = p;
|
||||
}
|
||||
|
||||
export class EventEmitter implements EventEmitterType {
|
||||
|
||||
private _events: EventMap | undefined = undefined;
|
||||
|
|
@ -44,8 +36,10 @@ export class EventEmitter implements EventEmitterType {
|
|||
private _maxListeners: number | undefined = undefined;
|
||||
readonly _pendingHandlers = new Map<EventType, Set<Promise<void>>>();
|
||||
private _rejectionHandler: ((error: Error) => void) | undefined;
|
||||
readonly _platform: Platform;
|
||||
|
||||
constructor() {
|
||||
constructor(platform: Platform) {
|
||||
this._platform = platform;
|
||||
if (this._events === undefined || this._events === Object.getPrototypeOf(this)._events) {
|
||||
this._events = Object.create(null);
|
||||
this._eventsCount = 0;
|
||||
|
|
@ -63,7 +57,7 @@ export class EventEmitter implements EventEmitterType {
|
|||
}
|
||||
|
||||
getMaxListeners(): number {
|
||||
return this._maxListeners === undefined ? platform.defaultMaxListeners() : this._maxListeners;
|
||||
return this._maxListeners === undefined ? this._platform.defaultMaxListeners() : this._maxListeners;
|
||||
}
|
||||
|
||||
emit(type: EventType, ...args: any[]): boolean {
|
||||
|
|
@ -161,7 +155,7 @@ export class EventEmitter implements EventEmitterType {
|
|||
w.emitter = this;
|
||||
w.type = type;
|
||||
w.count = existing.length;
|
||||
if (!platform.isUnderTest()) {
|
||||
if (!this._platform.isUnderTest()) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(w);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -338,16 +338,18 @@ export class APIResponse implements api.APIResponse {
|
|||
}
|
||||
|
||||
async body(): Promise<Buffer> {
|
||||
try {
|
||||
const result = await this._request._channel.fetchResponseBody({ fetchUid: this._fetchUid() });
|
||||
if (result.binary === undefined)
|
||||
throw new Error('Response has been disposed');
|
||||
return result.binary;
|
||||
} catch (e) {
|
||||
if (isTargetClosedError(e))
|
||||
throw new Error('Response has been disposed');
|
||||
throw e;
|
||||
}
|
||||
return await this._request._wrapApiCall(async () => {
|
||||
try {
|
||||
const result = await this._request._channel.fetchResponseBody({ fetchUid: this._fetchUid() });
|
||||
if (result.binary === undefined)
|
||||
throw new Error('Response has been disposed');
|
||||
return result.binary;
|
||||
} catch (e) {
|
||||
if (isTargetClosedError(e))
|
||||
throw new Error('Response has been disposed');
|
||||
throw e;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
async text(): Promise<string> {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
|
|||
|
||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.FrameInitializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
this._eventEmitter = new EventEmitter();
|
||||
this._eventEmitter = new EventEmitter(parent._platform);
|
||||
this._eventEmitter.setMaxListeners(0);
|
||||
this._parentFrame = Frame.fromNullable(initializer.parentFrame);
|
||||
if (this._parentFrame)
|
||||
|
|
|
|||
|
|
@ -218,6 +218,11 @@ export class Locator implements api.Locator {
|
|||
return new Locator(this._frame, this._selector + ` >> nth=${index}`);
|
||||
}
|
||||
|
||||
visible(options: { visible?: boolean } = {}): Locator {
|
||||
const { visible = true } = options;
|
||||
return new Locator(this._frame, this._selector + ` >> visible=${visible ? 'true' : 'false'}`);
|
||||
}
|
||||
|
||||
and(locator: Locator): Locator {
|
||||
if (locator._frame !== this._frame)
|
||||
throw new Error(`Locators must belong to the same frame.`);
|
||||
|
|
|
|||
|
|
@ -277,15 +277,15 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
|||
setDefaultNavigationTimeout(timeout: number) {
|
||||
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||
this._wrapApiCall(async () => {
|
||||
this._channel.setDefaultNavigationTimeoutNoReply({ timeout }).catch(() => {});
|
||||
}, true);
|
||||
await this._channel.setDefaultNavigationTimeoutNoReply({ timeout });
|
||||
}, true).catch(() => {});
|
||||
}
|
||||
|
||||
setDefaultTimeout(timeout: number) {
|
||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||
this._wrapApiCall(async () => {
|
||||
this._channel.setDefaultTimeoutNoReply({ timeout }).catch(() => {});
|
||||
}, true);
|
||||
await this._channel.setDefaultTimeoutNoReply({ timeout });
|
||||
}, true).catch(() => {});
|
||||
}
|
||||
|
||||
private _forceVideo(): Video {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { webColors, noColors } from '../utils/isomorphic/colors';
|
||||
import { webColors } from '../utils/isomorphic/colors';
|
||||
|
||||
import type * as fs from 'fs';
|
||||
import type * as path from 'path';
|
||||
|
|
@ -45,6 +45,7 @@ export type Platform = {
|
|||
coreDir?: string;
|
||||
createGuid: () => string;
|
||||
defaultMaxListeners: () => number;
|
||||
env: Record<string, string | undefined>;
|
||||
fs: () => typeof fs;
|
||||
inspectCustom: symbol | undefined;
|
||||
isDebugMode: () => boolean;
|
||||
|
|
@ -54,6 +55,7 @@ export type Platform = {
|
|||
log: (name: 'api' | 'channel', message: string | Error | object) => void;
|
||||
path: () => typeof path;
|
||||
pathSeparator: string;
|
||||
showInternalStackFrames: () => boolean,
|
||||
streamFile: (path: string, writable: Writable) => Promise<void>,
|
||||
streamReadable: (channel: channels.StreamChannel) => Readable,
|
||||
streamWritable: (channel: channels.WritableStreamChannel) => Writable,
|
||||
|
|
@ -69,7 +71,7 @@ export const emptyPlatform: Platform = {
|
|||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
colors: noColors,
|
||||
colors: webColors,
|
||||
|
||||
createGuid: () => {
|
||||
throw new Error('Not implemented');
|
||||
|
|
@ -77,6 +79,8 @@ export const emptyPlatform: Platform = {
|
|||
|
||||
defaultMaxListeners: () => 10,
|
||||
|
||||
env: {},
|
||||
|
||||
fs: () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
|
@ -101,6 +105,8 @@ export const emptyPlatform: Platform = {
|
|||
|
||||
pathSeparator: '/',
|
||||
|
||||
showInternalStackFrames: () => false,
|
||||
|
||||
streamFile(path: string, writable: Writable): Promise<void> {
|
||||
throw new Error('Streams are not available');
|
||||
},
|
||||
|
|
@ -115,23 +121,3 @@ export const emptyPlatform: Platform = {
|
|||
|
||||
zones: { empty: noopZone, current: () => noopZone },
|
||||
};
|
||||
|
||||
export const webPlatform: Platform = {
|
||||
...emptyPlatform,
|
||||
|
||||
name: 'web',
|
||||
|
||||
boxedStackPrefixes: () => [],
|
||||
|
||||
calculateSha1: async (text: string) => {
|
||||
const bytes = new TextEncoder().encode(text);
|
||||
const hashBuffer = await window.crypto.subtle.digest('SHA-1', bytes);
|
||||
return Array.from(new Uint8Array(hashBuffer), b => b.toString(16).padStart(2, '0')).join('');
|
||||
},
|
||||
|
||||
colors: webColors,
|
||||
|
||||
createGuid: () => {
|
||||
return Array.from(window.crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join('');
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
|
||||
import { Android } from './android';
|
||||
import { Browser } from './browser';
|
||||
import { BrowserType } from './browserType';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { Electron } from './electron';
|
||||
|
|
@ -86,6 +87,12 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
|
|||
return [this.chromium, this.firefox, this.webkit, this._bidiChromium, this._bidiFirefox];
|
||||
}
|
||||
|
||||
_preLaunchedBrowser(): Browser {
|
||||
const browser = Browser.from(this._initializer.preLaunchedBrowser!);
|
||||
browser._browserType = this[browser._name as 'chromium' | 'firefox' | 'webkit'];
|
||||
return browser;
|
||||
}
|
||||
|
||||
_allContexts() {
|
||||
return this._browserTypes().flatMap(type => [...type._contexts]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,8 +96,8 @@ export class Waiter {
|
|||
log(s: string) {
|
||||
this._logs.push(s);
|
||||
this._channelOwner._wrapApiCall(async () => {
|
||||
await this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'log', message: s } }).catch(() => {});
|
||||
}, true);
|
||||
await this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'log', message: s } });
|
||||
}, true).catch(() => {});
|
||||
}
|
||||
|
||||
private _rejectOn(promise: Promise<any>, dispose?: () => void) {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export async function connectOverWebSocket(parentConnection: Connection, params:
|
|||
const localUtils = parentConnection.localUtils();
|
||||
const transport = localUtils ? new JsonPipeTransport(localUtils) : new WebSocketTransport();
|
||||
const connectHeaders = await transport.connect(params);
|
||||
const connection = new Connection(parentConnection.platform, localUtils, parentConnection._instrumentation, connectHeaders);
|
||||
const connection = new Connection(parentConnection._platform, localUtils, parentConnection._instrumentation, connectHeaders);
|
||||
connection.markAsRemote();
|
||||
connection.on('close', () => transport.close());
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ export async function connectOverWebSocket(parentConnection: Connection, params:
|
|||
connection!.dispatch(message);
|
||||
} catch (e) {
|
||||
closeError = String(e);
|
||||
transport.close();
|
||||
transport.close().catch(() => {});
|
||||
}
|
||||
});
|
||||
return connection;
|
||||
|
|
@ -70,7 +70,7 @@ class JsonPipeTransport implements Transport {
|
|||
}
|
||||
|
||||
async send(message: object) {
|
||||
this._owner._wrapApiCall(async () => {
|
||||
await this._owner._wrapApiCall(async () => {
|
||||
await this._pipe!.send({ message });
|
||||
}, /* isInternal */ true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,19 +16,18 @@
|
|||
|
||||
import { AndroidServerLauncherImpl } from './androidServerImpl';
|
||||
import { BrowserServerLauncherImpl } from './browserServerImpl';
|
||||
import { createConnectionFactory } from './client/clientBundle';
|
||||
import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from './server';
|
||||
import { nodePlatform } from './server/utils/nodePlatform';
|
||||
import { Connection } from './client/connection';
|
||||
import { setPlatformForSelectors } from './client/selectors';
|
||||
|
||||
import type { Playwright as PlaywrightAPI } from './client/playwright';
|
||||
import type { Language } from './utils';
|
||||
|
||||
const connectionFactory = createConnectionFactory(nodePlatform);
|
||||
|
||||
export function createInProcessPlaywright(): PlaywrightAPI {
|
||||
const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript' });
|
||||
|
||||
const clientConnection = connectionFactory();
|
||||
setPlatformForSelectors(nodePlatform);
|
||||
const clientConnection = new Connection(nodePlatform);
|
||||
clientConnection.useRawBuffers();
|
||||
const dispatcherConnection = new DispatcherConnection(true /* local */);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,18 +15,19 @@
|
|||
*/
|
||||
|
||||
import * as childProcess from 'child_process';
|
||||
import * as path from 'path';
|
||||
import path from 'path';
|
||||
|
||||
import { createConnectionFactory } from './client/clientBundle';
|
||||
import { Connection } from './client/connection';
|
||||
import { PipeTransport } from './server/utils/pipeTransport';
|
||||
import { ManualPromise } from './utils/isomorphic/manualPromise';
|
||||
import { nodePlatform } from './server/utils/nodePlatform';
|
||||
import { setPlatformForSelectors } from './client/selectors';
|
||||
|
||||
import type { Playwright } from './client/playwright';
|
||||
|
||||
const connectionFactory = createConnectionFactory(nodePlatform);
|
||||
|
||||
export async function start(env: any = {}): Promise<{ playwright: Playwright, stop: () => Promise<void> }> {
|
||||
setPlatformForSelectors(nodePlatform);
|
||||
const client = new PlaywrightClient(env);
|
||||
const playwright = await client._playwright;
|
||||
(playwright as any).driverProcess = client._driverProcess;
|
||||
|
|
@ -50,7 +51,7 @@ class PlaywrightClient {
|
|||
this._driverProcess.unref();
|
||||
this._driverProcess.stderr!.on('data', data => process.stderr.write(data));
|
||||
|
||||
const connection = connectionFactory();
|
||||
const connection = new Connection(nodePlatform);
|
||||
const transport = new PipeTransport(this._driverProcess.stdin!, this._driverProcess.stdout!);
|
||||
connection.onmessage = message => transport.send(JSON.stringify(message));
|
||||
transport.onmessage = message => connection.dispatch(JSON.parse(message));
|
||||
|
|
|
|||
|
|
@ -14,17 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
let isUnderTest = () => false;
|
||||
|
||||
export function setIsUnderTestForValidator(getter: () => boolean) {
|
||||
isUnderTest = getter;
|
||||
}
|
||||
|
||||
export class ValidationError extends Error {}
|
||||
export type Validator = (arg: any, path: string, context: ValidatorContext) => any;
|
||||
export type ValidatorContext = {
|
||||
tChannelImpl: (names: '*' | string[], arg: any, path: string, context: ValidatorContext) => any,
|
||||
binary: 'toBase64' | 'fromBase64' | 'buffer',
|
||||
tChannelImpl: (names: '*' | string[], arg: any, path: string, context: ValidatorContext) => any;
|
||||
binary: 'toBase64' | 'fromBase64' | 'buffer';
|
||||
isUnderTest: () => boolean;
|
||||
};
|
||||
export const scheme: { [key: string]: Validator } = {};
|
||||
|
||||
|
|
@ -117,7 +112,7 @@ export const tObject = (s: { [key: string]: Validator }): Validator => {
|
|||
if (!Object.is(value, undefined))
|
||||
result[key] = value;
|
||||
}
|
||||
if (isUnderTest()) {
|
||||
if (context.isUnderTest()) {
|
||||
for (const [key, value] of Object.entries(arg)) {
|
||||
if (key.startsWith('__testHook'))
|
||||
result[key] = value;
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import { TimeoutSettings } from '../timeoutSettings';
|
||||
import { PipeTransport } from '../utils/pipeTransport';
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import * as net from 'net';
|
||||
import net from 'net';
|
||||
|
||||
import { assert } from '../../utils/isomorphic/assert';
|
||||
import { createGuid } from '../utils/crypto';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import fs from 'fs';
|
||||
|
||||
import { assert } from '../utils';
|
||||
import { TargetClosedError } from './errors';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as os from 'os';
|
||||
import os from 'os';
|
||||
|
||||
import { assert } from '../../utils';
|
||||
import { wrapInASCIIBox } from '../utils/ascii';
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assert } from '../../utils';
|
||||
import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
|
||||
import * as js from '../javascript';
|
||||
import * as dom from '../dom';
|
||||
|
|
@ -60,7 +61,7 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
|||
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
|
||||
}
|
||||
|
||||
async rawEvaluateHandle(expression: string): Promise<js.ObjectId> {
|
||||
async rawEvaluateHandle(context: js.ExecutionContext, expression: string): Promise<js.JSHandle> {
|
||||
const response = await this._session.send('script.evaluate', {
|
||||
expression,
|
||||
target: this._target,
|
||||
|
|
@ -71,7 +72,7 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
|||
});
|
||||
if (response.type === 'success') {
|
||||
if ('handle' in response.result)
|
||||
return response.result.handle!;
|
||||
return createHandle(context, response.result);
|
||||
throw new js.JavaScriptErrorInEvaluate('Cannot get handle: ' + JSON.stringify(response.result));
|
||||
}
|
||||
if (response.type === 'exception')
|
||||
|
|
@ -79,14 +80,14 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
|||
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
|
||||
}
|
||||
|
||||
async evaluateWithArguments(functionDeclaration: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
|
||||
async evaluateWithArguments(functionDeclaration: string, returnByValue: boolean, utilityScript: js.JSHandle, values: any[], handles: js.JSHandle[]): Promise<any> {
|
||||
const response = await this._session.send('script.callFunction', {
|
||||
functionDeclaration,
|
||||
target: this._target,
|
||||
arguments: [
|
||||
{ handle: utilityScript._objectId! },
|
||||
...values.map(BidiSerializer.serialize),
|
||||
...objectIds.map(handle => ({ handle })),
|
||||
...handles.map(handle => ({ handle: handle._objectId! })),
|
||||
],
|
||||
resultOwnership: returnByValue ? undefined : bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned.
|
||||
serializationOptions: returnByValue ? {} : { maxObjectDepth: 0, maxDomDepth: 0 },
|
||||
|
|
@ -98,43 +99,34 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
|||
if (response.type === 'success') {
|
||||
if (returnByValue)
|
||||
return parseEvaluationResultValue(BidiDeserializer.deserialize(response.result));
|
||||
const objectId = 'handle' in response.result ? response.result.handle : undefined ;
|
||||
return utilityScript._context.createHandle({ objectId, ...response.result });
|
||||
return createHandle(utilityScript._context, response.result);
|
||||
}
|
||||
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
|
||||
}
|
||||
|
||||
async getProperties(context: js.ExecutionContext, objectId: js.ObjectId): Promise<Map<string, js.JSHandle>> {
|
||||
const handle = this.createHandle(context, { objectId });
|
||||
try {
|
||||
const names = await handle.evaluate(object => {
|
||||
const names = [];
|
||||
const descriptors = Object.getOwnPropertyDescriptors(object);
|
||||
for (const name in descriptors) {
|
||||
if (descriptors[name]?.enumerable)
|
||||
names.push(name);
|
||||
}
|
||||
return names;
|
||||
});
|
||||
const values = await Promise.all(names.map(name => handle.evaluateHandle((object, name) => object[name], name)));
|
||||
const map = new Map<string, js.JSHandle>();
|
||||
for (let i = 0; i < names.length; i++)
|
||||
map.set(names[i], values[i]);
|
||||
return map;
|
||||
} finally {
|
||||
handle.dispose();
|
||||
}
|
||||
async getProperties(handle: js.JSHandle): Promise<Map<string, js.JSHandle>> {
|
||||
const names = await handle.evaluate(object => {
|
||||
const names = [];
|
||||
const descriptors = Object.getOwnPropertyDescriptors(object);
|
||||
for (const name in descriptors) {
|
||||
if (descriptors[name]?.enumerable)
|
||||
names.push(name);
|
||||
}
|
||||
return names;
|
||||
});
|
||||
const values = await Promise.all(names.map(name => handle.evaluateHandle((object, name) => object[name], name)));
|
||||
const map = new Map<string, js.JSHandle>();
|
||||
for (let i = 0; i < names.length; i++)
|
||||
map.set(names[i], values[i]);
|
||||
return map;
|
||||
}
|
||||
|
||||
createHandle(context: js.ExecutionContext, jsRemoteObject: js.RemoteObject): js.JSHandle {
|
||||
const remoteObject: bidi.Script.RemoteValue = jsRemoteObject as bidi.Script.RemoteValue;
|
||||
return new js.JSHandle(context, remoteObject.type, renderPreview(remoteObject), jsRemoteObject.objectId, remoteObjectValue(remoteObject));
|
||||
}
|
||||
|
||||
async releaseHandle(objectId: js.ObjectId): Promise<void> {
|
||||
async releaseHandle(handle: js.JSHandle): Promise<void> {
|
||||
if (!handle._objectId)
|
||||
return;
|
||||
await this._session.send('script.disown', {
|
||||
target: this._target,
|
||||
handles: [objectId],
|
||||
handles: [handle._objectId],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -149,11 +141,11 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
|||
};
|
||||
}
|
||||
|
||||
async remoteObjectForNodeId(nodeId: bidi.Script.SharedReference): Promise<js.RemoteObject> {
|
||||
const result = await this._remoteValueForReference(nodeId);
|
||||
if ('handle' in result)
|
||||
return { objectId: result.handle!, ...result };
|
||||
throw new Error('Can\'t get remote object for nodeId');
|
||||
async remoteObjectForNodeId(context: dom.FrameExecutionContext, nodeId: bidi.Script.SharedReference): Promise<js.JSHandle> {
|
||||
const result = await this._remoteValueForReference(nodeId, true);
|
||||
if (!('handle' in result))
|
||||
throw new Error('Can\'t get remote object for nodeId');
|
||||
return createHandle(context, result);
|
||||
}
|
||||
|
||||
async contentFrameIdForFrame(handle: dom.ElementHandle) {
|
||||
|
|
@ -172,16 +164,17 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
|||
return null;
|
||||
}
|
||||
|
||||
private async _remoteValueForReference(reference: bidi.Script.RemoteReference) {
|
||||
return await this._rawCallFunction('e => e', reference);
|
||||
private async _remoteValueForReference(reference: bidi.Script.RemoteReference, createHandle?: boolean) {
|
||||
return await this._rawCallFunction('e => e', reference, createHandle);
|
||||
}
|
||||
|
||||
private async _rawCallFunction(functionDeclaration: string, arg: bidi.Script.LocalValue): Promise<bidi.Script.RemoteValue> {
|
||||
private async _rawCallFunction(functionDeclaration: string, arg: bidi.Script.LocalValue, createHandle?: boolean): Promise<bidi.Script.RemoteValue> {
|
||||
const response = await this._session.send('script.callFunction', {
|
||||
functionDeclaration,
|
||||
target: this._target,
|
||||
arguments: [arg],
|
||||
resultOwnership: bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned.
|
||||
// "Root" is necessary for the handle to be returned.
|
||||
resultOwnership: createHandle ? bidi.Script.ResultOwnership.Root : bidi.Script.ResultOwnership.None,
|
||||
serializationOptions: { maxObjectDepth: 0, maxDomDepth: 0 },
|
||||
awaitPromise: true,
|
||||
userActivation: true,
|
||||
|
|
@ -215,3 +208,12 @@ function remoteObjectValue(remoteObject: bidi.Script.RemoteValue): any {
|
|||
return remoteObject.value;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function createHandle(context: js.ExecutionContext, remoteObject: bidi.Script.RemoteValue): js.JSHandle {
|
||||
if (remoteObject.type === 'node') {
|
||||
assert(context instanceof dom.FrameExecutionContext);
|
||||
return new dom.ElementHandle(context, remoteObject.handle!);
|
||||
}
|
||||
const objectId = 'handle' in remoteObject ? remoteObject.handle : undefined;
|
||||
return new js.JSHandle(context, remoteObject.type, renderPreview(remoteObject), objectId, remoteObjectValue(remoteObject));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import { assert } from '../../utils';
|
||||
import { wrapInASCIIBox } from '../utils/ascii';
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { BrowserContext } from '../browserContext';
|
|||
import * as dialog from '../dialog';
|
||||
import * as dom from '../dom';
|
||||
import { Page } from '../page';
|
||||
import { BidiExecutionContext } from './bidiExecutionContext';
|
||||
import { BidiExecutionContext, createHandle } from './bidiExecutionContext';
|
||||
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './bidiInput';
|
||||
import { BidiNetworkManager } from './bidiNetworkManager';
|
||||
import { BidiPDF } from './bidiPdf';
|
||||
|
|
@ -130,7 +130,6 @@ export class BidiPage implements PageDelegate {
|
|||
const frame = this._page._frameManager.frame(realmInfo.context);
|
||||
if (!frame)
|
||||
return;
|
||||
const delegate = new BidiExecutionContext(this._session, realmInfo);
|
||||
let worldName: types.World;
|
||||
if (!realmInfo.sandbox) {
|
||||
worldName = 'main';
|
||||
|
|
@ -141,8 +140,8 @@ export class BidiPage implements PageDelegate {
|
|||
} else {
|
||||
return;
|
||||
}
|
||||
const delegate = new BidiExecutionContext(this._session, realmInfo);
|
||||
const context = new dom.FrameExecutionContext(delegate, frame, worldName);
|
||||
(context as any)[contextDelegateSymbol] = delegate;
|
||||
frame._contextCreated(worldName, context);
|
||||
this._realmToContext.set(realmInfo.realm, context);
|
||||
}
|
||||
|
|
@ -242,7 +241,7 @@ export class BidiPage implements PageDelegate {
|
|||
return;
|
||||
const callFrame = params.stackTrace?.callFrames[0];
|
||||
const location = callFrame ?? { url: '', lineNumber: 1, columnNumber: 1 };
|
||||
this._page._addConsoleMessage(entry.method, entry.args.map(arg => context.createHandle({ objectId: (arg as any).handle, ...arg })), location, params.text || undefined);
|
||||
this._page._addConsoleMessage(entry.method, entry.args.map(arg => createHandle(context, arg)), location, params.text || undefined);
|
||||
}
|
||||
|
||||
async navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult> {
|
||||
|
|
@ -431,10 +430,6 @@ export class BidiPage implements PageDelegate {
|
|||
return executionContext.frameIdForWindowHandle(windowHandle);
|
||||
}
|
||||
|
||||
isElementHandle(remoteObject: bidi.Script.RemoteValue): boolean {
|
||||
return remoteObject.type === 'node';
|
||||
}
|
||||
|
||||
async getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> {
|
||||
const box = await handle.evaluate(element => {
|
||||
if (!(element instanceof Element))
|
||||
|
|
@ -532,10 +527,7 @@ export class BidiPage implements PageDelegate {
|
|||
const fromContext = toBidiExecutionContext(handle._context);
|
||||
const nodeId = await fromContext.nodeIdForElementHandle(handle);
|
||||
const executionContext = toBidiExecutionContext(to);
|
||||
const objectId = await executionContext.remoteObjectForNodeId(nodeId);
|
||||
if (objectId)
|
||||
return to.createHandle(objectId) as dom.ElementHandle<T>;
|
||||
throw new Error('Failed to adopt element handle.');
|
||||
return await executionContext.remoteObjectForNodeId(to, nodeId) as dom.ElementHandle<T>;
|
||||
}
|
||||
|
||||
async getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}> {
|
||||
|
|
@ -586,7 +578,5 @@ function addMainBinding(callback: (arg: any) => void) {
|
|||
}
|
||||
|
||||
function toBidiExecutionContext(executionContext: dom.FrameExecutionContext): BidiExecutionContext {
|
||||
return (executionContext as any)[contextDelegateSymbol] as BidiExecutionContext;
|
||||
return executionContext.delegate as BidiExecutionContext;
|
||||
}
|
||||
|
||||
const contextDelegateSymbol = Symbol('delegate');
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
/* eslint-disable curly, indent */
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { TimeoutSettings } from './timeoutSettings';
|
||||
import { createGuid } from './utils/crypto';
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import { normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
|
||||
import { DEFAULT_TIMEOUT, TimeoutSettings } from './timeoutSettings';
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import { chromiumSwitches } from './chromiumSwitches';
|
||||
import { CRBrowser } from './crBrowser';
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import path from 'path';
|
||||
|
||||
import { assert } from '../../utils/isomorphic/assert';
|
||||
import { createGuid } from '../utils/crypto';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import fs from 'fs';
|
||||
|
||||
import type { CRSession } from './crConnection';
|
||||
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assert } from '../../utils/isomorphic/assert';
|
||||
import { getExceptionMessage, releaseObject } from './crProtocolHelper';
|
||||
import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace';
|
||||
import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
|
||||
import * as js from '../javascript';
|
||||
import * as dom from '../dom';
|
||||
import { isSessionClosedError } from '../protocolError';
|
||||
|
||||
import type { CRSession } from './crConnection';
|
||||
|
|
@ -44,24 +46,24 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
|
|||
return remoteObject.value;
|
||||
}
|
||||
|
||||
async rawEvaluateHandle(expression: string): Promise<js.ObjectId> {
|
||||
async rawEvaluateHandle(context: js.ExecutionContext, expression: string): Promise<js.JSHandle> {
|
||||
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', {
|
||||
expression,
|
||||
contextId: this._contextId,
|
||||
}).catch(rewriteError);
|
||||
if (exceptionDetails)
|
||||
throw new js.JavaScriptErrorInEvaluate(getExceptionMessage(exceptionDetails));
|
||||
return remoteObject.objectId!;
|
||||
return createHandle(context, remoteObject);
|
||||
}
|
||||
|
||||
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
|
||||
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle, values: any[], handles: js.JSHandle[]): Promise<any> {
|
||||
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: expression,
|
||||
objectId: utilityScript._objectId,
|
||||
arguments: [
|
||||
{ objectId: utilityScript._objectId },
|
||||
...values.map(value => ({ value })),
|
||||
...objectIds.map(objectId => ({ objectId })),
|
||||
...handles.map(handle => ({ objectId: handle._objectId! })),
|
||||
],
|
||||
returnByValue,
|
||||
awaitPromise: true,
|
||||
|
|
@ -69,29 +71,27 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
|
|||
}).catch(rewriteError);
|
||||
if (exceptionDetails)
|
||||
throw new js.JavaScriptErrorInEvaluate(getExceptionMessage(exceptionDetails));
|
||||
return returnByValue ? parseEvaluationResultValue(remoteObject.value) : utilityScript._context.createHandle(remoteObject);
|
||||
return returnByValue ? parseEvaluationResultValue(remoteObject.value) : createHandle(utilityScript._context, remoteObject);
|
||||
}
|
||||
|
||||
async getProperties(context: js.ExecutionContext, objectId: js.ObjectId): Promise<Map<string, js.JSHandle>> {
|
||||
async getProperties(object: js.JSHandle): Promise<Map<string, js.JSHandle>> {
|
||||
const response = await this._client.send('Runtime.getProperties', {
|
||||
objectId,
|
||||
objectId: object._objectId!,
|
||||
ownProperties: true
|
||||
});
|
||||
const result = new Map();
|
||||
for (const property of response.result) {
|
||||
if (!property.enumerable || !property.value)
|
||||
continue;
|
||||
result.set(property.name, context.createHandle(property.value));
|
||||
result.set(property.name, createHandle(object._context, property.value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
createHandle(context: js.ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): js.JSHandle {
|
||||
return new js.JSHandle(context, remoteObject.subtype || remoteObject.type, renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
|
||||
}
|
||||
|
||||
async releaseHandle(objectId: js.ObjectId): Promise<void> {
|
||||
await releaseObject(this._client, objectId);
|
||||
async releaseHandle(handle: js.JSHandle): Promise<void> {
|
||||
if (!handle._objectId)
|
||||
return;
|
||||
await releaseObject(this._client, handle._objectId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,3 +132,11 @@ function renderPreview(object: Protocol.Runtime.RemoteObject): string | undefine
|
|||
return js.sparseArrayToString(object.preview.properties);
|
||||
return object.description;
|
||||
}
|
||||
|
||||
export function createHandle(context: js.ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): js.JSHandle {
|
||||
if (remoteObject.subtype === 'node') {
|
||||
assert(context instanceof dom.FrameExecutionContext);
|
||||
return new dom.ElementHandle(context, remoteObject.objectId!);
|
||||
}
|
||||
return new js.JSHandle(context, remoteObject.subtype || remoteObject.type, renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import path from 'path';
|
||||
|
||||
import { assert } from '../../utils/isomorphic/assert';
|
||||
import { createGuid } from '../utils/crypto';
|
||||
|
|
@ -33,7 +33,7 @@ import { getAccessibilityTree } from './crAccessibility';
|
|||
import { CRBrowserContext } from './crBrowser';
|
||||
import { CRCoverage } from './crCoverage';
|
||||
import { DragManager } from './crDragDrop';
|
||||
import { CRExecutionContext } from './crExecutionContext';
|
||||
import { createHandle, CRExecutionContext } from './crExecutionContext';
|
||||
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './crInput';
|
||||
import { CRNetworkManager } from './crNetworkManager';
|
||||
import { CRPDF } from './crPdf';
|
||||
|
|
@ -281,10 +281,6 @@ export class CRPage implements PageDelegate {
|
|||
return this._sessionForHandle(handle)._getOwnerFrame(handle);
|
||||
}
|
||||
|
||||
isElementHandle(remoteObject: any): boolean {
|
||||
return (remoteObject as Protocol.Runtime.RemoteObject).subtype === 'node';
|
||||
}
|
||||
|
||||
async getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> {
|
||||
return this._sessionForHandle(handle)._getBoundingBox(handle);
|
||||
}
|
||||
|
|
@ -679,7 +675,6 @@ class FrameSession {
|
|||
else if (contextPayload.name === UTILITY_WORLD_NAME)
|
||||
worldName = 'utility';
|
||||
const context = new dom.FrameExecutionContext(delegate, frame, worldName);
|
||||
(context as any)[contextDelegateSymbol] = delegate;
|
||||
if (worldName)
|
||||
frame._contextCreated(worldName, context);
|
||||
this._contextIdToContext.set(contextPayload.id, context);
|
||||
|
|
@ -739,7 +734,7 @@ class FrameSession {
|
|||
session.on('Target.attachedToTarget', event => this._onAttachedToTarget(event));
|
||||
session.on('Target.detachedFromTarget', event => this._onDetachedFromTarget(event));
|
||||
session.on('Runtime.consoleAPICalled', event => {
|
||||
const args = event.args.map(o => worker._existingExecutionContext!.createHandle(o));
|
||||
const args = event.args.map(o => createHandle(worker._existingExecutionContext!, o));
|
||||
this._page._addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace));
|
||||
});
|
||||
session.on('Runtime.exceptionThrown', exception => this._page.emitOnContextOnceInitialized(BrowserContext.Events.PageError, exceptionToError(exception.exceptionDetails), this._page));
|
||||
|
|
@ -802,7 +797,7 @@ class FrameSession {
|
|||
const context = this._contextIdToContext.get(event.executionContextId);
|
||||
if (!context)
|
||||
return;
|
||||
const values = event.args.map(arg => context.createHandle(arg));
|
||||
const values = event.args.map(arg => createHandle(context, arg));
|
||||
this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace));
|
||||
}
|
||||
|
||||
|
|
@ -1167,11 +1162,11 @@ class FrameSession {
|
|||
async _adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId, to: dom.FrameExecutionContext): Promise<dom.ElementHandle> {
|
||||
const result = await this._client._sendMayFail('DOM.resolveNode', {
|
||||
backendNodeId,
|
||||
executionContextId: ((to as any)[contextDelegateSymbol] as CRExecutionContext)._contextId,
|
||||
executionContextId: (to.delegate as CRExecutionContext)._contextId,
|
||||
});
|
||||
if (!result || result.object.subtype === 'null')
|
||||
throw new Error(dom.kUnableToAdoptErrorMessage);
|
||||
return to.createHandle(result.object).asElement()!;
|
||||
return createHandle(to, result.object).asElement()!;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1200,8 +1195,6 @@ async function emulateTimezone(session: CRSession, timezoneId: string) {
|
|||
}
|
||||
}
|
||||
|
||||
const contextDelegateSymbol = Symbol('delegate');
|
||||
|
||||
// Chromium reference: https://source.chromium.org/chromium/chromium/src/+/main:components/embedder_support/user_agent_utils.cc;l=434;drc=70a6711e08e9f9e0d8e4c48e9ba5cab62eb010c2
|
||||
function calculateUserAgentMetadata(options: types.BrowserContextOptions) {
|
||||
const ua = options.userAgent;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import fs from 'fs';
|
||||
|
||||
import { splitErrorMessage } from '../../utils/isomorphic/stackTrace';
|
||||
import { mkdirIfNeeded } from '../utils/fileUtils';
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Galaxy S5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -121,7 +121,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -132,7 +132,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S8": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 740
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S8 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 740,
|
||||
"height": 360
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S9+": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 320,
|
||||
"height": 658
|
||||
|
|
@ -165,7 +165,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S9+ landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 658,
|
||||
"height": 320
|
||||
|
|
@ -176,7 +176,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy Tab S4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 712,
|
||||
"height": 1138
|
||||
|
|
@ -187,7 +187,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy Tab S4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 1138,
|
||||
"height": 712
|
||||
|
|
@ -1098,7 +1098,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"LG Optimus L70": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 384,
|
||||
"height": 640
|
||||
|
|
@ -1109,7 +1109,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"LG Optimus L70 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 384
|
||||
|
|
@ -1120,7 +1120,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Microsoft Lumia 550": {
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36 Edge/14.14263",
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1131,7 +1131,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Microsoft Lumia 550 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36 Edge/14.14263",
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1142,7 +1142,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Microsoft Lumia 950": {
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36 Edge/14.14263",
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1153,7 +1153,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Microsoft Lumia 950 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36 Edge/14.14263",
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1164,7 +1164,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 10": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 800,
|
||||
"height": 1280
|
||||
|
|
@ -1175,7 +1175,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 10 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
|
|
@ -1186,7 +1186,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 384,
|
||||
"height": 640
|
||||
|
|
@ -1197,7 +1197,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 384
|
||||
|
|
@ -1208,7 +1208,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1219,7 +1219,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1230,7 +1230,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5X": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1241,7 +1241,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5X landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1252,7 +1252,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1263,7 +1263,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1274,7 +1274,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6P": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1285,7 +1285,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6P landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1296,7 +1296,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 7": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 600,
|
||||
"height": 960
|
||||
|
|
@ -1307,7 +1307,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 7 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 960,
|
||||
"height": 600
|
||||
|
|
@ -1362,7 +1362,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Pixel 2": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 411,
|
||||
"height": 731
|
||||
|
|
@ -1373,7 +1373,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 2 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 731,
|
||||
"height": 411
|
||||
|
|
@ -1384,7 +1384,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 2 XL": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 411,
|
||||
"height": 823
|
||||
|
|
@ -1395,7 +1395,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 2 XL landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 823,
|
||||
"height": 411
|
||||
|
|
@ -1406,7 +1406,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 3": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 393,
|
||||
"height": 786
|
||||
|
|
@ -1417,7 +1417,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 3 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 786,
|
||||
"height": 393
|
||||
|
|
@ -1428,7 +1428,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 353,
|
||||
"height": 745
|
||||
|
|
@ -1439,7 +1439,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 745,
|
||||
"height": 353
|
||||
|
|
@ -1450,7 +1450,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4a (5G)": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 412,
|
||||
"height": 892
|
||||
|
|
@ -1465,7 +1465,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4a (5G) landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"height": 892,
|
||||
"width": 412
|
||||
|
|
@ -1480,7 +1480,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 393,
|
||||
"height": 851
|
||||
|
|
@ -1495,7 +1495,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 851,
|
||||
"height": 393
|
||||
|
|
@ -1510,7 +1510,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 7": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 412,
|
||||
"height": 915
|
||||
|
|
@ -1525,7 +1525,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 7 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 915,
|
||||
"height": 412
|
||||
|
|
@ -1540,7 +1540,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Moto G4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1551,7 +1551,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Moto G4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1562,7 +1562,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Chrome HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Safari/537.36",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1577,7 +1577,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Edge HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36 Edg/134.0.6998.15",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Safari/537.36 Edg/134.0.6998.23",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1622,7 +1622,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Desktop Chrome": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Safari/537.36",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
@ -1637,7 +1637,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Edge": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36 Edg/134.0.6998.15",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.23 Safari/537.36 Edg/134.0.6998.23",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import fs from 'fs';
|
||||
|
||||
import { Dispatcher, existingDispatcher } from './dispatcher';
|
||||
import { StreamDispatcher } from './streamDispatcher';
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { ArtifactDispatcher } from './artifactDispatcher';
|
||||
|
|
|
|||
|
|
@ -200,13 +200,13 @@ export class DispatcherConnection {
|
|||
|
||||
sendEvent(dispatcher: DispatcherScope, event: string, params: any) {
|
||||
const validator = findValidator(dispatcher._type, event, 'Event');
|
||||
params = validator(params, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' });
|
||||
params = validator(params, '', this._validatorToWireContext());
|
||||
this.onmessage({ guid: dispatcher._guid, method: event, params });
|
||||
}
|
||||
|
||||
sendCreate(parent: DispatcherScope, type: string, guid: string, initializer: any) {
|
||||
const validator = findValidator(type, '', 'Initializer');
|
||||
initializer = validator(initializer, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' });
|
||||
initializer = validator(initializer, '', this._validatorToWireContext());
|
||||
this.onmessage({ guid: parent._guid, method: '__create__', params: { type, initializer, guid } });
|
||||
}
|
||||
|
||||
|
|
@ -218,6 +218,22 @@ export class DispatcherConnection {
|
|||
this.onmessage({ guid: dispatcher._guid, method: '__dispose__', params: { reason } });
|
||||
}
|
||||
|
||||
private _validatorToWireContext(): ValidatorContext {
|
||||
return {
|
||||
tChannelImpl: this._tChannelImplToWire.bind(this),
|
||||
binary: this._isLocal ? 'buffer' : 'toBase64',
|
||||
isUnderTest,
|
||||
};
|
||||
}
|
||||
|
||||
private _validatorFromWireContext(): ValidatorContext {
|
||||
return {
|
||||
tChannelImpl: this._tChannelImplFromWire.bind(this),
|
||||
binary: this._isLocal ? 'buffer' : 'fromBase64',
|
||||
isUnderTest,
|
||||
};
|
||||
}
|
||||
|
||||
private _tChannelImplFromWire(names: '*' | string[], arg: any, path: string, context: ValidatorContext): any {
|
||||
if (arg && typeof arg === 'object' && typeof arg.guid === 'string') {
|
||||
const guid = arg.guid;
|
||||
|
|
@ -279,8 +295,9 @@ export class DispatcherConnection {
|
|||
let validMetadata: channels.Metadata;
|
||||
try {
|
||||
const validator = findValidator(dispatcher._type, method, 'Params');
|
||||
validParams = validator(params, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this._isLocal ? 'buffer' : 'fromBase64' });
|
||||
validMetadata = metadataValidator(metadata, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this._isLocal ? 'buffer' : 'fromBase64' });
|
||||
const validatorContext = this._validatorFromWireContext();
|
||||
validParams = validator(params, '', validatorContext);
|
||||
validMetadata = metadataValidator(metadata, '', validatorContext);
|
||||
if (typeof (dispatcher as any)[method] !== 'function')
|
||||
throw new Error(`Mismatching dispatcher: "${dispatcher._type}" does not implement "${method}"`);
|
||||
} catch (e) {
|
||||
|
|
@ -338,7 +355,7 @@ export class DispatcherConnection {
|
|||
try {
|
||||
const result = await dispatcher._handleCommand(callMetadata, method, validParams);
|
||||
const validator = findValidator(dispatcher._type, method, 'Result');
|
||||
response.result = validator(result, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' });
|
||||
response.result = validator(result, '', this._validatorToWireContext());
|
||||
callMetadata.result = result;
|
||||
} catch (e) {
|
||||
if (isTargetClosedError(e) && sdkObject) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import fs from 'fs';
|
||||
|
||||
import { Dispatcher } from './dispatcher';
|
||||
import { createGuid } from '../utils/crypto';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import fs from 'fs';
|
||||
|
||||
import * as js from './javascript';
|
||||
import { ProgressController } from './progress';
|
||||
|
|
@ -83,12 +83,6 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
|||
return js.evaluateExpression(this, expression, { ...options, returnByValue: false }, arg);
|
||||
}
|
||||
|
||||
override createHandle(remoteObject: js.RemoteObject): js.JSHandle {
|
||||
if (this.frame._page._delegate.isElementHandle(remoteObject))
|
||||
return new ElementHandle(this, remoteObject.objectId!);
|
||||
return super.createHandle(remoteObject);
|
||||
}
|
||||
|
||||
injectedScript(): Promise<js.JSHandle<InjectedScript>> {
|
||||
if (!this._injectedScriptPromise) {
|
||||
const custom: string[] = [];
|
||||
|
|
@ -111,7 +105,11 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
|||
);
|
||||
})();
|
||||
`;
|
||||
this._injectedScriptPromise = this.rawEvaluateHandle(source).then(objectId => new js.JSHandle(this, 'object', 'InjectedScript', objectId));
|
||||
this._injectedScriptPromise = this.rawEvaluateHandle(source)
|
||||
.then(handle => {
|
||||
handle._setPreview('InjectedScript');
|
||||
return handle;
|
||||
});
|
||||
}
|
||||
return this._injectedScriptPromise;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import path from 'path';
|
||||
|
||||
import { Page } from './page';
|
||||
import { assert } from '../utils';
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import * as readline from 'readline';
|
||||
|
||||
import { TimeoutSettings } from '../timeoutSettings';
|
||||
|
|
@ -27,7 +27,7 @@ import { eventsHelper } from '../utils/eventsHelper';
|
|||
import { validateBrowserContextOptions } from '../browserContext';
|
||||
import { CRBrowser } from '../chromium/crBrowser';
|
||||
import { CRConnection } from '../chromium/crConnection';
|
||||
import { CRExecutionContext } from '../chromium/crExecutionContext';
|
||||
import { createHandle, CRExecutionContext } from '../chromium/crExecutionContext';
|
||||
import { toConsoleMessageLocation } from '../chromium/crProtocolHelper';
|
||||
import { ConsoleMessage } from '../console';
|
||||
import { helper } from '../helper';
|
||||
|
|
@ -116,7 +116,7 @@ export class ElectronApplication extends SdkObject {
|
|||
}
|
||||
if (!this._nodeExecutionContext)
|
||||
return;
|
||||
const args = event.args.map(arg => this._nodeExecutionContext!.createHandle(arg));
|
||||
const args = event.args.map(arg => createHandle(this._nodeExecutionContext!, arg));
|
||||
const message = new ConsoleMessage(null, event.type, undefined, args, toConsoleMessageLocation(event.stackTrace));
|
||||
this.emit(ElectronApplication.Events.Console, message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import { Transform, pipeline } from 'stream';
|
||||
import { TLSSocket } from 'tls';
|
||||
import * as url from 'url';
|
||||
import url from 'url';
|
||||
import * as zlib from 'zlib';
|
||||
|
||||
import { TimeoutSettings } from './timeoutSettings';
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { assert } from '../utils/isomorphic/assert';
|
||||
import { mime } from '../utilsBundle';
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assert } from '../../utils/isomorphic/assert';
|
||||
import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace';
|
||||
import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
|
||||
import * as js from '../javascript';
|
||||
import * as dom from '../dom';
|
||||
import { isSessionClosedError } from '../protocolError';
|
||||
|
||||
import type { FFSession } from './ffConnection';
|
||||
|
|
@ -42,23 +44,23 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
|
|||
return payload.result!.value;
|
||||
}
|
||||
|
||||
async rawEvaluateHandle(expression: string): Promise<js.ObjectId> {
|
||||
async rawEvaluateHandle(context: js.ExecutionContext, expression: string): Promise<js.JSHandle> {
|
||||
const payload = await this._session.send('Runtime.evaluate', {
|
||||
expression,
|
||||
returnByValue: false,
|
||||
executionContextId: this._executionContextId,
|
||||
}).catch(rewriteError);
|
||||
checkException(payload.exceptionDetails);
|
||||
return payload.result!.objectId!;
|
||||
return createHandle(context, payload.result!);
|
||||
}
|
||||
|
||||
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
|
||||
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle, values: any[], handles: js.JSHandle[]): Promise<any> {
|
||||
const payload = await this._session.send('Runtime.callFunction', {
|
||||
functionDeclaration: expression,
|
||||
args: [
|
||||
{ objectId: utilityScript._objectId, value: undefined },
|
||||
...values.map(value => ({ value })),
|
||||
...objectIds.map(objectId => ({ objectId, value: undefined })),
|
||||
...handles.map(handle => ({ objectId: handle._objectId!, value: undefined })),
|
||||
],
|
||||
returnByValue,
|
||||
executionContextId: this._executionContextId
|
||||
|
|
@ -66,28 +68,26 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
|
|||
checkException(payload.exceptionDetails);
|
||||
if (returnByValue)
|
||||
return parseEvaluationResultValue(payload.result!.value);
|
||||
return utilityScript._context.createHandle(payload.result!);
|
||||
return createHandle(utilityScript._context, payload.result!);
|
||||
}
|
||||
|
||||
async getProperties(context: js.ExecutionContext, objectId: js.ObjectId): Promise<Map<string, js.JSHandle>> {
|
||||
async getProperties(object: js.JSHandle): Promise<Map<string, js.JSHandle>> {
|
||||
const response = await this._session.send('Runtime.getObjectProperties', {
|
||||
executionContextId: this._executionContextId,
|
||||
objectId,
|
||||
objectId: object._objectId!,
|
||||
});
|
||||
const result = new Map();
|
||||
for (const property of response.properties)
|
||||
result.set(property.name, context.createHandle(property.value));
|
||||
result.set(property.name, createHandle(object._context, property.value));
|
||||
return result;
|
||||
}
|
||||
|
||||
createHandle(context: js.ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): js.JSHandle {
|
||||
return new js.JSHandle(context, remoteObject.subtype || remoteObject.type || '', renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
|
||||
}
|
||||
|
||||
async releaseHandle(objectId: js.ObjectId): Promise<void> {
|
||||
async releaseHandle(handle: js.JSHandle): Promise<void> {
|
||||
if (!handle._objectId)
|
||||
return;
|
||||
await this._session.send('Runtime.disposeObject', {
|
||||
executionContextId: this._executionContextId,
|
||||
objectId
|
||||
objectId: handle._objectId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -135,3 +135,11 @@ function renderPreview(object: Protocol.Runtime.RemoteObject): string | undefine
|
|||
if ('value' in object)
|
||||
return String(object.value);
|
||||
}
|
||||
|
||||
export function createHandle(context: js.ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): js.JSHandle {
|
||||
if (remoteObject.subtype === 'node') {
|
||||
assert(context instanceof dom.FrameExecutionContext);
|
||||
return new dom.ElementHandle(context, remoteObject.objectId!);
|
||||
}
|
||||
return new js.JSHandle(context, remoteObject.subtype || remoteObject.type || '', renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { InitScript } from '../page';
|
|||
import { Page, Worker } from '../page';
|
||||
import { getAccessibilityTree } from './ffAccessibility';
|
||||
import { FFSession } from './ffConnection';
|
||||
import { FFExecutionContext } from './ffExecutionContext';
|
||||
import { createHandle, FFExecutionContext } from './ffExecutionContext';
|
||||
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './ffInput';
|
||||
import { FFNetworkManager } from './ffNetworkManager';
|
||||
import { debugLogger } from '../utils/debugLogger';
|
||||
|
|
@ -150,7 +150,6 @@ export class FFPage implements PageDelegate {
|
|||
else if (!auxData.name)
|
||||
worldName = 'main';
|
||||
const context = new dom.FrameExecutionContext(delegate, frame, worldName);
|
||||
(context as any)[contextDelegateSymbol] = delegate;
|
||||
if (worldName)
|
||||
frame._contextCreated(worldName, context);
|
||||
this._contextIdToContext.set(executionContextId, context);
|
||||
|
|
@ -234,7 +233,7 @@ export class FFPage implements PageDelegate {
|
|||
if (!context)
|
||||
return;
|
||||
// Juggler reports 'warn' for some internal messages generated by the browser.
|
||||
this._page._addConsoleMessage(type === 'warn' ? 'warning' : type, args.map(arg => context.createHandle(arg)), location);
|
||||
this._page._addConsoleMessage(type === 'warn' ? 'warning' : type, args.map(arg => createHandle(context, arg)), location);
|
||||
}
|
||||
|
||||
_onDialogOpened(params: Protocol.Page.dialogOpenedPayload) {
|
||||
|
|
@ -262,7 +261,7 @@ export class FFPage implements PageDelegate {
|
|||
const context = this._contextIdToContext.get(executionContextId);
|
||||
if (!context)
|
||||
return;
|
||||
const handle = context.createHandle(element).asElement()!;
|
||||
const handle = createHandle(context, element).asElement()!;
|
||||
await this._page._onFileChooserOpened(handle);
|
||||
}
|
||||
|
||||
|
|
@ -286,7 +285,7 @@ export class FFPage implements PageDelegate {
|
|||
workerSession.on('Runtime.console', event => {
|
||||
const { type, args, location } = event;
|
||||
const context = worker._existingExecutionContext!;
|
||||
this._page._addConsoleMessage(type, args.map(arg => context.createHandle(arg)), location);
|
||||
this._page._addConsoleMessage(type, args.map(arg => createHandle(context, arg)), location);
|
||||
});
|
||||
// Note: we receive worker exceptions directly from the page.
|
||||
}
|
||||
|
|
@ -443,10 +442,6 @@ export class FFPage implements PageDelegate {
|
|||
return ownerFrameId || null;
|
||||
}
|
||||
|
||||
isElementHandle(remoteObject: any): boolean {
|
||||
return remoteObject.subtype === 'node';
|
||||
}
|
||||
|
||||
async getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> {
|
||||
const quads = await this.getContentQuads(handle);
|
||||
if (!quads || !quads.length)
|
||||
|
|
@ -531,11 +526,11 @@ export class FFPage implements PageDelegate {
|
|||
const result = await this._session.send('Page.adoptNode', {
|
||||
frameId: handle._context.frame._id,
|
||||
objectId: handle._objectId,
|
||||
executionContextId: ((to as any)[contextDelegateSymbol] as FFExecutionContext)._executionContextId
|
||||
executionContextId: (to.delegate as FFExecutionContext)._executionContextId
|
||||
});
|
||||
if (!result.remoteObject)
|
||||
throw new Error(dom.kUnableToAdoptErrorMessage);
|
||||
return to.createHandle(result.remoteObject) as dom.ElementHandle<T>;
|
||||
return createHandle(to, result.remoteObject) as dom.ElementHandle<T>;
|
||||
}
|
||||
|
||||
async getAccessibilityTree(needle?: dom.ElementHandle) {
|
||||
|
|
@ -560,11 +555,11 @@ export class FFPage implements PageDelegate {
|
|||
const context = await parent._mainContext();
|
||||
const result = await this._session.send('Page.adoptNode', {
|
||||
frameId: frame._id,
|
||||
executionContextId: ((context as any)[contextDelegateSymbol] as FFExecutionContext)._executionContextId
|
||||
executionContextId: (context.delegate as FFExecutionContext)._executionContextId
|
||||
});
|
||||
if (!result.remoteObject)
|
||||
throw new Error('Frame has been detached.');
|
||||
return context.createHandle(result.remoteObject) as dom.ElementHandle;
|
||||
return createHandle(context, result.remoteObject) as dom.ElementHandle;
|
||||
}
|
||||
|
||||
shouldToggleStyleSheetToSyncAnimations(): boolean {
|
||||
|
|
@ -575,5 +570,3 @@ export class FFPage implements PageDelegate {
|
|||
function webSocketId(frameId: string, wsid: string): string {
|
||||
return `${frameId}---${wsid}`;
|
||||
}
|
||||
|
||||
const contextDelegateSymbol = Symbol('delegate');
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import { FFBrowser } from './ffBrowser';
|
||||
import { kBrowserCloseMessageId } from './ffConnection';
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { Artifact } from '../artifact';
|
||||
import { HarTracer } from './harTracer';
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { createGuid } from './utils/crypto';
|
||||
import { ZipFile } from './utils/zipFile';
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ function trimFlatString(s: string): string {
|
|||
function asFlatString(s: string): string {
|
||||
// "Flat string" at https://w3c.github.io/accname/#terminology
|
||||
// Note that non-breaking spaces are preserved.
|
||||
return s.split('\u00A0').map(chunk => chunk.replace(/\r\n/g, '\n').replace(/\s\s*/g, ' ')).join('\u00A0').trim();
|
||||
return s.split('\u00A0').map(chunk => chunk.replace(/\r\n/g, '\n').replace(/[\u200b\u00ad]/g, '').replace(/\s\s*/g, ' ')).join('\u00A0').trim();
|
||||
}
|
||||
|
||||
function queryInAriaOwned(element: Element, selector: string): Element[] {
|
||||
|
|
|
|||
|
|
@ -23,12 +23,6 @@ import { LongStandingScope } from '../utils/isomorphic/manualPromise';
|
|||
import type * as dom from './dom';
|
||||
import type { UtilityScript } from './injected/utilityScript';
|
||||
|
||||
export type ObjectId = string;
|
||||
export type RemoteObject = {
|
||||
objectId?: ObjectId,
|
||||
value?: any
|
||||
};
|
||||
|
||||
interface TaggedAsJSHandle<T> {
|
||||
__jshandle: T;
|
||||
}
|
||||
|
|
@ -53,15 +47,14 @@ export type SmartHandle<T> = T extends Node ? dom.ElementHandle<T> : JSHandle<T>
|
|||
|
||||
export interface ExecutionContextDelegate {
|
||||
rawEvaluateJSON(expression: string): Promise<any>;
|
||||
rawEvaluateHandle(expression: string): Promise<ObjectId>;
|
||||
evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: JSHandle<any>, values: any[], objectIds: ObjectId[]): Promise<any>;
|
||||
getProperties(context: ExecutionContext, objectId: ObjectId): Promise<Map<string, JSHandle>>;
|
||||
createHandle(context: ExecutionContext, remoteObject: RemoteObject): JSHandle;
|
||||
releaseHandle(objectId: ObjectId): Promise<void>;
|
||||
rawEvaluateHandle(context: ExecutionContext, expression: string): Promise<JSHandle>;
|
||||
evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: JSHandle, values: any[], handles: JSHandle[]): Promise<any>;
|
||||
getProperties(object: JSHandle): Promise<Map<string, JSHandle>>;
|
||||
releaseHandle(handle: JSHandle): Promise<void>;
|
||||
}
|
||||
|
||||
export class ExecutionContext extends SdkObject {
|
||||
private _delegate: ExecutionContextDelegate;
|
||||
readonly delegate: ExecutionContextDelegate;
|
||||
private _utilityScriptPromise: Promise<JSHandle> | undefined;
|
||||
private _contextDestroyedScope = new LongStandingScope();
|
||||
readonly worldNameForTest: string;
|
||||
|
|
@ -69,7 +62,7 @@ export class ExecutionContext extends SdkObject {
|
|||
constructor(parent: SdkObject, delegate: ExecutionContextDelegate, worldNameForTest: string) {
|
||||
super(parent, 'execution-context');
|
||||
this.worldNameForTest = worldNameForTest;
|
||||
this._delegate = delegate;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
contextDestroyed(reason: string) {
|
||||
|
|
@ -81,34 +74,31 @@ export class ExecutionContext extends SdkObject {
|
|||
}
|
||||
|
||||
rawEvaluateJSON(expression: string): Promise<any> {
|
||||
return this._raceAgainstContextDestroyed(this._delegate.rawEvaluateJSON(expression));
|
||||
return this._raceAgainstContextDestroyed(this.delegate.rawEvaluateJSON(expression));
|
||||
}
|
||||
|
||||
rawEvaluateHandle(expression: string): Promise<ObjectId> {
|
||||
return this._raceAgainstContextDestroyed(this._delegate.rawEvaluateHandle(expression));
|
||||
rawEvaluateHandle(expression: string): Promise<JSHandle> {
|
||||
return this._raceAgainstContextDestroyed(this.delegate.rawEvaluateHandle(this, expression));
|
||||
}
|
||||
|
||||
evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: JSHandle<any>, values: any[], objectIds: ObjectId[]): Promise<any> {
|
||||
return this._raceAgainstContextDestroyed(this._delegate.evaluateWithArguments(expression, returnByValue, utilityScript, values, objectIds));
|
||||
async evaluateWithArguments(expression: string, returnByValue: boolean, values: any[], handles: JSHandle[]): Promise<any> {
|
||||
const utilityScript = await this._utilityScript();
|
||||
return this._raceAgainstContextDestroyed(this.delegate.evaluateWithArguments(expression, returnByValue, utilityScript, values, handles));
|
||||
}
|
||||
|
||||
getProperties(context: ExecutionContext, objectId: ObjectId): Promise<Map<string, JSHandle>> {
|
||||
return this._raceAgainstContextDestroyed(this._delegate.getProperties(context, objectId));
|
||||
getProperties(object: JSHandle): Promise<Map<string, JSHandle>> {
|
||||
return this._raceAgainstContextDestroyed(this.delegate.getProperties(object));
|
||||
}
|
||||
|
||||
createHandle(remoteObject: RemoteObject): JSHandle {
|
||||
return this._delegate.createHandle(this, remoteObject);
|
||||
}
|
||||
|
||||
releaseHandle(objectId: ObjectId): Promise<void> {
|
||||
return this._delegate.releaseHandle(objectId);
|
||||
releaseHandle(handle: JSHandle): Promise<void> {
|
||||
return this.delegate.releaseHandle(handle);
|
||||
}
|
||||
|
||||
adoptIfNeeded(handle: JSHandle): Promise<JSHandle> | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
utilityScript(): Promise<JSHandle<UtilityScript>> {
|
||||
private _utilityScript(): Promise<JSHandle<UtilityScript>> {
|
||||
if (!this._utilityScriptPromise) {
|
||||
const source = `
|
||||
(() => {
|
||||
|
|
@ -116,7 +106,11 @@ export class ExecutionContext extends SdkObject {
|
|||
${utilityScriptSource.source}
|
||||
return new (module.exports.UtilityScript())(${isUnderTest()});
|
||||
})();`;
|
||||
this._utilityScriptPromise = this._raceAgainstContextDestroyed(this._delegate.rawEvaluateHandle(source).then(objectId => new JSHandle(this, 'object', 'UtilityScript', objectId)));
|
||||
this._utilityScriptPromise = this._raceAgainstContextDestroyed(this.delegate.rawEvaluateHandle(this, source))
|
||||
.then(handle => {
|
||||
handle._setPreview('UtilityScript');
|
||||
return handle;
|
||||
});
|
||||
}
|
||||
return this._utilityScriptPromise;
|
||||
}
|
||||
|
|
@ -130,13 +124,13 @@ export class JSHandle<T = any> extends SdkObject {
|
|||
__jshandle: T = true as any;
|
||||
readonly _context: ExecutionContext;
|
||||
_disposed = false;
|
||||
readonly _objectId: ObjectId | undefined;
|
||||
readonly _objectId: string | undefined;
|
||||
readonly _value: any;
|
||||
private _objectType: string;
|
||||
protected _preview: string;
|
||||
private _previewCallback: ((preview: string) => void) | undefined;
|
||||
|
||||
constructor(context: ExecutionContext, type: string, preview: string | undefined, objectId?: ObjectId, value?: any) {
|
||||
constructor(context: ExecutionContext, type: string, preview: string | undefined, objectId?: string, value?: any) {
|
||||
super(context, 'handle');
|
||||
this._context = context;
|
||||
this._objectId = objectId;
|
||||
|
|
@ -182,7 +176,7 @@ export class JSHandle<T = any> extends SdkObject {
|
|||
async getProperties(): Promise<Map<string, JSHandle>> {
|
||||
if (!this._objectId)
|
||||
return new Map();
|
||||
return this._context.getProperties(this._context, this._objectId);
|
||||
return this._context.getProperties(this);
|
||||
}
|
||||
|
||||
rawValue() {
|
||||
|
|
@ -192,9 +186,8 @@ export class JSHandle<T = any> extends SdkObject {
|
|||
async jsonValue(): Promise<T> {
|
||||
if (!this._objectId)
|
||||
return this._value;
|
||||
const utilityScript = await this._context.utilityScript();
|
||||
const script = `(utilityScript, ...args) => utilityScript.jsonValue(...args)`;
|
||||
return this._context.evaluateWithArguments(script, true, utilityScript, [true], [this._objectId]);
|
||||
return this._context.evaluateWithArguments(script, true, [true], [this]);
|
||||
}
|
||||
|
||||
asElement(): dom.ElementHandle | null {
|
||||
|
|
@ -206,7 +199,7 @@ export class JSHandle<T = any> extends SdkObject {
|
|||
return;
|
||||
this._disposed = true;
|
||||
if (this._objectId) {
|
||||
this._context.releaseHandle(this._objectId).catch(e => {});
|
||||
this._context.releaseHandle(this).catch(e => {});
|
||||
if ((globalThis as any).leakedJSHandles)
|
||||
(globalThis as any).leakedJSHandles.delete(this);
|
||||
}
|
||||
|
|
@ -240,7 +233,6 @@ export async function evaluate(context: ExecutionContext, returnByValue: boolean
|
|||
}
|
||||
|
||||
export async function evaluateExpression(context: ExecutionContext, expression: string, options: { returnByValue?: boolean, isFunction?: boolean }, ...args: any[]): Promise<any> {
|
||||
const utilityScript = await context.utilityScript();
|
||||
expression = normalizeEvaluationExpression(expression, options.isFunction);
|
||||
const handles: (Promise<JSHandle>)[] = [];
|
||||
const toDispose: Promise<JSHandle>[] = [];
|
||||
|
|
@ -264,11 +256,11 @@ export async function evaluateExpression(context: ExecutionContext, expression:
|
|||
return { fallThrough: handle };
|
||||
}));
|
||||
|
||||
const utilityScriptObjectIds: ObjectId[] = [];
|
||||
const utilityScriptObjects: JSHandle[] = [];
|
||||
for (const handle of await Promise.all(handles)) {
|
||||
if (handle._context !== context)
|
||||
throw new JavaScriptErrorInEvaluate('JSHandles can be evaluated only in the context they were created!');
|
||||
utilityScriptObjectIds.push(handle._objectId!);
|
||||
utilityScriptObjects.push(handle);
|
||||
}
|
||||
|
||||
// See UtilityScript for arguments.
|
||||
|
|
@ -276,7 +268,7 @@ export async function evaluateExpression(context: ExecutionContext, expression:
|
|||
|
||||
const script = `(utilityScript, ...args) => utilityScript.evaluate(...args)`;
|
||||
try {
|
||||
return await context.evaluateWithArguments(script, options.returnByValue || false, utilityScript, utilityScriptValues, utilityScriptObjectIds);
|
||||
return await context.evaluateWithArguments(script, options.returnByValue || false, utilityScriptValues, utilityScriptObjects);
|
||||
} finally {
|
||||
toDispose.map(handlePromise => handlePromise.then(handle => handle.dispose()));
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue