Merge branch 'main' of https://github.com/microsoft/playwright into floating-promise-warning
This commit is contained in:
commit
9f216ec420
|
|
@ -1,6 +1,6 @@
|
||||||
# 🎭 Playwright
|
# 🎭 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)
|
## [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 |
|
| | Linux | macOS | Windows |
|
||||||
| :--- | :---: | :---: | :---: |
|
| :--- | :---: | :---: | :---: |
|
||||||
| Chromium <!-- GEN:chromium-version -->134.0.6998.3<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
| Chromium <!-- GEN:chromium-version -->134.0.6998.15<!-- 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: |
|
| 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: |
|
| Firefox <!-- GEN:firefox-version -->135.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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?
|
### 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:
|
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:
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ const noBooleanCompareRules = {
|
||||||
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 2,
|
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const noRestrictedGlobalsRules = {
|
const noWebGlobalsRules = {
|
||||||
'no-restricted-globals': [
|
'no-restricted-globals': [
|
||||||
'error',
|
'error',
|
||||||
{ 'name': 'window' },
|
{ 'name': 'window' },
|
||||||
|
|
@ -186,6 +186,13 @@ const noRestrictedGlobalsRules = {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const noNodeGlobalsRules = {
|
||||||
|
'no-restricted-globals': [
|
||||||
|
'error',
|
||||||
|
{ 'name': 'process' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const importOrderRules = {
|
const importOrderRules = {
|
||||||
'import/order': [2, {
|
'import/order': [2, {
|
||||||
'groups': ['builtin', 'external', 'internal', ['parent', 'sibling'], 'index', 'type'],
|
'groups': ['builtin', 'external', 'internal', ['parent', 'sibling'], 'index', 'type'],
|
||||||
|
|
@ -249,7 +256,19 @@ export default [{
|
||||||
files: ['packages/playwright-core/src/server/injected/**/*.ts'],
|
files: ['packages/playwright-core/src/server/injected/**/*.ts'],
|
||||||
languageOptions: languageOptionsWithTsConfig,
|
languageOptions: languageOptionsWithTsConfig,
|
||||||
rules: {
|
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,
|
...noFloatingPromisesRules,
|
||||||
...noBooleanCompareRules,
|
...noBooleanCompareRules,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3791
package-lock.json
generated
3791
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -98,15 +98,16 @@
|
||||||
"formidable": "^2.1.1",
|
"formidable": "^2.1.1",
|
||||||
"immutable": "^4.3.7",
|
"immutable": "^4.3.7",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
|
"markdown-to-jsx": "^7.7.3",
|
||||||
"mime": "^3.0.0",
|
"mime": "^3.0.0",
|
||||||
"node-stream-zip": "^1.15.0",
|
"node-stream-zip": "^1.15.0",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"ssim.js": "^3.5.0",
|
"ssim.js": "^3.5.0",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"vite": "^5.4.14",
|
"vite": "^6.1.0",
|
||||||
"ws": "^8.17.1",
|
"ws": "^8.17.1",
|
||||||
"xml2js": "^0.5.0",
|
"xml2js": "^0.5.0",
|
||||||
"yaml": "^2.6.0"
|
"yaml": "2.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,14 +107,24 @@ const InnerMetadataView = () => {
|
||||||
const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => {
|
const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => {
|
||||||
const email = info['revision.email'] ? ` <${info['revision.email']}>` : '';
|
const email = info['revision.email'] ? ` <${info['revision.email']}>` : '';
|
||||||
const author = `${info['revision.author'] || ''}${email}`;
|
const author = `${info['revision.author'] || ''}${email}`;
|
||||||
const subject = info['revision.subject'] || '';
|
|
||||||
|
let subject = info['revision.subject'] || '';
|
||||||
|
let link = info['revision.link'];
|
||||||
|
let shortSubject = info['revision.id']?.slice(0, 7) || 'unknown';
|
||||||
|
|
||||||
|
if (info['pull.link'] && info['pull.title']) {
|
||||||
|
subject = info['pull.title'];
|
||||||
|
link = info['pull.link'];
|
||||||
|
shortSubject = link ? 'Pull Request' : '';
|
||||||
|
}
|
||||||
|
|
||||||
const shortTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(info['revision.timestamp']);
|
const shortTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(info['revision.timestamp']);
|
||||||
const longTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).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'>
|
return <div className='hbox git-commit-info metadata-section'>
|
||||||
<div className='vbox metadata-properties'>
|
<div className='vbox metadata-properties'>
|
||||||
<div>
|
<div>
|
||||||
{info['revision.link'] ? (
|
{link ? (
|
||||||
<a href={info['revision.link']} target='_blank' rel='noopener noreferrer' title={subject}>
|
<a href={link} target='_blank' rel='noopener noreferrer' title={subject}>
|
||||||
{subject}
|
{subject}
|
||||||
</a>
|
</a>
|
||||||
) : <span title={subject}>
|
) : <span title={subject}>
|
||||||
|
|
@ -130,18 +140,12 @@ const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => {
|
||||||
<a href={info['ci.link']} target='_blank' rel='noopener noreferrer' title='CI/CD logs'>Logs</a>
|
<a href={info['ci.link']} target='_blank' rel='noopener noreferrer' title='CI/CD logs'>Logs</a>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{info['pull.link'] && (
|
|
||||||
<>
|
|
||||||
<span className='mx-2'>·</span>
|
|
||||||
<a href={info['pull.link']} target='_blank' rel='noopener noreferrer'>Pull Request</a>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!!info['revision.link'] ? (
|
{link ? (
|
||||||
<a href={info['revision.link']} target='_blank' rel='noopener noreferrer' title='View commit details'>
|
<a href={link} target='_blank' rel='noopener noreferrer' title='View commit details'>
|
||||||
{info['revision.id']?.slice(0, 7) || 'unknown'}
|
{shortSubject}
|
||||||
</a>
|
</a>
|
||||||
) : !!info['revision.id'] && <span>{info['revision.id'].slice(0, 7)}</span>}
|
) : !!shortSubject && <span>{shortSubject}</span>}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,27 @@
|
||||||
"browsers": [
|
"browsers": [
|
||||||
{
|
{
|
||||||
"name": "chromium",
|
"name": "chromium",
|
||||||
"revision": "1158",
|
"revision": "1159",
|
||||||
"installByDefault": true,
|
"installByDefault": true,
|
||||||
"browserVersion": "134.0.6998.3"
|
"browserVersion": "134.0.6998.15"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "chromium-headless-shell",
|
"name": "chromium-headless-shell",
|
||||||
"revision": "1158",
|
"revision": "1159",
|
||||||
"installByDefault": true,
|
"installByDefault": true,
|
||||||
"browserVersion": "134.0.6998.3"
|
"browserVersion": "134.0.6998.15"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "chromium-tip-of-tree",
|
"name": "chromium-tip-of-tree",
|
||||||
"revision": "1302",
|
"revision": "1304",
|
||||||
"installByDefault": false,
|
"installByDefault": false,
|
||||||
"browserVersion": "135.0.7011.0"
|
"browserVersion": "135.0.7021.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "chromium-tip-of-tree-headless-shell",
|
"name": "chromium-tip-of-tree-headless-shell",
|
||||||
"revision": "1302",
|
"revision": "1304",
|
||||||
"installByDefault": false,
|
"installByDefault": false,
|
||||||
"browserVersion": "135.0.7011.0"
|
"browserVersion": "135.0.7021.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "firefox",
|
"name": "firefox",
|
||||||
|
|
@ -33,13 +33,13 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "firefox-beta",
|
"name": "firefox-beta",
|
||||||
"revision": "1470",
|
"revision": "1471",
|
||||||
"installByDefault": false,
|
"installByDefault": false,
|
||||||
"browserVersion": "135.0b10"
|
"browserVersion": "136.0b4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "webkit",
|
"name": "webkit",
|
||||||
"revision": "2134",
|
"revision": "2138",
|
||||||
"installByDefault": true,
|
"installByDefault": true,
|
||||||
"revisionOverrides": {
|
"revisionOverrides": {
|
||||||
"debian11-x64": "2105",
|
"debian11-x64": "2105",
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,27 @@
|
||||||
[browserServerImpl.ts]
|
[browserServerImpl.ts]
|
||||||
**
|
remote/
|
||||||
|
server/
|
||||||
|
server/utils
|
||||||
|
utils/isomorphic/
|
||||||
|
utilsBundle.ts
|
||||||
|
|
||||||
[androidServerImpl.ts]
|
[androidServerImpl.ts]
|
||||||
**
|
remote/
|
||||||
|
server/
|
||||||
|
server/utils
|
||||||
|
utils/isomorphic/
|
||||||
|
utilsBundle.ts
|
||||||
|
|
||||||
[inProcessFactory.ts]
|
[inProcessFactory.ts]
|
||||||
**
|
**
|
||||||
|
|
||||||
[inprocess.ts]
|
[inprocess.ts]
|
||||||
common/
|
|
||||||
utils/
|
utils/
|
||||||
server/utils
|
server/utils
|
||||||
|
|
||||||
[outofprocess.ts]
|
[outofprocess.ts]
|
||||||
client/
|
client/
|
||||||
common/
|
|
||||||
protocol/
|
protocol/
|
||||||
utils/
|
utils/
|
||||||
utils/isomorphic
|
utils/isomorphic
|
||||||
server/utils
|
server/utils
|
||||||
common/
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { envObjectToArray } from './client/clientHelper';
|
|
||||||
import { SocksProxy } from './server/utils/socksProxy';
|
import { SocksProxy } from './server/utils/socksProxy';
|
||||||
import { PlaywrightServer } from './remote/playwrightServer';
|
import { PlaywrightServer } from './remote/playwrightServer';
|
||||||
import { helper } from './server/helper';
|
import { helper } from './server/helper';
|
||||||
|
|
@ -25,7 +24,7 @@ import { rewriteErrorMessage } from './utils/isomorphic/stackTrace';
|
||||||
import { ws } from './utilsBundle';
|
import { ws } from './utilsBundle';
|
||||||
|
|
||||||
import type { BrowserServer, BrowserServerLauncher } from './client/browserType';
|
import type { BrowserServer, BrowserServerLauncher } from './client/browserType';
|
||||||
import type { LaunchServerOptions, Logger } from './client/types';
|
import type { LaunchServerOptions, Logger, Env } from './client/types';
|
||||||
import type { ProtocolLogger } from './server/types';
|
import type { ProtocolLogger } from './server/types';
|
||||||
import type { WebSocketEventEmitter } from './utilsBundle';
|
import type { WebSocketEventEmitter } from './utilsBundle';
|
||||||
|
|
||||||
|
|
@ -85,3 +84,12 @@ function toProtocolLogger(logger: Logger | undefined): ProtocolLogger | undefine
|
||||||
logger.log('protocol', 'verbose', (direction === 'send' ? 'SEND ► ' : '◀ RECV ') + JSON.stringify(message), [], {});
|
logger.log('protocol', 'verbose', (direction === 'send' ? 'SEND ► ' : '◀ RECV ') + JSON.stringify(message), [], {});
|
||||||
} : undefined;
|
} : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function envObjectToArray(env: Env): { name: string, value: string }[] {
|
||||||
|
const result: { name: string, value: string }[] = [];
|
||||||
|
for (const name in env) {
|
||||||
|
if (!Object.is(env[name], undefined))
|
||||||
|
result.push({ name, value: String(env[name]) });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
[*]
|
[*]
|
||||||
../../
|
../../
|
||||||
../client
|
|
||||||
../common
|
|
||||||
../debug/injected
|
../debug/injected
|
||||||
../generated/
|
../generated/
|
||||||
../server/
|
../server/
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
|
||||||
import * as playwright from '../..';
|
import * as playwright from '../..';
|
||||||
import { PipeTransport } from '../utils/pipeTransport';
|
import { PipeTransport } from '../server/utils/pipeTransport';
|
||||||
import { PlaywrightServer } from '../remote/playwrightServer';
|
import { PlaywrightServer } from '../remote/playwrightServer';
|
||||||
import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from '../server';
|
import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from '../server';
|
||||||
import { gracefullyProcessExitDoNotHang } from '../server/utils/processLauncher';
|
import { gracefullyProcessExitDoNotHang } from '../server/utils/processLauncher';
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,10 @@ import * as path from 'path';
|
||||||
|
|
||||||
import * as playwright from '../..';
|
import * as playwright from '../..';
|
||||||
import { launchBrowserServer, printApiJson, runDriver, runServer } from './driver';
|
import { launchBrowserServer, printApiJson, runDriver, runServer } from './driver';
|
||||||
import { isTargetClosedError } from '../client/errors';
|
|
||||||
import { registry, writeDockerVersion } from '../server';
|
import { registry, writeDockerVersion } from '../server';
|
||||||
import { gracefullyProcessExitDoNotHang } from '../utils';
|
import { gracefullyProcessExitDoNotHang, isLikelyNpxGlobal } from '../utils';
|
||||||
import { runTraceInBrowser, runTraceViewerApp } from '../server/trace/viewer/traceViewer';
|
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 { wrapInASCIIBox } from '../server/utils/ascii';
|
||||||
import { dotenv, program } from '../utilsBundle';
|
import { dotenv, program } from '../utilsBundle';
|
||||||
|
|
||||||
|
|
@ -553,7 +552,7 @@ async function openPage(context: BrowserContext, url: string | undefined): Promi
|
||||||
else if (!url.startsWith('http') && !url.startsWith('file://') && !url.startsWith('about:') && !url.startsWith('data:'))
|
else if (!url.startsWith('http') && !url.startsWith('file://') && !url.startsWith('about:') && !url.startsWith('data:'))
|
||||||
url = 'http://' + url;
|
url = 'http://' + url;
|
||||||
await page.goto(url).catch(error => {
|
await page.goto(url).catch(error => {
|
||||||
if (process.env.PWTEST_CLI_AUTO_EXIT_WHEN && isTargetClosedError(error)) {
|
if (process.env.PWTEST_CLI_AUTO_EXIT_WHEN) {
|
||||||
// Tests with PWTEST_CLI_AUTO_EXIT_WHEN might close page too fast, resulting
|
// Tests with PWTEST_CLI_AUTO_EXIT_WHEN might close page too fast, resulting
|
||||||
// in a stray navigation aborted error. We should ignore it.
|
// in a stray navigation aborted error. We should ignore it.
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
[*]
|
[*]
|
||||||
../common/
|
|
||||||
../protocol/
|
../protocol/
|
||||||
../utils/**
|
../utils/isomorphic
|
||||||
../utilsBundle.ts
|
|
||||||
|
|
|
||||||
|
|
@ -14,23 +14,23 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from './eventEmitter';
|
||||||
|
|
||||||
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { TargetClosedError, isTargetClosedError } from './errors';
|
import { TargetClosedError, isTargetClosedError } from './errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
import { TimeoutSettings } from './timeoutSettings';
|
||||||
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
||||||
import { monotonicTime } from '../utils/isomorphic/time';
|
import { monotonicTime } from '../utils/isomorphic/time';
|
||||||
import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner';
|
import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner';
|
||||||
|
import { connectOverWebSocket } from './webSocket';
|
||||||
|
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type { AndroidServerLauncherImpl } from '../androidServerImpl';
|
import type { AndroidServerLauncherImpl } from '../androidServerImpl';
|
||||||
import type { Platform } from '../common/platform';
|
import type { Platform } from './platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
type Direction = 'down' | 'up' | 'left' | 'right';
|
type Direction = 'down' | 'up' | 'left' | 'right';
|
||||||
|
|
@ -46,12 +46,14 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
|
||||||
|
|
||||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.AndroidInitializer) {
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.AndroidInitializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
this._timeoutSettings = new TimeoutSettings();
|
this._timeoutSettings = new TimeoutSettings(this._platform);
|
||||||
}
|
}
|
||||||
|
|
||||||
setDefaultTimeout(timeout: number) {
|
setDefaultTimeout(timeout: number) {
|
||||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
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[]> {
|
async devices(options: { port?: number } = {}): Promise<AndroidDevice[]> {
|
||||||
|
|
@ -69,9 +71,8 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
|
||||||
return await this._wrapApiCall(async () => {
|
return await this._wrapApiCall(async () => {
|
||||||
const deadline = options.timeout ? monotonicTime() + options.timeout : 0;
|
const deadline = options.timeout ? monotonicTime() + options.timeout : 0;
|
||||||
const headers = { 'x-playwright-browser': 'android', ...options.headers };
|
const headers = { 'x-playwright-browser': 'android', ...options.headers };
|
||||||
const localUtils = this._connection.localUtils();
|
|
||||||
const connectParams: channels.LocalUtilsConnectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout };
|
const connectParams: channels.LocalUtilsConnectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout };
|
||||||
const connection = await localUtils.connect(connectParams);
|
const connection = await connectOverWebSocket(this._connection, connectParams);
|
||||||
|
|
||||||
let device: AndroidDevice;
|
let device: AndroidDevice;
|
||||||
connection.on('close', () => {
|
connection.on('close', () => {
|
||||||
|
|
@ -113,7 +114,7 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
|
||||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.AndroidDeviceInitializer) {
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.AndroidDeviceInitializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
this.input = new AndroidInput(this);
|
this.input = new AndroidInput(this);
|
||||||
this._timeoutSettings = new TimeoutSettings((parent as Android)._timeoutSettings);
|
this._timeoutSettings = new TimeoutSettings(this._platform, (parent as Android)._timeoutSettings);
|
||||||
this._channel.on('webViewAdded', ({ webView }) => this._onWebViewAdded(webView));
|
this._channel.on('webViewAdded', ({ webView }) => this._onWebViewAdded(webView));
|
||||||
this._channel.on('webViewRemoved', ({ socketName }) => this._onWebViewRemoved(socketName));
|
this._channel.on('webViewRemoved', ({ socketName }) => this._onWebViewRemoved(socketName));
|
||||||
this._channel.on('close', () => this._didClose());
|
this._channel.on('close', () => this._didClose());
|
||||||
|
|
@ -134,7 +135,9 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
|
||||||
|
|
||||||
setDefaultTimeout(timeout: number) {
|
setDefaultTimeout(timeout: number) {
|
||||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||||
this._channel.setDefaultTimeoutNoReply({ timeout });
|
this._wrapApiCall(async () => {
|
||||||
|
await this._channel.setDefaultTimeoutNoReply({ timeout });
|
||||||
|
}, true).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
serial(): string {
|
serial(): string {
|
||||||
|
|
@ -394,7 +397,7 @@ export class AndroidWebView extends EventEmitter implements api.AndroidWebView {
|
||||||
private _pagePromise: Promise<Page> | undefined;
|
private _pagePromise: Promise<Page> | undefined;
|
||||||
|
|
||||||
constructor(device: AndroidDevice, data: channels.AndroidWebView) {
|
constructor(device: AndroidDevice, data: channels.AndroidWebView) {
|
||||||
super();
|
super(device._platform);
|
||||||
this._device = device;
|
this._device = device;
|
||||||
this._data = data;
|
this._data = data;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { Stream } from './stream';
|
import { Stream } from './stream';
|
||||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
import { mkdirIfNeeded } from './fileUtils';
|
||||||
|
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import type { Readable } from 'stream';
|
import type { Readable } from 'stream';
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { CDPSession } from './cdpSession';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { isTargetClosedError } from './errors';
|
import { isTargetClosedError } from './errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
import { mkdirIfNeeded } from './fileUtils';
|
||||||
|
|
||||||
import type { BrowserType } from './browserType';
|
import type { BrowserType } from './browserType';
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ import { Tracing } from './tracing';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
import { WebError } from './webError';
|
import { WebError } from './webError';
|
||||||
import { Worker } from './worker';
|
import { Worker } from './worker';
|
||||||
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
import { TimeoutSettings } from './timeoutSettings';
|
||||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
import { mkdirIfNeeded } from './fileUtils';
|
||||||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||||
import { urlMatchesEqual } from '../utils/isomorphic/urlMatch';
|
import { urlMatchesEqual } from '../utils/isomorphic/urlMatch';
|
||||||
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
||||||
|
|
@ -46,7 +46,7 @@ import type { BrowserContextOptions, Headers, LaunchOptions, StorageState, WaitF
|
||||||
import type * as structs from '../../types/structs';
|
import type * as structs from '../../types/structs';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type { URLMatch } from '../utils/isomorphic/urlMatch';
|
import type { URLMatch } from '../utils/isomorphic/urlMatch';
|
||||||
import type { Platform } from '../common/platform';
|
import type { Platform } from './platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
|
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
|
||||||
|
|
@ -56,7 +56,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
readonly _browser: Browser | null = null;
|
readonly _browser: Browser | null = null;
|
||||||
_browserType: BrowserType | undefined;
|
_browserType: BrowserType | undefined;
|
||||||
readonly _bindings = new Map<string, (source: structs.BindingSource, ...args: any[]) => any>();
|
readonly _bindings = new Map<string, (source: structs.BindingSource, ...args: any[]) => any>();
|
||||||
_timeoutSettings = new TimeoutSettings();
|
_timeoutSettings: TimeoutSettings;
|
||||||
_ownerPage: Page | undefined;
|
_ownerPage: Page | undefined;
|
||||||
private _closedPromise: Promise<void>;
|
private _closedPromise: Promise<void>;
|
||||||
_options: channels.BrowserNewContextParams = { };
|
_options: channels.BrowserNewContextParams = { };
|
||||||
|
|
@ -83,6 +83,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
|
|
||||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.BrowserContextInitializer) {
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.BrowserContextInitializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
|
this._timeoutSettings = new TimeoutSettings(this._platform);
|
||||||
if (parent instanceof Browser)
|
if (parent instanceof Browser)
|
||||||
this._browser = parent;
|
this._browser = parent;
|
||||||
this._browser?._contexts.add(this);
|
this._browser?._contexts.add(this);
|
||||||
|
|
@ -245,15 +246,15 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
setDefaultNavigationTimeout(timeout: number | undefined) {
|
setDefaultNavigationTimeout(timeout: number | undefined) {
|
||||||
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||||
this._wrapApiCall(async () => {
|
this._wrapApiCall(async () => {
|
||||||
this._channel.setDefaultNavigationTimeoutNoReply({ timeout }).catch(() => {});
|
await this._channel.setDefaultNavigationTimeoutNoReply({ timeout });
|
||||||
}, true);
|
}, true).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
setDefaultTimeout(timeout: number | undefined) {
|
setDefaultTimeout(timeout: number | undefined) {
|
||||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||||
this._wrapApiCall(async () => {
|
this._wrapApiCall(async () => {
|
||||||
this._channel.setDefaultTimeoutNoReply({ timeout }).catch(() => {});
|
await this._channel.setDefaultTimeoutNoReply({ timeout });
|
||||||
}, true);
|
}, true).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
browser(): Browser | null {
|
browser(): Browser | null {
|
||||||
|
|
@ -338,7 +339,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
}
|
}
|
||||||
|
|
||||||
async route(url: URLMatch, handler: network.RouteHandlerCallback, options: { times?: number } = {}): Promise<void> {
|
async route(url: URLMatch, handler: network.RouteHandlerCallback, options: { times?: number } = {}): Promise<void> {
|
||||||
this._routes.unshift(new network.RouteHandler(this._options.baseURL, url, handler, options.times));
|
this._routes.unshift(new network.RouteHandler(this._platform, this._options.baseURL, url, handler, options.times));
|
||||||
await this._updateInterceptionPatterns();
|
await this._updateInterceptionPatterns();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -361,11 +362,14 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
}
|
}
|
||||||
|
|
||||||
async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full' } = {}): Promise<void> {
|
async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full' } = {}): Promise<void> {
|
||||||
|
const localUtils = this._connection.localUtils();
|
||||||
|
if (!localUtils)
|
||||||
|
throw new Error('Route from har is not supported in thin clients');
|
||||||
if (options.update) {
|
if (options.update) {
|
||||||
await this._recordIntoHAR(har, null, options);
|
await this._recordIntoHAR(har, null, options);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url });
|
const harRouter = await HarRouter.create(localUtils, har, options.notFound || 'abort', { urlMatch: options.url });
|
||||||
this._harRouters.push(harRouter);
|
this._harRouters.push(harRouter);
|
||||||
await harRouter.addContextRoute(this);
|
await harRouter.addContextRoute(this);
|
||||||
}
|
}
|
||||||
|
|
@ -484,8 +488,11 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
const isCompressed = harParams.content === 'attach' || harParams.path.endsWith('.zip');
|
const isCompressed = harParams.content === 'attach' || harParams.path.endsWith('.zip');
|
||||||
const needCompressed = harParams.path.endsWith('.zip');
|
const needCompressed = harParams.path.endsWith('.zip');
|
||||||
if (isCompressed && !needCompressed) {
|
if (isCompressed && !needCompressed) {
|
||||||
|
const localUtils = this._connection.localUtils();
|
||||||
|
if (!localUtils)
|
||||||
|
throw new Error('Uncompressed har is not supported in thin clients');
|
||||||
await artifact.saveAs(harParams.path + '.tmp');
|
await artifact.saveAs(harParams.path + '.tmp');
|
||||||
await this._connection.localUtils().harUnzip({ zipFile: harParams.path + '.tmp', harFile: harParams.path });
|
await localUtils.harUnzip({ zipFile: harParams.path + '.tmp', harFile: harParams.path });
|
||||||
} else {
|
} else {
|
||||||
await artifact.saveAs(harParams.path);
|
await artifact.saveAs(harParams.path);
|
||||||
}
|
}
|
||||||
|
|
@ -552,7 +559,7 @@ export async function prepareBrowserContextParams(platform: Platform, options: B
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (contextParams.recordVideo && contextParams.recordVideo.dir)
|
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;
|
return contextParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,17 +14,16 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
import { Browser } from './browser';
|
import { Browser } from './browser';
|
||||||
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { envObjectToArray } from './clientHelper';
|
import { envObjectToArray } from './clientHelper';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
import { assert } from '../utils/isomorphic/assert';
|
||||||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||||
import { monotonicTime } from '../utils/isomorphic/time';
|
import { monotonicTime } from '../utils/isomorphic/time';
|
||||||
import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner';
|
import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner';
|
||||||
|
import { connectOverWebSocket } from './webSocket';
|
||||||
|
|
||||||
import type { Playwright } from './playwright';
|
import type { Playwright } from './playwright';
|
||||||
import type { ConnectOptions, LaunchOptions, LaunchPersistentContextOptions, LaunchServerOptions, Logger } from './types';
|
import type { ConnectOptions, LaunchOptions, LaunchPersistentContextOptions, LaunchServerOptions, Logger } from './types';
|
||||||
|
|
@ -100,7 +99,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
||||||
env: options.env ? envObjectToArray(options.env) : undefined,
|
env: options.env ? envObjectToArray(options.env) : undefined,
|
||||||
channel: options.channel,
|
channel: options.channel,
|
||||||
userDataDir: (path.isAbsolute(userDataDir) || !userDataDir) ? userDataDir : path.resolve(userDataDir),
|
userDataDir: (this._platform.path().isAbsolute(userDataDir) || !userDataDir) ? userDataDir : this._platform.path().resolve(userDataDir),
|
||||||
};
|
};
|
||||||
return await this._wrapApiCall(async () => {
|
return await this._wrapApiCall(async () => {
|
||||||
const result = await this._channel.launchPersistentContext(persistentParams);
|
const result = await this._channel.launchPersistentContext(persistentParams);
|
||||||
|
|
@ -124,7 +123,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
return await this._wrapApiCall(async () => {
|
return await this._wrapApiCall(async () => {
|
||||||
const deadline = params.timeout ? monotonicTime() + params.timeout : 0;
|
const deadline = params.timeout ? monotonicTime() + params.timeout : 0;
|
||||||
const headers = { 'x-playwright-browser': this.name(), ...params.headers };
|
const headers = { 'x-playwright-browser': this.name(), ...params.headers };
|
||||||
const localUtils = this._connection.localUtils();
|
|
||||||
const connectParams: channels.LocalUtilsConnectParams = {
|
const connectParams: channels.LocalUtilsConnectParams = {
|
||||||
wsEndpoint: params.wsEndpoint,
|
wsEndpoint: params.wsEndpoint,
|
||||||
headers,
|
headers,
|
||||||
|
|
@ -134,7 +132,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
};
|
};
|
||||||
if ((params as any).__testHookRedirectPortForwarding)
|
if ((params as any).__testHookRedirectPortForwarding)
|
||||||
connectParams.socksProxyRedirectPortForTest = (params as any).__testHookRedirectPortForwarding;
|
connectParams.socksProxyRedirectPortForTest = (params as any).__testHookRedirectPortForwarding;
|
||||||
const connection = await localUtils.connect(connectParams);
|
const connection = await connectOverWebSocket(this._connection, connectParams);
|
||||||
let browser: Browser;
|
let browser: Browser;
|
||||||
connection.on('close', () => {
|
connection.on('close', () => {
|
||||||
// Emulate all pages, contexts and the browser closing upon disconnect.
|
// Emulate all pages, contexts and the browser closing upon disconnect.
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,14 @@
|
||||||
|
|
||||||
import { EventEmitter } from './eventEmitter';
|
import { EventEmitter } from './eventEmitter';
|
||||||
import { ValidationError, maybeFindValidator } from '../protocol/validator';
|
import { ValidationError, maybeFindValidator } from '../protocol/validator';
|
||||||
import { isUnderTest } from '../utils/isomorphic/debug';
|
import { captureLibraryStackTrace } from './clientStackTrace';
|
||||||
import { captureLibraryStackTrace, stringifyStackFrames } from '../utils/isomorphic/stackTrace';
|
import { stringifyStackFrames } from '../utils/isomorphic/stackTrace';
|
||||||
import { zones } from '../utils/zones';
|
|
||||||
|
|
||||||
import type { ClientInstrumentation } from './clientInstrumentation';
|
import type { ClientInstrumentation } from './clientInstrumentation';
|
||||||
import type { Connection } from './connection';
|
import type { Connection } from './connection';
|
||||||
import type { Logger } from './types';
|
import type { Logger } from './types';
|
||||||
import type { ValidatorContext } from '../protocol/validator';
|
import type { ValidatorContext } from '../protocol/validator';
|
||||||
import type { Platform } from '../common/platform';
|
import type { Platform } from './platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
type Listener = (...args: any[]) => void;
|
type Listener = (...args: any[]) => void;
|
||||||
|
|
@ -39,21 +38,20 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
||||||
readonly _channel: T;
|
readonly _channel: T;
|
||||||
readonly _initializer: channels.InitializerTraits<T>;
|
readonly _initializer: channels.InitializerTraits<T>;
|
||||||
_logger: Logger | undefined;
|
_logger: Logger | undefined;
|
||||||
readonly _platform: Platform;
|
|
||||||
readonly _instrumentation: ClientInstrumentation;
|
readonly _instrumentation: ClientInstrumentation;
|
||||||
private _eventToSubscriptionMapping: Map<string, string> = new Map();
|
private _eventToSubscriptionMapping: Map<string, string> = new Map();
|
||||||
private _isInternalType = false;
|
private _isInternalType = false;
|
||||||
_wasCollected: boolean = false;
|
_wasCollected: boolean = false;
|
||||||
|
|
||||||
constructor(parent: ChannelOwner | Connection, type: string, guid: string, initializer: channels.InitializerTraits<T>) {
|
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.setMaxListeners(0);
|
||||||
this._connection = parent instanceof ChannelOwner ? parent._connection : parent;
|
this._connection = connection;
|
||||||
this._type = type;
|
this._type = type;
|
||||||
this._guid = guid;
|
this._guid = guid;
|
||||||
this._parent = parent instanceof ChannelOwner ? parent : undefined;
|
this._parent = parent instanceof ChannelOwner ? parent : undefined;
|
||||||
this._instrumentation = this._connection._instrumentation;
|
this._instrumentation = this._connection._instrumentation;
|
||||||
this._platform = this._connection.platform;
|
|
||||||
|
|
||||||
this._connection._objects.set(guid, this);
|
this._connection._objects.set(guid, this);
|
||||||
if (this._parent) {
|
if (this._parent) {
|
||||||
|
|
@ -61,7 +59,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
||||||
this._logger = this._parent._logger;
|
this._logger = this._parent._logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._channel = this._createChannel(new EventEmitter());
|
this._channel = this._createChannel(new EventEmitter(connection._platform));
|
||||||
this._initializer = initializer;
|
this._initializer = initializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,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 {
|
private _createChannel(base: Object): T {
|
||||||
const channel = new Proxy(base, {
|
const channel = new Proxy(base, {
|
||||||
get: (obj: any, prop: string | symbol) => {
|
get: (obj: any, prop: string | symbol) => {
|
||||||
|
|
@ -151,7 +157,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
||||||
if (validator) {
|
if (validator) {
|
||||||
return async (params: any) => {
|
return async (params: any) => {
|
||||||
return await this._wrapApiCall(async apiZone => {
|
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) {
|
if (!apiZone.isInternal && !apiZone.reported) {
|
||||||
// Reporting/tracing/logging this api call for the first time.
|
// Reporting/tracing/logging this api call for the first time.
|
||||||
apiZone.params = params;
|
apiZone.params = params;
|
||||||
|
|
@ -176,24 +182,24 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
||||||
|
|
||||||
async _wrapApiCall<R>(func: (apiZone: ApiZone) => Promise<R>, isInternal?: boolean): Promise<R> {
|
async _wrapApiCall<R>(func: (apiZone: ApiZone) => Promise<R>, isInternal?: boolean): Promise<R> {
|
||||||
const logger = this._logger;
|
const logger = this._logger;
|
||||||
const existingApiZone = zones.zoneData<ApiZone>('apiZone');
|
const existingApiZone = this._platform.zones.current().data<ApiZone>();
|
||||||
if (existingApiZone)
|
if (existingApiZone)
|
||||||
return await func(existingApiZone);
|
return await func(existingApiZone);
|
||||||
|
|
||||||
if (isInternal === undefined)
|
if (isInternal === undefined)
|
||||||
isInternal = this._isInternalType;
|
isInternal = this._isInternalType;
|
||||||
const stackTrace = captureLibraryStackTrace(this._platform.pathSeparator);
|
const stackTrace = captureLibraryStackTrace(this._platform);
|
||||||
const apiZone: ApiZone = { apiName: stackTrace.apiName, frames: stackTrace.frames, isInternal, reported: false, userData: undefined, stepId: undefined };
|
const apiZone: ApiZone = { apiName: stackTrace.apiName, frames: stackTrace.frames, isInternal, reported: false, userData: undefined, stepId: undefined };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await zones.run('apiZone', apiZone, async () => await func(apiZone));
|
const result = await this._platform.zones.current().push(apiZone).run(async () => await func(apiZone));
|
||||||
if (!isInternal) {
|
if (!isInternal) {
|
||||||
logApiCall(this._platform, logger, `<= ${apiZone.apiName} succeeded`);
|
logApiCall(this._platform, logger, `<= ${apiZone.apiName} succeeded`);
|
||||||
this._instrumentation.onApiCallEnd(apiZone);
|
this._instrumentation.onApiCallEnd(apiZone);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const innerError = ((process.env.PWDEBUGIMPL || 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>'))
|
if (apiZone.apiName && !apiZone.apiName.includes('<anonymous>'))
|
||||||
e.message = apiZone.apiName + ': ' + e.message;
|
e.message = apiZone.apiName + ': ' + e.message;
|
||||||
const stackFrames = '\n' + stringifyStackFrames(stackTrace.frames).join('\n') + innerError;
|
const stackFrames = '\n' + stringifyStackFrames(stackTrace.frames).join('\n') + innerError;
|
||||||
|
|
|
||||||
25
packages/playwright-core/src/client/clientBundle.ts
Normal file
25
packages/playwright-core/src/client/clientBundle.ts
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 { Connection } from './connection';
|
||||||
|
import { setPlatformForSelectors } from './selectors';
|
||||||
|
|
||||||
|
import type { Platform } from './platform';
|
||||||
|
|
||||||
|
export function createConnectionFactory(platform: Platform): () => Connection {
|
||||||
|
setPlatformForSelectors(platform);
|
||||||
|
return () => new Connection(platform);
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
import { isString } from '../utils/isomorphic/rtti';
|
import { isString } from '../utils/isomorphic/rtti';
|
||||||
|
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
import type { Platform } from '../common/platform';
|
import type { Platform } from './platform';
|
||||||
|
|
||||||
export function envObjectToArray(env: types.Env): { name: string, value: string }[] {
|
export function envObjectToArray(env: types.Env): { name: string, value: string }[] {
|
||||||
const result: { name: string, value: string }[] = [];
|
const result: { name: string, value: string }[] = [];
|
||||||
|
|
|
||||||
76
packages/playwright-core/src/client/clientStackTrace.ts
Normal file
76
packages/playwright-core/src/client/clientStackTrace.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
/**
|
||||||
|
* 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 { captureRawStack, parseStackFrame } from '../utils/isomorphic/stackTrace';
|
||||||
|
|
||||||
|
import type { Platform } from './platform';
|
||||||
|
import type { StackFrame } from '@isomorphic/stackTrace';
|
||||||
|
|
||||||
|
export function captureLibraryStackTrace(platform: Platform): { frames: StackFrame[], apiName: string } {
|
||||||
|
const stack = captureRawStack();
|
||||||
|
|
||||||
|
type ParsedFrame = {
|
||||||
|
frame: StackFrame;
|
||||||
|
frameText: string;
|
||||||
|
isPlaywrightLibrary: boolean;
|
||||||
|
};
|
||||||
|
let parsedFrames = stack.map(line => {
|
||||||
|
const frame = parseStackFrame(line, platform.pathSeparator, platform.showInternalStackFrames());
|
||||||
|
if (!frame || !frame.file)
|
||||||
|
return null;
|
||||||
|
const isPlaywrightLibrary = !!platform.coreDir && frame.file.startsWith(platform.coreDir);
|
||||||
|
const parsed: ParsedFrame = {
|
||||||
|
frame,
|
||||||
|
frameText: line,
|
||||||
|
isPlaywrightLibrary
|
||||||
|
};
|
||||||
|
return parsed;
|
||||||
|
}).filter(Boolean) as ParsedFrame[];
|
||||||
|
|
||||||
|
let apiName = '';
|
||||||
|
|
||||||
|
// Deepest transition between non-client code calling into client
|
||||||
|
// code is the api entry.
|
||||||
|
for (let i = 0; i < parsedFrames.length - 1; i++) {
|
||||||
|
const parsedFrame = parsedFrames[i];
|
||||||
|
if (parsedFrame.isPlaywrightLibrary && !parsedFrames[i + 1].isPlaywrightLibrary) {
|
||||||
|
apiName = apiName || normalizeAPIName(parsedFrame.frame.function);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAPIName(name?: string): string {
|
||||||
|
if (!name)
|
||||||
|
return '';
|
||||||
|
const match = name.match(/(API|JS|CDP|[A-Z])(.*)/);
|
||||||
|
if (!match)
|
||||||
|
return name;
|
||||||
|
return match[1].toLowerCase() + match[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for the inspector so that it did not include the test runner stack frames.
|
||||||
|
const filterPrefixes = platform.boxedStackPrefixes();
|
||||||
|
parsedFrames = parsedFrames.filter(f => {
|
||||||
|
if (filterPrefixes.some(prefix => f.frame.file.startsWith(prefix)))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
frames: parsedFrames.map(p => p.frame),
|
||||||
|
apiName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -14,8 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from './eventEmitter';
|
||||||
|
|
||||||
import { Android, AndroidDevice, AndroidSocket } from './android';
|
import { Android, AndroidDevice, AndroidSocket } from './android';
|
||||||
import { Artifact } from './artifact';
|
import { Artifact } from './artifact';
|
||||||
import { Browser } from './browser';
|
import { Browser } from './browser';
|
||||||
|
|
@ -42,13 +41,12 @@ import { Tracing } from './tracing';
|
||||||
import { Worker } from './worker';
|
import { Worker } from './worker';
|
||||||
import { WritableStream } from './writableStream';
|
import { WritableStream } from './writableStream';
|
||||||
import { ValidationError, findValidator } from '../protocol/validator';
|
import { ValidationError, findValidator } from '../protocol/validator';
|
||||||
import { formatCallLog, rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
|
import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
|
||||||
import { zones } from '../utils/zones';
|
|
||||||
|
|
||||||
import type { ClientInstrumentation } from './clientInstrumentation';
|
import type { ClientInstrumentation } from './clientInstrumentation';
|
||||||
import type { HeadersArray } from './types';
|
import type { HeadersArray } from './types';
|
||||||
import type { ValidatorContext } from '../protocol/validator';
|
import type { ValidatorContext } from '../protocol/validator';
|
||||||
import type { Platform } from '../common/platform';
|
import type { Platform } from './platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
class Root extends ChannelOwner<channels.RootChannel> {
|
class Root extends ChannelOwner<channels.RootChannel> {
|
||||||
|
|
@ -80,15 +78,13 @@ export class Connection extends EventEmitter {
|
||||||
toImpl: ((client: ChannelOwner) => any) | undefined;
|
toImpl: ((client: ChannelOwner) => any) | undefined;
|
||||||
private _tracingCount = 0;
|
private _tracingCount = 0;
|
||||||
readonly _instrumentation: ClientInstrumentation;
|
readonly _instrumentation: ClientInstrumentation;
|
||||||
readonly platform: Platform;
|
|
||||||
// Used from @playwright/test fixtures -> TODO remove?
|
// Used from @playwright/test fixtures -> TODO remove?
|
||||||
readonly headers: HeadersArray;
|
readonly headers: HeadersArray;
|
||||||
|
|
||||||
constructor(localUtils: LocalUtils | undefined, platform: Platform, instrumentation: ClientInstrumentation | undefined, headers: HeadersArray) {
|
constructor(platform: Platform, localUtils?: LocalUtils, instrumentation?: ClientInstrumentation, headers: HeadersArray = []) {
|
||||||
super();
|
super(platform);
|
||||||
this._instrumentation = instrumentation || createInstrumentation();
|
this._instrumentation = instrumentation || createInstrumentation();
|
||||||
this._localUtils = localUtils;
|
this._localUtils = localUtils;
|
||||||
this.platform = platform;
|
|
||||||
this._rootObject = new Root(this);
|
this._rootObject = new Root(this);
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
}
|
}
|
||||||
|
|
@ -109,8 +105,8 @@ export class Connection extends EventEmitter {
|
||||||
return this._rawBuffers;
|
return this._rawBuffers;
|
||||||
}
|
}
|
||||||
|
|
||||||
localUtils(): LocalUtils {
|
localUtils(): LocalUtils | undefined {
|
||||||
return this._localUtils!;
|
return this._localUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
async initializePlaywright(): Promise<Playwright> {
|
async initializePlaywright(): Promise<Playwright> {
|
||||||
|
|
@ -138,9 +134,9 @@ export class Connection extends EventEmitter {
|
||||||
const type = object._type;
|
const type = object._type;
|
||||||
const id = ++this._lastId;
|
const id = ++this._lastId;
|
||||||
const message = { id, guid, method, params };
|
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.
|
// 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 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 };
|
const metadata: channels.Metadata = { apiName, location, internal: !apiName, stepId };
|
||||||
|
|
@ -148,35 +144,43 @@ export class Connection extends EventEmitter {
|
||||||
this._localUtils?.addStackToTracingNoReply({ callData: { stack: frames, id } }).catch(() => {});
|
this._localUtils?.addStackToTracingNoReply({ callData: { stack: frames, id } }).catch(() => {});
|
||||||
// We need to exit zones before calling into the server, otherwise
|
// 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.
|
// when we receive events from the server, we would be in an API zone.
|
||||||
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 }));
|
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) {
|
dispatch(message: object) {
|
||||||
if (this._closedError)
|
if (this._closedError)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const { id, guid, method, params, result, error, log } = message as any;
|
const { id, guid, method, params, result, error, log } = message as any;
|
||||||
if (id) {
|
if (id) {
|
||||||
if (this.platform.isLogEnabled('channel'))
|
if (this._platform.isLogEnabled('channel'))
|
||||||
this.platform.log('channel', '<RECV ' + JSON.stringify(message));
|
this._platform.log('channel', '<RECV ' + JSON.stringify(message));
|
||||||
const callback = this._callbacks.get(id);
|
const callback = this._callbacks.get(id);
|
||||||
if (!callback)
|
if (!callback)
|
||||||
throw new Error(`Cannot find command to respond: ${id}`);
|
throw new Error(`Cannot find command to respond: ${id}`);
|
||||||
this._callbacks.delete(id);
|
this._callbacks.delete(id);
|
||||||
if (error && !result) {
|
if (error && !result) {
|
||||||
const parsedError = parseError(error);
|
const parsedError = parseError(error);
|
||||||
rewriteErrorMessage(parsedError, parsedError.message + formatCallLog(this.platform, log));
|
rewriteErrorMessage(parsedError, parsedError.message + formatCallLog(this._platform, log));
|
||||||
callback.reject(parsedError);
|
callback.reject(parsedError);
|
||||||
} else {
|
} else {
|
||||||
const validator = findValidator(callback.type, callback.method, 'Result');
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.platform.isLogEnabled('channel'))
|
if (this._platform.isLogEnabled('channel'))
|
||||||
this.platform.log('channel', '<EVENT ' + JSON.stringify(message));
|
this._platform.log('channel', '<EVENT ' + JSON.stringify(message));
|
||||||
if (method === '__create__') {
|
if (method === '__create__') {
|
||||||
this._createRemoteObject(guid, params.type, params.guid, params.initializer);
|
this._createRemoteObject(guid, params.type, params.guid, params.initializer);
|
||||||
return;
|
return;
|
||||||
|
|
@ -200,7 +204,7 @@ export class Connection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
const validator = findValidator(object._type, method, 'Event');
|
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) {
|
close(cause?: string) {
|
||||||
|
|
@ -231,7 +235,7 @@ export class Connection extends EventEmitter {
|
||||||
throw new Error(`Cannot find parent object ${parentGuid} to create ${guid}`);
|
throw new Error(`Cannot find parent object ${parentGuid} to create ${guid}`);
|
||||||
let result: ChannelOwner<any>;
|
let result: ChannelOwner<any>;
|
||||||
const validator = findValidator(type, '', 'Initializer');
|
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) {
|
switch (type) {
|
||||||
case 'Android':
|
case 'Android':
|
||||||
result = new Android(parent, type, guid, initializer);
|
result = new Android(parent, type, guid, initializer);
|
||||||
|
|
@ -334,3 +338,12 @@ export class Connection extends EventEmitter {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatCallLog(platform: Platform, log: string[] | undefined): string {
|
||||||
|
if (!log || !log.some(l => !!l))
|
||||||
|
return '';
|
||||||
|
return `
|
||||||
|
Call log:
|
||||||
|
${platform.colors.dim(log.join('\n'))}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import { JSHandle } from './jsHandle';
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
|
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type { Platform } from '../common/platform';
|
import type { Platform } from './platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
type ConsoleMessageLocation = channels.BrowserContextConsoleEvent['location'];
|
type ConsoleMessageLocation = channels.BrowserContextConsoleEvent['location'];
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import { TargetClosedError, isTargetClosedError } from './errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { JSHandle, parseResult, serializeArgument } from './jsHandle';
|
import { JSHandle, parseResult, serializeArgument } from './jsHandle';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
import { TimeoutSettings } from './timeoutSettings';
|
||||||
|
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
import type { BrowserContextOptions, Env, Headers, WaitForEventOptions } from './types';
|
import type { BrowserContextOptions, Env, Headers, WaitForEventOptions } from './types';
|
||||||
|
|
@ -54,7 +54,7 @@ export class Electron extends ChannelOwner<channels.ElectronChannel> implements
|
||||||
async launch(options: ElectronOptions = {}): Promise<ElectronApplication> {
|
async launch(options: ElectronOptions = {}): Promise<ElectronApplication> {
|
||||||
const params: channels.ElectronLaunchParams = {
|
const params: channels.ElectronLaunchParams = {
|
||||||
...await prepareBrowserContextParams(this._platform, options),
|
...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,
|
tracesDir: options.tracesDir,
|
||||||
};
|
};
|
||||||
const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);
|
const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);
|
||||||
|
|
@ -66,7 +66,7 @@ export class Electron extends ChannelOwner<channels.ElectronChannel> implements
|
||||||
export class ElectronApplication extends ChannelOwner<channels.ElectronApplicationChannel> implements api.ElectronApplication {
|
export class ElectronApplication extends ChannelOwner<channels.ElectronApplicationChannel> implements api.ElectronApplication {
|
||||||
readonly _context: BrowserContext;
|
readonly _context: BrowserContext;
|
||||||
private _windows = new Set<Page>();
|
private _windows = new Set<Page>();
|
||||||
private _timeoutSettings = new TimeoutSettings();
|
private _timeoutSettings: TimeoutSettings;
|
||||||
|
|
||||||
static from(electronApplication: channels.ElectronApplicationChannel): ElectronApplication {
|
static from(electronApplication: channels.ElectronApplicationChannel): ElectronApplication {
|
||||||
return (electronApplication as any)._object;
|
return (electronApplication as any)._object;
|
||||||
|
|
@ -74,6 +74,8 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
|
||||||
|
|
||||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.ElectronApplicationInitializer) {
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.ElectronApplicationInitializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
|
|
||||||
|
this._timeoutSettings = new TimeoutSettings(this._platform);
|
||||||
this._context = BrowserContext.from(initializer.context);
|
this._context = BrowserContext.from(initializer.context);
|
||||||
for (const page of this._context._pages)
|
for (const page of this._context._pages)
|
||||||
this._onPage(page);
|
this._onPage(page);
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { pipeline } from 'stream';
|
|
||||||
import { promisify } from 'util';
|
|
||||||
|
|
||||||
import { Frame } from './frame';
|
import { Frame } from './frame';
|
||||||
import { JSHandle, parseResult, serializeArgument } from './jsHandle';
|
import { JSHandle, parseResult, serializeArgument } from './jsHandle';
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
import { assert } from '../utils/isomorphic/assert';
|
||||||
import { fileUploadSizeLimit, mkdirIfNeeded } from '../common/fileUtils';
|
import { fileUploadSizeLimit, mkdirIfNeeded } from './fileUtils';
|
||||||
import { isString } from '../utils/isomorphic/rtti';
|
import { isString } from '../utils/isomorphic/rtti';
|
||||||
import { mime } from '../utilsBundle';
|
|
||||||
import { WritableStream } from './writableStream';
|
import { WritableStream } from './writableStream';
|
||||||
|
import { getMimeTypeForPath } from '../utils/isomorphic/mimeType';
|
||||||
|
|
||||||
import type { BrowserContext } from './browserContext';
|
import type { BrowserContext } from './browserContext';
|
||||||
import type { ChannelOwner } from './channelOwner';
|
import type { ChannelOwner } from './channelOwner';
|
||||||
|
|
@ -31,11 +28,9 @@ import type { Locator } from './locator';
|
||||||
import type { FilePayload, Rect, SelectOption, SelectOptionOptions } from './types';
|
import type { FilePayload, Rect, SelectOption, SelectOptionOptions } from './types';
|
||||||
import type * as structs from '../../types/structs';
|
import type * as structs from '../../types/structs';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type { Platform } from '../common/platform';
|
import type { Platform } from './platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
const pipelineAsync = promisify(pipeline);
|
|
||||||
|
|
||||||
export class ElementHandle<T extends Node = Node> extends JSHandle<T> implements api.ElementHandle {
|
export class ElementHandle<T extends Node = Node> extends JSHandle<T> implements api.ElementHandle {
|
||||||
readonly _elementChannel: channels.ElementHandleChannel;
|
readonly _elementChannel: channels.ElementHandleChannel;
|
||||||
|
|
||||||
|
|
@ -306,7 +301,7 @@ export async function convertInputFiles(platform: Platform, files: string | File
|
||||||
}), true);
|
}), true);
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
const writable = WritableStream.from(writableStreams[i]);
|
const writable = WritableStream.from(writableStreams[i]);
|
||||||
await pipelineAsync(platform.fs().createReadStream(files[i]), writable.stream());
|
await platform.streamFile(files[i], writable.stream());
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
directoryStream: rootDir,
|
directoryStream: rootDir,
|
||||||
|
|
@ -327,7 +322,7 @@ export async function convertInputFiles(platform: Platform, files: string | File
|
||||||
|
|
||||||
export function determineScreenshotType(options: { path?: string, type?: 'png' | 'jpeg' }): 'png' | 'jpeg' | undefined {
|
export function determineScreenshotType(options: { path?: string, type?: 'png' | 'jpeg' }): 'png' | 'jpeg' | undefined {
|
||||||
if (options.path) {
|
if (options.path) {
|
||||||
const mimeType = mime.getType(options.path);
|
const mimeType = getMimeTypeForPath(options.path);
|
||||||
if (mimeType === 'image/png')
|
if (mimeType === 'image/png')
|
||||||
return 'png';
|
return 'png';
|
||||||
else if (mimeType === 'image/jpeg')
|
else if (mimeType === 'image/jpeg')
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,8 @@
|
||||||
* USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter as OriginalEventEmitter } from 'events';
|
|
||||||
|
|
||||||
import { isUnderTest } from '../utils/isomorphic/debug';
|
|
||||||
|
|
||||||
import type { EventEmitter as EventEmitterType } from 'events';
|
import type { EventEmitter as EventEmitterType } from 'events';
|
||||||
|
import type { Platform } from './platform';
|
||||||
|
|
||||||
type EventType = string | symbol;
|
type EventType = string | symbol;
|
||||||
type Listener = (...args: any[]) => any;
|
type Listener = (...args: any[]) => any;
|
||||||
|
|
@ -39,8 +36,10 @@ export class EventEmitter implements EventEmitterType {
|
||||||
private _maxListeners: number | undefined = undefined;
|
private _maxListeners: number | undefined = undefined;
|
||||||
readonly _pendingHandlers = new Map<EventType, Set<Promise<void>>>();
|
readonly _pendingHandlers = new Map<EventType, Set<Promise<void>>>();
|
||||||
private _rejectionHandler: ((error: Error) => void) | undefined;
|
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) {
|
if (this._events === undefined || this._events === Object.getPrototypeOf(this)._events) {
|
||||||
this._events = Object.create(null);
|
this._events = Object.create(null);
|
||||||
this._eventsCount = 0;
|
this._eventsCount = 0;
|
||||||
|
|
@ -58,7 +57,7 @@ export class EventEmitter implements EventEmitterType {
|
||||||
}
|
}
|
||||||
|
|
||||||
getMaxListeners(): number {
|
getMaxListeners(): number {
|
||||||
return this._maxListeners === undefined ? OriginalEventEmitter.defaultMaxListeners : this._maxListeners;
|
return this._maxListeners === undefined ? this._platform.defaultMaxListeners() : this._maxListeners;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(type: EventType, ...args: any[]): boolean {
|
emit(type: EventType, ...args: any[]): boolean {
|
||||||
|
|
@ -156,7 +155,7 @@ export class EventEmitter implements EventEmitterType {
|
||||||
w.emitter = this;
|
w.emitter = this;
|
||||||
w.type = type;
|
w.type = type;
|
||||||
w.count = existing.length;
|
w.count = existing.length;
|
||||||
if (!isUnderTest()) {
|
if (!this._platform.isUnderTest()) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.warn(w);
|
console.warn(w);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import { ChannelOwner } from './channelOwner';
|
||||||
import { TargetClosedError, isTargetClosedError } from './errors';
|
import { TargetClosedError, isTargetClosedError } from './errors';
|
||||||
import { RawHeaders } from './network';
|
import { RawHeaders } from './network';
|
||||||
import { Tracing } from './tracing';
|
import { Tracing } from './tracing';
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
import { assert } from '../utils/isomorphic/assert';
|
||||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
import { mkdirIfNeeded } from './fileUtils';
|
||||||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||||
import { isString } from '../utils/isomorphic/rtti';
|
import { isString } from '../utils/isomorphic/rtti';
|
||||||
|
|
||||||
|
|
@ -28,8 +28,8 @@ import type { Playwright } from './playwright';
|
||||||
import type { ClientCertificate, FilePayload, Headers, SetStorageState, StorageState } from './types';
|
import type { ClientCertificate, FilePayload, Headers, SetStorageState, StorageState } from './types';
|
||||||
import type { Serializable } from '../../types/structs';
|
import type { Serializable } from '../../types/structs';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type { HeadersArray, NameValue } from '../common/types';
|
import type { HeadersArray, NameValue } from '../utils/isomorphic/types';
|
||||||
import type { Platform } from '../common/platform';
|
import type { Platform } from './platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import type * as fs from 'fs';
|
import type * as fs from 'fs';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,10 @@
|
||||||
|
|
||||||
import type { Platform } from './platform';
|
import type { Platform } from './platform';
|
||||||
|
|
||||||
|
// Keep in sync with the server.
|
||||||
export const fileUploadSizeLimit = 50 * 1024 * 1024;
|
export const fileUploadSizeLimit = 50 * 1024 * 1024;
|
||||||
|
|
||||||
export async function mkdirIfNeeded(platform: Platform, filePath: string) {
|
export async function mkdirIfNeeded(platform: Platform, filePath: string) {
|
||||||
// This will harmlessly throw on windows if the dirname is the root directory.
|
// This will harmlessly throw on windows if the dirname is the root directory.
|
||||||
await platform.fs().promises.mkdir(platform.path().dirname(filePath), { recursive: true }).catch(() => {});
|
await platform.fs().promises.mkdir(platform.path().dirname(filePath), { recursive: true }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeFolders(platform: Platform, dirs: string[]): Promise<Error[]> {
|
|
||||||
return await Promise.all(dirs.map((dir: string) =>
|
|
||||||
platform.fs().promises.rm(dir, { recursive: true, force: true, maxRetries: 10 }).catch(e => e)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from './eventEmitter';
|
||||||
|
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { addSourceUrlToScript } from './clientHelper';
|
import { addSourceUrlToScript } from './clientHelper';
|
||||||
import { ElementHandle, convertInputFiles, convertSelectOptionValues } from './elementHandle';
|
import { ElementHandle, convertInputFiles, convertSelectOptionValues } from './elementHandle';
|
||||||
|
|
@ -26,7 +25,7 @@ import { FrameLocator, Locator, testIdAttributeName } from './locator';
|
||||||
import * as network from './network';
|
import * as network from './network';
|
||||||
import { kLifecycleEvents } from './types';
|
import { kLifecycleEvents } from './types';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
import { assert } from '../utils/isomorphic/assert';
|
||||||
import { getByAltTextSelector, getByLabelSelector, getByPlaceholderSelector, getByRoleSelector, getByTestIdSelector, getByTextSelector, getByTitleSelector } from '../utils/isomorphic/locatorUtils';
|
import { getByAltTextSelector, getByLabelSelector, getByPlaceholderSelector, getByRoleSelector, getByTestIdSelector, getByTextSelector, getByTitleSelector } from '../utils/isomorphic/locatorUtils';
|
||||||
import { urlMatches } from '../utils/isomorphic/urlMatch';
|
import { urlMatches } from '../utils/isomorphic/urlMatch';
|
||||||
|
|
||||||
|
|
@ -65,7 +64,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
|
||||||
|
|
||||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.FrameInitializer) {
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.FrameInitializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
this._eventEmitter = new EventEmitter();
|
this._eventEmitter = new EventEmitter(parent._platform);
|
||||||
this._eventEmitter.setMaxListeners(0);
|
this._eventEmitter.setMaxListeners(0);
|
||||||
this._parentFrame = Frame.fromNullable(initializer.parentFrame);
|
this._parentFrame = Frame.fromNullable(initializer.parentFrame);
|
||||||
if (this._parentFrame)
|
if (this._parentFrame)
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { Connection } from './connection';
|
|
||||||
import * as localUtils from '../common/localUtils';
|
|
||||||
|
|
||||||
import type { HeadersArray, Size } from './types';
|
import type { Size } from './types';
|
||||||
import type { HarBackend } from '../common/harBackend';
|
|
||||||
import type { Platform } from '../common/platform';
|
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
type DeviceDescriptor = {
|
type DeviceDescriptor = {
|
||||||
|
|
@ -35,8 +31,6 @@ type Devices = { [name: string]: DeviceDescriptor };
|
||||||
|
|
||||||
export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
|
export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
|
||||||
readonly devices: Devices;
|
readonly devices: Devices;
|
||||||
private _harBackends = new Map<string, HarBackend>();
|
|
||||||
private _stackSessions = new Map<string, localUtils.StackSession>();
|
|
||||||
|
|
||||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.LocalUtilsInitializer) {
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.LocalUtilsInitializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
|
|
@ -47,132 +41,34 @@ export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
||||||
return await localUtils.zip(this._platform, this._stackSessions, params);
|
return await this._channel.zip(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async harOpen(params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> {
|
async harOpen(params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> {
|
||||||
return await localUtils.harOpen(this._platform, this._harBackends, params);
|
return await this._channel.harOpen(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async harLookup(params: channels.LocalUtilsHarLookupParams): Promise<channels.LocalUtilsHarLookupResult> {
|
async harLookup(params: channels.LocalUtilsHarLookupParams): Promise<channels.LocalUtilsHarLookupResult> {
|
||||||
return await localUtils.harLookup(this._harBackends, params);
|
return await this._channel.harLookup(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async harClose(params: channels.LocalUtilsHarCloseParams): Promise<void> {
|
async harClose(params: channels.LocalUtilsHarCloseParams): Promise<void> {
|
||||||
return await localUtils.harClose(this._harBackends, params);
|
return await this._channel.harClose(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async harUnzip(params: channels.LocalUtilsHarUnzipParams): Promise<void> {
|
async harUnzip(params: channels.LocalUtilsHarUnzipParams): Promise<void> {
|
||||||
return await localUtils.harUnzip(params);
|
return await this._channel.harUnzip(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async tracingStarted(params: channels.LocalUtilsTracingStartedParams): Promise<channels.LocalUtilsTracingStartedResult> {
|
async tracingStarted(params: channels.LocalUtilsTracingStartedParams): Promise<channels.LocalUtilsTracingStartedResult> {
|
||||||
return await localUtils.tracingStarted(this._stackSessions, params);
|
return await this._channel.tracingStarted(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
|
async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
|
||||||
return await localUtils.traceDiscarded(this._platform, this._stackSessions, params);
|
return await this._channel.traceDiscarded(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> {
|
async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> {
|
||||||
return await localUtils.addStackToTracingNoReply(this._stackSessions, params);
|
return await this._channel.addStackToTracingNoReply(params);
|
||||||
}
|
|
||||||
|
|
||||||
async connect(params: channels.LocalUtilsConnectParams): Promise<Connection> {
|
|
||||||
const transport = this._platform.ws ? new WebSocketTransport(this._platform) : new JsonPipeTransport(this);
|
|
||||||
const connectHeaders = await transport.connect(params);
|
|
||||||
const connection = new Connection(this, this._platform, this._instrumentation, connectHeaders);
|
|
||||||
connection.markAsRemote();
|
|
||||||
connection.on('close', () => transport.close());
|
|
||||||
|
|
||||||
let closeError: string | undefined;
|
|
||||||
const onTransportClosed = (reason?: string) => {
|
|
||||||
connection.close(reason || closeError);
|
|
||||||
};
|
|
||||||
transport.onClose(reason => onTransportClosed(reason));
|
|
||||||
connection.onmessage = message => transport.send(message).catch(() => onTransportClosed());
|
|
||||||
transport.onMessage(message => {
|
|
||||||
try {
|
|
||||||
connection!.dispatch(message);
|
|
||||||
} catch (e) {
|
|
||||||
closeError = String(e);
|
|
||||||
transport.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
interface Transport {
|
|
||||||
connect(params: channels.LocalUtilsConnectParams): Promise<HeadersArray>;
|
|
||||||
send(message: any): Promise<void>;
|
|
||||||
onMessage(callback: (message: object) => void): void;
|
|
||||||
onClose(callback: (reason?: string) => void): void;
|
|
||||||
close(): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class JsonPipeTransport implements Transport {
|
|
||||||
private _pipe: channels.JsonPipeChannel | undefined;
|
|
||||||
private _owner: ChannelOwner<channels.LocalUtilsChannel>;
|
|
||||||
|
|
||||||
constructor(owner: ChannelOwner<channels.LocalUtilsChannel>) {
|
|
||||||
this._owner = owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
async connect(params: channels.LocalUtilsConnectParams) {
|
|
||||||
const { pipe, headers: connectHeaders } = await this._owner._wrapApiCall(async () => {
|
|
||||||
return await this._owner._channel.connect(params);
|
|
||||||
}, /* isInternal */ true);
|
|
||||||
this._pipe = pipe;
|
|
||||||
return connectHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
async send(message: object) {
|
|
||||||
this._owner._wrapApiCall(async () => {
|
|
||||||
await this._pipe!.send({ message });
|
|
||||||
}, /* isInternal */ true);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage(callback: (message: object) => void) {
|
|
||||||
this._pipe!.on('message', ({ message }) => callback(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose(callback: (reason?: string) => void) {
|
|
||||||
this._pipe!.on('closed', ({ reason }) => callback(reason));
|
|
||||||
}
|
|
||||||
|
|
||||||
async close() {
|
|
||||||
await this._owner._wrapApiCall(async () => {
|
|
||||||
await this._pipe!.close().catch(() => {});
|
|
||||||
}, /* isInternal */ true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WebSocketTransport implements Transport {
|
|
||||||
private _platform: Platform;
|
|
||||||
private _ws: WebSocket | undefined;
|
|
||||||
|
|
||||||
constructor(platform: Platform) {
|
|
||||||
this._platform = platform;
|
|
||||||
}
|
|
||||||
|
|
||||||
async connect(params: channels.LocalUtilsConnectParams) {
|
|
||||||
this._ws = this._platform.ws!(params.wsEndpoint);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
async send(message: object) {
|
|
||||||
this._ws!.send(JSON.stringify(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
onMessage(callback: (message: object) => void) {
|
|
||||||
this._ws!.addEventListener('message', event => callback(JSON.parse(event.data)));
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose(callback: (reason?: string) => void) {
|
|
||||||
this._ws!.addEventListener('close', () => callback());
|
|
||||||
}
|
|
||||||
|
|
||||||
async close() {
|
|
||||||
this._ws!.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { URLSearchParams } from 'url';
|
|
||||||
|
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { isTargetClosedError } from './errors';
|
import { isTargetClosedError } from './errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
|
|
@ -23,25 +21,24 @@ import { APIResponse } from './fetch';
|
||||||
import { Frame } from './frame';
|
import { Frame } from './frame';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
import { Worker } from './worker';
|
import { Worker } from './worker';
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
import { assert } from '../utils/isomorphic/assert';
|
||||||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||||
import { urlMatches } from '../utils/isomorphic/urlMatch';
|
import { urlMatches } from '../utils/isomorphic/urlMatch';
|
||||||
import { LongStandingScope, ManualPromise } from '../utils/isomorphic/manualPromise';
|
import { LongStandingScope, ManualPromise } from '../utils/isomorphic/manualPromise';
|
||||||
import { MultiMap } from '../utils/isomorphic/multimap';
|
import { MultiMap } from '../utils/isomorphic/multimap';
|
||||||
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
import { isRegExp, isString } from '../utils/isomorphic/rtti';
|
||||||
import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
|
import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
|
||||||
import { zones } from '../utils/zones';
|
import { getMimeTypeForPath } from '../utils/isomorphic/mimeType';
|
||||||
import { mime } from '../utilsBundle';
|
|
||||||
|
|
||||||
import type { BrowserContext } from './browserContext';
|
import type { BrowserContext } from './browserContext';
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
import type { Headers, RemoteAddr, SecurityDetails, WaitForEventOptions } from './types';
|
import type { Headers, RemoteAddr, SecurityDetails, WaitForEventOptions } from './types';
|
||||||
import type { Serializable } from '../../types/structs';
|
import type { Serializable } from '../../types/structs';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type { HeadersArray } from '../common/types';
|
import type { HeadersArray } from '../utils/isomorphic/types';
|
||||||
import type { URLMatch } from '../utils/isomorphic/urlMatch';
|
import type { URLMatch } from '../utils/isomorphic/urlMatch';
|
||||||
import type { Zone } from '../utils/zones';
|
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
import type { Platform, Zone } from './platform';
|
||||||
|
|
||||||
export type NetworkCookie = {
|
export type NetworkCookie = {
|
||||||
name: string,
|
name: string,
|
||||||
|
|
@ -414,7 +411,7 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
|
||||||
else if (options.json)
|
else if (options.json)
|
||||||
headers['content-type'] = 'application/json';
|
headers['content-type'] = 'application/json';
|
||||||
else if (options.path)
|
else if (options.path)
|
||||||
headers['content-type'] = mime.getType(options.path) || 'application/octet-stream';
|
headers['content-type'] = getMimeTypeForPath(options.path) || 'application/octet-stream';
|
||||||
if (length && !('content-length' in headers))
|
if (length && !('content-length' in headers))
|
||||||
headers['content-length'] = String(length);
|
headers['content-length'] = String(length);
|
||||||
|
|
||||||
|
|
@ -821,14 +818,14 @@ export class RouteHandler {
|
||||||
readonly handler: RouteHandlerCallback;
|
readonly handler: RouteHandlerCallback;
|
||||||
private _ignoreException: boolean = false;
|
private _ignoreException: boolean = false;
|
||||||
private _activeInvocations: Set<{ complete: Promise<void>, route: Route }> = new Set();
|
private _activeInvocations: Set<{ complete: Promise<void>, route: Route }> = new Set();
|
||||||
private _svedZone: Zone;
|
private _savedZone: Zone;
|
||||||
|
|
||||||
constructor(baseURL: string | undefined, url: URLMatch, handler: RouteHandlerCallback, times: number = Number.MAX_SAFE_INTEGER) {
|
constructor(platform: Platform, baseURL: string | undefined, url: URLMatch, handler: RouteHandlerCallback, times: number = Number.MAX_SAFE_INTEGER) {
|
||||||
this._baseURL = baseURL;
|
this._baseURL = baseURL;
|
||||||
this._times = times;
|
this._times = times;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this._svedZone = zones.current().without('apiZone');
|
this._savedZone = platform.zones.current().pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
static prepareInterceptionPatterns(handlers: RouteHandler[]) {
|
static prepareInterceptionPatterns(handlers: RouteHandler[]) {
|
||||||
|
|
@ -852,7 +849,7 @@ export class RouteHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(route: Route): Promise<boolean> {
|
public async handle(route: Route): Promise<boolean> {
|
||||||
return await this._svedZone.run(async () => this._handleImpl(route));
|
return await this._savedZone.run(async () => this._handleImpl(route));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleImpl(route: Route): Promise<boolean> {
|
private async _handleImpl(route: Route): Promise<boolean> {
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@ import { Response, Route, RouteHandler, WebSocket, WebSocketRoute, WebSocketRou
|
||||||
import { Video } from './video';
|
import { Video } from './video';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
import { Worker } from './worker';
|
import { Worker } from './worker';
|
||||||
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
import { TimeoutSettings } from './timeoutSettings';
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
import { assert } from '../utils/isomorphic/assert';
|
||||||
import { mkdirIfNeeded } from '../common/fileUtils';
|
import { mkdirIfNeeded } from './fileUtils';
|
||||||
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
import { headersObjectToArray } from '../utils/isomorphic/headers';
|
||||||
import { trimStringWithEllipsis } from '../utils/isomorphic/stringUtils';
|
import { trimStringWithEllipsis } from '../utils/isomorphic/stringUtils';
|
||||||
import { urlMatches, urlMatchesEqual } from '../utils/isomorphic/urlMatch';
|
import { urlMatches, urlMatchesEqual } from '../utils/isomorphic/urlMatch';
|
||||||
|
|
@ -118,7 +118,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.PageInitializer) {
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.PageInitializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
this._browserContext = parent as unknown as BrowserContext;
|
this._browserContext = parent as unknown as BrowserContext;
|
||||||
this._timeoutSettings = new TimeoutSettings(this._browserContext._timeoutSettings);
|
this._timeoutSettings = new TimeoutSettings(this._platform, this._browserContext._timeoutSettings);
|
||||||
|
|
||||||
this.accessibility = new Accessibility(this._channel);
|
this.accessibility = new Accessibility(this._channel);
|
||||||
this.keyboard = new Keyboard(this);
|
this.keyboard = new Keyboard(this);
|
||||||
|
|
@ -277,15 +277,15 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||||
setDefaultNavigationTimeout(timeout: number) {
|
setDefaultNavigationTimeout(timeout: number) {
|
||||||
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||||
this._wrapApiCall(async () => {
|
this._wrapApiCall(async () => {
|
||||||
this._channel.setDefaultNavigationTimeoutNoReply({ timeout }).catch(() => {});
|
await this._channel.setDefaultNavigationTimeoutNoReply({ timeout });
|
||||||
}, true);
|
}, true).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
setDefaultTimeout(timeout: number) {
|
setDefaultTimeout(timeout: number) {
|
||||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||||
this._wrapApiCall(async () => {
|
this._wrapApiCall(async () => {
|
||||||
this._channel.setDefaultTimeoutNoReply({ timeout }).catch(() => {});
|
await this._channel.setDefaultTimeoutNoReply({ timeout });
|
||||||
}, true);
|
}, true).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _forceVideo(): Video {
|
private _forceVideo(): Video {
|
||||||
|
|
@ -520,16 +520,19 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||||
}
|
}
|
||||||
|
|
||||||
async route(url: URLMatch, handler: RouteHandlerCallback, options: { times?: number } = {}): Promise<void> {
|
async route(url: URLMatch, handler: RouteHandlerCallback, options: { times?: number } = {}): Promise<void> {
|
||||||
this._routes.unshift(new RouteHandler(this._browserContext._options.baseURL, url, handler, options.times));
|
this._routes.unshift(new RouteHandler(this._platform, this._browserContext._options.baseURL, url, handler, options.times));
|
||||||
await this._updateInterceptionPatterns();
|
await this._updateInterceptionPatterns();
|
||||||
}
|
}
|
||||||
|
|
||||||
async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full'} = {}): Promise<void> {
|
async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full'} = {}): Promise<void> {
|
||||||
|
const localUtils = this._connection.localUtils();
|
||||||
|
if (!localUtils)
|
||||||
|
throw new Error('Route from har is not supported in thin clients');
|
||||||
if (options.update) {
|
if (options.update) {
|
||||||
await this._browserContext._recordIntoHAR(har, this, options);
|
await this._browserContext._recordIntoHAR(har, this, options);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url });
|
const harRouter = await HarRouter.create(localUtils, har, options.notFound || 'abort', { urlMatch: options.url });
|
||||||
this._harRouters.push(harRouter);
|
this._harRouters.push(harRouter);
|
||||||
await harRouter.addPageRoute(this);
|
await harRouter.addPageRoute(this);
|
||||||
}
|
}
|
||||||
|
|
@ -796,7 +799,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||||
}
|
}
|
||||||
|
|
||||||
async pause(_options?: { __testHookKeepTestTimeout: boolean }) {
|
async pause(_options?: { __testHookKeepTestTimeout: boolean }) {
|
||||||
if (require('inspector').url())
|
if (this._platform.isJSDebuggerAttached())
|
||||||
return;
|
return;
|
||||||
const defaultNavigationTimeout = this._browserContext._timeoutSettings.defaultNavigationTimeout();
|
const defaultNavigationTimeout = this._browserContext._timeoutSettings.defaultNavigationTimeout();
|
||||||
const defaultTimeout = this._browserContext._timeoutSettings.defaultTimeout();
|
const defaultTimeout = this._browserContext._timeoutSettings.defaultTimeout();
|
||||||
|
|
|
||||||
143
packages/playwright-core/src/client/platform.ts
Normal file
143
packages/playwright-core/src/client/platform.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
/**
|
||||||
|
* 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 { webColors, noColors } from '../utils/isomorphic/colors';
|
||||||
|
|
||||||
|
import type * as fs from 'fs';
|
||||||
|
import type * as path from 'path';
|
||||||
|
import type { Readable, Writable } from 'stream';
|
||||||
|
import type { Colors } from '@isomorphic/colors';
|
||||||
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
|
export type Zone = {
|
||||||
|
push(data: unknown): Zone;
|
||||||
|
pop(): Zone;
|
||||||
|
run<R>(func: () => R): R;
|
||||||
|
data<T>(): T | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const noopZone: Zone = {
|
||||||
|
push: () => noopZone,
|
||||||
|
pop: () => noopZone,
|
||||||
|
run: func => func(),
|
||||||
|
data: () => undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Platform = {
|
||||||
|
name: 'node' | 'web' | 'empty';
|
||||||
|
|
||||||
|
boxedStackPrefixes: () => string[];
|
||||||
|
calculateSha1: (text: string) => Promise<string>;
|
||||||
|
colors: Colors;
|
||||||
|
coreDir?: string;
|
||||||
|
createGuid: () => string;
|
||||||
|
defaultMaxListeners: () => number;
|
||||||
|
env: Record<string, string | undefined>;
|
||||||
|
fs: () => typeof fs;
|
||||||
|
inspectCustom: symbol | undefined;
|
||||||
|
isDebugMode: () => boolean;
|
||||||
|
isJSDebuggerAttached: () => boolean;
|
||||||
|
isLogEnabled: (name: 'api' | 'channel') => boolean;
|
||||||
|
isUnderTest: () => boolean,
|
||||||
|
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,
|
||||||
|
zones: { empty: Zone, current: () => Zone; };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emptyPlatform: Platform = {
|
||||||
|
name: 'empty',
|
||||||
|
|
||||||
|
boxedStackPrefixes: () => [],
|
||||||
|
|
||||||
|
calculateSha1: async () => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
colors: noColors,
|
||||||
|
|
||||||
|
createGuid: () => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultMaxListeners: () => 10,
|
||||||
|
|
||||||
|
env: {},
|
||||||
|
|
||||||
|
fs: () => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
|
inspectCustom: undefined,
|
||||||
|
|
||||||
|
isDebugMode: () => false,
|
||||||
|
|
||||||
|
isJSDebuggerAttached: () => false,
|
||||||
|
|
||||||
|
isLogEnabled(name: 'api' | 'channel') {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
isUnderTest: () => false,
|
||||||
|
|
||||||
|
log(name: 'api' | 'channel', message: string | Error | object) { },
|
||||||
|
|
||||||
|
path: () => {
|
||||||
|
throw new Error('Function not implemented.');
|
||||||
|
},
|
||||||
|
|
||||||
|
pathSeparator: '/',
|
||||||
|
|
||||||
|
showInternalStackFrames: () => false,
|
||||||
|
|
||||||
|
streamFile(path: string, writable: Writable): Promise<void> {
|
||||||
|
throw new Error('Streams are not available');
|
||||||
|
},
|
||||||
|
|
||||||
|
streamReadable: (channel: channels.StreamChannel) => {
|
||||||
|
throw new Error('Streams are not available');
|
||||||
|
},
|
||||||
|
|
||||||
|
streamWritable: (channel: channels.WritableStreamChannel) => {
|
||||||
|
throw new Error('Streams are not available');
|
||||||
|
},
|
||||||
|
|
||||||
|
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('');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -17,12 +17,12 @@
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { evaluationScript } from './clientHelper';
|
import { evaluationScript } from './clientHelper';
|
||||||
import { setTestIdAttribute, testIdAttributeName } from './locator';
|
import { setTestIdAttribute, testIdAttributeName } from './locator';
|
||||||
import { emptyPlatform } from '../common/platform';
|
import { emptyPlatform } from './platform';
|
||||||
|
|
||||||
import type { SelectorEngine } from './types';
|
import type { SelectorEngine } from './types';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import type { Platform } from '../common/platform';
|
import type { Platform } from './platform';
|
||||||
|
|
||||||
let platform = emptyPlatform;
|
let platform = emptyPlatform;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,29 +30,6 @@ export class Stream extends ChannelOwner<channels.StreamChannel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
stream(): Readable {
|
stream(): Readable {
|
||||||
return new StreamImpl(this._channel);
|
return this._platform.streamReadable(this._channel);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class StreamImpl extends Readable {
|
|
||||||
private _channel: channels.StreamChannel;
|
|
||||||
|
|
||||||
constructor(channel: channels.StreamChannel) {
|
|
||||||
super();
|
|
||||||
this._channel = channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
override async _read() {
|
|
||||||
const result = await this._channel.read({ size: 1024 * 1024 });
|
|
||||||
if (result.binary.byteLength)
|
|
||||||
this.push(result.binary);
|
|
||||||
else
|
|
||||||
this.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
override _destroy(error: Error | null, callback: (error: Error | null | undefined) => void): void {
|
|
||||||
// Stream might be destroyed after the connection was closed.
|
|
||||||
this._channel.close().catch(e => null);
|
|
||||||
super._destroy(error, callback);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
76
packages/playwright-core/src/client/timeoutSettings.ts
Normal file
76
packages/playwright-core/src/client/timeoutSettings.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2019 Google Inc. All rights reserved.
|
||||||
|
* Modifications 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 { Platform } from './platform';
|
||||||
|
|
||||||
|
// Keep in sync with server.
|
||||||
|
export const DEFAULT_TIMEOUT = 30000;
|
||||||
|
export const DEFAULT_LAUNCH_TIMEOUT = 3 * 60 * 1000; // 3 minutes
|
||||||
|
|
||||||
|
export class TimeoutSettings {
|
||||||
|
private _parent: TimeoutSettings | undefined;
|
||||||
|
private _defaultTimeout: number | undefined;
|
||||||
|
private _defaultNavigationTimeout: number | undefined;
|
||||||
|
private _platform: Platform;
|
||||||
|
|
||||||
|
constructor(platform: Platform, parent?: TimeoutSettings) {
|
||||||
|
this._parent = parent;
|
||||||
|
this._platform = platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaultTimeout(timeout: number | undefined) {
|
||||||
|
this._defaultTimeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaultNavigationTimeout(timeout: number | undefined) {
|
||||||
|
this._defaultNavigationTimeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultNavigationTimeout() {
|
||||||
|
return this._defaultNavigationTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultTimeout() {
|
||||||
|
return this._defaultTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigationTimeout(options: { timeout?: number }): number {
|
||||||
|
if (typeof options.timeout === 'number')
|
||||||
|
return options.timeout;
|
||||||
|
if (this._defaultNavigationTimeout !== undefined)
|
||||||
|
return this._defaultNavigationTimeout;
|
||||||
|
if (this._platform.isDebugMode())
|
||||||
|
return 0;
|
||||||
|
if (this._defaultTimeout !== undefined)
|
||||||
|
return this._defaultTimeout;
|
||||||
|
if (this._parent)
|
||||||
|
return this._parent.navigationTimeout(options);
|
||||||
|
return DEFAULT_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout(options: { timeout?: number }): number {
|
||||||
|
if (typeof options.timeout === 'number')
|
||||||
|
return options.timeout;
|
||||||
|
if (this._platform.isDebugMode())
|
||||||
|
return 0;
|
||||||
|
if (this._defaultTimeout !== undefined)
|
||||||
|
return this._defaultTimeout;
|
||||||
|
if (this._parent)
|
||||||
|
return this._parent.timeout(options);
|
||||||
|
return DEFAULT_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -69,8 +69,8 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
this._isTracing = true;
|
this._isTracing = true;
|
||||||
this._connection.setIsTracing(true);
|
this._connection.setIsTracing(true);
|
||||||
}
|
}
|
||||||
const result = await this._connection.localUtils().tracingStarted({ tracesDir: this._tracesDir, traceName });
|
const result = await this._connection.localUtils()?.tracingStarted({ tracesDir: this._tracesDir, traceName });
|
||||||
this._stacksId = result.stacksId;
|
this._stacksId = result?.stacksId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async stopChunk(options: { path?: string } = {}) {
|
async stopChunk(options: { path?: string } = {}) {
|
||||||
|
|
@ -89,15 +89,19 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
// Not interested in artifacts.
|
// Not interested in artifacts.
|
||||||
await this._channel.tracingStopChunk({ mode: 'discard' });
|
await this._channel.tracingStopChunk({ mode: 'discard' });
|
||||||
if (this._stacksId)
|
if (this._stacksId)
|
||||||
await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId });
|
await this._connection.localUtils()!.traceDiscarded({ stacksId: this._stacksId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const localUtils = this._connection.localUtils();
|
||||||
|
if (!localUtils)
|
||||||
|
throw new Error('Cannot save trace in thin clients');
|
||||||
|
|
||||||
const isLocal = !this._connection.isRemote();
|
const isLocal = !this._connection.isRemote();
|
||||||
|
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
const result = await this._channel.tracingStopChunk({ mode: 'entries' });
|
const result = await this._channel.tracingStopChunk({ mode: 'entries' });
|
||||||
await this._connection.localUtils().zip({ zipFile: filePath, entries: result.entries!, mode: 'write', stacksId: this._stacksId, includeSources: this._includeSources });
|
await localUtils.zip({ zipFile: filePath, entries: result.entries!, mode: 'write', stacksId: this._stacksId, includeSources: this._includeSources });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +110,7 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
// The artifact may be missing if the browser closed while stopping tracing.
|
// The artifact may be missing if the browser closed while stopping tracing.
|
||||||
if (!result.artifact) {
|
if (!result.artifact) {
|
||||||
if (this._stacksId)
|
if (this._stacksId)
|
||||||
await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId });
|
await localUtils.traceDiscarded({ stacksId: this._stacksId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +119,7 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
await artifact.saveAs(filePath);
|
await artifact.saveAs(filePath);
|
||||||
await artifact.delete();
|
await artifact.delete();
|
||||||
|
|
||||||
await this._connection.localUtils().zip({ zipFile: filePath, entries: [], mode: 'append', stacksId: this._stacksId, includeSources: this._includeSources });
|
await localUtils.zip({ zipFile: filePath, entries: [], mode: 'append', stacksId: this._stacksId, includeSources: this._includeSources });
|
||||||
}
|
}
|
||||||
|
|
||||||
_resetStackCounter() {
|
_resetStackCounter() {
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Size } from '../common/types';
|
import type { Size } from '../utils/isomorphic/types';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
export type { HeadersArray, Point, Quad, Rect, Size, TimeoutOptions } from '../common/types';
|
export type { HeadersArray, Point, Quad, Rect, Size, TimeoutOptions } from '../utils/isomorphic/types';
|
||||||
|
|
||||||
type LoggerSeverity = 'verbose' | 'info' | 'warning' | 'error';
|
type LoggerSeverity = 'verbose' | 'info' | 'warning' | 'error';
|
||||||
export interface Logger {
|
export interface Logger {
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,11 @@
|
||||||
|
|
||||||
import { TimeoutError } from './errors';
|
import { TimeoutError } from './errors';
|
||||||
import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
|
import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
|
||||||
import { zones } from '../utils/zones';
|
|
||||||
|
|
||||||
import type { ChannelOwner } from './channelOwner';
|
import type { ChannelOwner } from './channelOwner';
|
||||||
import type { Zone } from '../utils/zones';
|
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import type { EventEmitter } from 'events';
|
import type { EventEmitter } from 'events';
|
||||||
|
import type { Zone } from './platform';
|
||||||
|
|
||||||
export class Waiter {
|
export class Waiter {
|
||||||
private _dispose: (() => void)[];
|
private _dispose: (() => void)[];
|
||||||
|
|
@ -36,7 +35,7 @@ export class Waiter {
|
||||||
constructor(channelOwner: ChannelOwner<channels.EventTargetChannel>, event: string) {
|
constructor(channelOwner: ChannelOwner<channels.EventTargetChannel>, event: string) {
|
||||||
this._waitId = channelOwner._platform.createGuid();
|
this._waitId = channelOwner._platform.createGuid();
|
||||||
this._channelOwner = channelOwner;
|
this._channelOwner = channelOwner;
|
||||||
this._savedZone = zones.current().without('apiZone');
|
this._savedZone = channelOwner._platform.zones.current().pop();
|
||||||
|
|
||||||
this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'before', event } }).catch(() => {});
|
this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'before', event } }).catch(() => {});
|
||||||
this._dispose = [
|
this._dispose = [
|
||||||
|
|
@ -97,8 +96,8 @@ export class Waiter {
|
||||||
log(s: string) {
|
log(s: string) {
|
||||||
this._logs.push(s);
|
this._logs.push(s);
|
||||||
this._channelOwner._wrapApiCall(async () => {
|
this._channelOwner._wrapApiCall(async () => {
|
||||||
await this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'log', message: s } }).catch(() => {});
|
await this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'log', message: s } });
|
||||||
}, true);
|
}, true).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _rejectOn(promise: Promise<any>, dispose?: () => void) {
|
private _rejectOn(promise: Promise<any>, dispose?: () => void) {
|
||||||
|
|
|
||||||
116
packages/playwright-core/src/client/webSocket.ts
Normal file
116
packages/playwright-core/src/client/webSocket.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
/**
|
||||||
|
* 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 { ChannelOwner } from './channelOwner';
|
||||||
|
import { Connection } from './connection';
|
||||||
|
|
||||||
|
import type { HeadersArray } from './types';
|
||||||
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
|
export async function connectOverWebSocket(parentConnection: Connection, params: channels.LocalUtilsConnectParams): Promise<Connection> {
|
||||||
|
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);
|
||||||
|
connection.markAsRemote();
|
||||||
|
connection.on('close', () => transport.close());
|
||||||
|
|
||||||
|
let closeError: string | undefined;
|
||||||
|
const onTransportClosed = (reason?: string) => {
|
||||||
|
connection.close(reason || closeError);
|
||||||
|
};
|
||||||
|
transport.onClose(reason => onTransportClosed(reason));
|
||||||
|
connection.onmessage = message => transport.send(message).catch(() => onTransportClosed());
|
||||||
|
transport.onMessage(message => {
|
||||||
|
try {
|
||||||
|
connection!.dispatch(message);
|
||||||
|
} catch (e) {
|
||||||
|
closeError = String(e);
|
||||||
|
transport.close().catch(() => {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Transport {
|
||||||
|
connect(params: channels.LocalUtilsConnectParams): Promise<HeadersArray>;
|
||||||
|
send(message: any): Promise<void>;
|
||||||
|
onMessage(callback: (message: object) => void): void;
|
||||||
|
onClose(callback: (reason?: string) => void): void;
|
||||||
|
close(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
class JsonPipeTransport implements Transport {
|
||||||
|
private _pipe: channels.JsonPipeChannel | undefined;
|
||||||
|
private _owner: ChannelOwner<channels.LocalUtilsChannel>;
|
||||||
|
|
||||||
|
constructor(owner: ChannelOwner<channels.LocalUtilsChannel>) {
|
||||||
|
this._owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(params: channels.LocalUtilsConnectParams) {
|
||||||
|
const { pipe, headers: connectHeaders } = await this._owner._wrapApiCall(async () => {
|
||||||
|
return await this._owner._channel.connect(params);
|
||||||
|
}, /* isInternal */ true);
|
||||||
|
this._pipe = pipe;
|
||||||
|
return connectHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(message: object) {
|
||||||
|
await this._owner._wrapApiCall(async () => {
|
||||||
|
await this._pipe!.send({ message });
|
||||||
|
}, /* isInternal */ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(callback: (message: object) => void) {
|
||||||
|
this._pipe!.on('message', ({ message }) => callback(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(callback: (reason?: string) => void) {
|
||||||
|
this._pipe!.on('closed', ({ reason }) => callback(reason));
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
await this._owner._wrapApiCall(async () => {
|
||||||
|
await this._pipe!.close().catch(() => {});
|
||||||
|
}, /* isInternal */ true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketTransport implements Transport {
|
||||||
|
private _ws: WebSocket | undefined;
|
||||||
|
|
||||||
|
async connect(params: channels.LocalUtilsConnectParams) {
|
||||||
|
this._ws = new window.WebSocket(params.wsEndpoint);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(message: object) {
|
||||||
|
this._ws!.send(JSON.stringify(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(callback: (message: object) => void) {
|
||||||
|
this._ws!.addEventListener('message', event => callback(JSON.parse(event.data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(callback: (reason?: string) => void) {
|
||||||
|
this._ws!.addEventListener('close', () => callback());
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
this._ws!.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,11 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Writable } from 'stream';
|
|
||||||
|
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
|
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
import type { Writable } from 'stream';
|
||||||
|
|
||||||
export class WritableStream extends ChannelOwner<channels.WritableStreamChannel> {
|
export class WritableStream extends ChannelOwner<channels.WritableStreamChannel> {
|
||||||
static from(Stream: channels.WritableStreamChannel): WritableStream {
|
static from(Stream: channels.WritableStreamChannel): WritableStream {
|
||||||
|
|
@ -30,26 +29,6 @@ export class WritableStream extends ChannelOwner<channels.WritableStreamChannel>
|
||||||
}
|
}
|
||||||
|
|
||||||
stream(): Writable {
|
stream(): Writable {
|
||||||
return new WritableStreamImpl(this._channel);
|
return this._platform.streamWritable(this._channel);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WritableStreamImpl extends Writable {
|
|
||||||
private _channel: channels.WritableStreamChannel;
|
|
||||||
|
|
||||||
constructor(channel: channels.WritableStreamChannel) {
|
|
||||||
super();
|
|
||||||
this._channel = channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
override async _write(chunk: Buffer | string, encoding: BufferEncoding, callback: (error?: Error | null) => void) {
|
|
||||||
const error = await this._channel.write({ binary: typeof chunk === 'string' ? Buffer.from(chunk) : chunk }).catch(e => e);
|
|
||||||
callback(error || null);
|
|
||||||
}
|
|
||||||
|
|
||||||
override async _final(callback: (error?: Error | null) => void) {
|
|
||||||
// Stream might be destroyed after the connection was closed.
|
|
||||||
const error = await this._channel.close().catch(e => e);
|
|
||||||
callback(error || null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,2 @@
|
||||||
[*]
|
[*]
|
||||||
../utils/
|
|
||||||
../utils/isomorphic/
|
../utils/isomorphic/
|
||||||
../utilsBundle.ts
|
|
||||||
../zipBundle.ts
|
|
||||||
|
|
|
||||||
|
|
@ -1,102 +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 * as crypto from 'crypto';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
import { webColors, noColors } from '../utils/isomorphic/colors';
|
|
||||||
|
|
||||||
import type { Colors } from '../utils/isomorphic/colors';
|
|
||||||
|
|
||||||
|
|
||||||
export type Platform = {
|
|
||||||
calculateSha1(text: string): Promise<string>;
|
|
||||||
colors: Colors;
|
|
||||||
createGuid: () => string;
|
|
||||||
fs: () => typeof fs;
|
|
||||||
inspectCustom: symbol | undefined;
|
|
||||||
isLogEnabled(name: 'api' | 'channel'): boolean;
|
|
||||||
log(name: 'api' | 'channel', message: string | Error | object): void;
|
|
||||||
path: () => typeof path;
|
|
||||||
pathSeparator: string;
|
|
||||||
ws?: (url: string) => WebSocket;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const webPlatform: Platform = {
|
|
||||||
calculateSha1: async (text: string) => {
|
|
||||||
const bytes = new TextEncoder().encode(text);
|
|
||||||
const hashBuffer = await 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(crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join('');
|
|
||||||
},
|
|
||||||
|
|
||||||
fs: () => {
|
|
||||||
throw new Error('File system is not available');
|
|
||||||
},
|
|
||||||
|
|
||||||
inspectCustom: undefined,
|
|
||||||
|
|
||||||
|
|
||||||
isLogEnabled(name: 'api' | 'channel') {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
log(name: 'api' | 'channel', message: string | Error | object) {},
|
|
||||||
|
|
||||||
path: () => {
|
|
||||||
throw new Error('Path module is not available');
|
|
||||||
},
|
|
||||||
|
|
||||||
pathSeparator: '/',
|
|
||||||
|
|
||||||
ws: (url: string) => new WebSocket(url),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const emptyPlatform: Platform = {
|
|
||||||
calculateSha1: async () => {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
},
|
|
||||||
|
|
||||||
colors: noColors,
|
|
||||||
|
|
||||||
createGuid: () => {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
},
|
|
||||||
|
|
||||||
fs: () => {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
},
|
|
||||||
|
|
||||||
inspectCustom: undefined,
|
|
||||||
|
|
||||||
isLogEnabled(name: 'api' | 'channel') {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
log(name: 'api' | 'channel', message: string | Error | object) { },
|
|
||||||
|
|
||||||
path: () => {
|
|
||||||
throw new Error('Function not implemented.');
|
|
||||||
},
|
|
||||||
|
|
||||||
pathSeparator: '/'
|
|
||||||
};
|
|
||||||
|
|
@ -14,31 +14,21 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
import { AndroidServerLauncherImpl } from './androidServerImpl';
|
import { AndroidServerLauncherImpl } from './androidServerImpl';
|
||||||
import { BrowserServerLauncherImpl } from './browserServerImpl';
|
import { BrowserServerLauncherImpl } from './browserServerImpl';
|
||||||
import { Connection } from './client/connection';
|
import { createConnectionFactory } from './client/clientBundle';
|
||||||
import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from './server';
|
import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from './server';
|
||||||
import { setLibraryStackPrefix } from './utils/isomorphic/stackTrace';
|
|
||||||
import { setDebugMode } from './utils/isomorphic/debug';
|
|
||||||
import { getFromENV } from './server/utils/env';
|
|
||||||
import { nodePlatform } from './server/utils/nodePlatform';
|
import { nodePlatform } from './server/utils/nodePlatform';
|
||||||
import { setPlatformForSelectors } from './client/selectors';
|
|
||||||
|
|
||||||
import type { Playwright as PlaywrightAPI } from './client/playwright';
|
import type { Playwright as PlaywrightAPI } from './client/playwright';
|
||||||
import type { Language } from './utils';
|
import type { Language } from './utils';
|
||||||
import type { Platform } from './common/platform';
|
|
||||||
|
|
||||||
|
const connectionFactory = createConnectionFactory(nodePlatform);
|
||||||
|
|
||||||
export function createInProcessPlaywright(platform: Platform): PlaywrightAPI {
|
export function createInProcessPlaywright(): PlaywrightAPI {
|
||||||
const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript' });
|
const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript' });
|
||||||
setDebugMode(getFromENV('PWDEBUG') || '');
|
|
||||||
setPlatformForSelectors(nodePlatform);
|
|
||||||
|
|
||||||
setLibraryStackPrefix(path.join(__dirname, '..'));
|
const clientConnection = connectionFactory();
|
||||||
|
|
||||||
const clientConnection = new Connection(undefined, platform, undefined, []);
|
|
||||||
clientConnection.useRawBuffers();
|
clientConnection.useRawBuffers();
|
||||||
const dispatcherConnection = new DispatcherConnection(true /* local */);
|
const dispatcherConnection = new DispatcherConnection(true /* local */);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,5 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createInProcessPlaywright } from './inProcessFactory';
|
import { createInProcessPlaywright } from './inProcessFactory';
|
||||||
import { nodePlatform } from './server/utils/nodePlatform';
|
|
||||||
|
|
||||||
module.exports = createInProcessPlaywright(nodePlatform);
|
module.exports = createInProcessPlaywright();
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,15 @@
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { Connection } from './client/connection';
|
import { createConnectionFactory } from './client/clientBundle';
|
||||||
import { PipeTransport } from './utils/pipeTransport';
|
import { PipeTransport } from './server/utils/pipeTransport';
|
||||||
import { ManualPromise } from './utils/isomorphic/manualPromise';
|
import { ManualPromise } from './utils/isomorphic/manualPromise';
|
||||||
import { nodePlatform } from './server/utils/nodePlatform';
|
import { nodePlatform } from './server/utils/nodePlatform';
|
||||||
|
|
||||||
import type { Playwright } from './client/playwright';
|
import type { Playwright } from './client/playwright';
|
||||||
|
|
||||||
|
const connectionFactory = createConnectionFactory(nodePlatform);
|
||||||
|
|
||||||
export async function start(env: any = {}): Promise<{ playwright: Playwright, stop: () => Promise<void> }> {
|
export async function start(env: any = {}): Promise<{ playwright: Playwright, stop: () => Promise<void> }> {
|
||||||
const client = new PlaywrightClient(env);
|
const client = new PlaywrightClient(env);
|
||||||
const playwright = await client._playwright;
|
const playwright = await client._playwright;
|
||||||
|
|
@ -48,7 +50,7 @@ class PlaywrightClient {
|
||||||
this._driverProcess.unref();
|
this._driverProcess.unref();
|
||||||
this._driverProcess.stderr!.on('data', data => process.stderr.write(data));
|
this._driverProcess.stderr!.on('data', data => process.stderr.write(data));
|
||||||
|
|
||||||
const connection = new Connection(undefined, nodePlatform, undefined, []);
|
const connection = connectionFactory();
|
||||||
const transport = new PipeTransport(this._driverProcess.stdin!, this._driverProcess.stdout!);
|
const transport = new PipeTransport(this._driverProcess.stdin!, this._driverProcess.stdout!);
|
||||||
connection.onmessage = message => transport.send(JSON.stringify(message));
|
connection.onmessage = message => transport.send(JSON.stringify(message));
|
||||||
transport.onmessage = message => connection.dispatch(JSON.parse(message));
|
transport.onmessage = message => connection.dispatch(JSON.parse(message));
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,2 @@
|
||||||
[*]
|
[*]
|
||||||
../common/
|
../utils/isomorphic
|
||||||
../utils/
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isUnderTest } from '../utils';
|
|
||||||
|
|
||||||
export class ValidationError extends Error {}
|
export class ValidationError extends Error {}
|
||||||
export type Validator = (arg: any, path: string, context: ValidatorContext) => any;
|
export type Validator = (arg: any, path: string, context: ValidatorContext) => any;
|
||||||
export type ValidatorContext = {
|
export type ValidatorContext = {
|
||||||
tChannelImpl: (names: '*' | string[], arg: any, path: string, context: ValidatorContext) => any,
|
tChannelImpl: (names: '*' | string[], arg: any, path: string, context: ValidatorContext) => any;
|
||||||
binary: 'toBase64' | 'fromBase64' | 'buffer',
|
binary: 'toBase64' | 'fromBase64' | 'buffer';
|
||||||
|
isUnderTest: () => boolean;
|
||||||
};
|
};
|
||||||
export const scheme: { [key: string]: Validator } = {};
|
export const scheme: { [key: string]: Validator } = {};
|
||||||
|
|
||||||
|
|
@ -113,7 +112,7 @@ export const tObject = (s: { [key: string]: Validator }): Validator => {
|
||||||
if (!Object.is(value, undefined))
|
if (!Object.is(value, undefined))
|
||||||
result[key] = value;
|
result[key] = value;
|
||||||
}
|
}
|
||||||
if (isUnderTest()) {
|
if (context.isUnderTest()) {
|
||||||
for (const [key, value] of Object.entries(arg)) {
|
for (const [key, value] of Object.entries(arg)) {
|
||||||
if (key.startsWith('__testHook'))
|
if (key.startsWith('__testHook'))
|
||||||
result[key] = value;
|
result[key] = value;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
[*]
|
[*]
|
||||||
../client/
|
|
||||||
../common/
|
|
||||||
../server/
|
../server/
|
||||||
../server/android/
|
../server/android/
|
||||||
../server/dispatchers/
|
../server/dispatchers/
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ import { AndroidDevice } from '../server/android/android';
|
||||||
import { Browser } from '../server/browser';
|
import { Browser } from '../server/browser';
|
||||||
import { DebugControllerDispatcher } from '../server/dispatchers/debugControllerDispatcher';
|
import { DebugControllerDispatcher } from '../server/dispatchers/debugControllerDispatcher';
|
||||||
import { serverSideCallMetadata } from '../server/instrumentation';
|
import { serverSideCallMetadata } from '../server/instrumentation';
|
||||||
import { assert, isUnderTest } from '../utils';
|
import { assert } from '../utils/isomorphic/assert';
|
||||||
|
import { isUnderTest } from '../server/utils/debug';
|
||||||
import { startProfiling, stopProfiling } from '../server/utils/profiler';
|
import { startProfiling, stopProfiling } from '../server/utils/profiler';
|
||||||
import { monotonicTime } from '../utils';
|
import { monotonicTime } from '../utils';
|
||||||
import { debugLogger } from '../server/utils/debugLogger';
|
import { debugLogger } from '../server/utils/debugLogger';
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
[*]
|
[*]
|
||||||
../common/
|
|
||||||
../generated/
|
../generated/
|
||||||
../protocol/
|
../protocol/
|
||||||
../utils/
|
../utils
|
||||||
../utils/isomorphic/
|
../utils/isomorphic/
|
||||||
../utilsBundle.ts
|
../utilsBundle.ts
|
||||||
../zipBundle.ts
|
../zipBundle.ts
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
[*]
|
[*]
|
||||||
../
|
../
|
||||||
../../common/
|
|
||||||
../../protocol/
|
../../protocol/
|
||||||
../../utils/
|
|
||||||
../../utils/isomorphic/
|
../../utils/isomorphic/
|
||||||
../../utilsBundle.ts
|
../../utilsBundle.ts
|
||||||
../chromium/
|
../chromium/
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,12 @@ import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { TimeoutSettings } from '../../utils/isomorphic/timeoutSettings';
|
import { TimeoutSettings } from '../timeoutSettings';
|
||||||
import { PipeTransport } from '../../utils/pipeTransport';
|
import { PipeTransport } from '../utils/pipeTransport';
|
||||||
import { createGuid } from '../utils/crypto';
|
import { createGuid } from '../utils/crypto';
|
||||||
import { isUnderTest } from '../../utils/isomorphic/debug';
|
import { isUnderTest } from '../utils/debug';
|
||||||
import { getPackageManagerExecCommand } from '../utils/env';
|
import { getPackageManagerExecCommand } from '../utils/env';
|
||||||
import { makeWaitForNextTask } from '../../utils/task';
|
import { makeWaitForNextTask } from '../utils/task';
|
||||||
import { RecentLogsCollector } from '../utils/debugLogger';
|
import { RecentLogsCollector } from '../utils/debugLogger';
|
||||||
import { debug } from '../../utilsBundle';
|
import { debug } from '../../utilsBundle';
|
||||||
import { wsReceiver, wsSender } from '../../utilsBundle';
|
import { wsReceiver, wsSender } from '../../utilsBundle';
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import * as net from 'net';
|
import * as net from 'net';
|
||||||
|
|
||||||
import { assert } from '../../utils/isomorphic/debug';
|
import { assert } from '../../utils/isomorphic/assert';
|
||||||
import { createGuid } from '../utils/crypto';
|
import { createGuid } from '../utils/crypto';
|
||||||
import { debug } from '../../utilsBundle';
|
import { debug } from '../../utilsBundle';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -519,10 +519,6 @@ export class BidiPage implements PageDelegate {
|
||||||
return quads as types.Quad[];
|
return quads as types.Quad[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void> {
|
|
||||||
throw new Error('Setting FilePayloads is not supported in Bidi.');
|
|
||||||
}
|
|
||||||
|
|
||||||
async setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, paths: string[]): Promise<void> {
|
async setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, paths: string[]): Promise<void> {
|
||||||
const fromContext = toBidiExecutionContext(handle._context);
|
const fromContext = toBidiExecutionContext(handle._context);
|
||||||
await this._session.send('input.setFiles', {
|
await this._session.send('input.setFiles', {
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
import { TimeoutSettings } from './timeoutSettings';
|
||||||
import { createGuid } from './utils/crypto';
|
import { createGuid } from './utils/crypto';
|
||||||
import { debugMode } from '../utils/isomorphic/debug';
|
import { debugMode } from './utils/debug';
|
||||||
import { Clock } from './clock';
|
import { Clock } from './clock';
|
||||||
import { Debugger } from './debugger';
|
import { Debugger } from './debugger';
|
||||||
import { BrowserContextAPIRequestContext } from './fetch';
|
import { BrowserContextAPIRequestContext } from './fetch';
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,10 @@ import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
|
import { normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
|
||||||
import { DEFAULT_TIMEOUT, TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
import { DEFAULT_TIMEOUT, TimeoutSettings } from './timeoutSettings';
|
||||||
import { ManualPromise, assert, debugMode } from '../utils';
|
import { debugMode } from './utils/debug';
|
||||||
|
import { assert } from '../utils/isomorphic/assert';
|
||||||
|
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
||||||
import { existsAsync } from './utils/fileUtils';
|
import { existsAsync } from './utils/fileUtils';
|
||||||
import { helper } from './helper';
|
import { helper } from './helper';
|
||||||
import { SdkObject } from './instrumentation';
|
import { SdkObject } from './instrumentation';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (c) Microsoft Corporation.
|
* Copyright (c) Microsoft Corporation.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the 'License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
|
|
@ -14,7 +14,27 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function findRepeatedSubsequences(s: string[]): { sequence: string[]; count: number }[] {
|
export function compressCallLog(log: string[]): string[] {
|
||||||
|
const lines: string[] = [];
|
||||||
|
|
||||||
|
for (const block of findRepeatedSubsequences(log)) {
|
||||||
|
for (let i = 0; i < block.sequence.length; i++) {
|
||||||
|
const line = block.sequence[i];
|
||||||
|
const leadingWhitespace = line.match(/^\s*/);
|
||||||
|
const whitespacePrefix = ' ' + leadingWhitespace?.[0] || '';
|
||||||
|
const countPrefix = `${block.count} × `;
|
||||||
|
if (block.count > 1 && i === 0)
|
||||||
|
lines.push(whitespacePrefix + countPrefix + line.trim());
|
||||||
|
else if (block.count > 1)
|
||||||
|
lines.push(whitespacePrefix + ' '.repeat(countPrefix.length - 2) + '- ' + line.trim());
|
||||||
|
else
|
||||||
|
lines.push(whitespacePrefix + '- ' + line.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findRepeatedSubsequences(s: string[]): { sequence: string[]; count: number }[] {
|
||||||
const n = s.length;
|
const n = s.length;
|
||||||
const result = [];
|
const result = [];
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
@ -64,3 +84,5 @@ export function findRepeatedSubsequences(s: string[]): { sequence: string[]; cou
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const findRepeatedSubsequencesForTest = findRepeatedSubsequences;
|
||||||
|
|
@ -22,7 +22,7 @@ import * as path from 'path';
|
||||||
import { chromiumSwitches } from './chromiumSwitches';
|
import { chromiumSwitches } from './chromiumSwitches';
|
||||||
import { CRBrowser } from './crBrowser';
|
import { CRBrowser } from './crBrowser';
|
||||||
import { kBrowserCloseMessageId } from './crConnection';
|
import { kBrowserCloseMessageId } from './crConnection';
|
||||||
import { TimeoutSettings } from '../../utils/isomorphic/timeoutSettings';
|
import { TimeoutSettings } from '../timeoutSettings';
|
||||||
import { debugMode, headersArrayToObject, headersObjectToArray, } from '../../utils';
|
import { debugMode, headersArrayToObject, headersObjectToArray, } from '../../utils';
|
||||||
import { wrapInASCIIBox } from '../utils/ascii';
|
import { wrapInASCIIBox } from '../utils/ascii';
|
||||||
import { RecentLogsCollector } from '../utils/debugLogger';
|
import { RecentLogsCollector } from '../utils/debugLogger';
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { assert } from '../../utils/isomorphic/debug';
|
import { assert } from '../../utils/isomorphic/assert';
|
||||||
import { createGuid } from '../utils/crypto';
|
import { createGuid } from '../utils/crypto';
|
||||||
import { Artifact } from '../artifact';
|
import { Artifact } from '../artifact';
|
||||||
import { Browser } from '../browser';
|
import { Browser } from '../browser';
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { assert } from '../../utils/isomorphic/debug';
|
import { assert } from '../../utils/isomorphic/assert';
|
||||||
import { createGuid } from '../utils/crypto';
|
import { createGuid } from '../utils/crypto';
|
||||||
import { eventsHelper } from '../utils/eventsHelper';
|
import { eventsHelper } from '../utils/eventsHelper';
|
||||||
import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace';
|
import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace';
|
||||||
|
|
@ -314,11 +314,6 @@ export class CRPage implements PageDelegate {
|
||||||
return this._sessionForHandle(handle)._getContentQuads(handle);
|
return this._sessionForHandle(handle)._getContentQuads(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void> {
|
|
||||||
await handle.evaluateInUtility(([injected, node, files]) =>
|
|
||||||
injected.setInputFiles(node, files), files);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, files: string[]): Promise<void> {
|
async setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, files: string[]): Promise<void> {
|
||||||
const frame = await handle.ownerFrame();
|
const frame = await handle.ownerFrame();
|
||||||
if (!frame)
|
if (!frame)
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@
|
||||||
"defaultBrowserType": "webkit"
|
"defaultBrowserType": "webkit"
|
||||||
},
|
},
|
||||||
"Galaxy S5": {
|
"Galaxy S5": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 360,
|
"width": 360,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -121,7 +121,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy S5 landscape": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 360
|
"height": 360
|
||||||
|
|
@ -132,7 +132,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy S8": {
|
"Galaxy S8": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 360,
|
"width": 360,
|
||||||
"height": 740
|
"height": 740
|
||||||
|
|
@ -143,7 +143,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy S8 landscape": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 740,
|
"width": 740,
|
||||||
"height": 360
|
"height": 360
|
||||||
|
|
@ -154,7 +154,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy S9+": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 320,
|
"width": 320,
|
||||||
"height": 658
|
"height": 658
|
||||||
|
|
@ -165,7 +165,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy S9+ landscape": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 658,
|
"width": 658,
|
||||||
"height": 320
|
"height": 320
|
||||||
|
|
@ -176,7 +176,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy Tab S4": {
|
"Galaxy Tab S4": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36",
|
"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",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 712,
|
"width": 712,
|
||||||
"height": 1138
|
"height": 1138
|
||||||
|
|
@ -187,7 +187,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy Tab S4 landscape": {
|
"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.3 Safari/537.36",
|
"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",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 1138,
|
"width": 1138,
|
||||||
"height": 712
|
"height": 712
|
||||||
|
|
@ -1098,7 +1098,7 @@
|
||||||
"defaultBrowserType": "webkit"
|
"defaultBrowserType": "webkit"
|
||||||
},
|
},
|
||||||
"LG Optimus L70": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 384,
|
"width": 384,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -1109,7 +1109,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"LG Optimus L70 landscape": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 384
|
"height": 384
|
||||||
|
|
@ -1120,7 +1120,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Microsoft Lumia 550": {
|
"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.3 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.15 Mobile Safari/537.36 Edge/14.14263",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 360,
|
"width": 360,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -1131,7 +1131,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Microsoft Lumia 550 landscape": {
|
"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.3 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.15 Mobile Safari/537.36 Edge/14.14263",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 360
|
"height": 360
|
||||||
|
|
@ -1142,7 +1142,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Microsoft Lumia 950": {
|
"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.3 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.15 Mobile Safari/537.36 Edge/14.14263",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 360,
|
"width": 360,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -1153,7 +1153,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Microsoft Lumia 950 landscape": {
|
"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.3 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.15 Mobile Safari/537.36 Edge/14.14263",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 360
|
"height": 360
|
||||||
|
|
@ -1164,7 +1164,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 10": {
|
"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.3 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.15 Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 800,
|
"width": 800,
|
||||||
"height": 1280
|
"height": 1280
|
||||||
|
|
@ -1175,7 +1175,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 10 landscape": {
|
"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.3 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.15 Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 1280,
|
"width": 1280,
|
||||||
"height": 800
|
"height": 800
|
||||||
|
|
@ -1186,7 +1186,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 4": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 384,
|
"width": 384,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -1197,7 +1197,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 4 landscape": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 384
|
"height": 384
|
||||||
|
|
@ -1208,7 +1208,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 5": {
|
"Nexus 5": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 360,
|
"width": 360,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -1219,7 +1219,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 5 landscape": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 360
|
"height": 360
|
||||||
|
|
@ -1230,7 +1230,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 5X": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 412,
|
"width": 412,
|
||||||
"height": 732
|
"height": 732
|
||||||
|
|
@ -1241,7 +1241,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 5X landscape": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 732,
|
"width": 732,
|
||||||
"height": 412
|
"height": 412
|
||||||
|
|
@ -1252,7 +1252,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 6": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 412,
|
"width": 412,
|
||||||
"height": 732
|
"height": 732
|
||||||
|
|
@ -1263,7 +1263,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 6 landscape": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 732,
|
"width": 732,
|
||||||
"height": 412
|
"height": 412
|
||||||
|
|
@ -1274,7 +1274,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 6P": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 412,
|
"width": 412,
|
||||||
"height": 732
|
"height": 732
|
||||||
|
|
@ -1285,7 +1285,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 6P landscape": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 732,
|
"width": 732,
|
||||||
"height": 412
|
"height": 412
|
||||||
|
|
@ -1296,7 +1296,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 7": {
|
"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.3 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.15 Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 600,
|
"width": 600,
|
||||||
"height": 960
|
"height": 960
|
||||||
|
|
@ -1307,7 +1307,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 7 landscape": {
|
"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.3 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.15 Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 960,
|
"width": 960,
|
||||||
"height": 600
|
"height": 600
|
||||||
|
|
@ -1362,7 +1362,7 @@
|
||||||
"defaultBrowserType": "webkit"
|
"defaultBrowserType": "webkit"
|
||||||
},
|
},
|
||||||
"Pixel 2": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 411,
|
"width": 411,
|
||||||
"height": 731
|
"height": 731
|
||||||
|
|
@ -1373,7 +1373,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 2 landscape": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 731,
|
"width": 731,
|
||||||
"height": 411
|
"height": 411
|
||||||
|
|
@ -1384,7 +1384,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 2 XL": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 411,
|
"width": 411,
|
||||||
"height": 823
|
"height": 823
|
||||||
|
|
@ -1395,7 +1395,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 2 XL landscape": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 823,
|
"width": 823,
|
||||||
"height": 411
|
"height": 411
|
||||||
|
|
@ -1406,7 +1406,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 3": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 393,
|
"width": 393,
|
||||||
"height": 786
|
"height": 786
|
||||||
|
|
@ -1417,7 +1417,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 3 landscape": {
|
"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.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 786,
|
"width": 786,
|
||||||
"height": 393
|
"height": 393
|
||||||
|
|
@ -1428,7 +1428,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 4": {
|
"Pixel 4": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 353,
|
"width": 353,
|
||||||
"height": 745
|
"height": 745
|
||||||
|
|
@ -1439,7 +1439,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 4 landscape": {
|
"Pixel 4 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 745,
|
"width": 745,
|
||||||
"height": 353
|
"height": 353
|
||||||
|
|
@ -1450,7 +1450,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 4a (5G)": {
|
"Pixel 4a (5G)": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
"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",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 412,
|
"width": 412,
|
||||||
"height": 892
|
"height": 892
|
||||||
|
|
@ -1465,7 +1465,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 4a (5G) landscape": {
|
"Pixel 4a (5G) landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
"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",
|
||||||
"screen": {
|
"screen": {
|
||||||
"height": 892,
|
"height": 892,
|
||||||
"width": 412
|
"width": 412
|
||||||
|
|
@ -1480,7 +1480,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 5": {
|
"Pixel 5": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 393,
|
"width": 393,
|
||||||
"height": 851
|
"height": 851
|
||||||
|
|
@ -1495,7 +1495,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 5 landscape": {
|
"Pixel 5 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 851,
|
"width": 851,
|
||||||
"height": 393
|
"height": 393
|
||||||
|
|
@ -1510,7 +1510,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 7": {
|
"Pixel 7": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 412,
|
"width": 412,
|
||||||
"height": 915
|
"height": 915
|
||||||
|
|
@ -1525,7 +1525,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 7 landscape": {
|
"Pixel 7 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Mobile Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 915,
|
"width": 915,
|
||||||
"height": 412
|
"height": 412
|
||||||
|
|
@ -1540,7 +1540,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Moto G4": {
|
"Moto G4": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 360,
|
"width": 360,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -1551,7 +1551,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Moto G4 landscape": {
|
"Moto G4 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 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.15 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 360
|
"height": 360
|
||||||
|
|
@ -1562,7 +1562,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Desktop Chrome HiDPI": {
|
"Desktop Chrome HiDPI": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 1792,
|
"width": 1792,
|
||||||
"height": 1120
|
"height": 1120
|
||||||
|
|
@ -1577,7 +1577,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Desktop Edge HiDPI": {
|
"Desktop Edge HiDPI": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36 Edg/134.0.6998.3",
|
"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",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 1792,
|
"width": 1792,
|
||||||
"height": 1120
|
"height": 1120
|
||||||
|
|
@ -1622,7 +1622,7 @@
|
||||||
"defaultBrowserType": "webkit"
|
"defaultBrowserType": "webkit"
|
||||||
},
|
},
|
||||||
"Desktop Chrome": {
|
"Desktop Chrome": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.15 Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 1920,
|
"width": 1920,
|
||||||
"height": 1080
|
"height": 1080
|
||||||
|
|
@ -1637,7 +1637,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Desktop Edge": {
|
"Desktop Edge": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36 Edg/134.0.6998.3",
|
"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",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 1920,
|
"width": 1920,
|
||||||
"height": 1080
|
"height": 1080
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
[*]
|
[*]
|
||||||
../../common/
|
|
||||||
../../generated/
|
../../generated/
|
||||||
../../protocol/
|
../../protocol/
|
||||||
../../utils/
|
../../utils/
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,12 @@ import { EventEmitter } from 'events';
|
||||||
|
|
||||||
import { eventsHelper } from '../utils/eventsHelper';
|
import { eventsHelper } from '../utils/eventsHelper';
|
||||||
import { ValidationError, createMetadataValidator, findValidator } from '../../protocol/validator';
|
import { ValidationError, createMetadataValidator, findValidator } from '../../protocol/validator';
|
||||||
import { LongStandingScope, assert, compressCallLog, isUnderTest, monotonicTime, rewriteErrorMessage } from '../../utils';
|
import { LongStandingScope, assert, monotonicTime, rewriteErrorMessage } from '../../utils';
|
||||||
|
import { isUnderTest } from '../utils/debug';
|
||||||
import { TargetClosedError, isTargetClosedError, serializeError } from '../errors';
|
import { TargetClosedError, isTargetClosedError, serializeError } from '../errors';
|
||||||
import { SdkObject } from '../instrumentation';
|
import { SdkObject } from '../instrumentation';
|
||||||
import { isProtocolError } from '../protocolError';
|
import { isProtocolError } from '../protocolError';
|
||||||
|
import { compressCallLog } from '../callLog';
|
||||||
|
|
||||||
import type { CallMetadata } from '../instrumentation';
|
import type { CallMetadata } from '../instrumentation';
|
||||||
import type { PlaywrightDispatcher } from './playwrightDispatcher';
|
import type { PlaywrightDispatcher } from './playwrightDispatcher';
|
||||||
|
|
@ -198,13 +200,13 @@ export class DispatcherConnection {
|
||||||
|
|
||||||
sendEvent(dispatcher: DispatcherScope, event: string, params: any) {
|
sendEvent(dispatcher: DispatcherScope, event: string, params: any) {
|
||||||
const validator = findValidator(dispatcher._type, event, 'Event');
|
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 });
|
this.onmessage({ guid: dispatcher._guid, method: event, params });
|
||||||
}
|
}
|
||||||
|
|
||||||
sendCreate(parent: DispatcherScope, type: string, guid: string, initializer: any) {
|
sendCreate(parent: DispatcherScope, type: string, guid: string, initializer: any) {
|
||||||
const validator = findValidator(type, '', 'Initializer');
|
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 } });
|
this.onmessage({ guid: parent._guid, method: '__create__', params: { type, initializer, guid } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,6 +218,22 @@ export class DispatcherConnection {
|
||||||
this.onmessage({ guid: dispatcher._guid, method: '__dispose__', params: { reason } });
|
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 {
|
private _tChannelImplFromWire(names: '*' | string[], arg: any, path: string, context: ValidatorContext): any {
|
||||||
if (arg && typeof arg === 'object' && typeof arg.guid === 'string') {
|
if (arg && typeof arg === 'object' && typeof arg.guid === 'string') {
|
||||||
const guid = arg.guid;
|
const guid = arg.guid;
|
||||||
|
|
@ -277,8 +295,9 @@ export class DispatcherConnection {
|
||||||
let validMetadata: channels.Metadata;
|
let validMetadata: channels.Metadata;
|
||||||
try {
|
try {
|
||||||
const validator = findValidator(dispatcher._type, method, 'Params');
|
const validator = findValidator(dispatcher._type, method, 'Params');
|
||||||
validParams = validator(params, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this._isLocal ? 'buffer' : 'fromBase64' });
|
const validatorContext = this._validatorFromWireContext();
|
||||||
validMetadata = metadataValidator(metadata, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this._isLocal ? 'buffer' : 'fromBase64' });
|
validParams = validator(params, '', validatorContext);
|
||||||
|
validMetadata = metadataValidator(metadata, '', validatorContext);
|
||||||
if (typeof (dispatcher as any)[method] !== 'function')
|
if (typeof (dispatcher as any)[method] !== 'function')
|
||||||
throw new Error(`Mismatching dispatcher: "${dispatcher._type}" does not implement "${method}"`);
|
throw new Error(`Mismatching dispatcher: "${dispatcher._type}" does not implement "${method}"`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -336,7 +355,7 @@ export class DispatcherConnection {
|
||||||
try {
|
try {
|
||||||
const result = await dispatcher._handleCommand(callMetadata, method, validParams);
|
const result = await dispatcher._handleCommand(callMetadata, method, validParams);
|
||||||
const validator = findValidator(dispatcher._type, method, 'Result');
|
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;
|
callMetadata.result = result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (isTargetClosedError(e) && sdkObject) {
|
if (isTargetClosedError(e) && sdkObject) {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import { ElementHandleDispatcher } from './elementHandlerDispatcher';
|
||||||
import { parseArgument, serializeResult } from './jsHandleDispatcher';
|
import { parseArgument, serializeResult } from './jsHandleDispatcher';
|
||||||
import { ResponseDispatcher } from './networkDispatchers';
|
import { ResponseDispatcher } from './networkDispatchers';
|
||||||
import { RequestDispatcher } from './networkDispatchers';
|
import { RequestDispatcher } from './networkDispatchers';
|
||||||
import { debugAssert } from '../../utils';
|
|
||||||
import { parseAriaSnapshotUnsafe } from '../../utils/isomorphic/ariaSnapshot';
|
import { parseAriaSnapshotUnsafe } from '../../utils/isomorphic/ariaSnapshot';
|
||||||
import { yaml } from '../../utilsBundle';
|
import { yaml } from '../../utilsBundle';
|
||||||
|
|
||||||
|
|
@ -50,7 +49,6 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Br
|
||||||
// Main frames are gc'ed separately from any other frames, so that
|
// Main frames are gc'ed separately from any other frames, so that
|
||||||
// methods on Page that redirect to the main frame remain operational.
|
// methods on Page that redirect to the main frame remain operational.
|
||||||
// Note: we cannot check parentFrame() here because it may be null after the frame has been detached.
|
// Note: we cannot check parentFrame() here because it may be null after the frame has been detached.
|
||||||
debugAssert(frame._page.mainFrame(), 'Cannot determine whether the frame is a main frame');
|
|
||||||
const gcBucket = frame._page.mainFrame() === frame ? 'MainFrame' : 'Frame';
|
const gcBucket = frame._page.mainFrame() === frame ? 'MainFrame' : 'Frame';
|
||||||
const pageDispatcher = existingDispatcher<PageDispatcher>(frame._page);
|
const pageDispatcher = existingDispatcher<PageDispatcher>(frame._page);
|
||||||
super(pageDispatcher || scope, frame, 'Frame', {
|
super(pageDispatcher || scope, frame, 'Frame', {
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,7 @@
|
||||||
|
|
||||||
import { Dispatcher } from './dispatcher';
|
import { Dispatcher } from './dispatcher';
|
||||||
import { SdkObject } from '../../server/instrumentation';
|
import { SdkObject } from '../../server/instrumentation';
|
||||||
import * as localUtils from '../../common/localUtils';
|
import * as localUtils from '../localUtils';
|
||||||
import { nodePlatform } from '../utils/nodePlatform';
|
|
||||||
import { getUserAgent } from '../utils/userAgent';
|
import { getUserAgent } from '../utils/userAgent';
|
||||||
import { deviceDescriptors as descriptors } from '../deviceDescriptors';
|
import { deviceDescriptors as descriptors } from '../deviceDescriptors';
|
||||||
import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher';
|
import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher';
|
||||||
|
|
@ -26,7 +25,7 @@ import { SocksInterceptor } from '../socksInterceptor';
|
||||||
import { WebSocketTransport } from '../transport';
|
import { WebSocketTransport } from '../transport';
|
||||||
import { fetchData } from '../utils/network';
|
import { fetchData } from '../utils/network';
|
||||||
|
|
||||||
import type { HarBackend } from '../../common/harBackend';
|
import type { HarBackend } from '../harBackend';
|
||||||
import type { CallMetadata } from '../instrumentation';
|
import type { CallMetadata } from '../instrumentation';
|
||||||
import type { Playwright } from '../playwright';
|
import type { Playwright } from '../playwright';
|
||||||
import type { RootDispatcher } from './dispatcher';
|
import type { RootDispatcher } from './dispatcher';
|
||||||
|
|
@ -50,11 +49,11 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
|
||||||
}
|
}
|
||||||
|
|
||||||
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
||||||
return await localUtils.zip(nodePlatform, this._stackSessions, params);
|
return await localUtils.zip(this._stackSessions, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async harOpen(params: channels.LocalUtilsHarOpenParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarOpenResult> {
|
async harOpen(params: channels.LocalUtilsHarOpenParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarOpenResult> {
|
||||||
return await localUtils.harOpen(nodePlatform, this._harBackends, params);
|
return await localUtils.harOpen(this._harBackends, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async harLookup(params: channels.LocalUtilsHarLookupParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarLookupResult> {
|
async harLookup(params: channels.LocalUtilsHarLookupParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarLookupResult> {
|
||||||
|
|
@ -74,7 +73,7 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
|
||||||
}
|
}
|
||||||
|
|
||||||
async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams, metadata?: CallMetadata | undefined): Promise<void> {
|
async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams, metadata?: CallMetadata | undefined): Promise<void> {
|
||||||
return await localUtils.traceDiscarded(nodePlatform, this._stackSessions, params);
|
return await localUtils.traceDiscarded(this._stackSessions, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams, metadata?: CallMetadata | undefined): Promise<void> {
|
async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams, metadata?: CallMetadata | undefined): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import type { Page } from './page';
|
||||||
import type { Progress } from './progress';
|
import type { Progress } from './progress';
|
||||||
import type { ScreenshotOptions } from './screenshotter';
|
import type { ScreenshotOptions } from './screenshotter';
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
import type { TimeoutOptions } from '../common/types';
|
import type { TimeoutOptions } from '../utils/isomorphic/types';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -705,7 +705,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
await this._page._delegate.setInputFilePaths(retargeted, localPathsOrDirectory);
|
await this._page._delegate.setInputFilePaths(retargeted, localPathsOrDirectory);
|
||||||
await waitForInputEvent;
|
await waitForInputEvent;
|
||||||
} else {
|
} else {
|
||||||
await this._page._delegate.setInputFiles(retargeted, filePayloads!);
|
await retargeted.evaluateInUtility(([injected, node, files]) =>
|
||||||
|
injected.setInputFiles(node, files), filePayloads!);
|
||||||
}
|
}
|
||||||
return 'done';
|
return 'done';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
[*]
|
[*]
|
||||||
../
|
../
|
||||||
../../common/
|
|
||||||
../../utils/
|
../../utils/
|
||||||
../../utils/isomorphic/
|
../../utils/isomorphic/
|
||||||
../chromium/
|
../chromium/
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as readline from 'readline';
|
import * as readline from 'readline';
|
||||||
|
|
||||||
import { TimeoutSettings } from '../../utils/isomorphic/timeoutSettings';
|
import { TimeoutSettings } from '../timeoutSettings';
|
||||||
import { ManualPromise } from '../../utils';
|
import { ManualPromise } from '../../utils';
|
||||||
import { wrapInASCIIBox } from '../utils/ascii';
|
import { wrapInASCIIBox } from '../utils/ascii';
|
||||||
import { RecentLogsCollector } from '../utils/debugLogger';
|
import { RecentLogsCollector } from '../utils/debugLogger';
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import { TLSSocket } from 'tls';
|
||||||
import * as url from 'url';
|
import * as url from 'url';
|
||||||
import * as zlib from 'zlib';
|
import * as zlib from 'zlib';
|
||||||
|
|
||||||
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
import { TimeoutSettings } from './timeoutSettings';
|
||||||
import { assert, constructURLBasedOnBaseURL, eventsHelper, monotonicTime } from '../utils';
|
import { assert, constructURLBasedOnBaseURL, eventsHelper, monotonicTime } from '../utils';
|
||||||
import { createGuid } from './utils/crypto';
|
import { createGuid } from './utils/crypto';
|
||||||
import { getUserAgent } from './utils/userAgent';
|
import { getUserAgent } from './utils/userAgent';
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,7 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
import { assert } from '../utils/isomorphic/assert';
|
||||||
import { fileUploadSizeLimit } from '../common/fileUtils';
|
|
||||||
import { mime } from '../utilsBundle';
|
import { mime } from '../utilsBundle';
|
||||||
|
|
||||||
import type { WritableStreamDispatcher } from './dispatchers/writableStreamDispatcher';
|
import type { WritableStreamDispatcher } from './dispatchers/writableStreamDispatcher';
|
||||||
|
|
@ -27,6 +26,9 @@ import type { Frame } from './frames';
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
|
// Keep in sync with the client.
|
||||||
|
export const fileUploadSizeLimit = 50 * 1024 * 1024;
|
||||||
|
|
||||||
async function filesExceedUploadLimit(files: string[]) {
|
async function filesExceedUploadLimit(files: string[]) {
|
||||||
const sizes = await Promise.all(files.map(async file => (await fs.promises.stat(file)).size));
|
const sizes = await Promise.all(files.map(async file => (await fs.promises.stat(file)).size));
|
||||||
return sizes.reduce((total, size) => total + size, 0) >= fileUploadSizeLimit;
|
return sizes.reduce((total, size) => total + size, 0) >= fileUploadSizeLimit;
|
||||||
|
|
|
||||||
|
|
@ -519,11 +519,6 @@ export class FFPage implements PageDelegate {
|
||||||
return result.quads.map(quad => [quad.p1, quad.p2, quad.p3, quad.p4]);
|
return result.quads.map(quad => [quad.p1, quad.p2, quad.p3, quad.p4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void> {
|
|
||||||
await handle.evaluateInUtility(([injected, node, files]) =>
|
|
||||||
injected.setInputFiles(node, files), files);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, files: string[]): Promise<void> {
|
async setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, files: string[]): Promise<void> {
|
||||||
await this._session.send('Page.setFileInputFiles', {
|
await this._session.send('Page.setFileInputFiles', {
|
||||||
frameId: handle._context.frame._id,
|
frameId: handle._context.frame._id,
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,13 @@ import * as network from './network';
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
import { ProgressController } from './progress';
|
import { ProgressController } from './progress';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
import { LongStandingScope, asLocator, assert, compressCallLog, constructURLBasedOnBaseURL, makeWaitForNextTask, monotonicTime } from '../utils';
|
import { LongStandingScope, asLocator, assert, constructURLBasedOnBaseURL, makeWaitForNextTask, monotonicTime } from '../utils';
|
||||||
import { isSessionClosedError } from './protocolError';
|
import { isSessionClosedError } from './protocolError';
|
||||||
import { debugLogger } from './utils/debugLogger';
|
import { debugLogger } from './utils/debugLogger';
|
||||||
import { eventsHelper } from './utils/eventsHelper';
|
import { eventsHelper } from './utils/eventsHelper';
|
||||||
import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser';
|
import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser';
|
||||||
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
||||||
|
import { compressCallLog } from './callLog';
|
||||||
|
|
||||||
import type { ConsoleMessage } from './console';
|
import type { ConsoleMessage } from './console';
|
||||||
import type { Dialog } from './dialog';
|
import type { Dialog } from './dialog';
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,14 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ZipFile } from '../utils/zipFile';
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
import type { HeadersArray } from './types';
|
import { createGuid } from './utils/crypto';
|
||||||
|
import { ZipFile } from './utils/zipFile';
|
||||||
|
|
||||||
|
import type { HeadersArray } from '../utils/isomorphic/types';
|
||||||
import type * as har from '@trace/har';
|
import type * as har from '@trace/har';
|
||||||
import type { Platform } from './platform';
|
|
||||||
|
|
||||||
const redirectStatus = [301, 302, 303, 307, 308];
|
const redirectStatus = [301, 302, 303, 307, 308];
|
||||||
|
|
||||||
|
|
@ -27,11 +30,9 @@ export class HarBackend {
|
||||||
private _harFile: har.HARFile;
|
private _harFile: har.HARFile;
|
||||||
private _zipFile: ZipFile | null;
|
private _zipFile: ZipFile | null;
|
||||||
private _baseDir: string | null;
|
private _baseDir: string | null;
|
||||||
private _platform: Platform;
|
|
||||||
|
|
||||||
constructor(platform: Platform, harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) {
|
constructor(harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) {
|
||||||
this._platform = platform;
|
this.id = createGuid();
|
||||||
this.id = platform.createGuid();
|
|
||||||
this._harFile = harFile;
|
this._harFile = harFile;
|
||||||
this._baseDir = baseDir;
|
this._baseDir = baseDir;
|
||||||
this._zipFile = zipFile;
|
this._zipFile = zipFile;
|
||||||
|
|
@ -79,7 +80,7 @@ export class HarBackend {
|
||||||
if (this._zipFile)
|
if (this._zipFile)
|
||||||
buffer = await this._zipFile.read(file);
|
buffer = await this._zipFile.read(file);
|
||||||
else
|
else
|
||||||
buffer = await this._platform.fs().promises.readFile(this._platform.path().resolve(this._baseDir!, file));
|
buffer = await fs.promises.readFile(path.resolve(this._baseDir!, file));
|
||||||
} else {
|
} else {
|
||||||
buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8');
|
buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import clipPaths from './clipPaths';
|
import clipPaths from './clipPaths';
|
||||||
|
|
||||||
import type { Point } from '../../../common/types';
|
import type { Point } from '../../../utils/isomorphic/types';
|
||||||
import type { Highlight, HighlightOptions } from '../highlight';
|
import type { Highlight, HighlightOptions } from '../highlight';
|
||||||
import type { InjectedScript } from '../injectedScript';
|
import type { InjectedScript } from '../injectedScript';
|
||||||
import type { ElementText } from '../selectorUtils';
|
import type { ElementText } from '../selectorUtils';
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,15 @@ import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { removeFolders } from './fileUtils';
|
import { calculateSha1 } from './utils/crypto';
|
||||||
import { HarBackend } from './harBackend';
|
import { HarBackend } from './harBackend';
|
||||||
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
||||||
import { ZipFile } from '../utils/zipFile';
|
import { ZipFile } from './utils/zipFile';
|
||||||
import { yauzl, yazl } from '../zipBundle';
|
import { yauzl, yazl } from '../zipBundle';
|
||||||
import { serializeClientSideCallMetadata } from '../utils/isomorphic/traceUtils';
|
import { serializeClientSideCallMetadata } from '../utils/isomorphic/traceUtils';
|
||||||
import { assert } from '../utils/isomorphic/debug';
|
import { assert } from '../utils/isomorphic/assert';
|
||||||
|
import { removeFolders } from './utils/fileUtils';
|
||||||
|
|
||||||
import type { Platform } from './platform';
|
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import type * as har from '@trace/har';
|
import type * as har from '@trace/har';
|
||||||
import type EventEmitter from 'events';
|
import type EventEmitter from 'events';
|
||||||
|
|
@ -39,7 +39,7 @@ export type StackSession = {
|
||||||
callStacks: channels.ClientSideCallMetadata[];
|
callStacks: channels.ClientSideCallMetadata[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function zip(platform: Platform, stackSessions: Map<string, StackSession>, params: channels.LocalUtilsZipParams): Promise<void> {
|
export async function zip(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsZipParams): Promise<void> {
|
||||||
const promise = new ManualPromise<void>();
|
const promise = new ManualPromise<void>();
|
||||||
const zipFile = new yazl.ZipFile();
|
const zipFile = new yazl.ZipFile();
|
||||||
(zipFile as any as EventEmitter).on('error', error => promise.reject(error));
|
(zipFile as any as EventEmitter).on('error', error => promise.reject(error));
|
||||||
|
|
@ -77,7 +77,7 @@ export async function zip(platform: Platform, stackSessions: Map<string, StackSe
|
||||||
sourceFiles.add(file);
|
sourceFiles.add(file);
|
||||||
}
|
}
|
||||||
for (const sourceFile of sourceFiles)
|
for (const sourceFile of sourceFiles)
|
||||||
addFile(sourceFile, 'resources/src@' + await platform.calculateSha1(sourceFile) + '.txt');
|
addFile(sourceFile, 'resources/src@' + await calculateSha1(sourceFile) + '.txt');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.mode === 'write') {
|
if (params.mode === 'write') {
|
||||||
|
|
@ -89,7 +89,7 @@ export async function zip(platform: Platform, stackSessions: Map<string, StackSe
|
||||||
.on('error', error => promise.reject(error));
|
.on('error', error => promise.reject(error));
|
||||||
});
|
});
|
||||||
await promise;
|
await promise;
|
||||||
await deleteStackSession(platform, stackSessions, params.stacksId);
|
await deleteStackSession(stackSessions, params.stacksId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,20 +124,20 @@ export async function zip(platform: Platform, stackSessions: Map<string, StackSe
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await promise;
|
await promise;
|
||||||
await deleteStackSession(platform, stackSessions, params.stacksId);
|
await deleteStackSession(stackSessions, params.stacksId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteStackSession(platform: Platform, stackSessions: Map<string, StackSession>, stacksId?: string) {
|
async function deleteStackSession(stackSessions: Map<string, StackSession>, stacksId?: string) {
|
||||||
const session = stacksId ? stackSessions.get(stacksId) : undefined;
|
const session = stacksId ? stackSessions.get(stacksId) : undefined;
|
||||||
if (!session)
|
if (!session)
|
||||||
return;
|
return;
|
||||||
await session.writer;
|
await session.writer;
|
||||||
if (session.tmpDir)
|
if (session.tmpDir)
|
||||||
await removeFolders(platform, [session.tmpDir]);
|
await removeFolders([session.tmpDir]);
|
||||||
stackSessions.delete(stacksId!);
|
stackSessions.delete(stacksId!);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function harOpen(platform: Platform, harBackends: Map<string, HarBackend>, params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> {
|
export async function harOpen(harBackends: Map<string, HarBackend>, params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> {
|
||||||
let harBackend: HarBackend;
|
let harBackend: HarBackend;
|
||||||
if (params.file.endsWith('.zip')) {
|
if (params.file.endsWith('.zip')) {
|
||||||
const zipFile = new ZipFile(params.file);
|
const zipFile = new ZipFile(params.file);
|
||||||
|
|
@ -147,10 +147,10 @@ export async function harOpen(platform: Platform, harBackends: Map<string, HarBa
|
||||||
return { error: 'Specified archive does not have a .har file' };
|
return { error: 'Specified archive does not have a .har file' };
|
||||||
const har = await zipFile.read(harEntryName);
|
const har = await zipFile.read(harEntryName);
|
||||||
const harFile = JSON.parse(har.toString()) as har.HARFile;
|
const harFile = JSON.parse(har.toString()) as har.HARFile;
|
||||||
harBackend = new HarBackend(platform, harFile, null, zipFile);
|
harBackend = new HarBackend(harFile, null, zipFile);
|
||||||
} else {
|
} else {
|
||||||
const harFile = JSON.parse(await fs.promises.readFile(params.file, 'utf-8')) as har.HARFile;
|
const harFile = JSON.parse(await fs.promises.readFile(params.file, 'utf-8')) as har.HARFile;
|
||||||
harBackend = new HarBackend(platform, harFile, path.dirname(params.file), null);
|
harBackend = new HarBackend(harFile, path.dirname(params.file), null);
|
||||||
}
|
}
|
||||||
harBackends.set(harBackend.id, harBackend);
|
harBackends.set(harBackend.id, harBackend);
|
||||||
return { harId: harBackend.id };
|
return { harId: harBackend.id };
|
||||||
|
|
@ -194,8 +194,8 @@ export async function tracingStarted(stackSessions: Map<string, StackSession>, p
|
||||||
return { stacksId: traceStacksFile };
|
return { stacksId: traceStacksFile };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function traceDiscarded(platform: Platform, stackSessions: Map<string, StackSession>, params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
|
export async function traceDiscarded(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
|
||||||
await deleteStackSession(platform, stackSessions, params.stacksId);
|
await deleteStackSession(stackSessions, params.stacksId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addStackToTracingNoReply(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> {
|
export async function addStackToTracingNoReply(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> {
|
||||||
|
|
@ -25,7 +25,7 @@ import type * as frames from './frames';
|
||||||
import type * as pages from './page';
|
import type * as pages from './page';
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
import type { NormalizedContinueOverrides } from './types';
|
import type { NormalizedContinueOverrides } from './types';
|
||||||
import type { HeadersArray, NameValue } from '../common/types';
|
import type { HeadersArray, NameValue } from '../utils/isomorphic/types';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,15 @@ import { parseEvaluationResultValue, source } from './isomorphic/utilityScriptSe
|
||||||
import * as js from './javascript';
|
import * as js from './javascript';
|
||||||
import { ProgressController } from './progress';
|
import { ProgressController } from './progress';
|
||||||
import { Screenshotter, validateScreenshotOptions } from './screenshotter';
|
import { Screenshotter, validateScreenshotOptions } from './screenshotter';
|
||||||
import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
|
import { TimeoutSettings } from './timeoutSettings';
|
||||||
import { LongStandingScope, assert, compressCallLog, trimStringWithEllipsis } from '../utils';
|
import { LongStandingScope, assert, trimStringWithEllipsis } from '../utils';
|
||||||
import { createGuid } from './utils/crypto';
|
import { createGuid } from './utils/crypto';
|
||||||
import { asLocator } from '../utils';
|
import { asLocator } from '../utils';
|
||||||
import { getComparator } from './utils/comparators';
|
import { getComparator } from './utils/comparators';
|
||||||
import { debugLogger } from './utils/debugLogger';
|
import { debugLogger } from './utils/debugLogger';
|
||||||
import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser';
|
import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser';
|
||||||
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
||||||
|
import { compressCallLog } from './callLog';
|
||||||
|
|
||||||
import type { Artifact } from './artifact';
|
import type { Artifact } from './artifact';
|
||||||
import type * as dom from './dom';
|
import type * as dom from './dom';
|
||||||
|
|
@ -45,7 +46,7 @@ import type * as network from './network';
|
||||||
import type { Progress } from './progress';
|
import type { Progress } from './progress';
|
||||||
import type { ScreenshotOptions } from './screenshotter';
|
import type { ScreenshotOptions } from './screenshotter';
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
import type { TimeoutOptions } from '../common/types';
|
import type { TimeoutOptions } from '../utils/isomorphic/types';
|
||||||
import type { ImageComparatorOptions } from './utils/comparators';
|
import type { ImageComparatorOptions } from './utils/comparators';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
|
|
@ -79,7 +80,6 @@ export interface PageDelegate {
|
||||||
getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null>; // Only called for frame owner elements.
|
getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null>; // Only called for frame owner elements.
|
||||||
getOwnerFrame(handle: dom.ElementHandle): Promise<string | null>; // Returns frameId.
|
getOwnerFrame(handle: dom.ElementHandle): Promise<string | null>; // Returns frameId.
|
||||||
getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null | 'error:notconnected'>;
|
getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null | 'error:notconnected'>;
|
||||||
setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void>;
|
|
||||||
setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, files: string[]): Promise<void>;
|
setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, files: string[]): Promise<void>;
|
||||||
getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>;
|
getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>;
|
||||||
getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle>;
|
getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle>;
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,14 @@ import { assert, monotonicTime } from '../utils';
|
||||||
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
import { ManualPromise } from '../utils/isomorphic/manualPromise';
|
||||||
|
|
||||||
import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation';
|
import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation';
|
||||||
import type { Progress as CommonProgress } from '../common/progress';
|
|
||||||
import type { LogName } from './utils/debugLogger';
|
import type { LogName } from './utils/debugLogger';
|
||||||
|
|
||||||
export interface Progress extends CommonProgress {
|
export interface Progress {
|
||||||
|
log(message: string): void;
|
||||||
|
timeUntilDeadline(): number;
|
||||||
|
isRunning(): boolean;
|
||||||
|
cleanupWhenAborted(cleanup: () => any): void;
|
||||||
|
throwIfAborted(): void;
|
||||||
metadata: CallMetadata;
|
metadata: CallMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import type { Frame } from './frames';
|
||||||
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';
|
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
import type { IRecorder, IRecorderApp, IRecorderAppFactory } from './recorder/recorderFrontend';
|
import type { IRecorder, IRecorderApp, IRecorderAppFactory } from './recorder/recorderFrontend';
|
||||||
import type { Point } from '../common/types';
|
import type { Point } from '../utils/isomorphic/types';
|
||||||
import type { AriaTemplateNode } from '@isomorphic/ariaSnapshot';
|
import type { AriaTemplateNode } from '@isomorphic/ariaSnapshot';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import type * as actions from '@recorder/actions';
|
import type * as actions from '@recorder/actions';
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
../codegen/languages.ts
|
../codegen/languages.ts
|
||||||
../isomorphic/**
|
../isomorphic/**
|
||||||
../registry/**
|
../registry/**
|
||||||
../../common/
|
../utils/**
|
||||||
../../generated/pollingRecorderSource.ts
|
../../generated/pollingRecorderSource.ts
|
||||||
../../protocol/
|
../../protocol/
|
||||||
../../utils/**
|
../../utils/**
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import { EventEmitter } from 'events';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { isUnderTest } from '../../utils';
|
import { isUnderTest } from '../utils/debug';
|
||||||
import { mime } from '../../utilsBundle';
|
import { mime } from '../../utilsBundle';
|
||||||
import { serverSideCallMetadata } from '../instrumentation';
|
import { serverSideCallMetadata } from '../instrumentation';
|
||||||
import { syncLocalStorageWithSettings } from '../launchApp';
|
import { syncLocalStorageWithSettings } from '../launchApp';
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import { EventEmitter } from 'events';
|
||||||
|
|
||||||
import { performAction } from './recorderRunner';
|
import { performAction } from './recorderRunner';
|
||||||
import { collapseActions } from './recorderUtils';
|
import { collapseActions } from './recorderUtils';
|
||||||
import { isUnderTest } from '../../utils/isomorphic/debug';
|
import { isUnderTest } from '../utils/debug';
|
||||||
import { monotonicTime } from '../../utils/isomorphic/time';
|
import { monotonicTime } from '../../utils/isomorphic/time';
|
||||||
|
|
||||||
import type { Signal } from '../../../../recorder/src/actions';
|
import type { Signal } from '../../../../recorder/src/actions';
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import type { Frame } from './frames';
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
import type { Progress } from './progress';
|
import type { Progress } from './progress';
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
import type { Rect } from '../common/types';
|
import type { Rect } from '../utils/isomorphic/types';
|
||||||
import type { ParsedSelector } from '../utils/isomorphic/selectorParser';
|
import type { ParsedSelector } from '../utils/isomorphic/selectorParser';
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import EventEmitter from 'events';
|
||||||
|
|
||||||
import * as socks from './utils/socksProxy';
|
import * as socks from './utils/socksProxy';
|
||||||
import { ValidationError, findValidator } from '../protocol/validator';
|
import { ValidationError, findValidator } from '../protocol/validator';
|
||||||
|
import { isUnderTest } from './utils/debug';
|
||||||
|
|
||||||
import type { WebSocketTransport } from './transport';
|
import type { WebSocketTransport } from './transport';
|
||||||
import type { ValidatorContext } from '../protocol/validator';
|
import type { ValidatorContext } from '../protocol/validator';
|
||||||
|
|
@ -42,7 +43,7 @@ export class SocksInterceptor {
|
||||||
const id = --lastId;
|
const id = --lastId;
|
||||||
this._ids.add(id);
|
this._ids.add(id);
|
||||||
const validator = findValidator('SocksSupport', prop, 'Params');
|
const validator = findValidator('SocksSupport', prop, 'Params');
|
||||||
params = validator(params, '', { tChannelImpl: tChannelForSocks, binary: 'toBase64' });
|
params = validator(params, '', { tChannelImpl: tChannelForSocks, binary: 'toBase64', isUnderTest });
|
||||||
transport.send({ id, guid: this._socksSupportObjectGuid, method: prop, params, metadata: { stack: [], apiName: '', internal: true } } as any);
|
transport.send({ id, guid: this._socksSupportObjectGuid, method: prop, params, metadata: { stack: [], apiName: '', internal: true } } as any);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +75,7 @@ export class SocksInterceptor {
|
||||||
}
|
}
|
||||||
if (this._socksSupportObjectGuid && message.guid === this._socksSupportObjectGuid) {
|
if (this._socksSupportObjectGuid && message.guid === this._socksSupportObjectGuid) {
|
||||||
const validator = findValidator('SocksSupport', message.method, 'Event');
|
const validator = findValidator('SocksSupport', message.method, 'Event');
|
||||||
const params = validator(message.params, '', { tChannelImpl: tChannelForSocks, binary: 'fromBase64' });
|
const params = validator(message.params, '', { tChannelImpl: tChannelForSocks, binary: 'fromBase64', isUnderTest });
|
||||||
this._channel.emit(message.method, params);
|
this._channel.emit(message.method, params);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { debugMode } from './debug';
|
import { debugMode } from './utils/debug';
|
||||||
|
|
||||||
|
// Keep in sync with client.
|
||||||
export const DEFAULT_TIMEOUT = 30000;
|
export const DEFAULT_TIMEOUT = 30000;
|
||||||
export const DEFAULT_LAUNCH_TIMEOUT = 3 * 60 * 1000; // 3 minutes
|
export const DEFAULT_LAUNCH_TIMEOUT = 3 * 60 * 1000; // 3 minutes
|
||||||
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
[*]
|
[*]
|
||||||
../../
|
../../
|
||||||
../../har/
|
../../har/
|
||||||
../../../common/
|
|
||||||
../../../protocol/
|
../../../protocol/
|
||||||
../../../utils/
|
|
||||||
../../../utilsBundle.ts
|
../../../utilsBundle.ts
|
||||||
../../../utils/isomorphic/
|
../../../utils/isomorphic/
|
||||||
../../../zipBundle.ts
|
../../../zipBundle.ts
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import * as path from 'path';
|
||||||
|
|
||||||
import { Snapshotter } from './snapshotter';
|
import { Snapshotter } from './snapshotter';
|
||||||
import { commandsWithTracingSnapshots } from '../../../protocol/debug';
|
import { commandsWithTracingSnapshots } from '../../../protocol/debug';
|
||||||
import { assert } from '../../../utils/isomorphic/debug';
|
import { assert } from '../../../utils/isomorphic/assert';
|
||||||
import { monotonicTime } from '../../../utils/isomorphic/time';
|
import { monotonicTime } from '../../../utils/isomorphic/time';
|
||||||
import { eventsHelper } from '../../utils/eventsHelper';
|
import { eventsHelper } from '../../utils/eventsHelper';
|
||||||
import { createGuid } from '../../utils/crypto';
|
import { createGuid } from '../../utils/crypto';
|
||||||
|
|
@ -34,7 +34,7 @@ import { SdkObject } from '../../instrumentation';
|
||||||
import { Page } from '../../page';
|
import { Page } from '../../page';
|
||||||
|
|
||||||
import type { SnapshotterBlob, SnapshotterDelegate } from './snapshotter';
|
import type { SnapshotterBlob, SnapshotterDelegate } from './snapshotter';
|
||||||
import type { NameValue } from '../../../common/types';
|
import type { NameValue } from '../../../utils/isomorphic/types';
|
||||||
import type { RegisteredListener } from '../../../utils';
|
import type { RegisteredListener } from '../../../utils';
|
||||||
import type { ConsoleMessage } from '../../console';
|
import type { ConsoleMessage } from '../../console';
|
||||||
import type { Dialog } from '../../dialog';
|
import type { Dialog } from '../../dialog';
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,16 @@ export async function installRootRedirect(server: HttpServer, traceUrls: string[
|
||||||
server.routePath('/', (_, response) => {
|
server.routePath('/', (_, response) => {
|
||||||
response.statusCode = 302;
|
response.statusCode = 302;
|
||||||
response.setHeader('Location', urlPath);
|
response.setHeader('Location', urlPath);
|
||||||
|
|
||||||
|
if (process.env.OPENAI_API_KEY)
|
||||||
|
response.appendHeader('Set-Cookie', `openai_api_key=${process.env.OPENAI_API_KEY}`);
|
||||||
|
if (process.env.OPENAI_BASE_URL)
|
||||||
|
response.appendHeader('Set-Cookie', `openai_base_url=${process.env.OPENAI_BASE_URL}`);
|
||||||
|
if (process.env.ANTHROPIC_API_KEY)
|
||||||
|
response.appendHeader('Set-Cookie', `anthropic_api_key=${process.env.ANTHROPIC_API_KEY}`);
|
||||||
|
if (process.env.ANTHROPIC_BASE_URL)
|
||||||
|
response.appendHeader('Set-Cookie', `anthropic_base_url=${process.env.ANTHROPIC_BASE_URL}`);
|
||||||
|
|
||||||
response.end();
|
response.end();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { HeadersArray, Point, Size, TimeoutOptions } from '../common/types';
|
import type { HeadersArray, Point, Size, TimeoutOptions } from '../utils/isomorphic/types';
|
||||||
export type { HeadersArray, Point, Quad, Rect, Size, TimeoutOptions } from '../common/types';
|
export type { HeadersArray, Point, Quad, Rect, Size, TimeoutOptions } from '../utils/isomorphic/types';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
export type StrictOptions = {
|
export type StrictOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
[*]
|
[*]
|
||||||
../../common
|
|
||||||
../../utils
|
../../utils
|
||||||
../../utils/isomorphic
|
../../utils/isomorphic
|
||||||
../../utilsBundle.ts
|
../../utilsBundle.ts
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
import { assert } from '../../utils/isomorphic/debug';
|
import { assert } from '../../utils/isomorphic/assert';
|
||||||
|
|
||||||
export function createGuid(): string {
|
export function createGuid(): string {
|
||||||
return crypto.randomBytes(16).toString('hex');
|
return crypto.randomBytes(16).toString('hex');
|
||||||
|
|
|
||||||
|
|
@ -14,21 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function assert(value: any, message?: string): asserts value {
|
import { getFromENV } from './env';
|
||||||
if (!value)
|
|
||||||
throw new Error(message || 'Assertion error');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function debugAssert(value: any, message?: string): asserts value {
|
const _debugMode = getFromENV('PWDEBUG') || '';
|
||||||
if (isUnderTest() && !value)
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _debugMode: string | undefined;
|
|
||||||
|
|
||||||
export function setDebugMode(mode: string) {
|
|
||||||
_debugMode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function debugMode() {
|
export function debugMode() {
|
||||||
if (_debugMode === 'console')
|
if (_debugMode === 'console')
|
||||||
|
|
@ -47,3 +47,7 @@ export function getPackageManagerExecCommand() {
|
||||||
return 'pnpm exec';
|
return 'pnpm exec';
|
||||||
return 'npx';
|
return 'npx';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isLikelyNpxGlobal() {
|
||||||
|
return process.argv.length >= 2 && process.argv[1].includes('_npx');
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import * as https from 'https';
|
||||||
import * as net from 'net';
|
import * as net from 'net';
|
||||||
import * as tls from 'tls';
|
import * as tls from 'tls';
|
||||||
|
|
||||||
import { assert } from '../../utils/isomorphic/debug';
|
import { assert } from '../../utils/isomorphic/assert';
|
||||||
import { ManualPromise } from '../../utils/isomorphic/manualPromise';
|
import { ManualPromise } from '../../utils/isomorphic/manualPromise';
|
||||||
import { monotonicTime } from '../../utils/isomorphic/time';
|
import { monotonicTime } from '../../utils/isomorphic/time';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import * as path from 'path';
|
||||||
|
|
||||||
import { mime, wsServer } from '../../utilsBundle';
|
import { mime, wsServer } from '../../utilsBundle';
|
||||||
import { createGuid } from './crypto';
|
import { createGuid } from './crypto';
|
||||||
import { assert } from '../../utils/isomorphic/debug';
|
import { assert } from '../../utils/isomorphic/assert';
|
||||||
import { ManualPromise } from '../../utils/isomorphic/manualPromise';
|
import { ManualPromise } from '../../utils/isomorphic/manualPromise';
|
||||||
import { createHttpServer } from './network';
|
import { createHttpServer } from './network';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,60 @@ import * as crypto from 'crypto';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
|
import { Readable, Writable, pipeline } from 'stream';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
import { colors } from '../../utilsBundle';
|
import { colors } from '../../utilsBundle';
|
||||||
import { Platform } from '../../common/platform';
|
|
||||||
import { debugLogger } from './debugLogger';
|
import { debugLogger } from './debugLogger';
|
||||||
|
import { currentZone, emptyZone } from './zones';
|
||||||
|
import { debugMode, isUnderTest } from './debug';
|
||||||
|
|
||||||
|
import type { Platform, Zone } from '../../client/platform';
|
||||||
|
import type { Zone as ZoneImpl } from './zones';
|
||||||
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
|
const pipelineAsync = util.promisify(pipeline);
|
||||||
|
|
||||||
|
class NodeZone implements Zone {
|
||||||
|
private _zone: ZoneImpl;
|
||||||
|
|
||||||
|
constructor(zone: ZoneImpl) {
|
||||||
|
this._zone = zone;
|
||||||
|
}
|
||||||
|
|
||||||
|
push<T>(data: T) {
|
||||||
|
return new NodeZone(this._zone.with('apiZone', data));
|
||||||
|
}
|
||||||
|
|
||||||
|
pop() {
|
||||||
|
return new NodeZone(this._zone.without('apiZone'));
|
||||||
|
}
|
||||||
|
|
||||||
|
run<R>(func: () => R): R {
|
||||||
|
return this._zone.run(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
data<T>(): T | undefined {
|
||||||
|
return this._zone.data('apiZone');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let boxedStackPrefixes: string[] = [];
|
||||||
|
export function setBoxedStackPrefixes(prefixes: string[]) {
|
||||||
|
boxedStackPrefixes = prefixes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const coreDir = path.dirname(require.resolve('../../../package.json'));
|
||||||
|
|
||||||
export const nodePlatform: Platform = {
|
export const nodePlatform: Platform = {
|
||||||
|
name: 'node',
|
||||||
|
|
||||||
|
boxedStackPrefixes: () => {
|
||||||
|
if (process.env.PWDEBUGIMPL)
|
||||||
|
return [];
|
||||||
|
return [coreDir, ...boxedStackPrefixes];
|
||||||
|
},
|
||||||
|
|
||||||
calculateSha1: (text: string) => {
|
calculateSha1: (text: string) => {
|
||||||
const sha1 = crypto.createHash('sha1');
|
const sha1 = crypto.createHash('sha1');
|
||||||
sha1.update(text);
|
sha1.update(text);
|
||||||
|
|
@ -32,21 +80,94 @@ export const nodePlatform: Platform = {
|
||||||
|
|
||||||
colors,
|
colors,
|
||||||
|
|
||||||
|
coreDir,
|
||||||
|
|
||||||
createGuid: () => crypto.randomBytes(16).toString('hex'),
|
createGuid: () => crypto.randomBytes(16).toString('hex'),
|
||||||
|
|
||||||
|
defaultMaxListeners: () => EventEmitter.defaultMaxListeners,
|
||||||
fs: () => fs,
|
fs: () => fs,
|
||||||
|
|
||||||
|
env: process.env,
|
||||||
|
|
||||||
inspectCustom: util.inspect.custom,
|
inspectCustom: util.inspect.custom,
|
||||||
|
|
||||||
|
isDebugMode: () => !!debugMode(),
|
||||||
|
|
||||||
|
isJSDebuggerAttached: () => !!require('inspector').url(),
|
||||||
|
|
||||||
isLogEnabled(name: 'api' | 'channel') {
|
isLogEnabled(name: 'api' | 'channel') {
|
||||||
return debugLogger.isEnabled(name);
|
return debugLogger.isEnabled(name);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isUnderTest: () => isUnderTest(),
|
||||||
|
|
||||||
log(name: 'api' | 'channel', message: string | Error | object) {
|
log(name: 'api' | 'channel', message: string | Error | object) {
|
||||||
debugLogger.log(name, message);
|
debugLogger.log(name, message);
|
||||||
},
|
},
|
||||||
|
|
||||||
path: () => path,
|
path: () => path,
|
||||||
|
|
||||||
pathSeparator: path.sep
|
pathSeparator: path.sep,
|
||||||
|
|
||||||
|
showInternalStackFrames: () => !!process.env.PWDEBUGIMPL,
|
||||||
|
|
||||||
|
async streamFile(path: string, stream: Writable): Promise<void> {
|
||||||
|
await pipelineAsync(fs.createReadStream(path), stream);
|
||||||
|
},
|
||||||
|
|
||||||
|
streamReadable: (channel: channels.StreamChannel) => {
|
||||||
|
return new ReadableStreamImpl(channel);
|
||||||
|
},
|
||||||
|
|
||||||
|
streamWritable: (channel: channels.WritableStreamChannel) => {
|
||||||
|
return new WritableStreamImpl(channel);
|
||||||
|
},
|
||||||
|
|
||||||
|
zones: {
|
||||||
|
current: () => new NodeZone(currentZone()),
|
||||||
|
empty: new NodeZone(emptyZone),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ReadableStreamImpl extends Readable {
|
||||||
|
private _channel: channels.StreamChannel;
|
||||||
|
|
||||||
|
constructor(channel: channels.StreamChannel) {
|
||||||
|
super();
|
||||||
|
this._channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async _read() {
|
||||||
|
const result = await this._channel.read({ size: 1024 * 1024 });
|
||||||
|
if (result.binary.byteLength)
|
||||||
|
this.push(result.binary);
|
||||||
|
else
|
||||||
|
this.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
override _destroy(error: Error | null, callback: (error: Error | null | undefined) => void): void {
|
||||||
|
// Stream might be destroyed after the connection was closed.
|
||||||
|
this._channel.close().catch(e => null);
|
||||||
|
super._destroy(error, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WritableStreamImpl extends Writable {
|
||||||
|
private _channel: channels.WritableStreamChannel;
|
||||||
|
|
||||||
|
constructor(channel: channels.WritableStreamChannel) {
|
||||||
|
super();
|
||||||
|
this._channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async _write(chunk: Buffer | string, encoding: BufferEncoding, callback: (error?: Error | null) => void) {
|
||||||
|
const error = await this._channel.write({ binary: typeof chunk === 'string' ? Buffer.from(chunk) : chunk }).catch(e => e);
|
||||||
|
callback(error || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async _final(callback: (error?: Error | null) => void) {
|
||||||
|
// Stream might be destroyed after the connection was closed.
|
||||||
|
const error = await this._channel.close().catch(e => e);
|
||||||
|
callback(error || null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import * as net from 'net';
|
import * as net from 'net';
|
||||||
|
|
||||||
import { assert } from '../../utils/isomorphic/debug';
|
import { assert } from '../../utils/isomorphic/assert';
|
||||||
import { createGuid } from './crypto';
|
import { createGuid } from './crypto';
|
||||||
import { debugLogger } from './debugLogger';
|
import { debugLogger } from './debugLogger';
|
||||||
import { createSocket } from './happyEyeballs';
|
import { createSocket } from './happyEyeballs';
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue