diff --git a/.github/workflows/create_test_report.yml b/.github/workflows/create_test_report.yml index dfc6961c2c..3799f14159 100644 --- a/.github/workflows/create_test_report.yml +++ b/.github/workflows/create_test_report.yml @@ -33,7 +33,7 @@ jobs: - name: Merge reports run: | - npx playwright merge-reports --config .github/workflows/merge.config.ts ./all-blob-reports + npx playwright merge-reports --reporter=html,packages/playwright/lib/reporters/markdown.js ./all-blob-reports env: NODE_OPTIONS: --max-old-space-size=8192 diff --git a/.github/workflows/merge.config.ts b/.github/workflows/merge.config.ts deleted file mode 100644 index e8582ed521..0000000000 --- a/.github/workflows/merge.config.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default { - testDir: '../../tests', - reporter: [[require.resolve('../../packages/playwright/lib/reporters/markdown')], ['html']] -}; \ No newline at end of file diff --git a/README.md b/README.md index f236eafd1a..e8a9231fd0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🎭 Playwright -[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-134.0.6998.3-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-135.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-134.0.6998.15-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-135.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 134.0.6998.3 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 134.0.6998.15 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 18.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 135.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/package-lock.json b/package-lock.json index eb69ce2f36..e1edd82bf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "formidable": "^2.1.1", "immutable": "^4.3.7", "license-checker": "^25.0.1", + "markdown-to-jsx": "^7.7.3", "mime": "^3.0.0", "node-stream-zip": "^1.15.0", "react": "^18.1.0", @@ -5897,7 +5898,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -5953,6 +5953,18 @@ "semver": "bin/semver" } }, + "node_modules/markdown-to-jsx": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.7.3.tgz", + "integrity": "sha512-o35IhJDFP6Fv60zPy+hbvZSQMmgvSGdK5j8NRZ7FeZMY+Bgqw+dSg7SC1ZEzC26++CiOUCqkbq96/c3j/FfTEQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -6784,7 +6796,6 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -8975,6 +8986,7 @@ "packages/trace-viewer": { "version": "0.0.0", "dependencies": { + "markdown-to-jsx": "^7.7.3", "yaml": "^2.6.0" } }, diff --git a/package.json b/package.json index 8b54d54e6c..f27ad12a76 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "formidable": "^2.1.1", "immutable": "^4.3.7", "license-checker": "^25.0.1", + "markdown-to-jsx": "^7.7.3", "mime": "^3.0.0", "node-stream-zip": "^1.15.0", "react": "^18.1.0", diff --git a/packages/html-reporter/src/metadataView.tsx b/packages/html-reporter/src/metadataView.tsx index 03a5ed06f4..0f54bb4249 100644 --- a/packages/html-reporter/src/metadataView.tsx +++ b/packages/html-reporter/src/metadataView.tsx @@ -107,14 +107,24 @@ const InnerMetadataView = () => { const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => { const email = info['revision.email'] ? ` <${info['revision.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 longTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).format(info['revision.timestamp']); return
- {info['revision.link'] ? ( - + {link ? ( + {subject} ) : @@ -130,18 +140,12 @@ const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => { Logs )} - {info['pull.link'] && ( - <> - · - Pull Request - - )}
- {!!info['revision.link'] ? ( - - {info['revision.id']?.slice(0, 7) || 'unknown'} + {link ? ( + + {shortSubject} - ) : !!info['revision.id'] && {info['revision.id'].slice(0, 7)}} + ) : !!shortSubject && {shortSubject}}
; }; diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 38c5d71834..05ab76b816 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,27 +3,27 @@ "browsers": [ { "name": "chromium", - "revision": "1158", + "revision": "1159", "installByDefault": true, - "browserVersion": "134.0.6998.3" + "browserVersion": "134.0.6998.15" }, { "name": "chromium-headless-shell", - "revision": "1158", + "revision": "1159", "installByDefault": true, - "browserVersion": "134.0.6998.3" + "browserVersion": "134.0.6998.15" }, { "name": "chromium-tip-of-tree", - "revision": "1302", + "revision": "1303", "installByDefault": false, - "browserVersion": "135.0.7011.0" + "browserVersion": "135.0.7015.0" }, { "name": "chromium-tip-of-tree-headless-shell", - "revision": "1302", + "revision": "1303", "installByDefault": false, - "browserVersion": "135.0.7011.0" + "browserVersion": "135.0.7015.0" }, { "name": "firefox", @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "2134", + "revision": "2137", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", diff --git a/packages/playwright-core/src/DEPS.list b/packages/playwright-core/src/DEPS.list index 2ffa077b4e..d21eb103c4 100644 --- a/packages/playwright-core/src/DEPS.list +++ b/packages/playwright-core/src/DEPS.list @@ -1,22 +1,27 @@ [browserServerImpl.ts] -** +remote/ +server/ +server/utils +utils/isomorphic/ +utilsBundle.ts [androidServerImpl.ts] -** +remote/ +server/ +server/utils +utils/isomorphic/ +utilsBundle.ts [inProcessFactory.ts] ** [inprocess.ts] -common/ utils/ server/utils [outofprocess.ts] client/ -common/ protocol/ utils/ utils/isomorphic server/utils -common/ diff --git a/packages/playwright-core/src/browserServerImpl.ts b/packages/playwright-core/src/browserServerImpl.ts index 77fb9a9844..7e6b90741b 100644 --- a/packages/playwright-core/src/browserServerImpl.ts +++ b/packages/playwright-core/src/browserServerImpl.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { envObjectToArray } from './client/clientHelper'; import { SocksProxy } from './server/utils/socksProxy'; import { PlaywrightServer } from './remote/playwrightServer'; import { helper } from './server/helper'; @@ -25,7 +24,7 @@ import { rewriteErrorMessage } from './utils/isomorphic/stackTrace'; import { ws } from './utilsBundle'; 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 { 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), [], {}); } : 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; +} diff --git a/packages/playwright-core/src/cli/DEPS.list b/packages/playwright-core/src/cli/DEPS.list index 6d8d6a1569..06a2e3baae 100644 --- a/packages/playwright-core/src/cli/DEPS.list +++ b/packages/playwright-core/src/cli/DEPS.list @@ -1,7 +1,5 @@ [*] ../../ -../client -../common ../debug/injected ../generated/ ../server/ diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts index 360ab4726f..6c0ccd230c 100644 --- a/packages/playwright-core/src/cli/program.ts +++ b/packages/playwright-core/src/cli/program.ts @@ -22,7 +22,6 @@ import * as path from 'path'; import * as playwright from '../..'; import { launchBrowserServer, printApiJson, runDriver, runServer } from './driver'; -import { isTargetClosedError } from '../client/errors'; import { registry, writeDockerVersion } from '../server'; import { gracefullyProcessExitDoNotHang } from '../utils'; import { runTraceInBrowser, runTraceViewerApp } from '../server/trace/viewer/traceViewer'; @@ -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:')) url = 'http://' + url; 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 // in a stray navigation aborted error. We should ignore it. } else { diff --git a/packages/playwright-core/src/client/DEPS.list b/packages/playwright-core/src/client/DEPS.list index e886cdbe63..1c44a4a48b 100644 --- a/packages/playwright-core/src/client/DEPS.list +++ b/packages/playwright-core/src/client/DEPS.list @@ -1,4 +1,3 @@ [*] -../common/ ../protocol/ ../utils/isomorphic diff --git a/packages/playwright-core/src/client/android.ts b/packages/playwright-core/src/client/android.ts index 134be7c7bb..c6eb4225b4 100644 --- a/packages/playwright-core/src/client/android.ts +++ b/packages/playwright-core/src/client/android.ts @@ -20,7 +20,7 @@ import { ChannelOwner } from './channelOwner'; import { TargetClosedError, isTargetClosedError } from './errors'; import { Events } from './events'; import { Waiter } from './waiter'; -import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; +import { TimeoutSettings } from './timeoutSettings'; import { isRegExp, isString } from '../utils/isomorphic/rtti'; import { monotonicTime } from '../utils/isomorphic/time'; import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner'; @@ -30,7 +30,7 @@ import type { Page } from './page'; import type * as types from './types'; import type * as api from '../../types/types'; import type { AndroidServerLauncherImpl } from '../androidServerImpl'; -import type { Platform } from '../common/platform'; +import type { Platform } from './platform'; import type * as channels from '@protocol/channels'; type Direction = 'down' | 'up' | 'left' | 'right'; @@ -46,7 +46,7 @@ export class Android extends ChannelOwner implements ap constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.AndroidInitializer) { super(parent, type, guid, initializer); - this._timeoutSettings = new TimeoutSettings(); + this._timeoutSettings = new TimeoutSettings(this._platform); } setDefaultTimeout(timeout: number) { @@ -112,7 +112,7 @@ export class AndroidDevice extends ChannelOwner i constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.AndroidDeviceInitializer) { super(parent, type, guid, initializer); 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('webViewRemoved', ({ socketName }) => this._onWebViewRemoved(socketName)); this._channel.on('close', () => this._didClose()); diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 21f5a200b7..5ed9262b02 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -34,7 +34,7 @@ import { Tracing } from './tracing'; import { Waiter } from './waiter'; import { WebError } from './webError'; import { Worker } from './worker'; -import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; +import { TimeoutSettings } from './timeoutSettings'; import { mkdirIfNeeded } from './fileUtils'; import { headersObjectToArray } from '../utils/isomorphic/headers'; import { urlMatchesEqual } from '../utils/isomorphic/urlMatch'; @@ -46,7 +46,7 @@ import type { BrowserContextOptions, Headers, LaunchOptions, StorageState, WaitF import type * as structs from '../../types/structs'; import type * as api from '../../types/types'; 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'; export class BrowserContext extends ChannelOwner implements api.BrowserContext { @@ -56,7 +56,7 @@ export class BrowserContext extends ChannelOwner readonly _browser: Browser | null = null; _browserType: BrowserType | undefined; readonly _bindings = new Map any>(); - _timeoutSettings = new TimeoutSettings(); + _timeoutSettings: TimeoutSettings; _ownerPage: Page | undefined; private _closedPromise: Promise; _options: channels.BrowserNewContextParams = { }; @@ -83,6 +83,7 @@ export class BrowserContext extends ChannelOwner constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.BrowserContextInitializer) { super(parent, type, guid, initializer); + this._timeoutSettings = new TimeoutSettings(this._platform); if (parent instanceof Browser) this._browser = parent; this._browser?._contexts.add(this); diff --git a/packages/playwright-core/src/client/browserType.ts b/packages/playwright-core/src/client/browserType.ts index e4942bd7f0..f67c97ced8 100644 --- a/packages/playwright-core/src/client/browserType.ts +++ b/packages/playwright-core/src/client/browserType.ts @@ -19,7 +19,7 @@ import { BrowserContext, prepareBrowserContextParams } from './browserContext'; import { ChannelOwner } from './channelOwner'; import { envObjectToArray } from './clientHelper'; import { Events } from './events'; -import { assert } from '../utils/isomorphic/debug'; +import { assert } from '../utils/isomorphic/assert'; import { headersObjectToArray } from '../utils/isomorphic/headers'; import { monotonicTime } from '../utils/isomorphic/time'; import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner'; diff --git a/packages/playwright-core/src/client/channelOwner.ts b/packages/playwright-core/src/client/channelOwner.ts index 92deaecabd..3597afd730 100644 --- a/packages/playwright-core/src/client/channelOwner.ts +++ b/packages/playwright-core/src/client/channelOwner.ts @@ -16,14 +16,14 @@ import { EventEmitter } from './eventEmitter'; import { ValidationError, maybeFindValidator } from '../protocol/validator'; -import { isUnderTest } from '../utils/isomorphic/debug'; -import { captureLibraryStackTrace, stringifyStackFrames } from '../utils/isomorphic/stackTrace'; +import { captureLibraryStackTrace } from './clientStackTrace'; +import { stringifyStackFrames } from '../utils/isomorphic/stackTrace'; import type { ClientInstrumentation } from './clientInstrumentation'; import type { Connection } from './connection'; import type { Logger } from './types'; import type { ValidatorContext } from '../protocol/validator'; -import type { Platform } from '../common/platform'; +import type { Platform } from './platform'; import type * as channels from '@protocol/channels'; type Listener = (...args: any[]) => void; @@ -181,7 +181,7 @@ export abstract class ChannelOwner\n' + e.stack : ''; + const innerError = ((process.env.PWDEBUGIMPL || this._platform.isUnderTest()) && e.stack) ? '\n\n' + e.stack : ''; if (apiZone.apiName && !apiZone.apiName.includes('')) e.message = apiZone.apiName + ': ' + e.message; const stackFrames = '\n' + stringifyStackFrames(stackTrace.frames).join('\n') + innerError; diff --git a/packages/playwright-core/src/client/clientBundle.ts b/packages/playwright-core/src/client/clientBundle.ts new file mode 100644 index 0000000000..a81e13daa9 --- /dev/null +++ b/packages/playwright-core/src/client/clientBundle.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the 'License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Connection } from './connection'; +import { setPlatformForSelectors } from './selectors'; +import { setPlatformForEventEmitter } from './eventEmitter'; +import { setIsUnderTestForValidator } from '../protocol/validatorPrimitives'; + +import type { Platform } from './platform'; + +export function createConnectionFactory(platform: Platform): () => Connection { + setPlatformForSelectors(platform); + setPlatformForEventEmitter(platform); + setIsUnderTestForValidator(() => platform.isUnderTest()); + return () => new Connection(platform); +} diff --git a/packages/playwright-core/src/client/clientHelper.ts b/packages/playwright-core/src/client/clientHelper.ts index afb6077c13..aecc92a156 100644 --- a/packages/playwright-core/src/client/clientHelper.ts +++ b/packages/playwright-core/src/client/clientHelper.ts @@ -18,7 +18,7 @@ import { isString } from '../utils/isomorphic/rtti'; 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 }[] { const result: { name: string, value: string }[] = []; diff --git a/packages/playwright-core/src/client/clientStackTrace.ts b/packages/playwright-core/src/client/clientStackTrace.ts new file mode 100644 index 0000000000..8a18787517 --- /dev/null +++ b/packages/playwright-core/src/client/clientStackTrace.ts @@ -0,0 +1,78 @@ +/** + * 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); + 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.coreDir ? [platform.coreDir, ...platform.boxedStackPrefixes()] : platform.boxedStackPrefixes(); + parsedFrames = parsedFrames.filter(f => { + if (process.env.PWDEBUGIMPL) + return true; + if (filterPrefixes.some(prefix => f.frame.file.startsWith(prefix))) + return false; + return true; + }); + + return { + frames: parsedFrames.map(p => p.frame), + apiName + }; +} diff --git a/packages/playwright-core/src/client/connection.ts b/packages/playwright-core/src/client/connection.ts index 9f2745a8ec..ad5e16f866 100644 --- a/packages/playwright-core/src/client/connection.ts +++ b/packages/playwright-core/src/client/connection.ts @@ -14,7 +14,6 @@ * limitations under the License. */ - import { EventEmitter } from './eventEmitter'; import { Android, AndroidDevice, AndroidSocket } from './android'; import { Artifact } from './artifact'; @@ -42,12 +41,12 @@ import { Tracing } from './tracing'; import { Worker } from './worker'; import { WritableStream } from './writableStream'; import { ValidationError, findValidator } from '../protocol/validator'; -import { formatCallLog, rewriteErrorMessage } from '../utils/isomorphic/stackTrace'; +import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace'; import type { ClientInstrumentation } from './clientInstrumentation'; import type { HeadersArray } from './types'; import type { ValidatorContext } from '../protocol/validator'; -import type { Platform } from '../common/platform'; +import type { Platform } from './platform'; import type * as channels from '@protocol/channels'; class Root extends ChannelOwner { @@ -83,7 +82,7 @@ export class Connection extends EventEmitter { // Used from @playwright/test fixtures -> TODO remove? readonly headers: HeadersArray; - constructor(localUtils: LocalUtils | undefined, platform: Platform, instrumentation: ClientInstrumentation | undefined, headers: HeadersArray) { + constructor(platform: Platform, localUtils?: LocalUtils, instrumentation?: ClientInstrumentation, headers: HeadersArray = []) { super(); this._instrumentation = instrumentation || createInstrumentation(); this._localUtils = localUtils; @@ -333,3 +332,12 @@ export class Connection extends EventEmitter { 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'))} +`; +} diff --git a/packages/playwright-core/src/client/consoleMessage.ts b/packages/playwright-core/src/client/consoleMessage.ts index 5d215cf2ad..9cf9acd0ba 100644 --- a/packages/playwright-core/src/client/consoleMessage.ts +++ b/packages/playwright-core/src/client/consoleMessage.ts @@ -18,7 +18,7 @@ import { JSHandle } from './jsHandle'; import { Page } from './page'; 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'; type ConsoleMessageLocation = channels.BrowserContextConsoleEvent['location']; diff --git a/packages/playwright-core/src/client/electron.ts b/packages/playwright-core/src/client/electron.ts index 90ae6dc4d4..f721d89f07 100644 --- a/packages/playwright-core/src/client/electron.ts +++ b/packages/playwright-core/src/client/electron.ts @@ -22,7 +22,7 @@ import { TargetClosedError, isTargetClosedError } from './errors'; import { Events } from './events'; import { JSHandle, parseResult, serializeArgument } from './jsHandle'; import { Waiter } from './waiter'; -import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; +import { TimeoutSettings } from './timeoutSettings'; import type { Page } from './page'; import type { BrowserContextOptions, Env, Headers, WaitForEventOptions } from './types'; @@ -66,7 +66,7 @@ export class Electron extends ChannelOwner implements export class ElectronApplication extends ChannelOwner implements api.ElectronApplication { readonly _context: BrowserContext; private _windows = new Set(); - private _timeoutSettings = new TimeoutSettings(); + private _timeoutSettings: TimeoutSettings; static from(electronApplication: channels.ElectronApplicationChannel): ElectronApplication { return (electronApplication as any)._object; @@ -74,6 +74,8 @@ export class ElectronApplication extends ChannelOwner extends JSHandle implements api.ElementHandle { readonly _elementChannel: channels.ElementHandleChannel; @@ -306,7 +301,7 @@ export async function convertInputFiles(platform: Platform, files: string | File }), true); for (let i = 0; i < files.length; i++) { const writable = WritableStream.from(writableStreams[i]); - await pipelineAsync(platform.fs().createReadStream(files[i]), writable.stream()); + await platform.streamFile(files[i], writable.stream()); } return { directoryStream: rootDir, diff --git a/packages/playwright-core/src/client/eventEmitter.ts b/packages/playwright-core/src/client/eventEmitter.ts index a0781534e3..73d048b274 100644 --- a/packages/playwright-core/src/client/eventEmitter.ts +++ b/packages/playwright-core/src/client/eventEmitter.ts @@ -22,18 +22,19 @@ * USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { isUnderTest } from '../utils/isomorphic/debug'; +import { emptyPlatform } from './platform'; import type { EventEmitter as EventEmitterType } from 'events'; +import type { Platform } from './platform'; type EventType = string | symbol; type Listener = (...args: any[]) => any; type EventMap = Record; -let defaultMaxListenersProvider = () => 10; +let platform = emptyPlatform; -export function setDefaultMaxListenersProvider(provider: () => number) { - defaultMaxListenersProvider = provider; +export function setPlatformForEventEmitter(p: Platform) { + platform = p; } export class EventEmitter implements EventEmitterType { @@ -62,7 +63,7 @@ export class EventEmitter implements EventEmitterType { } getMaxListeners(): number { - return this._maxListeners === undefined ? defaultMaxListenersProvider() : this._maxListeners; + return this._maxListeners === undefined ? platform.defaultMaxListeners() : this._maxListeners; } emit(type: EventType, ...args: any[]): boolean { @@ -160,7 +161,7 @@ export class EventEmitter implements EventEmitterType { w.emitter = this; w.type = type; w.count = existing.length; - if (!isUnderTest()) { + if (!platform.isUnderTest()) { // eslint-disable-next-line no-console console.warn(w); } diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index 41070b8665..8bdf38631d 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -19,7 +19,7 @@ import { ChannelOwner } from './channelOwner'; import { TargetClosedError, isTargetClosedError } from './errors'; import { RawHeaders } from './network'; import { Tracing } from './tracing'; -import { assert } from '../utils/isomorphic/debug'; +import { assert } from '../utils/isomorphic/assert'; import { mkdirIfNeeded } from './fileUtils'; import { headersObjectToArray } from '../utils/isomorphic/headers'; 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 { Serializable } from '../../types/structs'; import type * as api from '../../types/types'; -import type { HeadersArray, NameValue } from '../common/types'; -import type { Platform } from '../common/platform'; +import type { HeadersArray, NameValue } from '../utils/isomorphic/types'; +import type { Platform } from './platform'; import type * as channels from '@protocol/channels'; import type * as fs from 'fs'; diff --git a/packages/playwright-core/src/client/fileUtils.ts b/packages/playwright-core/src/client/fileUtils.ts index 0d21b195c0..1eb4833946 100644 --- a/packages/playwright-core/src/client/fileUtils.ts +++ b/packages/playwright-core/src/client/fileUtils.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Platform } from '../common/platform'; +import type { Platform } from './platform'; // Keep in sync with the server. export const fileUploadSizeLimit = 50 * 1024 * 1024; diff --git a/packages/playwright-core/src/client/frame.ts b/packages/playwright-core/src/client/frame.ts index ecbfe3cb11..69d7e2b73c 100644 --- a/packages/playwright-core/src/client/frame.ts +++ b/packages/playwright-core/src/client/frame.ts @@ -25,7 +25,7 @@ import { FrameLocator, Locator, testIdAttributeName } from './locator'; import * as network from './network'; import { kLifecycleEvents } from './types'; 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 { urlMatches } from '../utils/isomorphic/urlMatch'; diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index a4f5ff68d0..01525b2939 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -21,7 +21,7 @@ import { APIResponse } from './fetch'; import { Frame } from './frame'; import { Waiter } from './waiter'; import { Worker } from './worker'; -import { assert } from '../utils/isomorphic/debug'; +import { assert } from '../utils/isomorphic/assert'; import { headersObjectToArray } from '../utils/isomorphic/headers'; import { urlMatches } from '../utils/isomorphic/urlMatch'; import { LongStandingScope, ManualPromise } from '../utils/isomorphic/manualPromise'; @@ -35,10 +35,10 @@ import type { Page } from './page'; import type { Headers, RemoteAddr, SecurityDetails, WaitForEventOptions } from './types'; import type { Serializable } from '../../types/structs'; 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 * as channels from '@protocol/channels'; -import type { Platform, Zone } from '../common/platform'; +import type { Platform, Zone } from './platform'; export type NetworkCookie = { name: string, diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index cb6d0656b8..0aa5aeb667 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -33,8 +33,8 @@ import { Response, Route, RouteHandler, WebSocket, WebSocketRoute, WebSocketRou import { Video } from './video'; import { Waiter } from './waiter'; import { Worker } from './worker'; -import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; -import { assert } from '../utils/isomorphic/debug'; +import { TimeoutSettings } from './timeoutSettings'; +import { assert } from '../utils/isomorphic/assert'; import { mkdirIfNeeded } from './fileUtils'; import { headersObjectToArray } from '../utils/isomorphic/headers'; import { trimStringWithEllipsis } from '../utils/isomorphic/stringUtils'; @@ -118,7 +118,7 @@ export class Page extends ChannelOwner implements api.Page constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.PageInitializer) { super(parent, type, guid, initializer); 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.keyboard = new Keyboard(this); @@ -799,7 +799,7 @@ export class Page extends ChannelOwner implements api.Page } async pause(_options?: { __testHookKeepTestTimeout: boolean }) { - if (this._platform.isDebuggerAttached()) + if (this._platform.isJSDebuggerAttached()) return; const defaultNavigationTimeout = this._browserContext._timeoutSettings.defaultNavigationTimeout(); const defaultTimeout = this._browserContext._timeoutSettings.defaultTimeout(); diff --git a/packages/playwright-core/src/common/platform.ts b/packages/playwright-core/src/client/platform.ts similarity index 65% rename from packages/playwright-core/src/common/platform.ts rename to packages/playwright-core/src/client/platform.ts index 10bdc35922..31d6705378 100644 --- a/packages/playwright-core/src/common/platform.ts +++ b/packages/playwright-core/src/client/platform.ts @@ -18,7 +18,9 @@ import { webColors, noColors } from '../utils/isomorphic/colors'; import type * as fs from 'fs'; import type * as path from 'path'; -import type { Colors } from '../utils/isomorphic/colors'; +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; @@ -37,22 +39,90 @@ const noopZone: Zone = { export type Platform = { name: 'node' | 'web' | 'empty'; - calculateSha1(text: string): Promise; + boxedStackPrefixes: () => string[]; + calculateSha1: (text: string) => Promise; colors: Colors; + coreDir?: string; createGuid: () => string; + defaultMaxListeners: () => number; fs: () => typeof fs; inspectCustom: symbol | undefined; - isDebuggerAttached(): boolean; - isLogEnabled(name: 'api' | 'channel'): boolean; - log(name: 'api' | 'channel', message: string | Error | object): void; + 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; + streamFile: (path: string, writable: Writable) => Promise, + 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, + + 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: '/', + + streamFile(path: string, writable: Writable): Promise { + 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); @@ -64,62 +134,4 @@ export const webPlatform: Platform = { createGuid: () => { return Array.from(window.crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join(''); }, - - fs: () => { - throw new Error('File system is not available'); - }, - - inspectCustom: undefined, - - isDebuggerAttached: () => false, - - isLogEnabled(name: 'api' | 'channel') { - return false; - }, - - log(name: 'api' | 'channel', message: string | Error | object) {}, - - path: () => { - throw new Error('Path module is not available'); - }, - - pathSeparator: '/', - - zones: { empty: noopZone, current: () => noopZone }, -}; - -export const emptyPlatform: Platform = { - name: 'empty', - - calculateSha1: async () => { - throw new Error('Not implemented'); - }, - - colors: noColors, - - createGuid: () => { - throw new Error('Not implemented'); - }, - - fs: () => { - throw new Error('Not implemented'); - }, - - inspectCustom: undefined, - - isDebuggerAttached: () => false, - - isLogEnabled(name: 'api' | 'channel') { - return false; - }, - - log(name: 'api' | 'channel', message: string | Error | object) { }, - - path: () => { - throw new Error('Function not implemented.'); - }, - - pathSeparator: '/', - - zones: { empty: noopZone, current: () => noopZone }, }; diff --git a/packages/playwright-core/src/client/selectors.ts b/packages/playwright-core/src/client/selectors.ts index 3eca51bf36..d93723ab9a 100644 --- a/packages/playwright-core/src/client/selectors.ts +++ b/packages/playwright-core/src/client/selectors.ts @@ -17,12 +17,12 @@ import { ChannelOwner } from './channelOwner'; import { evaluationScript } from './clientHelper'; import { setTestIdAttribute, testIdAttributeName } from './locator'; -import { emptyPlatform } from '../common/platform'; +import { emptyPlatform } from './platform'; import type { SelectorEngine } from './types'; import type * as api from '../../types/types'; import type * as channels from '@protocol/channels'; -import type { Platform } from '../common/platform'; +import type { Platform } from './platform'; let platform = emptyPlatform; diff --git a/packages/playwright-core/src/client/stream.ts b/packages/playwright-core/src/client/stream.ts index 4f43b9afad..97c1e4b634 100644 --- a/packages/playwright-core/src/client/stream.ts +++ b/packages/playwright-core/src/client/stream.ts @@ -30,29 +30,6 @@ export class Stream extends ChannelOwner { } stream(): Readable { - return new StreamImpl(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); + return this._platform.streamReadable(this._channel); } } diff --git a/packages/playwright-core/src/client/timeoutSettings.ts b/packages/playwright-core/src/client/timeoutSettings.ts new file mode 100644 index 0000000000..90cb7e7558 --- /dev/null +++ b/packages/playwright-core/src/client/timeoutSettings.ts @@ -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; + } +} diff --git a/packages/playwright-core/src/client/types.ts b/packages/playwright-core/src/client/types.ts index 2cad775712..29dcd5112c 100644 --- a/packages/playwright-core/src/client/types.ts +++ b/packages/playwright-core/src/client/types.ts @@ -15,9 +15,9 @@ * limitations under the License. */ -import type { Size } from '../common/types'; +import type { Size } from '../utils/isomorphic/types'; 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'; export interface Logger { diff --git a/packages/playwright-core/src/client/waiter.ts b/packages/playwright-core/src/client/waiter.ts index 7d07d2128e..8c01193d7a 100644 --- a/packages/playwright-core/src/client/waiter.ts +++ b/packages/playwright-core/src/client/waiter.ts @@ -20,7 +20,7 @@ import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace'; import type { ChannelOwner } from './channelOwner'; import type * as channels from '@protocol/channels'; import type { EventEmitter } from 'events'; -import type { Zone } from '../common/platform'; +import type { Zone } from './platform'; export class Waiter { private _dispose: (() => void)[]; diff --git a/packages/playwright-core/src/client/webSocket.ts b/packages/playwright-core/src/client/webSocket.ts index cef07a4dfa..6fdd56aa4a 100644 --- a/packages/playwright-core/src/client/webSocket.ts +++ b/packages/playwright-core/src/client/webSocket.ts @@ -24,7 +24,7 @@ export async function connectOverWebSocket(parentConnection: Connection, params: const localUtils = parentConnection.localUtils(); const transport = localUtils ? new JsonPipeTransport(localUtils) : new WebSocketTransport(); const connectHeaders = await transport.connect(params); - const connection = new Connection(localUtils, parentConnection.platform, parentConnection._instrumentation, connectHeaders); + const connection = new Connection(parentConnection.platform, localUtils, parentConnection._instrumentation, connectHeaders); connection.markAsRemote(); connection.on('close', () => transport.close()); diff --git a/packages/playwright-core/src/client/writableStream.ts b/packages/playwright-core/src/client/writableStream.ts index 66cf17201d..38a2d0214a 100644 --- a/packages/playwright-core/src/client/writableStream.ts +++ b/packages/playwright-core/src/client/writableStream.ts @@ -14,11 +14,10 @@ * limitations under the License. */ -import { Writable } from 'stream'; - import { ChannelOwner } from './channelOwner'; import type * as channels from '@protocol/channels'; +import type { Writable } from 'stream'; export class WritableStream extends ChannelOwner { static from(Stream: channels.WritableStreamChannel): WritableStream { @@ -30,26 +29,6 @@ export class WritableStream extends ChannelOwner } stream(): Writable { - return new WritableStreamImpl(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); + return this._platform.streamWritable(this._channel); } } diff --git a/packages/playwright-core/src/inProcessFactory.ts b/packages/playwright-core/src/inProcessFactory.ts index a53aa8c2e3..5e5bad19e9 100644 --- a/packages/playwright-core/src/inProcessFactory.ts +++ b/packages/playwright-core/src/inProcessFactory.ts @@ -14,34 +14,21 @@ * limitations under the License. */ -import * as path from 'path'; -import { EventEmitter } from 'events'; - import { AndroidServerLauncherImpl } from './androidServerImpl'; import { BrowserServerLauncherImpl } from './browserServerImpl'; -import { Connection } from './client/connection'; +import { createConnectionFactory } from './client/clientBundle'; 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 { setPlatformForSelectors } from './client/selectors'; -import { setDefaultMaxListenersProvider } from './client/eventEmitter'; import type { Playwright as PlaywrightAPI } from './client/playwright'; 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' }); - setDebugMode(getFromENV('PWDEBUG') || ''); - setPlatformForSelectors(nodePlatform); - setDefaultMaxListenersProvider(() => EventEmitter.defaultMaxListeners); - setLibraryStackPrefix(path.join(__dirname, '..')); - - const clientConnection = new Connection(undefined, platform, undefined, []); + const clientConnection = connectionFactory(); clientConnection.useRawBuffers(); const dispatcherConnection = new DispatcherConnection(true /* local */); diff --git a/packages/playwright-core/src/inprocess.ts b/packages/playwright-core/src/inprocess.ts index fc0550e924..90b1bf499d 100644 --- a/packages/playwright-core/src/inprocess.ts +++ b/packages/playwright-core/src/inprocess.ts @@ -15,6 +15,5 @@ */ import { createInProcessPlaywright } from './inProcessFactory'; -import { nodePlatform } from './server/utils/nodePlatform'; -module.exports = createInProcessPlaywright(nodePlatform); +module.exports = createInProcessPlaywright(); diff --git a/packages/playwright-core/src/outofprocess.ts b/packages/playwright-core/src/outofprocess.ts index c2427b818d..6906b7ea1c 100644 --- a/packages/playwright-core/src/outofprocess.ts +++ b/packages/playwright-core/src/outofprocess.ts @@ -17,13 +17,15 @@ import * as childProcess from 'child_process'; import * as path from 'path'; -import { Connection } from './client/connection'; +import { createConnectionFactory } from './client/clientBundle'; import { PipeTransport } from './server/utils/pipeTransport'; import { ManualPromise } from './utils/isomorphic/manualPromise'; import { nodePlatform } from './server/utils/nodePlatform'; import type { Playwright } from './client/playwright'; +const connectionFactory = createConnectionFactory(nodePlatform); + export async function start(env: any = {}): Promise<{ playwright: Playwright, stop: () => Promise }> { const client = new PlaywrightClient(env); const playwright = await client._playwright; @@ -48,7 +50,7 @@ class PlaywrightClient { this._driverProcess.unref(); 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!); connection.onmessage = message => transport.send(JSON.stringify(message)); transport.onmessage = message => connection.dispatch(JSON.parse(message)); diff --git a/packages/playwright-core/src/protocol/DEPS.list b/packages/playwright-core/src/protocol/DEPS.list index dbdeafe86c..d4e5c2cb00 100644 --- a/packages/playwright-core/src/protocol/DEPS.list +++ b/packages/playwright-core/src/protocol/DEPS.list @@ -1,3 +1,2 @@ [*] -../common/ ../utils/isomorphic diff --git a/packages/playwright-core/src/protocol/validatorPrimitives.ts b/packages/playwright-core/src/protocol/validatorPrimitives.ts index f57a63acea..2c1a097da5 100644 --- a/packages/playwright-core/src/protocol/validatorPrimitives.ts +++ b/packages/playwright-core/src/protocol/validatorPrimitives.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { isUnderTest } from '../utils/isomorphic/debug'; +let isUnderTest = () => false; + +export function setIsUnderTestForValidator(getter: () => boolean) { + isUnderTest = getter; +} export class ValidationError extends Error {} export type Validator = (arg: any, path: string, context: ValidatorContext) => any; diff --git a/packages/playwright-core/src/remote/DEPS.list b/packages/playwright-core/src/remote/DEPS.list index bf3843dca0..f9c5148cac 100644 --- a/packages/playwright-core/src/remote/DEPS.list +++ b/packages/playwright-core/src/remote/DEPS.list @@ -1,6 +1,4 @@ [*] -../client/ -../common/ ../server/ ../server/android/ ../server/dispatchers/ diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index 00ca33b353..c5c3c36b02 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -20,7 +20,8 @@ import { AndroidDevice } from '../server/android/android'; import { Browser } from '../server/browser'; import { DebugControllerDispatcher } from '../server/dispatchers/debugControllerDispatcher'; 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 { monotonicTime } from '../utils'; import { debugLogger } from '../server/utils/debugLogger'; diff --git a/packages/playwright-core/src/server/DEPS.list b/packages/playwright-core/src/server/DEPS.list index 6b389baead..fa67d86bed 100644 --- a/packages/playwright-core/src/server/DEPS.list +++ b/packages/playwright-core/src/server/DEPS.list @@ -1,8 +1,7 @@ [*] -../common/ ../generated/ ../protocol/ -../utils/ +../utils ../utils/isomorphic/ ../utilsBundle.ts ../zipBundle.ts diff --git a/packages/playwright-core/src/server/android/DEPS.list b/packages/playwright-core/src/server/android/DEPS.list index b852561b95..1ecc153814 100644 --- a/packages/playwright-core/src/server/android/DEPS.list +++ b/packages/playwright-core/src/server/android/DEPS.list @@ -1,8 +1,6 @@ [*] ../ -../../common/ ../../protocol/ -../../utils/ ../../utils/isomorphic/ ../../utilsBundle.ts ../chromium/ diff --git a/packages/playwright-core/src/server/android/android.ts b/packages/playwright-core/src/server/android/android.ts index a4bf8f8ded..5215cc6d01 100644 --- a/packages/playwright-core/src/server/android/android.ts +++ b/packages/playwright-core/src/server/android/android.ts @@ -19,10 +19,10 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { TimeoutSettings } from '../../utils/isomorphic/timeoutSettings'; +import { TimeoutSettings } from '../timeoutSettings'; import { PipeTransport } from '../utils/pipeTransport'; import { createGuid } from '../utils/crypto'; -import { isUnderTest } from '../../utils/isomorphic/debug'; +import { isUnderTest } from '../utils/debug'; import { getPackageManagerExecCommand } from '../utils/env'; import { makeWaitForNextTask } from '../utils/task'; import { RecentLogsCollector } from '../utils/debugLogger'; diff --git a/packages/playwright-core/src/server/android/backendAdb.ts b/packages/playwright-core/src/server/android/backendAdb.ts index 036c74703e..4b1787a78b 100644 --- a/packages/playwright-core/src/server/android/backendAdb.ts +++ b/packages/playwright-core/src/server/android/backendAdb.ts @@ -17,7 +17,7 @@ import { EventEmitter } from 'events'; import * as net from 'net'; -import { assert } from '../../utils/isomorphic/debug'; +import { assert } from '../../utils/isomorphic/assert'; import { createGuid } from '../utils/crypto'; import { debug } from '../../utilsBundle'; diff --git a/packages/playwright-core/src/server/bidi/bidiPage.ts b/packages/playwright-core/src/server/bidi/bidiPage.ts index 153bb830de..db708f7ba3 100644 --- a/packages/playwright-core/src/server/bidi/bidiPage.ts +++ b/packages/playwright-core/src/server/bidi/bidiPage.ts @@ -519,11 +519,6 @@ export class BidiPage implements PageDelegate { return quads as types.Quad[]; } - async setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise { - await handle.evaluateInUtility(([injected, node, files]) => - injected.setInputFiles(node, files), files); - } - async setInputFilePaths(handle: dom.ElementHandle, paths: string[]): Promise { const fromContext = toBidiExecutionContext(handle._context); await this._session.send('input.setFiles', { diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index fd50994343..3fdb08577b 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -18,9 +18,9 @@ import * as fs from 'fs'; import * as path from 'path'; -import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; +import { TimeoutSettings } from './timeoutSettings'; import { createGuid } from './utils/crypto'; -import { debugMode } from '../utils/isomorphic/debug'; +import { debugMode } from './utils/debug'; import { Clock } from './clock'; import { Debugger } from './debugger'; import { BrowserContextAPIRequestContext } from './fetch'; diff --git a/packages/playwright-core/src/server/browserType.ts b/packages/playwright-core/src/server/browserType.ts index 7fe3844c7d..0ecfccc510 100644 --- a/packages/playwright-core/src/server/browserType.ts +++ b/packages/playwright-core/src/server/browserType.ts @@ -19,8 +19,10 @@ import * as os from 'os'; import * as path from 'path'; import { normalizeProxySettings, validateBrowserContextOptions } from './browserContext'; -import { DEFAULT_TIMEOUT, TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; -import { ManualPromise, assert, debugMode } from '../utils'; +import { DEFAULT_TIMEOUT, TimeoutSettings } from './timeoutSettings'; +import { debugMode } from './utils/debug'; +import { assert } from '../utils/isomorphic/assert'; +import { ManualPromise } from '../utils/isomorphic/manualPromise'; import { existsAsync } from './utils/fileUtils'; import { helper } from './helper'; import { SdkObject } from './instrumentation'; diff --git a/packages/playwright-core/src/utils/isomorphic/sequence.ts b/packages/playwright-core/src/server/callLog.ts similarity index 66% rename from packages/playwright-core/src/utils/isomorphic/sequence.ts rename to packages/playwright-core/src/server/callLog.ts index b063e5c488..4de5a907d6 100644 --- a/packages/playwright-core/src/utils/isomorphic/sequence.ts +++ b/packages/playwright-core/src/server/callLog.ts @@ -1,7 +1,7 @@ /** * 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 obtain a copy of the License at * @@ -14,7 +14,27 @@ * 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 result = []; let i = 0; @@ -64,3 +84,5 @@ export function findRepeatedSubsequences(s: string[]): { sequence: string[]; cou return result; } + +export const findRepeatedSubsequencesForTest = findRepeatedSubsequences; diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index 3f06e135cd..9cbb84b2ac 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -22,7 +22,7 @@ import * as path from 'path'; import { chromiumSwitches } from './chromiumSwitches'; import { CRBrowser } from './crBrowser'; import { kBrowserCloseMessageId } from './crConnection'; -import { TimeoutSettings } from '../../utils/isomorphic/timeoutSettings'; +import { TimeoutSettings } from '../timeoutSettings'; import { debugMode, headersArrayToObject, headersObjectToArray, } from '../../utils'; import { wrapInASCIIBox } from '../utils/ascii'; import { RecentLogsCollector } from '../utils/debugLogger'; diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index 7cc6030486..fe76876a92 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -17,7 +17,7 @@ import * as path from 'path'; -import { assert } from '../../utils/isomorphic/debug'; +import { assert } from '../../utils/isomorphic/assert'; import { createGuid } from '../utils/crypto'; import { Artifact } from '../artifact'; import { Browser } from '../browser'; diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 34470470dc..781f24e932 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -17,7 +17,7 @@ import * as path from 'path'; -import { assert } from '../../utils/isomorphic/debug'; +import { assert } from '../../utils/isomorphic/assert'; import { createGuid } from '../utils/crypto'; import { eventsHelper } from '../utils/eventsHelper'; import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace'; @@ -314,11 +314,6 @@ export class CRPage implements PageDelegate { return this._sessionForHandle(handle)._getContentQuads(handle); } - async setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise { - await handle.evaluateInUtility(([injected, node, files]) => - injected.setInputFiles(node, files), files); - } - async setInputFilePaths(handle: dom.ElementHandle, files: string[]): Promise { const frame = await handle.ownerFrame(); if (!frame) diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index 1e6d0be7b8..cbbf0608ec 100644 --- a/packages/playwright-core/src/server/deviceDescriptorsSource.json +++ b/packages/playwright-core/src/server/deviceDescriptorsSource.json @@ -110,7 +110,7 @@ "defaultBrowserType": "webkit" }, "Galaxy S5": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 360, "height": 640 @@ -121,7 +121,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 640, "height": 360 @@ -132,7 +132,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 360, "height": 740 @@ -143,7 +143,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 740, "height": 360 @@ -154,7 +154,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 320, "height": 658 @@ -165,7 +165,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+ landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 658, "height": 320 @@ -176,7 +176,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 712, "height": 1138 @@ -187,7 +187,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 1138, "height": 712 @@ -1098,7 +1098,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.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": { "width": 384, "height": 640 @@ -1109,7 +1109,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.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": { "width": 640, "height": 384 @@ -1120,7 +1120,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 360, "height": 640 @@ -1131,7 +1131,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 640, "height": 360 @@ -1142,7 +1142,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 360, "height": 640 @@ -1153,7 +1153,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 640, "height": 360 @@ -1164,7 +1164,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 800, "height": 1280 @@ -1175,7 +1175,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 1280, "height": 800 @@ -1186,7 +1186,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 384, "height": 640 @@ -1197,7 +1197,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 640, "height": 384 @@ -1208,7 +1208,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 360, "height": 640 @@ -1219,7 +1219,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 640, "height": 360 @@ -1230,7 +1230,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 412, "height": 732 @@ -1241,7 +1241,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 732, "height": 412 @@ -1252,7 +1252,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 412, "height": 732 @@ -1263,7 +1263,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 732, "height": 412 @@ -1274,7 +1274,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 412, "height": 732 @@ -1285,7 +1285,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 732, "height": 412 @@ -1296,7 +1296,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 600, "height": 960 @@ -1307,7 +1307,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 960, "height": 600 @@ -1362,7 +1362,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 411, "height": 731 @@ -1373,7 +1373,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 731, "height": 411 @@ -1384,7 +1384,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 411, "height": 823 @@ -1395,7 +1395,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 823, "height": 411 @@ -1406,7 +1406,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 393, "height": 786 @@ -1417,7 +1417,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 786, "height": 393 @@ -1428,7 +1428,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 353, "height": 745 @@ -1439,7 +1439,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 745, "height": 353 @@ -1450,7 +1450,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 412, "height": 892 @@ -1465,7 +1465,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "height": 892, "width": 412 @@ -1480,7 +1480,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 393, "height": 851 @@ -1495,7 +1495,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 851, "height": 393 @@ -1510,7 +1510,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 412, "height": 915 @@ -1525,7 +1525,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 915, "height": 412 @@ -1540,7 +1540,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 360, "height": 640 @@ -1551,7 +1551,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 640, "height": 360 @@ -1562,7 +1562,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 1792, "height": 1120 @@ -1577,7 +1577,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 1792, "height": 1120 @@ -1622,7 +1622,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 1920, "height": 1080 @@ -1637,7 +1637,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.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": { "width": 1920, "height": 1080 diff --git a/packages/playwright-core/src/server/dispatchers/DEPS.list b/packages/playwright-core/src/server/dispatchers/DEPS.list index cefc3fa04c..b91f84e960 100644 --- a/packages/playwright-core/src/server/dispatchers/DEPS.list +++ b/packages/playwright-core/src/server/dispatchers/DEPS.list @@ -1,5 +1,4 @@ [*] -../../common/ ../../generated/ ../../protocol/ ../../utils/ diff --git a/packages/playwright-core/src/server/dispatchers/dispatcher.ts b/packages/playwright-core/src/server/dispatchers/dispatcher.ts index 9912de9849..c6eb6f7868 100644 --- a/packages/playwright-core/src/server/dispatchers/dispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dispatcher.ts @@ -18,10 +18,12 @@ import { EventEmitter } from 'events'; import { eventsHelper } from '../utils/eventsHelper'; 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 { SdkObject } from '../instrumentation'; import { isProtocolError } from '../protocolError'; +import { compressCallLog } from '../callLog'; import type { CallMetadata } from '../instrumentation'; import type { PlaywrightDispatcher } from './playwrightDispatcher'; diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index 7e99511d33..ef8b891d2f 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -20,7 +20,6 @@ import { ElementHandleDispatcher } from './elementHandlerDispatcher'; import { parseArgument, serializeResult } from './jsHandleDispatcher'; import { ResponseDispatcher } from './networkDispatchers'; import { RequestDispatcher } from './networkDispatchers'; -import { debugAssert } from '../../utils'; import { parseAriaSnapshotUnsafe } from '../../utils/isomorphic/ariaSnapshot'; import { yaml } from '../../utilsBundle'; @@ -50,7 +49,6 @@ export class FrameDispatcher extends Dispatcher(frame._page); super(pageDispatcher || scope, frame, 'Frame', { diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index 7bb1862645..f694336f59 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -30,7 +30,7 @@ import type { Page } from './page'; import type { Progress } from './progress'; import type { ScreenshotOptions } from './screenshotter'; 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'; @@ -705,7 +705,8 @@ export class ElementHandle extends js.JSHandle { await this._page._delegate.setInputFilePaths(retargeted, localPathsOrDirectory); await waitForInputEvent; } else { - await this._page._delegate.setInputFiles(retargeted, filePayloads!); + await retargeted.evaluateInUtility(([injected, node, files]) => + injected.setInputFiles(node, files), filePayloads!); } return 'done'; } diff --git a/packages/playwright-core/src/server/electron/DEPS.list b/packages/playwright-core/src/server/electron/DEPS.list index 1acb5c5a0e..193adeb6d6 100644 --- a/packages/playwright-core/src/server/electron/DEPS.list +++ b/packages/playwright-core/src/server/electron/DEPS.list @@ -1,6 +1,5 @@ [*] ../ -../../common/ ../../utils/ ../../utils/isomorphic/ ../chromium/ diff --git a/packages/playwright-core/src/server/electron/electron.ts b/packages/playwright-core/src/server/electron/electron.ts index 4713324ed1..d6fe4b01dd 100644 --- a/packages/playwright-core/src/server/electron/electron.ts +++ b/packages/playwright-core/src/server/electron/electron.ts @@ -19,7 +19,7 @@ import * as os from 'os'; import * as path from 'path'; import * as readline from 'readline'; -import { TimeoutSettings } from '../../utils/isomorphic/timeoutSettings'; +import { TimeoutSettings } from '../timeoutSettings'; import { ManualPromise } from '../../utils'; import { wrapInASCIIBox } from '../utils/ascii'; import { RecentLogsCollector } from '../utils/debugLogger'; diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 6e1d6d02f1..23f0c87a51 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -21,7 +21,7 @@ import { TLSSocket } from 'tls'; import * as url from 'url'; import * as zlib from 'zlib'; -import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; +import { TimeoutSettings } from './timeoutSettings'; import { assert, constructURLBasedOnBaseURL, eventsHelper, monotonicTime } from '../utils'; import { createGuid } from './utils/crypto'; import { getUserAgent } from './utils/userAgent'; diff --git a/packages/playwright-core/src/server/fileUploadUtils.ts b/packages/playwright-core/src/server/fileUploadUtils.ts index 5a15617c8b..d2e18572b9 100644 --- a/packages/playwright-core/src/server/fileUploadUtils.ts +++ b/packages/playwright-core/src/server/fileUploadUtils.ts @@ -17,7 +17,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { assert } from '../utils/isomorphic/debug'; +import { assert } from '../utils/isomorphic/assert'; import { mime } from '../utilsBundle'; import type { WritableStreamDispatcher } from './dispatchers/writableStreamDispatcher'; diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index 22229dff3e..75cfffd24e 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -519,11 +519,6 @@ export class FFPage implements PageDelegate { return result.quads.map(quad => [quad.p1, quad.p2, quad.p3, quad.p4]); } - async setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise { - await handle.evaluateInUtility(([injected, node, files]) => - injected.setInputFiles(node, files), files); - } - async setInputFilePaths(handle: dom.ElementHandle, files: string[]): Promise { await this._session.send('Page.setFileInputFiles', { frameId: handle._context.frame._id, diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index dee2ad7f08..69ef1ecba5 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -27,12 +27,13 @@ import * as network from './network'; import { Page } from './page'; import { ProgressController } from './progress'; 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 { debugLogger } from './utils/debugLogger'; import { eventsHelper } from './utils/eventsHelper'; import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser'; import { ManualPromise } from '../utils/isomorphic/manualPromise'; +import { compressCallLog } from './callLog'; import type { ConsoleMessage } from './console'; import type { Dialog } from './dialog'; diff --git a/packages/playwright-core/src/server/harBackend.ts b/packages/playwright-core/src/server/harBackend.ts index 0814be9c5f..4a59b9ab60 100644 --- a/packages/playwright-core/src/server/harBackend.ts +++ b/packages/playwright-core/src/server/harBackend.ts @@ -20,7 +20,7 @@ import * as path from 'path'; import { createGuid } from './utils/crypto'; import { ZipFile } from './utils/zipFile'; -import type { HeadersArray } from '../common/types'; +import type { HeadersArray } from '../utils/isomorphic/types'; import type * as har from '@trace/har'; const redirectStatus = [301, 302, 303, 307, 308]; diff --git a/packages/playwright-core/src/server/injected/recorder/recorder.ts b/packages/playwright-core/src/server/injected/recorder/recorder.ts index 27e4f35a10..68fa96e77f 100644 --- a/packages/playwright-core/src/server/injected/recorder/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder/recorder.ts @@ -16,7 +16,7 @@ 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 { InjectedScript } from '../injectedScript'; import type { ElementText } from '../selectorUtils'; diff --git a/packages/playwright-core/src/server/localUtils.ts b/packages/playwright-core/src/server/localUtils.ts index 1f5f0064b5..7751d20fdb 100644 --- a/packages/playwright-core/src/server/localUtils.ts +++ b/packages/playwright-core/src/server/localUtils.ts @@ -24,7 +24,7 @@ import { ManualPromise } from '../utils/isomorphic/manualPromise'; import { ZipFile } from './utils/zipFile'; import { yauzl, yazl } from '../zipBundle'; 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 * as channels from '@protocol/channels'; diff --git a/packages/playwright-core/src/server/network.ts b/packages/playwright-core/src/server/network.ts index dfee496fc2..e40e0e99a9 100644 --- a/packages/playwright-core/src/server/network.ts +++ b/packages/playwright-core/src/server/network.ts @@ -25,7 +25,7 @@ import type * as frames from './frames'; import type * as pages from './page'; import type * as types 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'; diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index d2db2e4211..f78222c67d 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -28,14 +28,15 @@ import { parseEvaluationResultValue, source } from './isomorphic/utilityScriptSe import * as js from './javascript'; import { ProgressController } from './progress'; import { Screenshotter, validateScreenshotOptions } from './screenshotter'; -import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; -import { LongStandingScope, assert, compressCallLog, trimStringWithEllipsis } from '../utils'; +import { TimeoutSettings } from './timeoutSettings'; +import { LongStandingScope, assert, trimStringWithEllipsis } from '../utils'; import { createGuid } from './utils/crypto'; import { asLocator } from '../utils'; import { getComparator } from './utils/comparators'; import { debugLogger } from './utils/debugLogger'; import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser'; import { ManualPromise } from '../utils/isomorphic/manualPromise'; +import { compressCallLog } from './callLog'; import type { Artifact } from './artifact'; import type * as dom from './dom'; @@ -45,7 +46,7 @@ import type * as network from './network'; import type { Progress } from './progress'; import type { ScreenshotOptions } from './screenshotter'; 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 * as channels from '@protocol/channels'; @@ -79,7 +80,6 @@ export interface PageDelegate { getContentFrame(handle: dom.ElementHandle): Promise; // Only called for frame owner elements. getOwnerFrame(handle: dom.ElementHandle): Promise; // Returns frameId. getContentQuads(handle: dom.ElementHandle): Promise; - setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise; setInputFilePaths(handle: dom.ElementHandle, files: string[]): Promise; getBoundingBox(handle: dom.ElementHandle): Promise; getFrameElement(frame: frames.Frame): Promise; diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 79165df8f2..f8fc140219 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -30,7 +30,7 @@ import type { Frame } from './frames'; import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation'; import type { Page } from './page'; 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 * as channels from '@protocol/channels'; import type * as actions from '@recorder/actions'; diff --git a/packages/playwright-core/src/server/recorder/DEPS.list b/packages/playwright-core/src/server/recorder/DEPS.list index b130c181dc..6151537ccb 100644 --- a/packages/playwright-core/src/server/recorder/DEPS.list +++ b/packages/playwright-core/src/server/recorder/DEPS.list @@ -4,7 +4,7 @@ ../codegen/languages.ts ../isomorphic/** ../registry/** -../../common/ +../utils/** ../../generated/pollingRecorderSource.ts ../../protocol/ ../../utils/** diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 79c7d2d4d0..5233edaedd 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -18,7 +18,7 @@ import { EventEmitter } from 'events'; import * as fs from 'fs'; import * as path from 'path'; -import { isUnderTest } from '../../utils'; +import { isUnderTest } from '../utils/debug'; import { mime } from '../../utilsBundle'; import { serverSideCallMetadata } from '../instrumentation'; import { syncLocalStorageWithSettings } from '../launchApp'; diff --git a/packages/playwright-core/src/server/recorder/recorderCollection.ts b/packages/playwright-core/src/server/recorder/recorderCollection.ts index 8c7b344b8e..b8802a920a 100644 --- a/packages/playwright-core/src/server/recorder/recorderCollection.ts +++ b/packages/playwright-core/src/server/recorder/recorderCollection.ts @@ -18,7 +18,7 @@ import { EventEmitter } from 'events'; import { performAction } from './recorderRunner'; import { collapseActions } from './recorderUtils'; -import { isUnderTest } from '../../utils/isomorphic/debug'; +import { isUnderTest } from '../utils/debug'; import { monotonicTime } from '../../utils/isomorphic/time'; import type { Signal } from '../../../../recorder/src/actions'; diff --git a/packages/playwright-core/src/server/screenshotter.ts b/packages/playwright-core/src/server/screenshotter.ts index b32f51a6de..77c83b8325 100644 --- a/packages/playwright-core/src/server/screenshotter.ts +++ b/packages/playwright-core/src/server/screenshotter.ts @@ -24,7 +24,7 @@ import type { Frame } from './frames'; import type { Page } from './page'; import type { Progress } from './progress'; 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'; diff --git a/packages/playwright-core/src/utils/isomorphic/timeoutSettings.ts b/packages/playwright-core/src/server/timeoutSettings.ts similarity index 97% rename from packages/playwright-core/src/utils/isomorphic/timeoutSettings.ts rename to packages/playwright-core/src/server/timeoutSettings.ts index 02c02a33b5..1c71437e2a 100644 --- a/packages/playwright-core/src/utils/isomorphic/timeoutSettings.ts +++ b/packages/playwright-core/src/server/timeoutSettings.ts @@ -15,8 +15,9 @@ * 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_LAUNCH_TIMEOUT = 3 * 60 * 1000; // 3 minutes diff --git a/packages/playwright-core/src/server/trace/recorder/DEPS.list b/packages/playwright-core/src/server/trace/recorder/DEPS.list index bf35323ecc..195bdb5eb8 100644 --- a/packages/playwright-core/src/server/trace/recorder/DEPS.list +++ b/packages/playwright-core/src/server/trace/recorder/DEPS.list @@ -1,9 +1,7 @@ [*] ../../ ../../har/ -../../../common/ ../../../protocol/ -../../../utils/ ../../../utilsBundle.ts ../../../utils/isomorphic/ ../../../zipBundle.ts diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index 309867f3b0..41d2680757 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -20,7 +20,7 @@ import * as path from 'path'; import { Snapshotter } from './snapshotter'; import { commandsWithTracingSnapshots } from '../../../protocol/debug'; -import { assert } from '../../../utils/isomorphic/debug'; +import { assert } from '../../../utils/isomorphic/assert'; import { monotonicTime } from '../../../utils/isomorphic/time'; import { eventsHelper } from '../../utils/eventsHelper'; import { createGuid } from '../../utils/crypto'; @@ -34,7 +34,7 @@ import { SdkObject } from '../../instrumentation'; import { Page } from '../../page'; 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 { ConsoleMessage } from '../../console'; import type { Dialog } from '../../dialog'; diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index e0f05bac41..3f322e7125 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -142,6 +142,16 @@ export async function installRootRedirect(server: HttpServer, traceUrls: string[ server.routePath('/', (_, response) => { response.statusCode = 302; 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(); return true; }); diff --git a/packages/playwright-core/src/server/types.ts b/packages/playwright-core/src/server/types.ts index e7bf2c11f3..a8681fd6cf 100644 --- a/packages/playwright-core/src/server/types.ts +++ b/packages/playwright-core/src/server/types.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import type { HeadersArray, Point, Size, TimeoutOptions } from '../common/types'; -export type { HeadersArray, Point, Quad, Rect, Size, TimeoutOptions } from '../common/types'; +import type { HeadersArray, Point, Size, TimeoutOptions } from '../utils/isomorphic/types'; +export type { HeadersArray, Point, Quad, Rect, Size, TimeoutOptions } from '../utils/isomorphic/types'; import type * as channels from '@protocol/channels'; export type StrictOptions = { diff --git a/packages/playwright-core/src/server/utils/DEPS.list b/packages/playwright-core/src/server/utils/DEPS.list index 685e00df46..8d1b7a30f2 100644 --- a/packages/playwright-core/src/server/utils/DEPS.list +++ b/packages/playwright-core/src/server/utils/DEPS.list @@ -1,5 +1,4 @@ [*] -../../common ../../utils ../../utils/isomorphic ../../utilsBundle.ts diff --git a/packages/playwright-core/src/server/utils/crypto.ts b/packages/playwright-core/src/server/utils/crypto.ts index 7189f38b00..c3ca75efd7 100644 --- a/packages/playwright-core/src/server/utils/crypto.ts +++ b/packages/playwright-core/src/server/utils/crypto.ts @@ -16,7 +16,7 @@ import * as crypto from 'crypto'; -import { assert } from '../../utils/isomorphic/debug'; +import { assert } from '../../utils/isomorphic/assert'; export function createGuid(): string { return crypto.randomBytes(16).toString('hex'); diff --git a/packages/playwright-core/src/utils/isomorphic/debug.ts b/packages/playwright-core/src/server/utils/debug.ts similarity index 71% rename from packages/playwright-core/src/utils/isomorphic/debug.ts rename to packages/playwright-core/src/server/utils/debug.ts index 1eec988aa9..489c772b70 100644 --- a/packages/playwright-core/src/utils/isomorphic/debug.ts +++ b/packages/playwright-core/src/server/utils/debug.ts @@ -14,21 +14,9 @@ * limitations under the License. */ -export function assert(value: any, message?: string): asserts value { - if (!value) - throw new Error(message || 'Assertion error'); -} +import { getFromENV } from './env'; -export function debugAssert(value: any, message?: string): asserts value { - if (isUnderTest() && !value) - throw new Error(message); -} - -let _debugMode: string | undefined; - -export function setDebugMode(mode: string) { - _debugMode = mode; -} +const _debugMode = getFromENV('PWDEBUG') || ''; export function debugMode() { if (_debugMode === 'console') diff --git a/packages/playwright-core/src/server/utils/happyEyeballs.ts b/packages/playwright-core/src/server/utils/happyEyeballs.ts index da3481f4c1..c8c0f4182c 100644 --- a/packages/playwright-core/src/server/utils/happyEyeballs.ts +++ b/packages/playwright-core/src/server/utils/happyEyeballs.ts @@ -20,7 +20,7 @@ import * as https from 'https'; import * as net from 'net'; import * as tls from 'tls'; -import { assert } from '../../utils/isomorphic/debug'; +import { assert } from '../../utils/isomorphic/assert'; import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { monotonicTime } from '../../utils/isomorphic/time'; diff --git a/packages/playwright-core/src/server/utils/httpServer.ts b/packages/playwright-core/src/server/utils/httpServer.ts index 0c9f0fe25a..7c8a67e10c 100644 --- a/packages/playwright-core/src/server/utils/httpServer.ts +++ b/packages/playwright-core/src/server/utils/httpServer.ts @@ -19,7 +19,7 @@ import * as path from 'path'; import { mime, wsServer } from '../../utilsBundle'; import { createGuid } from './crypto'; -import { assert } from '../../utils/isomorphic/debug'; +import { assert } from '../../utils/isomorphic/assert'; import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { createHttpServer } from './network'; diff --git a/packages/playwright-core/src/server/utils/nodePlatform.ts b/packages/playwright-core/src/server/utils/nodePlatform.ts index e9883da361..4a36322036 100644 --- a/packages/playwright-core/src/server/utils/nodePlatform.ts +++ b/packages/playwright-core/src/server/utils/nodePlatform.ts @@ -18,13 +18,19 @@ import * as crypto from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; import * as util from 'util'; +import { Readable, Writable, pipeline } from 'stream'; +import { EventEmitter } from 'events'; import { colors } from '../../utilsBundle'; import { debugLogger } from './debugLogger'; import { currentZone, emptyZone } from './zones'; +import { debugMode, isUnderTest } from './debug'; -import type { Platform, Zone } from '../../common/platform'; +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; @@ -50,9 +56,16 @@ class NodeZone implements Zone { } } +let boxedStackPrefixes: string[] = []; +export function setBoxedStackPrefixes(prefixes: string[]) { + boxedStackPrefixes = prefixes; +} + export const nodePlatform: Platform = { name: 'node', + boxedStackPrefixes: () => boxedStackPrefixes, + calculateSha1: (text: string) => { const sha1 = crypto.createHash('sha1'); sha1.update(text); @@ -61,18 +74,25 @@ export const nodePlatform: Platform = { colors, + coreDir: path.dirname(require.resolve('../../../package.json')), + createGuid: () => crypto.randomBytes(16).toString('hex'), + defaultMaxListeners: () => EventEmitter.defaultMaxListeners, fs: () => fs, inspectCustom: util.inspect.custom, - isDebuggerAttached: () => !!require('inspector').url(), + isDebugMode: () => !!debugMode(), + + isJSDebuggerAttached: () => !!require('inspector').url(), isLogEnabled(name: 'api' | 'channel') { return debugLogger.isEnabled(name); }, + isUnderTest: () => isUnderTest(), + log(name: 'api' | 'channel', message: string | Error | object) { debugLogger.log(name, message); }, @@ -81,8 +101,63 @@ export const nodePlatform: Platform = { pathSeparator: path.sep, + async streamFile(path: string, stream: Writable): Promise { + 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); + } +} diff --git a/packages/playwright-core/src/server/utils/socksProxy.ts b/packages/playwright-core/src/server/utils/socksProxy.ts index c0fe0f126a..bd36ed58f5 100644 --- a/packages/playwright-core/src/server/utils/socksProxy.ts +++ b/packages/playwright-core/src/server/utils/socksProxy.ts @@ -17,7 +17,7 @@ import EventEmitter from 'events'; import * as net from 'net'; -import { assert } from '../../utils/isomorphic/debug'; +import { assert } from '../../utils/isomorphic/assert'; import { createGuid } from './crypto'; import { debugLogger } from './debugLogger'; import { createSocket } from './happyEyeballs'; diff --git a/packages/playwright-core/src/server/webkit/protocol.d.ts b/packages/playwright-core/src/server/webkit/protocol.d.ts index ea34222382..c915b74e11 100644 --- a/packages/playwright-core/src/server/webkit/protocol.d.ts +++ b/packages/playwright-core/src/server/webkit/protocol.d.ts @@ -193,142 +193,6 @@ export module Protocol { } } - export module ApplicationCache { - /** - * Detailed application cache resource information. - */ - export interface ApplicationCacheResource { - /** - * Resource url. - */ - url: string; - /** - * Resource size. - */ - size: number; - /** - * Resource type. - */ - type: string; - } - /** - * Detailed application cache information. - */ - export interface ApplicationCache { - /** - * Manifest URL. - */ - manifestURL: string; - /** - * Application cache size. - */ - size: number; - /** - * Application cache creation time. - */ - creationTime: number; - /** - * Application cache update time. - */ - updateTime: number; - /** - * Application cache resources. - */ - resources: ApplicationCacheResource[]; - } - /** - * Frame identifier - manifest URL pair. - */ - export interface FrameWithManifest { - /** - * Frame identifier. - */ - frameId: Network.FrameId; - /** - * Manifest URL. - */ - manifestURL: string; - /** - * Application cache status. - */ - status: number; - } - - export type applicationCacheStatusUpdatedPayload = { - /** - * Identifier of the frame containing document whose application cache updated status. - */ - frameId: Network.FrameId; - /** - * Manifest URL. - */ - manifestURL: string; - /** - * Updated application cache status. - */ - status: number; - } - export type networkStateUpdatedPayload = { - isNowOnline: boolean; - } - - /** - * Returns array of frame identifiers with manifest urls for each frame containing a document associated with some application cache. - */ - export type getFramesWithManifestsParameters = { - } - export type getFramesWithManifestsReturnValue = { - /** - * Array of frame identifiers with manifest urls for each frame containing a document associated with some application cache. - */ - frameIds: FrameWithManifest[]; - } - /** - * Enables application cache domain notifications. - */ - export type enableParameters = { - } - export type enableReturnValue = { - } - /** - * Disable application cache domain notifications. - */ - export type disableParameters = { - } - export type disableReturnValue = { - } - /** - * Returns manifest URL for document in the given frame. - */ - export type getManifestForFrameParameters = { - /** - * Identifier of the frame containing document whose manifest is retrieved. - */ - frameId: Network.FrameId; - } - export type getManifestForFrameReturnValue = { - /** - * Manifest URL for document in the given frame. - */ - manifestURL: string; - } - /** - * Returns relevant application cache data for the document in given frame. - */ - export type getApplicationCacheForFrameParameters = { - /** - * Identifier of the frame containing document whose application cache is retrieved. - */ - frameId: Network.FrameId; - } - export type getApplicationCacheForFrameReturnValue = { - /** - * Relevant application cache data for the document in given frame. - */ - applicationCache: ApplicationCache; - } - } - export module Audit { @@ -3644,81 +3508,6 @@ might return multiple quads for inline nodes. } } - export module Database { - /** - * Unique identifier of Database object. - */ - export type DatabaseId = string; - /** - * Database object. - */ - export interface Database { - /** - * Database ID. - */ - id: DatabaseId; - /** - * Database domain. - */ - domain: string; - /** - * Database name. - */ - name: string; - /** - * Database version. - */ - version: string; - } - /** - * Database error. - */ - export interface Error { - /** - * Error message. - */ - message: string; - /** - * Error code. - */ - code: number; - } - - export type addDatabasePayload = { - database: Database; - } - - /** - * Enables database tracking, database events will now be delivered to the client. - */ - export type enableParameters = { - } - export type enableReturnValue = { - } - /** - * Disables database tracking, prevents database events from being sent to the client. - */ - export type disableParameters = { - } - export type disableReturnValue = { - } - export type getDatabaseTableNamesParameters = { - databaseId: DatabaseId; - } - export type getDatabaseTableNamesReturnValue = { - tableNames: string[]; - } - export type executeSQLParameters = { - databaseId: DatabaseId; - query: string; - } - export type executeSQLReturnValue = { - columnNames?: string[]; - values?: any[]; - sqlError?: Error; - } - } - /** * Debugger domain exposes JavaScript debugging capabilities. It allows setting and removing breakpoints, stepping through execution, exploring stack traces, etc. */ @@ -9285,8 +9074,6 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the "Animation.trackingStart": Animation.trackingStartPayload; "Animation.trackingUpdate": Animation.trackingUpdatePayload; "Animation.trackingComplete": Animation.trackingCompletePayload; - "ApplicationCache.applicationCacheStatusUpdated": ApplicationCache.applicationCacheStatusUpdatedPayload; - "ApplicationCache.networkStateUpdated": ApplicationCache.networkStateUpdatedPayload; "Browser.extensionsEnabled": Browser.extensionsEnabledPayload; "Browser.extensionsDisabled": Browser.extensionsDisabledPayload; "CPUProfiler.trackingStart": CPUProfiler.trackingStartPayload; @@ -9336,7 +9123,6 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the "DOMStorage.domStorageItemRemoved": DOMStorage.domStorageItemRemovedPayload; "DOMStorage.domStorageItemAdded": DOMStorage.domStorageItemAddedPayload; "DOMStorage.domStorageItemUpdated": DOMStorage.domStorageItemUpdatedPayload; - "Database.addDatabase": Database.addDatabasePayload; "Debugger.globalObjectCleared": Debugger.globalObjectClearedPayload; "Debugger.scriptParsed": Debugger.scriptParsedPayload; "Debugger.scriptFailedToParse": Debugger.scriptFailedToParsePayload; @@ -9418,11 +9204,6 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the "Animation.resolveAnimation": Animation.resolveAnimationParameters; "Animation.startTracking": Animation.startTrackingParameters; "Animation.stopTracking": Animation.stopTrackingParameters; - "ApplicationCache.getFramesWithManifests": ApplicationCache.getFramesWithManifestsParameters; - "ApplicationCache.enable": ApplicationCache.enableParameters; - "ApplicationCache.disable": ApplicationCache.disableParameters; - "ApplicationCache.getManifestForFrame": ApplicationCache.getManifestForFrameParameters; - "ApplicationCache.getApplicationCacheForFrame": ApplicationCache.getApplicationCacheForFrameParameters; "Audit.setup": Audit.setupParameters; "Audit.run": Audit.runParameters; "Audit.teardown": Audit.teardownParameters; @@ -9532,10 +9313,6 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the "DOMStorage.setDOMStorageItem": DOMStorage.setDOMStorageItemParameters; "DOMStorage.removeDOMStorageItem": DOMStorage.removeDOMStorageItemParameters; "DOMStorage.clearDOMStorageItems": DOMStorage.clearDOMStorageItemsParameters; - "Database.enable": Database.enableParameters; - "Database.disable": Database.disableParameters; - "Database.getDatabaseTableNames": Database.getDatabaseTableNamesParameters; - "Database.executeSQL": Database.executeSQLParameters; "Debugger.enable": Debugger.enableParameters; "Debugger.disable": Debugger.disableParameters; "Debugger.setAsyncStackTraceDepth": Debugger.setAsyncStackTraceDepthParameters; @@ -9731,11 +9508,6 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the "Animation.resolveAnimation": Animation.resolveAnimationReturnValue; "Animation.startTracking": Animation.startTrackingReturnValue; "Animation.stopTracking": Animation.stopTrackingReturnValue; - "ApplicationCache.getFramesWithManifests": ApplicationCache.getFramesWithManifestsReturnValue; - "ApplicationCache.enable": ApplicationCache.enableReturnValue; - "ApplicationCache.disable": ApplicationCache.disableReturnValue; - "ApplicationCache.getManifestForFrame": ApplicationCache.getManifestForFrameReturnValue; - "ApplicationCache.getApplicationCacheForFrame": ApplicationCache.getApplicationCacheForFrameReturnValue; "Audit.setup": Audit.setupReturnValue; "Audit.run": Audit.runReturnValue; "Audit.teardown": Audit.teardownReturnValue; @@ -9845,10 +9617,6 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the "DOMStorage.setDOMStorageItem": DOMStorage.setDOMStorageItemReturnValue; "DOMStorage.removeDOMStorageItem": DOMStorage.removeDOMStorageItemReturnValue; "DOMStorage.clearDOMStorageItems": DOMStorage.clearDOMStorageItemsReturnValue; - "Database.enable": Database.enableReturnValue; - "Database.disable": Database.disableReturnValue; - "Database.getDatabaseTableNames": Database.getDatabaseTableNamesReturnValue; - "Database.executeSQL": Database.executeSQLReturnValue; "Debugger.enable": Debugger.enableReturnValue; "Debugger.disable": Debugger.disableReturnValue; "Debugger.setAsyncStackTraceDepth": Debugger.setAsyncStackTraceDepthReturnValue; diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index f9e9517fb2..5846b800d8 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -17,7 +17,7 @@ import * as path from 'path'; -import { assert, debugAssert } from '../../utils'; +import { assert } from '../../utils'; import { headersArrayToObject } from '../../utils/isomorphic/headers'; import { createGuid } from '../utils/crypto'; import { eventsHelper } from '../utils/eventsHelper'; @@ -294,7 +294,6 @@ export class WKPage implements PageDelegate { } handleWindowOpen(event: Protocol.Playwright.windowOpenPayload) { - debugAssert(!this._nextWindowOpenPopupFeatures); this._nextWindowOpenPopupFeatures = event.windowFeatures; } @@ -947,16 +946,6 @@ export class WKPage implements PageDelegate { ]); } - async setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise { - const objectId = handle._objectId; - const protocolFiles = files.map(file => ({ - name: file.name, - type: file.mimeType, - data: file.buffer, - })); - await this._session.send('DOM.setInputFiles', { objectId, files: protocolFiles }); - } - async setInputFilePaths(handle: dom.ElementHandle, paths: string[]): Promise { const pageProxyId = this._pageProxySession.sessionId; const objectId = handle._objectId; diff --git a/packages/playwright-core/src/utils.ts b/packages/playwright-core/src/utils.ts index dc5f284955..4f2f04fc11 100644 --- a/packages/playwright-core/src/utils.ts +++ b/packages/playwright-core/src/utils.ts @@ -15,24 +15,24 @@ */ export * from './utils/isomorphic/colors'; -export * from './utils/isomorphic/debug'; +export * from './utils/isomorphic/assert'; +export * from './utils/isomorphic/headers'; export * from './utils/isomorphic/locatorGenerators'; export * from './utils/isomorphic/manualPromise'; export * from './utils/isomorphic/mimeType'; export * from './utils/isomorphic/multimap'; export * from './utils/isomorphic/rtti'; +export * from './utils/isomorphic/semaphore'; +export * from './utils/isomorphic/stackTrace'; export * from './utils/isomorphic/stringUtils'; export * from './utils/isomorphic/time'; export * from './utils/isomorphic/timeoutRunner'; export * from './utils/isomorphic/urlMatch'; -export * from './utils/isomorphic/headers'; -export * from './utils/isomorphic/semaphore'; -export * from './utils/isomorphic/stackTrace'; - export * from './server/utils/ascii'; export * from './server/utils/comparators'; export * from './server/utils/crypto'; +export * from './server/utils/debug'; export * from './server/utils/debugLogger'; export * from './server/utils/env'; export * from './server/utils/eventsHelper'; diff --git a/packages/playwright-core/src/utils/isomorphic/assert.ts b/packages/playwright-core/src/utils/isomorphic/assert.ts new file mode 100644 index 0000000000..bd0c38d413 --- /dev/null +++ b/packages/playwright-core/src/utils/isomorphic/assert.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function assert(value: any, message?: string): asserts value { + if (!value) + throw new Error(message || 'Assertion error'); +} diff --git a/packages/playwright-core/src/utils/isomorphic/manualPromise.ts b/packages/playwright-core/src/utils/isomorphic/manualPromise.ts index a5034e05ec..467b8de0db 100644 --- a/packages/playwright-core/src/utils/isomorphic/manualPromise.ts +++ b/packages/playwright-core/src/utils/isomorphic/manualPromise.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { captureRawStack } from './stackTrace'; + export class ManualPromise extends Promise { private _resolve!: (t: T) => void; private _reject!: (e: Error) => void; @@ -116,12 +118,3 @@ function cloneError(error: Error, frames: string[]) { clone.stack = [error.name + ':' + error.message, ...frames].join('\n'); return clone; } - -function captureRawStack(): string[] { - const stackTraceLimit = Error.stackTraceLimit; - Error.stackTraceLimit = 50; - const error = new Error(); - const stack = error.stack || ''; - Error.stackTraceLimit = stackTraceLimit; - return stack.split('\n'); -} diff --git a/packages/playwright-core/src/utils/isomorphic/stackTrace.ts b/packages/playwright-core/src/utils/isomorphic/stackTrace.ts index 26b79c5c27..87d9e84eaa 100644 --- a/packages/playwright-core/src/utils/isomorphic/stackTrace.ts +++ b/packages/playwright-core/src/utils/isomorphic/stackTrace.ts @@ -1,62 +1,33 @@ /** - * Copyright (c) Microsoft Corporation. + * The MIT License (MIT) + * 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 + * Copyright (c) 2016-2023 Isaac Z. Schlueter i@izs.me, James Talmage james@talmage.io (github.com/jamestalmage), and + * Contributors * - * http://www.apache.org/licenses/LICENSE-2.0 + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: * - * 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. + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { findRepeatedSubsequences } from './sequence'; -import { parseStackFrame } from './stackUtils'; - -import type { StackFrame } from '@protocol/channels'; -import type { Platform } from '../../common/platform'; - -export function parseStackTraceLine(line: string, pathSeparator: string): StackFrame | null { - const frame = parseStackFrame(line, pathSeparator); - if (!frame) - return null; - if (!process.env.PWDEBUGIMPL && (frame.file?.startsWith('internal') || frame.file?.startsWith('node:'))) - return null; - if (!frame.file) - return null; - return { - file: frame.file, - line: frame.line || 0, - column: frame.column || 0, - function: frame.function, - }; -} - -export function rewriteErrorMessage(e: E, newMessage: string): E { - const lines: string[] = (e.stack?.split('\n') || []).filter(l => l.startsWith(' at ')); - e.message = newMessage; - const errorTitle = `${e.name}: ${e.message}`; - if (lines.length) - e.stack = `${errorTitle}\n${lines.join('\n')}`; - return e; -} - -let coreDir: string | undefined; - -const playwrightStackPrefixes: string[] = []; -export const addInternalStackPrefix = (prefix: string) => playwrightStackPrefixes.push(prefix); - -export const setLibraryStackPrefix = (prefix: string) => { - coreDir = prefix; - playwrightStackPrefixes.push(prefix); -}; - export type RawStack = string[]; +export type StackFrame = { + file: string, + line: number, + column: number, + function?: string, +}; + export function captureRawStack(): RawStack { const stackTraceLimit = Error.stackTraceLimit; Error.stackTraceLimit = 50; @@ -66,61 +37,82 @@ export function captureRawStack(): RawStack { return stack.split('\n'); } -export function captureLibraryStackTrace(pathSeparator: string): { frames: StackFrame[], apiName: string } { - const stack = captureRawStack(); +export function parseStackFrame(text: string, pathSeparator: string): StackFrame | null { + const match = text && text.match(re); + if (!match) + return null; - type ParsedFrame = { - frame: StackFrame; - frameText: string; - isPlaywrightLibrary: boolean; + let fname = match[2]; + let file = match[7]; + if (!file) + return null; + if (!process.env.PWDEBUGIMPL && (file.startsWith('internal') || file.startsWith('node:'))) + return null; + + const line = match[8]; + const column = match[9]; + const closeParen = match[11] === ')'; + + const frame: StackFrame = { + file: '', + line: 0, + column: 0, }; - let parsedFrames = stack.map(line => { - const frame = parseStackTraceLine(line, pathSeparator); - if (!frame || !frame.file) - return null; - const isPlaywrightLibrary = !!coreDir && frame.file.startsWith(coreDir); - const parsed: ParsedFrame = { - frame, - frameText: line, - isPlaywrightLibrary - }; - return parsed; - }).filter(Boolean) as ParsedFrame[]; - let apiName = ''; + if (line) + frame.line = Number(line); - // 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; + if (column) + frame.column = Number(column); + + if (closeParen && file) { + // make sure parens are balanced + // if we have a file like "asdf) [as foo] (xyz.js", then odds are + // that the fname should be += " (asdf) [as foo]" and the file + // should be just "xyz.js" + // walk backwards from the end to find the last unbalanced ( + let closes = 0; + for (let i = file.length - 1; i > 0; i--) { + if (file.charAt(i) === ')') { + closes++; + } else if (file.charAt(i) === '(' && file.charAt(i - 1) === ' ') { + closes--; + if (closes === -1 && file.charAt(i - 1) === ' ') { + const before = file.slice(0, i - 1); + const after = file.slice(i + 1); + file = after; + fname += ` (${before}`; + 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]; + if (fname) { + const methodMatch = fname.match(methodRe); + if (methodMatch) + fname = methodMatch[1]; } - // This is for the inspector so that it did not include the test runner stack frames. - parsedFrames = parsedFrames.filter(f => { - if (process.env.PWDEBUGIMPL) - return true; - if (playwrightStackPrefixes.some(prefix => f.frame.file.startsWith(prefix))) - return false; - return true; - }); + if (file) { + if (file.startsWith('file://')) + file = fileURLToPath(file, pathSeparator); + frame.file = file; + } - return { - frames: parsedFrames.map(p => p.frame), - apiName - }; + if (fname) + frame.function = fname; + + return frame; +} + +export function rewriteErrorMessage(e: E, newMessage: string): E { + const lines: string[] = (e.stack?.split('\n') || []).filter(l => l.startsWith(' at ')); + e.message = newMessage; + const errorTitle = `${e.name}: ${e.message}`; + if (lines.length) + e.stack = `${errorTitle}\n${lines.join('\n')}`; + return e; } export function stringifyStackFrames(frames: StackFrame[]): string[] { @@ -142,31 +134,40 @@ export function splitErrorMessage(message: string): { name: string, message: str }; } -export function formatCallLog(platform: Platform, log: string[] | undefined): string { - if (!log || !log.some(l => !!l)) - return ''; - return ` -Call log: -${platform.colors.dim(log.join('\n'))} -`; -} +const re = new RegExp('^' + + // Sometimes we strip out the ' at' because it's noisy + '(?:\\s*at )?' + + // $1 = ctor if 'new' + '(?:(new) )?' + + // $2 = function name (can be literally anything) + // May contain method at the end as [as xyz] + '(?:(.*?) \\()?' + + // (eval at (file.js:1:1), + // $3 = eval origin + // $4:$5:$6 are eval file/line/col, but not normally reported + '(?:eval at ([^ ]+) \\((.+?):(\\d+):(\\d+)\\), )?' + + // file:line:col + // $7:$8:$9 + // $10 = 'native' if native + '(?:(.+?):(\\d+):(\\d+)|(native))' + + // maybe close the paren, then end + // if $11 is ), then we only allow balanced parens in the filename + // any imbalance is placed on the fname. This is a heuristic, and + // bound to be incorrect in some edge cases. The bet is that + // having weird characters in method names is more common than + // having weird characters in filenames, which seems reasonable. + '(\\)?)$' +); -export function compressCallLog(log: string[]): string[] { - const lines: string[] = []; +const methodRe = /^(.*?) \[as (.*?)\]$/; - 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 fileURLToPath(fileUrl: string, pathSeparator: string): string { + if (!fileUrl.startsWith('file://')) + return fileUrl; + + let path = decodeURIComponent(fileUrl.slice(7)); + if (path.startsWith('/') && /^[a-zA-Z]:/.test(path.slice(1))) + path = path.slice(1); + + return path.replace(/\//g, pathSeparator); } diff --git a/packages/playwright-core/src/utils/isomorphic/stackUtils.ts b/packages/playwright-core/src/utils/isomorphic/stackUtils.ts deleted file mode 100644 index 20be8081af..0000000000 --- a/packages/playwright-core/src/utils/isomorphic/stackUtils.ts +++ /dev/null @@ -1,158 +0,0 @@ -/** - * The MIT License (MIT) - * Modifications copyright (c) Microsoft Corporation. - * - * Copyright (c) 2016-2023 Isaac Z. Schlueter i@izs.me, James Talmage james@talmage.io (github.com/jamestalmage), and - * Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated - * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -type StackData = { - line?: number; - column?: number; - file?: string; - isConstructor?: boolean; - evalOrigin?: string; - native?: boolean; - function?: string; - method?: string; - evalLine?: number | undefined; - evalColumn?: number | undefined; - evalFile?: string | undefined; -}; - -export function parseStackFrame(line: string, pathSeparator: string): StackData | null { - const match = line && line.match(re); - if (!match) - return null; - - const ctor = match[1] === 'new'; - let fname = match[2]; - const evalOrigin = match[3]; - const evalFile = match[4]; - const evalLine = Number(match[5]); - const evalCol = Number(match[6]); - let file = match[7]; - const lnum = match[8]; - const col = match[9]; - const native = match[10] === 'native'; - const closeParen = match[11] === ')'; - let method; - - const res: StackData = {}; - - if (lnum) - res.line = Number(lnum); - - if (col) - res.column = Number(col); - - if (closeParen && file) { - // make sure parens are balanced - // if we have a file like "asdf) [as foo] (xyz.js", then odds are - // that the fname should be += " (asdf) [as foo]" and the file - // should be just "xyz.js" - // walk backwards from the end to find the last unbalanced ( - let closes = 0; - for (let i = file.length - 1; i > 0; i--) { - if (file.charAt(i) === ')') { - closes++; - } else if (file.charAt(i) === '(' && file.charAt(i - 1) === ' ') { - closes--; - if (closes === -1 && file.charAt(i - 1) === ' ') { - const before = file.slice(0, i - 1); - const after = file.slice(i + 1); - file = after; - fname += ` (${before}`; - break; - } - } - } - } - - if (fname) { - const methodMatch = fname.match(methodRe); - if (methodMatch) { - fname = methodMatch[1]; - method = methodMatch[2]; - } - } - - setFile(res, file, pathSeparator); - - if (ctor) - res.isConstructor = true; - - if (evalOrigin) { - res.evalOrigin = evalOrigin; - res.evalLine = evalLine; - res.evalColumn = evalCol; - res.evalFile = evalFile && evalFile.replace(/\\/g, '/'); - } - - if (native) - res.native = true; - if (fname) - res.function = fname; - if (method && fname !== method) - res.method = method; - return res; -} - -function setFile(result: StackData, filename: string, pathSeparator: string) { - if (filename) { - if (filename.startsWith('file://')) - filename = fileURLToPath(filename, pathSeparator); - result.file = filename; - } -} - -const re = new RegExp('^' + - // Sometimes we strip out the ' at' because it's noisy - '(?:\\s*at )?' + - // $1 = ctor if 'new' - '(?:(new) )?' + - // $2 = function name (can be literally anything) - // May contain method at the end as [as xyz] - '(?:(.*?) \\()?' + - // (eval at (file.js:1:1), - // $3 = eval origin - // $4:$5:$6 are eval file/line/col, but not normally reported - '(?:eval at ([^ ]+) \\((.+?):(\\d+):(\\d+)\\), )?' + - // file:line:col - // $7:$8:$9 - // $10 = 'native' if native - '(?:(.+?):(\\d+):(\\d+)|(native))' + - // maybe close the paren, then end - // if $11 is ), then we only allow balanced parens in the filename - // any imbalance is placed on the fname. This is a heuristic, and - // bound to be incorrect in some edge cases. The bet is that - // having weird characters in method names is more common than - // having weird characters in filenames, which seems reasonable. - '(\\)?)$' -); - -const methodRe = /^(.*?) \[as (.*?)\]$/; - -function fileURLToPath(fileUrl: string, pathSeparator: string): string { - if (!fileUrl.startsWith('file://')) - return fileUrl; - - let path = decodeURIComponent(fileUrl.slice(7)); - if (path.startsWith('/') && /^[a-zA-Z]:/.test(path.slice(1))) - path = path.slice(1); - - return path.replace(/\//g, pathSeparator); -} diff --git a/packages/playwright-core/src/common/types.ts b/packages/playwright-core/src/utils/isomorphic/types.ts similarity index 100% rename from packages/playwright-core/src/common/types.ts rename to packages/playwright-core/src/utils/isomorphic/types.ts diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 9d6089614c..bb0ebd9513 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -18,7 +18,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as playwrightLibrary from 'playwright-core'; -import { addInternalStackPrefix, asLocator, createGuid, currentZone, debugMode, isString, jsonStringifyForceASCII } from 'playwright-core/lib/utils'; +import { setBoxedStackPrefixes, asLocator, createGuid, currentZone, debugMode, isString, jsonStringifyForceASCII } from 'playwright-core/lib/utils'; import { currentTestInfo } from './common/globals'; import { rootTestType } from './common/testType'; @@ -32,7 +32,7 @@ import type { APIRequestContext, Browser, BrowserContext, BrowserContextOptions, export { expect } from './matchers/expect'; export const _baseTest: TestType<{}, {}> = rootTestType.test; -addInternalStackPrefix(path.dirname(require.resolve('../package.json'))); +setBoxedStackPrefixes([path.dirname(require.resolve('../package.json'))]); if ((process as any)['__pw_initiator__']) { const originalStackTraceLimit = Error.stackTraceLimit; diff --git a/packages/playwright/src/isomorphic/types.d.ts b/packages/playwright/src/isomorphic/types.d.ts index 213f350514..2619c0df33 100644 --- a/packages/playwright/src/isomorphic/types.d.ts +++ b/packages/playwright/src/isomorphic/types.d.ts @@ -25,5 +25,6 @@ export interface GitCommitInfo { 'pull.link'?: string; 'pull.diff'?: string; 'pull.base'?: string; + 'pull.title'?: string; 'ci.link'?: string; } diff --git a/packages/playwright/src/plugins/gitCommitInfoPlugin.ts b/packages/playwright/src/plugins/gitCommitInfoPlugin.ts index 29010183c7..7ade4a005c 100644 --- a/packages/playwright/src/plugins/gitCommitInfoPlugin.ts +++ b/packages/playwright/src/plugins/gitCommitInfoPlugin.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import fs from 'fs'; + import { createGuid, spawnAsync } from 'playwright-core/lib/utils'; import type { TestRunnerPlugin } from './'; @@ -33,7 +35,7 @@ export const gitCommitInfo = (options?: GitCommitInfoPluginOptions): TestRunnerP name: 'playwright:git-commit-info', setup: async (config: FullConfig, configDir: string) => { - const fromEnv = linksFromEnv(); + const fromEnv = await linksFromEnv(); const fromCLI = await gitStatusFromCLI(options?.directory || configDir, fromEnv); config.metadata = config.metadata || {}; config.metadata['git.commit.info'] = { ...fromEnv, ...fromCLI }; @@ -45,7 +47,7 @@ interface GitCommitInfoPluginOptions { directory?: string; } -function linksFromEnv() { +async function linksFromEnv() { const out: Partial = {}; // Jenkins: https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables if (process.env.BUILD_URL) @@ -55,15 +57,21 @@ function linksFromEnv() { out['revision.link'] = `${process.env.CI_PROJECT_URL}/-/commit/${process.env.CI_COMMIT_SHA}`; if (process.env.CI_JOB_URL) out['ci.link'] = process.env.CI_JOB_URL; - // GitHub: https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables + // GitHub: https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_SHA) out['revision.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${process.env.GITHUB_SHA}`; if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID) out['ci.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; - if (process.env.GITHUB_REF_NAME && process.env.GITHUB_REF_NAME.endsWith('/merge')) { - const pullId = process.env.GITHUB_REF_NAME.substring(0, process.env.GITHUB_REF_NAME.indexOf('/merge')); - out['pull.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${pullId}`; - out['pull.base'] = process.env.GITHUB_BASE_REF; + if (process.env.GITHUB_EVENT_PATH) { + try { + const json = JSON.parse(await fs.promises.readFile(process.env.GITHUB_EVENT_PATH, 'utf8')); + if (json.pull_request) { + out['pull.title'] = json.pull_request.title; + out['pull.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${json.pull_request.number}`; + out['pull.base'] = json.pull_request.base.ref; + } + } catch { + } } return out; } diff --git a/packages/playwright/src/reporters/base.ts b/packages/playwright/src/reporters/base.ts index 1748cac9b9..79d0d254a1 100644 --- a/packages/playwright/src/reporters/base.ts +++ b/packages/playwright/src/reporters/base.ts @@ -17,7 +17,7 @@ import * as path from 'path'; import { getPackageManagerExecCommand } from 'playwright-core/lib/utils'; -import { parseStackTraceLine } from 'playwright-core/lib/utils'; +import { parseStackFrame } from 'playwright-core/lib/utils'; import { ms as milliseconds } from 'playwright-core/lib/utilsBundle'; import { colors as realColors, noColors } from 'playwright-core/lib/utils'; @@ -522,7 +522,7 @@ export function prepareErrorStack(stack: string): { const stackLines = lines.slice(firstStackLine); let location: Location | undefined; for (const line of stackLines) { - const frame = parseStackTraceLine(line, path.sep); + const frame = parseStackFrame(line, path.sep); if (!frame || !frame.file) continue; if (belongsToNodeModules(frame.file)) diff --git a/packages/playwright/src/util.ts b/packages/playwright/src/util.ts index b80a3f387d..d10babff56 100644 --- a/packages/playwright/src/util.ts +++ b/packages/playwright/src/util.ts @@ -19,9 +19,8 @@ import * as path from 'path'; import * as url from 'url'; import util from 'util'; -import { parseStackTraceLine, sanitizeForFilePath, calculateSha1, formatCallLog, isRegExp, isString, stringifyStackFrames } from 'playwright-core/lib/utils'; -import { debug, mime, minimatch } from 'playwright-core/lib/utilsBundle'; -import { nodePlatform } from 'playwright-core/lib/utils'; +import { parseStackFrame, sanitizeForFilePath, calculateSha1, isRegExp, isString, stringifyStackFrames } from 'playwright-core/lib/utils'; +import { colors, debug, mime, minimatch } from 'playwright-core/lib/utilsBundle'; import type { Location } from './../types/testReporter'; import type { TestInfoErrorImpl } from './common/ipc'; @@ -56,7 +55,7 @@ export function filterStackFile(file: string) { export function filteredStackTrace(rawStack: RawStack): StackFrame[] { const frames: StackFrame[] = []; for (const line of rawStack) { - const frame = parseStackTraceLine(line, path.sep); + const frame = parseStackFrame(line, path.sep); if (!frame || !frame.file) continue; if (!filterStackFile(frame.file)) @@ -225,7 +224,14 @@ export function getContainedPath(parentPath: string, subPath: string = ''): stri export const debugTest = debug('pw:test'); -export const callLogText = (log: string[] | undefined) => formatCallLog(nodePlatform, log); +export const callLogText = (log: string[] | undefined) => { + if (!log || !log.some(l => !!l)) + return ''; + return ` +Call log: +${colors.dim(log.join('\n'))} +`; +}; const folderToPackageJsonPath = new Map(); diff --git a/packages/trace-viewer/package.json b/packages/trace-viewer/package.json index 57a3dfd409..fde4eb6766 100644 --- a/packages/trace-viewer/package.json +++ b/packages/trace-viewer/package.json @@ -4,6 +4,7 @@ "version": "0.0.0", "type": "module", "dependencies": { - "yaml": "^2.6.0" + "yaml": "^2.6.0", + "markdown-to-jsx": "^7.7.3" } } diff --git a/packages/trace-viewer/src/sw/main.ts b/packages/trace-viewer/src/sw/main.ts index cd23082d91..cdce3261fe 100644 --- a/packages/trace-viewer/src/sw/main.ts +++ b/packages/trace-viewer/src/sw/main.ts @@ -78,6 +78,12 @@ async function doFetch(event: FetchEvent): Promise { if (event.request.url.startsWith('chrome-extension://')) return fetch(event.request); + if (event.request.headers.get('x-pw-serviceworker') === 'forward') { + const request = new Request(event.request); + request.headers.delete('x-pw-serviceworker'); + return fetch(request); + } + const request = event.request; const client = await self.clients.get(event.clientId); diff --git a/packages/trace-viewer/src/ui/aiConversation.css b/packages/trace-viewer/src/ui/aiConversation.css new file mode 100644 index 0000000000..214496017a --- /dev/null +++ b/packages/trace-viewer/src/ui/aiConversation.css @@ -0,0 +1,128 @@ +/** + * 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. + */ + +.chat-container { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + color: #e0e0e0; +} + +.chat-disclaimer { + text-align: center; + color: var(--vscode-editorBracketMatch-border); + margin: 0px; +} + +.chat-container hr { + width: 100%; + border: none; + border-top: 1px solid var(--vscode-titleBar-inactiveBackground); +} + +.messages-container { + flex: 1; + overflow-y: auto; + padding: 16px; + + display: flex; + flex-direction: column; + gap: 16px; +} + +.message { + gap: 12px; + max-width: 85%; +} + +.user-message { + flex-direction: row-reverse; + margin-left: auto; + width: fit-content +} + +.message-icon { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--vscode-titleBar-inactiveBackground); + flex-shrink: 0; +} + +.message-content { + background-color: var(--vscode-titleBar-inactiveBackground); + color: var(--vscode-titleBar-activeForeground); + padding: 2px 8px; +} + +.message-content pre { + text-wrap: auto; + overflow-wrap: anywhere; +} + +.user-message .message-content { + background-color: var(--vscode-titleBar-activeBackground); +} + +/* Input form styles */ +.input-form { + position: sticky; + bottom: 0; + display: flex; + height: 64px; + gap: 8px; + padding: 10px; + background-color: var(--vscode-sideBar-background); + border-top: 1px solid var(--vscode-sideBarSectionHeader-border); +} + +.message-input { + flex: 1; + padding: 8px 12px; + border: 1px solid var(--vscode-settings-textInputBorder); + background-color: var(--vscode-settings-textInputBackground); + font-size: 14px; + outline: none; + transition: border-color 0.2s; +} + +.message-input:focus { + border-color: #0078d4; +} + +.send-button { + display: flex; + align-items: center; + justify-content: center; + padding: 8px; + background-color: var(--vscode-button-background); + border: none; + color: white; + cursor: pointer; + transition: background-color 0.2s; +} + +.send-button:hover { + background-color: var(--vscode-button-hoverBackground); +} + +.send-button:disabled { + background-color: var(--vscode-disabledForeground); + cursor: not-allowed; +} diff --git a/packages/trace-viewer/src/ui/aiConversation.tsx b/packages/trace-viewer/src/ui/aiConversation.tsx new file mode 100644 index 0000000000..548ed626f0 --- /dev/null +++ b/packages/trace-viewer/src/ui/aiConversation.tsx @@ -0,0 +1,84 @@ +/** + * 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 { useCallback, useState } from 'react'; +import Markdown from 'markdown-to-jsx'; +import './aiConversation.css'; +import { clsx } from '@web/uiUtils'; +import { useLLMConversation } from './llm'; + +export function AIConversation({ conversationId }: { conversationId: string }) { + const [history, conversation] = useLLMConversation(conversationId); + const [input, setInput] = useState(''); + + const onSubmit = useCallback(() => { + setInput(content => { + conversation.send(content); + return ''; + }); + }, [conversation]); + + return ( +
+

Chat based on {conversation.chat.api.name}. Check for mistakes.

+
+
+ {history.filter(({ role }) => role !== 'developer').map((message, index) => ( +
+ {message.role === 'assistant' && ( +
+ +
+ )} +
+ {message.displayContent ?? message.content} +
+
+ ))} +
+ +
+