diff --git a/docs/src/test-api/class-testconfig.md b/docs/src/test-api/class-testconfig.md index 5b89e62b88..5515252b39 100644 --- a/docs/src/test-api/class-testconfig.md +++ b/docs/src/test-api/class-testconfig.md @@ -334,6 +334,8 @@ Whether to populate `'git.commit.info'` field of the [`property: TestConfig.meta This information will appear in the HTML and JSON reports and is available in the Reporter API. +On Github Actions, this feature is enabled by default. + **Usage** ```js title="playwright.config.ts" diff --git a/packages/html-reporter/index.html b/packages/html-reporter/index.html index 054507220c..54ab833d2b 100644 --- a/packages/html-reporter/index.html +++ b/packages/html-reporter/index.html @@ -15,7 +15,7 @@ --> - + diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 0f0d5f643b..38c5d71834 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "2132", + "revision": "2134", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", diff --git a/packages/playwright-core/bundles/utils/package-lock.json b/packages/playwright-core/bundles/utils/package-lock.json index 49e99ef5c3..bc58353c99 100644 --- a/packages/playwright-core/bundles/utils/package-lock.json +++ b/packages/playwright-core/bundles/utils/package-lock.json @@ -167,6 +167,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "license": "MIT", "engines": { "node": ">=0.1.90" } diff --git a/packages/playwright-core/src/DEPS.list b/packages/playwright-core/src/DEPS.list index 026473f98a..2ffa077b4e 100644 --- a/packages/playwright-core/src/DEPS.list +++ b/packages/playwright-core/src/DEPS.list @@ -8,10 +8,15 @@ ** [inprocess.ts] +common/ utils/ +server/utils [outofprocess.ts] client/ +common/ protocol/ utils/ -common/ \ No newline at end of file +utils/isomorphic +server/utils +common/ diff --git a/packages/playwright-core/src/androidServerImpl.ts b/packages/playwright-core/src/androidServerImpl.ts index aee8bf7b31..5f3758119a 100644 --- a/packages/playwright-core/src/androidServerImpl.ts +++ b/packages/playwright-core/src/androidServerImpl.ts @@ -16,7 +16,7 @@ import { PlaywrightServer } from './remote/playwrightServer'; import { createPlaywright } from './server/playwright'; -import { createGuid } from './utils'; +import { createGuid } from './server/utils/crypto'; import { ws } from './utilsBundle'; import type { BrowserServer } from './client/browserType'; diff --git a/packages/playwright-core/src/browserServerImpl.ts b/packages/playwright-core/src/browserServerImpl.ts index 3c5244b7b0..77fb9a9844 100644 --- a/packages/playwright-core/src/browserServerImpl.ts +++ b/packages/playwright-core/src/browserServerImpl.ts @@ -20,8 +20,8 @@ import { PlaywrightServer } from './remote/playwrightServer'; import { helper } from './server/helper'; import { serverSideCallMetadata } from './server/instrumentation'; import { createPlaywright } from './server/playwright'; -import { createGuid } from './utils'; -import { rewriteErrorMessage } from './utils/stackTrace'; +import { createGuid } from './server/utils/crypto'; +import { rewriteErrorMessage } from './utils/isomorphic/stackTrace'; import { ws } from './utilsBundle'; import type { BrowserServer, BrowserServerLauncher } from './client/browserType'; diff --git a/packages/playwright-core/src/cli/driver.ts b/packages/playwright-core/src/cli/driver.ts index 2b49b65ee7..1e16de00bf 100644 --- a/packages/playwright-core/src/cli/driver.ts +++ b/packages/playwright-core/src/cli/driver.ts @@ -19,7 +19,7 @@ import * as fs from 'fs'; import * as playwright from '../..'; -import { PipeTransport } from '../protocol/transport'; +import { PipeTransport } from '../utils/pipeTransport'; import { PlaywrightServer } from '../remote/playwrightServer'; import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from '../server'; import { gracefullyProcessExitDoNotHang } from '../server/utils/processLauncher'; diff --git a/packages/playwright-core/src/client/android.ts b/packages/playwright-core/src/client/android.ts index 7f062143fc..f0ca11848f 100644 --- a/packages/playwright-core/src/client/android.ts +++ b/packages/playwright-core/src/client/android.ts @@ -21,16 +21,16 @@ import { ChannelOwner } from './channelOwner'; import { TargetClosedError, isTargetClosedError } from './errors'; import { Events } from './events'; import { Waiter } from './waiter'; -import { TimeoutSettings } from '../common/timeoutSettings'; -import { isRegExp, isString } from '../utils/rtti'; -import { monotonicTime } from '../utils/time'; -import { raceAgainstDeadline } from '../utils/timeoutRunner'; +import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; +import { isRegExp, isString } from '../utils/isomorphic/rtti'; +import { monotonicTime } from '../utils/isomorphic/time'; +import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner'; 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 '../utils/platform'; +import type { Platform } from '../common/platform'; import type * as channels from '@protocol/channels'; type Direction = 'down' | 'up' | 'left' | 'right'; diff --git a/packages/playwright-core/src/client/artifact.ts b/packages/playwright-core/src/client/artifact.ts index fc1e9bca12..815c7a358b 100644 --- a/packages/playwright-core/src/client/artifact.ts +++ b/packages/playwright-core/src/client/artifact.ts @@ -16,7 +16,7 @@ import { ChannelOwner } from './channelOwner'; import { Stream } from './stream'; -import { mkdirIfNeeded } from '../utils/fileUtils'; +import { mkdirIfNeeded } from '../common/fileUtils'; import type * as channels from '@protocol/channels'; import type { Readable } from 'stream'; diff --git a/packages/playwright-core/src/client/browser.ts b/packages/playwright-core/src/client/browser.ts index 9d2ca1fab0..88140621fe 100644 --- a/packages/playwright-core/src/client/browser.ts +++ b/packages/playwright-core/src/client/browser.ts @@ -20,7 +20,7 @@ import { CDPSession } from './cdpSession'; import { ChannelOwner } from './channelOwner'; import { isTargetClosedError } from './errors'; import { Events } from './events'; -import { mkdirIfNeeded } from '../utils/fileUtils'; +import { mkdirIfNeeded } from '../common/fileUtils'; import type { BrowserType } from './browserType'; import type { Page } from './page'; diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index d625135f73..02aa857366 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -34,19 +34,19 @@ import { Tracing } from './tracing'; import { Waiter } from './waiter'; import { WebError } from './webError'; import { Worker } from './worker'; -import { TimeoutSettings } from '../common/timeoutSettings'; -import { mkdirIfNeeded } from '../utils/fileUtils'; -import { headersObjectToArray } from '../utils/headers'; +import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; +import { mkdirIfNeeded } from '../common/fileUtils'; +import { headersObjectToArray } from '../utils/isomorphic/headers'; import { urlMatchesEqual } from '../utils/isomorphic/urlMatch'; -import { isRegExp, isString } from '../utils/rtti'; -import { rewriteErrorMessage } from '../utils/stackTrace'; +import { isRegExp, isString } from '../utils/isomorphic/rtti'; +import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace'; import type { BrowserType } from './browserType'; import type { BrowserContextOptions, Headers, LaunchOptions, StorageState, WaitForEventOptions } from './types'; 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 '../utils/platform'; +import type { Platform } from '../common/platform'; import type * as channels from '@protocol/channels'; export class BrowserContext extends ChannelOwner implements api.BrowserContext { diff --git a/packages/playwright-core/src/client/browserType.ts b/packages/playwright-core/src/client/browserType.ts index 59ce8c56d7..06bae0419f 100644 --- a/packages/playwright-core/src/client/browserType.ts +++ b/packages/playwright-core/src/client/browserType.ts @@ -21,10 +21,10 @@ import { BrowserContext, prepareBrowserContextParams } from './browserContext'; import { ChannelOwner } from './channelOwner'; import { envObjectToArray } from './clientHelper'; import { Events } from './events'; -import { assert } from '../utils/debug'; -import { headersObjectToArray } from '../utils/headers'; -import { monotonicTime } from '../utils/time'; -import { raceAgainstDeadline } from '../utils/timeoutRunner'; +import { assert } from '../utils/isomorphic/debug'; +import { headersObjectToArray } from '../utils/isomorphic/headers'; +import { monotonicTime } from '../utils/isomorphic/time'; +import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner'; import type { Playwright } from './playwright'; import type { ConnectOptions, LaunchOptions, LaunchPersistentContextOptions, LaunchServerOptions, Logger } from './types'; @@ -100,7 +100,7 @@ export class BrowserType extends ChannelOwner imple ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs), env: options.env ? envObjectToArray(options.env) : undefined, channel: options.channel, - userDataDir: path.isAbsolute(userDataDir) ? userDataDir : path.resolve(userDataDir), + userDataDir: (path.isAbsolute(userDataDir) || !userDataDir) ? userDataDir : path.resolve(userDataDir), }; return await this._wrapApiCall(async () => { const result = await this._channel.launchPersistentContext(persistentParams); diff --git a/packages/playwright-core/src/client/channelOwner.ts b/packages/playwright-core/src/client/channelOwner.ts index 70a5c51777..40bf226c65 100644 --- a/packages/playwright-core/src/client/channelOwner.ts +++ b/packages/playwright-core/src/client/channelOwner.ts @@ -16,16 +16,15 @@ import { EventEmitter } from './eventEmitter'; import { ValidationError, maybeFindValidator } from '../protocol/validator'; -import { isUnderTest } from '../utils/debug'; -import { debugLogger } from '../utils/debugLogger'; -import { captureLibraryStackTrace, stringifyStackFrames } from '../utils/stackTrace'; +import { isUnderTest } from '../utils/isomorphic/debug'; +import { captureLibraryStackTrace, stringifyStackFrames } from '../utils/isomorphic/stackTrace'; import { zones } from '../utils/zones'; 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 '../utils/platform'; +import type { Platform } from '../common/platform'; import type * as channels from '@protocol/channels'; type Listener = (...args: any[]) => void; @@ -158,7 +157,7 @@ export abstract class ChannelOwner ${apiZone.apiName} started`); + logApiCall(this._platform, this._logger, `=> ${apiZone.apiName} started`); return await this._connection.sendMessageToServer(this, prop, validatedParams, apiZone.apiName, apiZone.frames, apiZone.stepId); } // Since this api call is either internal, or has already been reported/traced once, @@ -183,13 +182,13 @@ export abstract class ChannelOwner await func(apiZone)); if (!isInternal) { - logApiCall(logger, `<= ${apiZone.apiName} succeeded`); + logApiCall(this._platform, logger, `<= ${apiZone.apiName} succeeded`); this._instrumentation.onApiCallEnd(apiZone); } return result; @@ -204,7 +203,7 @@ export abstract class ChannelOwner { @@ -139,9 +138,9 @@ export class Connection extends EventEmitter { const type = object._type; const id = ++this._lastId; const message = { id, guid, method, params }; - if (debugLogger.isEnabled('channel')) { + if (this.platform.isLogEnabled('channel')) { // Do not include metadata in debug logs to avoid noise. - debugLogger.log('channel', 'SEND> ' + JSON.stringify(message)); + this.platform.log('channel', 'SEND> ' + JSON.stringify(message)); } const location = frames[0] ? { file: frames[0].file, line: frames[0].line, column: frames[0].column } : undefined; const metadata: channels.Metadata = { apiName, location, internal: !apiName, stepId }; @@ -159,15 +158,15 @@ export class Connection extends EventEmitter { const { id, guid, method, params, result, error, log } = message as any; if (id) { - if (debugLogger.isEnabled('channel')) - debugLogger.log('channel', ' { } async harOpen(params: channels.LocalUtilsHarOpenParams): Promise { - return await localUtils.harOpen(this._harBackends, params); + return await localUtils.harOpen(this._platform, this._harBackends, params); } async harLookup(params: channels.LocalUtilsHarLookupParams): Promise { diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index 04cfa5fadf..5d0f0aa0c3 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -19,8 +19,8 @@ import { parseResult, serializeArgument } from './jsHandle'; import { asLocator } from '../utils/isomorphic/locatorGenerators'; import { getByAltTextSelector, getByLabelSelector, getByPlaceholderSelector, getByRoleSelector, getByTestIdSelector, getByTextSelector, getByTitleSelector } from '../utils/isomorphic/locatorUtils'; import { escapeForTextSelector } from '../utils/isomorphic/stringUtils'; -import { isString } from '../utils/rtti'; -import { monotonicTime } from '../utils/time'; +import { isString } from '../utils/isomorphic/rtti'; +import { monotonicTime } from '../utils/isomorphic/time'; import type { Frame } from './frame'; import type { FilePayload, FrameExpectParams, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types'; diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index ae7c7bb251..a2c64c08b1 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -23,13 +23,13 @@ import { APIResponse } from './fetch'; import { Frame } from './frame'; import { Waiter } from './waiter'; import { Worker } from './worker'; -import { assert } from '../utils/debug'; -import { headersObjectToArray } from '../utils/headers'; +import { assert } from '../utils/isomorphic/debug'; +import { headersObjectToArray } from '../utils/isomorphic/headers'; import { urlMatches } from '../utils/isomorphic/urlMatch'; -import { LongStandingScope, ManualPromise } from '../utils/manualPromise'; -import { MultiMap } from '../utils/multimap'; -import { isRegExp, isString } from '../utils/rtti'; -import { rewriteErrorMessage } from '../utils/stackTrace'; +import { LongStandingScope, ManualPromise } from '../utils/isomorphic/manualPromise'; +import { MultiMap } from '../utils/isomorphic/multimap'; +import { isRegExp, isString } from '../utils/isomorphic/rtti'; +import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace'; import { zones } from '../utils/zones'; import { mime } from '../utilsBundle'; diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index d4bb32ffbd..b954cca502 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -33,14 +33,14 @@ import { Response, Route, RouteHandler, WebSocket, WebSocketRoute, WebSocketRou import { Video } from './video'; import { Waiter } from './waiter'; import { Worker } from './worker'; -import { TimeoutSettings } from '../common/timeoutSettings'; -import { assert } from '../utils/debug'; -import { mkdirIfNeeded } from '../utils/fileUtils'; -import { headersObjectToArray } from '../utils/headers'; +import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; +import { assert } from '../utils/isomorphic/debug'; +import { mkdirIfNeeded } from '../common/fileUtils'; +import { headersObjectToArray } from '../utils/isomorphic/headers'; import { trimStringWithEllipsis } from '../utils/isomorphic/stringUtils'; import { urlMatches, urlMatchesEqual } from '../utils/isomorphic/urlMatch'; -import { LongStandingScope } from '../utils/manualPromise'; -import { isObject, isRegExp, isString } from '../utils/rtti'; +import { LongStandingScope } from '../utils/isomorphic/manualPromise'; +import { isObject, isRegExp, isString } from '../utils/isomorphic/rtti'; import type { BrowserContext } from './browserContext'; import type { Clock } from './clock'; diff --git a/packages/playwright-core/src/client/selectors.ts b/packages/playwright-core/src/client/selectors.ts index 2a1097f7ec..3eca51bf36 100644 --- a/packages/playwright-core/src/client/selectors.ts +++ b/packages/playwright-core/src/client/selectors.ts @@ -17,19 +17,25 @@ import { ChannelOwner } from './channelOwner'; import { evaluationScript } from './clientHelper'; import { setTestIdAttribute, testIdAttributeName } from './locator'; -import { nodePlatform } from '../utils/platform'; +import { emptyPlatform } from '../common/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'; +let platform = emptyPlatform; + +export function setPlatformForSelectors(p: Platform) { + platform = p; +} export class Selectors implements api.Selectors { private _channels = new Set(); private _registrations: channels.SelectorsRegisterParams[] = []; async register(name: string, script: string | (() => SelectorEngine) | { path?: string, content?: string }, options: { contentScript?: boolean } = {}): Promise { - const source = await evaluationScript(nodePlatform, script, undefined, false); + const source = await evaluationScript(platform, script, undefined, false); const params = { ...options, name, source }; for (const channel of this._channels) await channel._channel.register(params); diff --git a/packages/playwright-core/src/client/video.ts b/packages/playwright-core/src/client/video.ts index 993647dc72..1ff4065be7 100644 --- a/packages/playwright-core/src/client/video.ts +++ b/packages/playwright-core/src/client/video.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ManualPromise } from '../utils/manualPromise'; +import { ManualPromise } from '../utils/isomorphic/manualPromise'; import type { Artifact } from './artifact'; import type { Connection } from './connection'; diff --git a/packages/playwright-core/src/client/waiter.ts b/packages/playwright-core/src/client/waiter.ts index 90e48108a2..e17f93d940 100644 --- a/packages/playwright-core/src/client/waiter.ts +++ b/packages/playwright-core/src/client/waiter.ts @@ -15,8 +15,7 @@ */ import { TimeoutError } from './errors'; -import { createGuid } from '../utils/crypto'; -import { rewriteErrorMessage } from '../utils/stackTrace'; +import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace'; import { zones } from '../utils/zones'; import type { ChannelOwner } from './channelOwner'; @@ -35,7 +34,7 @@ export class Waiter { private _savedZone: Zone; constructor(channelOwner: ChannelOwner, event: string) { - this._waitId = createGuid(); + this._waitId = channelOwner._platform.createGuid(); this._channelOwner = channelOwner; this._savedZone = zones.current().without('apiZone'); diff --git a/packages/playwright-core/src/client/worker.ts b/packages/playwright-core/src/client/worker.ts index ee9c2dcd6e..5a5a5fe246 100644 --- a/packages/playwright-core/src/client/worker.ts +++ b/packages/playwright-core/src/client/worker.ts @@ -18,7 +18,7 @@ import { ChannelOwner } from './channelOwner'; import { TargetClosedError } from './errors'; import { Events } from './events'; import { JSHandle, assertMaxArguments, parseResult, serializeArgument } from './jsHandle'; -import { LongStandingScope } from '../utils/manualPromise'; +import { LongStandingScope } from '../utils/isomorphic/manualPromise'; import type { BrowserContext } from './browserContext'; import type { Page } from './page'; diff --git a/packages/playwright-core/src/common/DEPS.list b/packages/playwright-core/src/common/DEPS.list index 43bff9dba4..a25dd41c36 100644 --- a/packages/playwright-core/src/common/DEPS.list +++ b/packages/playwright-core/src/common/DEPS.list @@ -1,4 +1,5 @@ [*] ../utils/ +../utils/isomorphic/ ../utilsBundle.ts ../zipBundle.ts diff --git a/packages/playwright-core/src/utils/fileUtils.ts b/packages/playwright-core/src/common/fileUtils.ts similarity index 100% rename from packages/playwright-core/src/utils/fileUtils.ts rename to packages/playwright-core/src/common/fileUtils.ts diff --git a/packages/playwright-core/src/utils/harBackend.ts b/packages/playwright-core/src/common/harBackend.ts similarity index 92% rename from packages/playwright-core/src/utils/harBackend.ts rename to packages/playwright-core/src/common/harBackend.ts index 5c68e87c7d..f59b7de5f1 100644 --- a/packages/playwright-core/src/utils/harBackend.ts +++ b/packages/playwright-core/src/common/harBackend.ts @@ -14,24 +14,24 @@ * limitations under the License. */ -import * as fs from 'fs'; -import * as path from 'path'; +import { ZipFile } from '../utils/zipFile'; -import { createGuid } from './crypto'; -import { ZipFile } from './zipFile'; - -import type { HeadersArray } from '../common/types'; +import type { HeadersArray } from './types'; import type * as har from '@trace/har'; +import type { Platform } from './platform'; const redirectStatus = [301, 302, 303, 307, 308]; export class HarBackend { - readonly id = createGuid(); + readonly id: string; private _harFile: har.HARFile; private _zipFile: ZipFile | null; private _baseDir: string | null; + private _platform: Platform; - constructor(harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) { + constructor(platform: Platform, harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) { + this._platform = platform; + this.id = platform.createGuid(); this._harFile = harFile; this._baseDir = baseDir; this._zipFile = zipFile; @@ -79,7 +79,7 @@ export class HarBackend { if (this._zipFile) buffer = await this._zipFile.read(file); else - buffer = await fs.promises.readFile(path.resolve(this._baseDir!, file)); + buffer = await this._platform.fs().promises.readFile(this._platform.path().resolve(this._baseDir!, file)); } else { buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8'); } diff --git a/packages/playwright-core/src/utils/localUtils.ts b/packages/playwright-core/src/common/localUtils.ts similarity index 91% rename from packages/playwright-core/src/utils/localUtils.ts rename to packages/playwright-core/src/common/localUtils.ts index d94a7da27e..5020d82b75 100644 --- a/packages/playwright-core/src/utils/localUtils.ts +++ b/packages/playwright-core/src/common/localUtils.ts @@ -20,11 +20,11 @@ import * as path from 'path'; import { removeFolders } from './fileUtils'; import { HarBackend } from './harBackend'; -import { ManualPromise } from './manualPromise'; -import { ZipFile } from './zipFile'; +import { ManualPromise } from '../utils/isomorphic/manualPromise'; +import { ZipFile } from '../utils/zipFile'; import { yauzl, yazl } from '../zipBundle'; -import { serializeClientSideCallMetadata } from '../utils'; -import { assert, calculateSha1 } from '../utils'; +import { serializeClientSideCallMetadata } from '../utils/isomorphic/traceUtils'; +import { assert } from '../utils/isomorphic/debug'; import type { Platform } from './platform'; import type * as channels from '@protocol/channels'; @@ -77,7 +77,7 @@ export async function zip(platform: Platform, stackSessions: Map, params: channels.LocalUtilsHarOpenParams): Promise { +export async function harOpen(platform: Platform, harBackends: Map, params: channels.LocalUtilsHarOpenParams): Promise { let harBackend: HarBackend; if (params.file.endsWith('.zip')) { const zipFile = new ZipFile(params.file); @@ -147,10 +147,10 @@ export async function harOpen(harBackends: Map, params: chan return { error: 'Specified archive does not have a .har file' }; const har = await zipFile.read(harEntryName); const harFile = JSON.parse(har.toString()) as har.HARFile; - harBackend = new HarBackend(harFile, null, zipFile); + harBackend = new HarBackend(platform, harFile, null, zipFile); } else { const harFile = JSON.parse(await fs.promises.readFile(params.file, 'utf-8')) as har.HARFile; - harBackend = new HarBackend(harFile, path.dirname(params.file), null); + harBackend = new HarBackend(platform, harFile, path.dirname(params.file), null); } harBackends.set(harBackend.id, harBackend); return { harId: harBackend.id }; diff --git a/packages/playwright-core/src/common/platform.ts b/packages/playwright-core/src/common/platform.ts new file mode 100644 index 0000000000..64cef77aab --- /dev/null +++ b/packages/playwright-core/src/common/platform.ts @@ -0,0 +1,102 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as crypto from 'crypto'; +import * as fs from 'fs'; +import * as path from 'path'; + +import { webColors, noColors } from '../utils/isomorphic/colors'; + +import type { Colors } from '../utils/isomorphic/colors'; + + +export type Platform = { + calculateSha1(text: string): Promise; + colors: Colors; + createGuid: () => string; + fs: () => typeof fs; + inspectCustom: symbol | undefined; + isLogEnabled(name: 'api' | 'channel'): boolean; + log(name: 'api' | 'channel', message: string | Error | object): void; + path: () => typeof path; + pathSeparator: string; + ws?: (url: string) => WebSocket; +}; + +export const webPlatform: Platform = { + calculateSha1: async (text: string) => { + const bytes = new TextEncoder().encode(text); + const hashBuffer = await crypto.subtle.digest('SHA-1', bytes); + return Array.from(new Uint8Array(hashBuffer), b => b.toString(16).padStart(2, '0')).join(''); + }, + + colors: webColors, + + createGuid: () => { + return Array.from(crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join(''); + }, + + fs: () => { + throw new Error('File system is not available'); + }, + + inspectCustom: undefined, + + + isLogEnabled(name: 'api' | 'channel') { + return false; + }, + + log(name: 'api' | 'channel', message: string | Error | object) {}, + + path: () => { + throw new Error('Path module is not available'); + }, + + pathSeparator: '/', + + ws: (url: string) => new WebSocket(url), +}; + +export const emptyPlatform: Platform = { + calculateSha1: async () => { + throw new Error('Not implemented'); + }, + + colors: noColors, + + createGuid: () => { + throw new Error('Not implemented'); + }, + + fs: () => { + throw new Error('Not implemented'); + }, + + inspectCustom: undefined, + + isLogEnabled(name: 'api' | 'channel') { + return false; + }, + + log(name: 'api' | 'channel', message: string | Error | object) { }, + + path: () => { + throw new Error('Function not implemented.'); + }, + + pathSeparator: '/' +}; diff --git a/packages/playwright-core/src/inProcessFactory.ts b/packages/playwright-core/src/inProcessFactory.ts index 296c742127..81b40afe19 100644 --- a/packages/playwright-core/src/inProcessFactory.ts +++ b/packages/playwright-core/src/inProcessFactory.ts @@ -14,17 +14,29 @@ * limitations under the License. */ +import path from 'path'; + import { AndroidServerLauncherImpl } from './androidServerImpl'; import { BrowserServerLauncherImpl } from './browserServerImpl'; import { Connection } from './client/connection'; 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 type { Playwright as PlaywrightAPI } from './client/playwright'; import type { Language } from './utils'; -import type { Platform } from './utils/platform'; +import type { Platform } from './common/platform'; + export function createInProcessPlaywright(platform: Platform): PlaywrightAPI { const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript' }); + setDebugMode(getFromENV('PWDEBUG') || ''); + setPlatformForSelectors(nodePlatform); + + setLibraryStackPrefix(path.join(__dirname, '..')); const clientConnection = new Connection(undefined, platform, undefined, []); clientConnection.useRawBuffers(); diff --git a/packages/playwright-core/src/inprocess.ts b/packages/playwright-core/src/inprocess.ts index c057f7b5c0..fc0550e924 100644 --- a/packages/playwright-core/src/inprocess.ts +++ b/packages/playwright-core/src/inprocess.ts @@ -15,6 +15,6 @@ */ import { createInProcessPlaywright } from './inProcessFactory'; -import { nodePlatform } from './utils/platform'; +import { nodePlatform } from './server/utils/nodePlatform'; module.exports = createInProcessPlaywright(nodePlatform); diff --git a/packages/playwright-core/src/outofprocess.ts b/packages/playwright-core/src/outofprocess.ts index 3af4065e1d..c24decb900 100644 --- a/packages/playwright-core/src/outofprocess.ts +++ b/packages/playwright-core/src/outofprocess.ts @@ -18,9 +18,9 @@ import * as childProcess from 'child_process'; import * as path from 'path'; import { Connection } from './client/connection'; -import { PipeTransport } from './protocol/transport'; -import { ManualPromise } from './utils/manualPromise'; -import { nodePlatform } from './utils/platform'; +import { PipeTransport } from './utils/pipeTransport'; +import { ManualPromise } from './utils/isomorphic/manualPromise'; +import { nodePlatform } from './server/utils/nodePlatform'; import type { Playwright } from './client/playwright'; diff --git a/packages/playwright-core/src/remote/DEPS.list b/packages/playwright-core/src/remote/DEPS.list index 62660da37d..bf3843dca0 100644 --- a/packages/playwright-core/src/remote/DEPS.list +++ b/packages/playwright-core/src/remote/DEPS.list @@ -6,4 +6,5 @@ ../server/dispatchers/ ../server/utils/ ../utils/ +../utils/isomorphic ../utilsBundle.ts diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index e276873668..00ca33b353 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -23,7 +23,7 @@ import { serverSideCallMetadata } from '../server/instrumentation'; import { assert, isUnderTest } from '../utils'; import { startProfiling, stopProfiling } from '../server/utils/profiler'; import { monotonicTime } from '../utils'; -import { debugLogger } from '../utils/debugLogger'; +import { debugLogger } from '../server/utils/debugLogger'; import type { DispatcherScope, Playwright } from '../server'; import type { LaunchOptions } from '../server/types'; diff --git a/packages/playwright-core/src/remote/playwrightServer.ts b/packages/playwright-core/src/remote/playwrightServer.ts index 06193a618a..7f6a58a0a5 100644 --- a/packages/playwright-core/src/remote/playwrightServer.ts +++ b/packages/playwright-core/src/remote/playwrightServer.ts @@ -16,11 +16,11 @@ import { PlaywrightConnection } from './playwrightConnection'; import { createPlaywright } from '../server/playwright'; -import { debugLogger } from '../utils/debugLogger'; -import { Semaphore } from '../utils/semaphore'; +import { debugLogger } from '../server/utils/debugLogger'; +import { Semaphore } from '../utils/isomorphic/semaphore'; import { WSServer } from '../server/utils/wsServer'; import { wrapInASCIIBox } from '../server/utils/ascii'; -import { getPlaywrightVersion } from '../utils/userAgent'; +import { getPlaywrightVersion } from '../server/utils/userAgent'; import type { ClientType } from './playwrightConnection'; import type { SocksProxy } from '../server/utils/socksProxy'; diff --git a/packages/playwright-core/src/server/android/DEPS.list b/packages/playwright-core/src/server/android/DEPS.list index b45c3a16a7..b852561b95 100644 --- a/packages/playwright-core/src/server/android/DEPS.list +++ b/packages/playwright-core/src/server/android/DEPS.list @@ -3,6 +3,7 @@ ../../common/ ../../protocol/ ../../utils/ +../../utils/isomorphic/ ../../utilsBundle.ts ../chromium/ ../utils diff --git a/packages/playwright-core/src/server/android/android.ts b/packages/playwright-core/src/server/android/android.ts index e47e115112..c1516dea76 100644 --- a/packages/playwright-core/src/server/android/android.ts +++ b/packages/playwright-core/src/server/android/android.ts @@ -19,10 +19,13 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { TimeoutSettings } from '../../common/timeoutSettings'; -import { PipeTransport } from '../../protocol/transport'; -import { createGuid, getPackageManagerExecCommand, isUnderTest, makeWaitForNextTask } from '../../utils'; -import { RecentLogsCollector } from '../../utils/debugLogger'; +import { TimeoutSettings } from '../../utils/isomorphic/timeoutSettings'; +import { PipeTransport } from '../../utils/pipeTransport'; +import { createGuid } from '../utils/crypto'; +import { isUnderTest } from '../../utils/isomorphic/debug'; +import { getPackageManagerExecCommand } from '../utils/env'; +import { makeWaitForNextTask } from '../../utils/task'; +import { RecentLogsCollector } from '../utils/debugLogger'; import { debug } from '../../utilsBundle'; import { wsReceiver, wsSender } from '../../utilsBundle'; import { validateBrowserContextOptions } from '../browserContext'; diff --git a/packages/playwright-core/src/server/android/backendAdb.ts b/packages/playwright-core/src/server/android/backendAdb.ts index a5f28f1d35..036c74703e 100644 --- a/packages/playwright-core/src/server/android/backendAdb.ts +++ b/packages/playwright-core/src/server/android/backendAdb.ts @@ -17,7 +17,8 @@ import { EventEmitter } from 'events'; import * as net from 'net'; -import { assert, createGuid } from '../../utils'; +import { assert } from '../../utils/isomorphic/debug'; +import { createGuid } from '../utils/crypto'; import { debug } from '../../utilsBundle'; import type { Backend, DeviceBackend, SocketBackend } from './android'; diff --git a/packages/playwright-core/src/server/artifact.ts b/packages/playwright-core/src/server/artifact.ts index 26b97d4d56..703e2d9e89 100644 --- a/packages/playwright-core/src/server/artifact.ts +++ b/packages/playwright-core/src/server/artifact.ts @@ -19,7 +19,7 @@ import * as fs from 'fs'; import { assert } from '../utils'; import { TargetClosedError } from './errors'; import { SdkObject } from './instrumentation'; -import { ManualPromise } from '../utils/manualPromise'; +import { ManualPromise } from '../utils/isomorphic/manualPromise'; type SaveCallback = (localPath: string, error?: Error) => Promise; type CancelCallback = () => Promise; diff --git a/packages/playwright-core/src/server/bidi/bidiBrowser.ts b/packages/playwright-core/src/server/bidi/bidiBrowser.ts index 45c12bc347..5c1cb400d1 100644 --- a/packages/playwright-core/src/server/bidi/bidiBrowser.ts +++ b/packages/playwright-core/src/server/bidi/bidiBrowser.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { eventsHelper } from '../../utils/eventsHelper'; +import { eventsHelper } from '../utils/eventsHelper'; import { Browser } from '../browser'; import { BrowserContext, assertBrowserContextIsNotOwned } from '../browserContext'; import * as network from '../network'; @@ -23,7 +23,7 @@ import { bidiBytesValueToString } from './bidiNetworkManager'; import { BidiPage } from './bidiPage'; import * as bidi from './third_party/bidiProtocol'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type { BrowserOptions } from '../browser'; import type { SdkObject } from '../instrumentation'; import type { InitScript, Page } from '../page'; diff --git a/packages/playwright-core/src/server/bidi/bidiConnection.ts b/packages/playwright-core/src/server/bidi/bidiConnection.ts index 87b3f59c9b..0882836c38 100644 --- a/packages/playwright-core/src/server/bidi/bidiConnection.ts +++ b/packages/playwright-core/src/server/bidi/bidiConnection.ts @@ -16,11 +16,11 @@ import { EventEmitter } from 'events'; -import { debugLogger } from '../../utils/debugLogger'; +import { debugLogger } from '../utils/debugLogger'; import { helper } from '../helper'; import { ProtocolError } from '../protocolError'; -import type { RecentLogsCollector } from '../../utils/debugLogger'; +import type { RecentLogsCollector } from '../utils/debugLogger'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import type { ProtocolLogger } from '../types'; import type * as bidiCommands from './third_party/bidiCommands'; diff --git a/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts b/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts index ec0cb681a0..aea4672196 100644 --- a/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts +++ b/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts @@ -16,6 +16,7 @@ import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers'; import * as js from '../javascript'; +import * as dom from '../dom'; import { BidiDeserializer } from './third_party/bidiDeserializer'; import * as bidi from './third_party/bidiProtocol'; import { BidiSerializer } from './third_party/bidiSerializer'; @@ -137,7 +138,45 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate { }); } - async rawCallFunction(functionDeclaration: string, arg: bidi.Script.LocalValue): Promise { + + async nodeIdForElementHandle(handle: dom.ElementHandle): Promise { + const shared = await this._remoteValueForReference({ handle: handle._objectId }); + // TODO: store sharedId in the handle. + if (!('sharedId' in shared)) + throw new Error('Element is not a node'); + return { + sharedId: shared.sharedId!, + }; + } + + async remoteObjectForNodeId(nodeId: bidi.Script.SharedReference): Promise { + const result = await this._remoteValueForReference(nodeId); + if ('handle' in result) + return { objectId: result.handle!, ...result }; + throw new Error('Can\'t get remote object for nodeId'); + } + + async contentFrameIdForFrame(handle: dom.ElementHandle) { + const contentWindow = await this._rawCallFunction('e => e.contentWindow', { handle: handle._objectId }); + if (contentWindow?.type === 'window') + return contentWindow.value.context; + return null; + } + + async frameIdForWindowHandle(handle: js.JSHandle): Promise { + if (!handle._objectId) + throw new Error('JSHandle is not a DOM node handle'); + const contentWindow = await this._remoteValueForReference({ handle: handle._objectId }); + if (contentWindow.type === 'window') + return contentWindow.value.context; + return null; + } + + private async _remoteValueForReference(reference: bidi.Script.RemoteReference) { + return await this._rawCallFunction('e => e', reference); + } + + private async _rawCallFunction(functionDeclaration: string, arg: bidi.Script.LocalValue): Promise { const response = await this._session.send('script.callFunction', { functionDeclaration, target: this._target, diff --git a/packages/playwright-core/src/server/bidi/bidiInput.ts b/packages/playwright-core/src/server/bidi/bidiInput.ts index e460a9f3ef..e40b13bb2e 100644 --- a/packages/playwright-core/src/server/bidi/bidiInput.ts +++ b/packages/playwright-core/src/server/bidi/bidiInput.ts @@ -79,6 +79,9 @@ export class RawMouseImpl implements input.RawMouse { } async move(x: number, y: number, button: types.MouseButton | 'none', buttons: Set, modifiers: Set, forClick: boolean): Promise { + // Bidi throws when x/y are not integers. + x = Math.floor(x); + y = Math.floor(y); await this._performActions([{ type: 'pointerMove', x, y }]); } @@ -91,6 +94,9 @@ export class RawMouseImpl implements input.RawMouse { } async wheel(x: number, y: number, buttons: Set, modifiers: Set, deltaX: number, deltaY: number): Promise { + // Bidi throws when x/y are not integers. + x = Math.floor(x); + y = Math.floor(y); await this._session.send('input.performActions', { context: this._session.sessionId, actions: [ diff --git a/packages/playwright-core/src/server/bidi/bidiNetworkManager.ts b/packages/playwright-core/src/server/bidi/bidiNetworkManager.ts index 185eee86a0..5da061fcdf 100644 --- a/packages/playwright-core/src/server/bidi/bidiNetworkManager.ts +++ b/packages/playwright-core/src/server/bidi/bidiNetworkManager.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { eventsHelper } from '../../utils/eventsHelper'; +import { eventsHelper } from '../utils/eventsHelper'; import { parseRawCookie } from '../cookieStore'; import * as network from '../network'; import * as bidi from './third_party/bidiProtocol'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type * as frames from '../frames'; import type { Page } from '../page'; import type * as types from '../types'; diff --git a/packages/playwright-core/src/server/bidi/bidiOverCdp.ts b/packages/playwright-core/src/server/bidi/bidiOverCdp.ts index adf7c9afbf..8acd45563d 100644 --- a/packages/playwright-core/src/server/bidi/bidiOverCdp.ts +++ b/packages/playwright-core/src/server/bidi/bidiOverCdp.ts @@ -17,7 +17,7 @@ import * as bidiMapper from 'chromium-bidi/lib/cjs/bidiMapper/BidiMapper'; import * as bidiCdpConnection from 'chromium-bidi/lib/cjs/cdp/CdpConnection'; -import { debugLogger } from '../../utils/debugLogger'; +import { debugLogger } from '../utils/debugLogger'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import type { ChromiumBidi } from 'chromium-bidi/lib/cjs/protocol/protocol'; diff --git a/packages/playwright-core/src/server/bidi/bidiPage.ts b/packages/playwright-core/src/server/bidi/bidiPage.ts index dd2d276c18..badd68d1a1 100644 --- a/packages/playwright-core/src/server/bidi/bidiPage.ts +++ b/packages/playwright-core/src/server/bidi/bidiPage.ts @@ -15,7 +15,7 @@ */ import { assert } from '../../utils'; -import { eventsHelper } from '../../utils/eventsHelper'; +import { eventsHelper } from '../utils/eventsHelper'; import { BrowserContext } from '../browserContext'; import * as dialog from '../dialog'; import * as dom from '../dom'; @@ -26,7 +26,7 @@ import { BidiNetworkManager } from './bidiNetworkManager'; import { BidiPDF } from './bidiPdf'; import * as bidi from './third_party/bidiProtocol'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type * as accessibility from '../accessibility'; import type * as frames from '../frames'; import type { InitScript, PageDelegate } from '../page'; @@ -413,17 +413,22 @@ export class BidiPage implements PageDelegate { async getContentFrame(handle: dom.ElementHandle): Promise { const executionContext = toBidiExecutionContext(handle._context); - const contentWindow = await executionContext.rawCallFunction('e => e.contentWindow', { handle: handle._objectId }); - if (contentWindow.type === 'window') { - const frameId = contentWindow.value.context; - const result = this._page._frameManager.frame(frameId); - return result; - } - return null; + const frameId = await executionContext.contentFrameIdForFrame(handle); + if (!frameId) + return null; + return this._page._frameManager.frame(frameId); } async getOwnerFrame(handle: dom.ElementHandle): Promise { - throw new Error('Method not implemented.'); + // TODO: switch to utility world? + const windowHandle = await handle.evaluateHandle(node => { + const doc = node.ownerDocument ?? node as Document; + return doc.defaultView; + }); + if (!windowHandle) + return null; + const executionContext = toBidiExecutionContext(handle._context); + return executionContext.frameIdForWindowHandle(windowHandle); } isElementHandle(remoteObject: bidi.Script.RemoteValue): boolean { @@ -515,24 +520,25 @@ export class BidiPage implements PageDelegate { } async setInputFiles(handle: dom.ElementHandle, files: types.FilePayload[]): Promise { - throw new Error('Method not implemented.'); + throw new Error('Setting FilePayloads is not supported in Bidi.'); } async setInputFilePaths(handle: dom.ElementHandle, paths: string[]): Promise { - throw new Error('Method not implemented.'); + const fromContext = toBidiExecutionContext(handle._context); + await this._session.send('input.setFiles', { + context: this._session.sessionId, + element: await fromContext.nodeIdForElementHandle(handle), + files: paths, + }); } async adoptElementHandle(handle: dom.ElementHandle, to: dom.FrameExecutionContext): Promise> { const fromContext = toBidiExecutionContext(handle._context); - const shared = await fromContext.rawCallFunction('x => x', { handle: handle._objectId }); - // TODO: store sharedId in the handle. - if (!('sharedId' in shared)) - throw new Error('Element is not a node'); - const sharedId = shared.sharedId!; + const nodeId = await fromContext.nodeIdForElementHandle(handle); const executionContext = toBidiExecutionContext(to); - const result = await executionContext.rawCallFunction('x => x', { sharedId }); - if ('handle' in result) - return to.createHandle({ objectId: result.handle!, ...result }) as dom.ElementHandle; + const objectId = await executionContext.remoteObjectForNodeId(nodeId); + if (objectId) + return to.createHandle(objectId) as dom.ElementHandle; throw new Error('Failed to adopt element handle.'); } diff --git a/packages/playwright-core/src/server/browser.ts b/packages/playwright-core/src/server/browser.ts index d99a3f8f8a..7cf00f73c8 100644 --- a/packages/playwright-core/src/server/browser.ts +++ b/packages/playwright-core/src/server/browser.ts @@ -24,7 +24,7 @@ import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor'; import type { CallMetadata } from './instrumentation'; import type * as types from './types'; import type { ProxySettings } from './types'; -import type { RecentLogsCollector } from '../utils/debugLogger'; +import type { RecentLogsCollector } from './utils/debugLogger'; import type * as channels from '@protocol/channels'; import type { ChildProcess } from 'child_process'; diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 6241b8d855..fd50994343 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -18,8 +18,9 @@ import * as fs from 'fs'; import * as path from 'path'; -import { TimeoutSettings } from '../common/timeoutSettings'; -import { createGuid, debugMode } from '../utils'; +import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; +import { createGuid } from './utils/crypto'; +import { debugMode } from '../utils/isomorphic/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 9952aef2f4..7fe3844c7d 100644 --- a/packages/playwright-core/src/server/browserType.ts +++ b/packages/playwright-core/src/server/browserType.ts @@ -19,7 +19,7 @@ import * as os from 'os'; import * as path from 'path'; import { normalizeProxySettings, validateBrowserContextOptions } from './browserContext'; -import { DEFAULT_TIMEOUT, TimeoutSettings } from '../common/timeoutSettings'; +import { DEFAULT_TIMEOUT, TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; import { ManualPromise, assert, debugMode } from '../utils'; import { existsAsync } from './utils/fileUtils'; import { helper } from './helper'; @@ -31,7 +31,7 @@ import { isProtocolError } from './protocolError'; import { registry } from './registry'; import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor'; import { WebSocketTransport } from './transport'; -import { RecentLogsCollector } from '../utils/debugLogger'; +import { RecentLogsCollector } from './utils/debugLogger'; import type { Browser, BrowserOptions, BrowserProcess } from './browser'; import type { BrowserContext } from './browserContext'; diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index ff5c4a826c..3f06e135cd 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -22,13 +22,13 @@ import * as path from 'path'; import { chromiumSwitches } from './chromiumSwitches'; import { CRBrowser } from './crBrowser'; import { kBrowserCloseMessageId } from './crConnection'; -import { TimeoutSettings } from '../../common/timeoutSettings'; +import { TimeoutSettings } from '../../utils/isomorphic/timeoutSettings'; import { debugMode, headersArrayToObject, headersObjectToArray, } from '../../utils'; import { wrapInASCIIBox } from '../utils/ascii'; -import { RecentLogsCollector } from '../../utils/debugLogger'; -import { ManualPromise } from '../../utils/manualPromise'; +import { RecentLogsCollector } from '../utils/debugLogger'; +import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { fetchData } from '../utils/network'; -import { getUserAgent } from '../../utils/userAgent'; +import { getUserAgent } from '../utils/userAgent'; import { validateBrowserContextOptions } from '../browserContext'; import { BrowserType, kNoXServerRunningError } from '../browserType'; import { BrowserReadyState } from '../browserType'; diff --git a/packages/playwright-core/src/server/chromium/chromiumSwitches.ts b/packages/playwright-core/src/server/chromium/chromiumSwitches.ts index 9d64d58f1c..0f6415745e 100644 --- a/packages/playwright-core/src/server/chromium/chromiumSwitches.ts +++ b/packages/playwright-core/src/server/chromium/chromiumSwitches.ts @@ -37,11 +37,10 @@ export const chromiumSwitches = [ // PaintHolding - https://github.com/microsoft/playwright/issues/28023 // ThirdPartyStoragePartitioning - https://github.com/microsoft/playwright/issues/32230 // LensOverlay - Hides the Lens feature in the URL address bar. Its not working in unofficial builds. - // PlzDedicatedWorker - https://github.com/microsoft/playwright/issues/31747 // DeferRendererTasksAfterInput - this makes Page.frameScheduledNavigation arrive much later after a click, // making our navigation auto-wait after click not working. Can be removed once we deperecate noWaitAfter. // See https://github.com/microsoft/playwright/pull/34372. - '--disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate,HttpsUpgrades,PaintHolding,ThirdPartyStoragePartitioning,LensOverlay,PlzDedicatedWorker,DeferRendererTasksAfterInput', + '--disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate,HttpsUpgrades,PaintHolding,ThirdPartyStoragePartitioning,LensOverlay,DeferRendererTasksAfterInput', '--allow-pre-commit-input', '--disable-hang-monitor', '--disable-ipc-flooding-protection', diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index 51ad0ae887..7cc6030486 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -17,7 +17,8 @@ import * as path from 'path'; -import { assert, createGuid } from '../../utils'; +import { assert } from '../../utils/isomorphic/debug'; +import { createGuid } from '../utils/crypto'; import { Artifact } from '../artifact'; import { Browser } from '../browser'; import { BrowserContext, assertBrowserContextIsNotOwned, verifyGeolocation } from '../browserContext'; diff --git a/packages/playwright-core/src/server/chromium/crConnection.ts b/packages/playwright-core/src/server/chromium/crConnection.ts index 424d64a500..98c566c82a 100644 --- a/packages/playwright-core/src/server/chromium/crConnection.ts +++ b/packages/playwright-core/src/server/chromium/crConnection.ts @@ -18,14 +18,14 @@ import { EventEmitter } from 'events'; import { assert, eventsHelper } from '../../utils'; -import { debugLogger } from '../../utils/debugLogger'; +import { debugLogger } from '../utils/debugLogger'; import { helper } from '../helper'; import { ProtocolError } from '../protocolError'; import type { RegisteredListener } from '../../utils'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import type { Protocol } from './protocol'; -import type { RecentLogsCollector } from '../../utils/debugLogger'; +import type { RecentLogsCollector } from '../utils/debugLogger'; import type { ProtocolLogger } from '../types'; diff --git a/packages/playwright-core/src/server/chromium/crCoverage.ts b/packages/playwright-core/src/server/chromium/crCoverage.ts index 81dd2c0f4c..0b4984211a 100644 --- a/packages/playwright-core/src/server/chromium/crCoverage.ts +++ b/packages/playwright-core/src/server/chromium/crCoverage.ts @@ -16,11 +16,11 @@ */ import { assert } from '../../utils'; -import { eventsHelper } from '../../utils/eventsHelper'; +import { eventsHelper } from '../utils/eventsHelper'; import type { CRSession } from './crConnection'; import type { Protocol } from './protocol'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type * as channels from '@protocol/channels'; diff --git a/packages/playwright-core/src/server/chromium/crExecutionContext.ts b/packages/playwright-core/src/server/chromium/crExecutionContext.ts index bd1b64099b..910932caf3 100644 --- a/packages/playwright-core/src/server/chromium/crExecutionContext.ts +++ b/packages/playwright-core/src/server/chromium/crExecutionContext.ts @@ -16,7 +16,7 @@ */ import { getExceptionMessage, releaseObject } from './crProtocolHelper'; -import { rewriteErrorMessage } from '../../utils/stackTrace'; +import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace'; import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers'; import * as js from '../javascript'; import { isSessionClosedError } from '../protocolError'; diff --git a/packages/playwright-core/src/server/chromium/crNetworkManager.ts b/packages/playwright-core/src/server/chromium/crNetworkManager.ts index b9a5447189..812f62dfac 100644 --- a/packages/playwright-core/src/server/chromium/crNetworkManager.ts +++ b/packages/playwright-core/src/server/chromium/crNetworkManager.ts @@ -16,14 +16,14 @@ */ import { assert, headersArrayToObject, headersObjectToArray } from '../../utils'; -import { eventsHelper } from '../../utils/eventsHelper'; +import { eventsHelper } from '../utils/eventsHelper'; import { helper } from '../helper'; import * as network from '../network'; import { isProtocolError, isSessionClosedError } from '../protocolError'; import type { CRSession } from './crConnection'; import type { Protocol } from './protocol'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type * as contexts from '../browserContext'; import type * as frames from '../frames'; import type { Page } from '../page'; diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 65122407a0..34470470dc 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -17,9 +17,10 @@ import * as path from 'path'; -import { assert, createGuid } from '../../utils'; -import { eventsHelper } from '../../utils/eventsHelper'; -import { rewriteErrorMessage } from '../../utils/stackTrace'; +import { assert } from '../../utils/isomorphic/debug'; +import { createGuid } from '../utils/crypto'; +import { eventsHelper } from '../utils/eventsHelper'; +import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace'; import * as dialog from '../dialog'; import * as dom from '../dom'; import * as frames from '../frames'; @@ -45,7 +46,7 @@ import { isSessionClosedError } from '../protocolError'; import type { CRSession } from './crConnection'; import type { Protocol } from './protocol'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type { InitScript, PageDelegate } from '../page'; import type { Progress } from '../progress'; import type * as types from '../types'; diff --git a/packages/playwright-core/src/server/chromium/crProtocolHelper.ts b/packages/playwright-core/src/server/chromium/crProtocolHelper.ts index 570b404d9b..f9e1409a79 100644 --- a/packages/playwright-core/src/server/chromium/crProtocolHelper.ts +++ b/packages/playwright-core/src/server/chromium/crProtocolHelper.ts @@ -17,7 +17,7 @@ import * as fs from 'fs'; -import { splitErrorMessage } from '../../utils/stackTrace'; +import { splitErrorMessage } from '../../utils/isomorphic/stackTrace'; import { mkdirIfNeeded } from '../utils/fileUtils'; import type { CRSession } from './crConnection'; diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index 3488964e8e..d3f110f54e 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -31,7 +31,8 @@ import { Recorder } from '../recorder'; import { TracingDispatcher } from './tracingDispatcher'; import { WebSocketRouteDispatcher } from './webSocketRouteDispatcher'; import { WritableStreamDispatcher } from './writableStreamDispatcher'; -import { createGuid, urlMatches } from '../../utils'; +import { createGuid } from '../utils/crypto'; +import { urlMatches } from '../../utils/isomorphic/urlMatch'; import { RecorderApp } from '../recorder/recorderApp'; import type { Artifact } from '../artifact'; diff --git a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts index a3acb24bd1..1636d9f778 100644 --- a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts @@ -19,7 +19,7 @@ import { DebugController } from '../debugController'; import { Dispatcher } from './dispatcher'; import type { DispatcherConnection, RootDispatcher } from './dispatcher'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type * as channels from '@protocol/channels'; diff --git a/packages/playwright-core/src/server/dispatchers/dispatcher.ts b/packages/playwright-core/src/server/dispatchers/dispatcher.ts index 34f2614e08..9912de9849 100644 --- a/packages/playwright-core/src/server/dispatchers/dispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dispatcher.ts @@ -16,7 +16,7 @@ import { EventEmitter } from 'events'; -import { eventsHelper } from '../..//utils/eventsHelper'; +import { eventsHelper } from '../utils/eventsHelper'; import { ValidationError, createMetadataValidator, findValidator } from '../../protocol/validator'; import { LongStandingScope, assert, compressCallLog, isUnderTest, monotonicTime, rewriteErrorMessage } from '../../utils'; import { TargetClosedError, isTargetClosedError, serializeError } from '../errors'; @@ -25,7 +25,7 @@ import { isProtocolError } from '../protocolError'; import type { CallMetadata } from '../instrumentation'; import type { PlaywrightDispatcher } from './playwrightDispatcher'; -import type { RegisteredListener } from '../..//utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type { ValidatorContext } from '../../protocol/validator'; import type * as channels from '@protocol/channels'; diff --git a/packages/playwright-core/src/server/dispatchers/jsonPipeDispatcher.ts b/packages/playwright-core/src/server/dispatchers/jsonPipeDispatcher.ts index 67f36e94a1..75c1c89fad 100644 --- a/packages/playwright-core/src/server/dispatchers/jsonPipeDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/jsonPipeDispatcher.ts @@ -15,7 +15,7 @@ */ import { Dispatcher } from './dispatcher'; -import { createGuid } from '../../utils'; +import { createGuid } from '../utils/crypto'; import type { LocalUtilsDispatcher } from './localUtilsDispatcher'; import type * as channels from '@protocol/channels'; diff --git a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts index 1b8301a1ac..b582aa8788 100644 --- a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts @@ -16,9 +16,9 @@ import { Dispatcher } from './dispatcher'; import { SdkObject } from '../../server/instrumentation'; -import * as localUtils from '../../utils/localUtils'; -import { nodePlatform } from '../../utils/platform'; -import { getUserAgent } from '../../utils/userAgent'; +import * as localUtils from '../../common/localUtils'; +import { nodePlatform } from '../utils/nodePlatform'; +import { getUserAgent } from '../utils/userAgent'; import { deviceDescriptors as descriptors } from '../deviceDescriptors'; import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher'; import { Progress, ProgressController } from '../progress'; @@ -26,7 +26,7 @@ import { SocksInterceptor } from '../socksInterceptor'; import { WebSocketTransport } from '../transport'; import { fetchData } from '../utils/network'; -import type { HarBackend } from '../../utils/harBackend'; +import type { HarBackend } from '../../common/harBackend'; import type { CallMetadata } from '../instrumentation'; import type { Playwright } from '../playwright'; import type { RootDispatcher } from './dispatcher'; @@ -54,7 +54,7 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels. } async harOpen(params: channels.LocalUtilsHarOpenParams, metadata: CallMetadata): Promise { - return await localUtils.harOpen(this._harBackends, params); + return await localUtils.harOpen(nodePlatform, this._harBackends, params); } async harLookup(params: channels.LocalUtilsHarLookupParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts index 1b8a0677dd..166284366c 100644 --- a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts @@ -25,7 +25,8 @@ import { RequestDispatcher } from './networkDispatchers'; import { ResponseDispatcher } from './networkDispatchers'; import { RouteDispatcher, WebSocketDispatcher } from './networkDispatchers'; import { WebSocketRouteDispatcher } from './webSocketRouteDispatcher'; -import { createGuid, urlMatches } from '../../utils'; +import { createGuid } from '../utils/crypto'; +import { urlMatches } from '../../utils/isomorphic/urlMatch'; import type { Artifact } from '../artifact'; import type { BrowserContext } from '../browserContext'; diff --git a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts index 80a1f6a696..6735e9c284 100644 --- a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts @@ -25,12 +25,12 @@ import { ElectronDispatcher } from './electronDispatcher'; import { LocalUtilsDispatcher } from './localUtilsDispatcher'; import { APIRequestContextDispatcher } from './networkDispatchers'; import { SelectorsDispatcher } from './selectorsDispatcher'; -import { createGuid } from '../../utils'; -import { eventsHelper } from '../../utils/eventsHelper'; +import { createGuid } from '../utils/crypto'; +import { eventsHelper } from '../utils/eventsHelper'; import type { RootDispatcher } from './dispatcher'; import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../utils/socksProxy'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type { AndroidDevice } from '../android/android'; import type { Browser } from '../browser'; import type { Playwright } from '../playwright'; diff --git a/packages/playwright-core/src/server/dispatchers/streamDispatcher.ts b/packages/playwright-core/src/server/dispatchers/streamDispatcher.ts index 8ceb69e669..f3a0a413b7 100644 --- a/packages/playwright-core/src/server/dispatchers/streamDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/streamDispatcher.ts @@ -15,7 +15,8 @@ */ import { Dispatcher } from './dispatcher'; -import { ManualPromise, createGuid } from '../../utils'; +import { ManualPromise } from '../../utils/isomorphic/manualPromise'; +import { createGuid } from '../utils/crypto'; import type { ArtifactDispatcher } from './artifactDispatcher'; import type * as channels from '@protocol/channels'; diff --git a/packages/playwright-core/src/server/dispatchers/webSocketRouteDispatcher.ts b/packages/playwright-core/src/server/dispatchers/webSocketRouteDispatcher.ts index fea8d06eec..f31bf7659d 100644 --- a/packages/playwright-core/src/server/dispatchers/webSocketRouteDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/webSocketRouteDispatcher.ts @@ -18,8 +18,9 @@ import { Page } from '../page'; import { Dispatcher, existingDispatcher } from './dispatcher'; import { PageDispatcher } from './pageDispatcher'; import * as webSocketMockSource from '../../generated/webSocketMockSource'; -import { createGuid, urlMatches } from '../../utils'; -import { eventsHelper } from '../../utils/eventsHelper'; +import { createGuid } from '../utils/crypto'; +import { urlMatches } from '../../utils/isomorphic/urlMatch'; +import { eventsHelper } from '../utils/eventsHelper'; import type { BrowserContextDispatcher } from './browserContextDispatcher'; import type { BrowserContext } from '../browserContext'; diff --git a/packages/playwright-core/src/server/dispatchers/writableStreamDispatcher.ts b/packages/playwright-core/src/server/dispatchers/writableStreamDispatcher.ts index 7d8ce2e4e1..08757f5d88 100644 --- a/packages/playwright-core/src/server/dispatchers/writableStreamDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/writableStreamDispatcher.ts @@ -17,7 +17,7 @@ import * as fs from 'fs'; import { Dispatcher } from './dispatcher'; -import { createGuid } from '../../utils'; +import { createGuid } from '../utils/crypto'; import type { BrowserContextDispatcher } from './browserContextDispatcher'; import type * as channels from '@protocol/channels'; diff --git a/packages/playwright-core/src/server/electron/DEPS.list b/packages/playwright-core/src/server/electron/DEPS.list index 1d51da425e..1acb5c5a0e 100644 --- a/packages/playwright-core/src/server/electron/DEPS.list +++ b/packages/playwright-core/src/server/electron/DEPS.list @@ -2,5 +2,6 @@ ../ ../../common/ ../../utils/ +../../utils/isomorphic/ ../chromium/ ../utils \ No newline at end of file diff --git a/packages/playwright-core/src/server/electron/electron.ts b/packages/playwright-core/src/server/electron/electron.ts index ce4480b428..4713324ed1 100644 --- a/packages/playwright-core/src/server/electron/electron.ts +++ b/packages/playwright-core/src/server/electron/electron.ts @@ -19,11 +19,11 @@ import * as os from 'os'; import * as path from 'path'; import * as readline from 'readline'; -import { TimeoutSettings } from '../../common/timeoutSettings'; +import { TimeoutSettings } from '../../utils/isomorphic/timeoutSettings'; import { ManualPromise } from '../../utils'; import { wrapInASCIIBox } from '../utils/ascii'; -import { RecentLogsCollector } from '../../utils/debugLogger'; -import { eventsHelper } from '../../utils/eventsHelper'; +import { RecentLogsCollector } from '../utils/debugLogger'; +import { eventsHelper } from '../utils/eventsHelper'; import { validateBrowserContextOptions } from '../browserContext'; import { CRBrowser } from '../chromium/crBrowser'; import { CRConnection } from '../chromium/crConnection'; diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 13358856d9..6e1d6d02f1 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -21,9 +21,10 @@ import { TLSSocket } from 'tls'; import * as url from 'url'; import * as zlib from 'zlib'; -import { TimeoutSettings } from '../common/timeoutSettings'; -import { assert, constructURLBasedOnBaseURL, createGuid, eventsHelper, monotonicTime } from '../utils'; -import { getUserAgent } from '../utils/userAgent'; +import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; +import { assert, constructURLBasedOnBaseURL, eventsHelper, monotonicTime } from '../utils'; +import { createGuid } from './utils/crypto'; +import { getUserAgent } from './utils/userAgent'; import { HttpsProxyAgent, SocksProxyAgent } from '../utilsBundle'; import { BrowserContext, verifyClientCertificates } from './browserContext'; import { CookieStore, domainMatches, parseRawCookie } from './cookieStore'; diff --git a/packages/playwright-core/src/server/fileUploadUtils.ts b/packages/playwright-core/src/server/fileUploadUtils.ts index 1c53812a0a..908e7644ac 100644 --- a/packages/playwright-core/src/server/fileUploadUtils.ts +++ b/packages/playwright-core/src/server/fileUploadUtils.ts @@ -17,8 +17,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import { assert } from '../utils/debug'; -import { fileUploadSizeLimit } from '../utils/fileUtils'; +import { assert } from '../utils/isomorphic/debug'; +import { fileUploadSizeLimit } from '../common/fileUtils'; import { mime } from '../utilsBundle'; import type { WritableStreamDispatcher } from './dispatchers/writableStreamDispatcher'; diff --git a/packages/playwright-core/src/server/firefox/ffConnection.ts b/packages/playwright-core/src/server/firefox/ffConnection.ts index 6001754219..1477ace249 100644 --- a/packages/playwright-core/src/server/firefox/ffConnection.ts +++ b/packages/playwright-core/src/server/firefox/ffConnection.ts @@ -17,13 +17,13 @@ import { EventEmitter } from 'events'; -import { debugLogger } from '../../utils/debugLogger'; +import { debugLogger } from '../utils/debugLogger'; import { helper } from '../helper'; import { ProtocolError } from '../protocolError'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import type { Protocol } from './protocol'; -import type { RecentLogsCollector } from '../../utils/debugLogger'; +import type { RecentLogsCollector } from '../utils/debugLogger'; import type { ProtocolLogger } from '../types'; diff --git a/packages/playwright-core/src/server/firefox/ffExecutionContext.ts b/packages/playwright-core/src/server/firefox/ffExecutionContext.ts index 7de27727cd..5de8cddae3 100644 --- a/packages/playwright-core/src/server/firefox/ffExecutionContext.ts +++ b/packages/playwright-core/src/server/firefox/ffExecutionContext.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { rewriteErrorMessage } from '../../utils/stackTrace'; +import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace'; import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers'; import * as js from '../javascript'; import { isSessionClosedError } from '../protocolError'; diff --git a/packages/playwright-core/src/server/firefox/ffNetworkManager.ts b/packages/playwright-core/src/server/firefox/ffNetworkManager.ts index 95df0643d0..fbd7330739 100644 --- a/packages/playwright-core/src/server/firefox/ffNetworkManager.ts +++ b/packages/playwright-core/src/server/firefox/ffNetworkManager.ts @@ -15,12 +15,12 @@ * limitations under the License. */ -import { eventsHelper } from '../../utils/eventsHelper'; +import { eventsHelper } from '../utils/eventsHelper'; import * as network from '../network'; import type { FFSession } from './ffConnection'; import type { HeadersArray } from '../../server/types'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type * as frames from '../frames'; import type { Page } from '../page'; import type * as types from '../types'; diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index 0e1755a2da..22229dff3e 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { eventsHelper } from '../../utils/eventsHelper'; +import { eventsHelper } from '../utils/eventsHelper'; import * as dialog from '../dialog'; import * as dom from '../dom'; import { InitScript } from '../page'; @@ -25,15 +25,15 @@ import { FFSession } from './ffConnection'; import { FFExecutionContext } from './ffExecutionContext'; import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './ffInput'; import { FFNetworkManager } from './ffNetworkManager'; -import { debugLogger } from '../../utils/debugLogger'; -import { splitErrorMessage } from '../../utils/stackTrace'; +import { debugLogger } from '../utils/debugLogger'; +import { splitErrorMessage } from '../../utils/isomorphic/stackTrace'; import { BrowserContext } from '../browserContext'; import { TargetClosedError } from '../errors'; import type { Progress } from '../progress'; import type { FFBrowserContext } from './ffBrowser'; import type { Protocol } from './protocol'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type * as frames from '../frames'; import type { PageDelegate } from '../page'; import type * as types from '../types'; diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 2183d3f409..dee2ad7f08 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -29,10 +29,10 @@ import { ProgressController } from './progress'; import * as types from './types'; import { LongStandingScope, asLocator, assert, compressCallLog, constructURLBasedOnBaseURL, makeWaitForNextTask, monotonicTime } from '../utils'; import { isSessionClosedError } from './protocolError'; -import { debugLogger } from '../utils/debugLogger'; -import { eventsHelper } from '../utils/eventsHelper'; +import { debugLogger } from './utils/debugLogger'; +import { eventsHelper } from './utils/eventsHelper'; import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser'; -import { ManualPromise } from '../utils/manualPromise'; +import { ManualPromise } from '../utils/isomorphic/manualPromise'; import type { ConsoleMessage } from './console'; import type { Dialog } from './dialog'; @@ -40,7 +40,7 @@ import type { ElementStateWithoutStable, FrameExpectParams, InjectedScript } fro import type { CallMetadata } from './instrumentation'; import type { Progress } from './progress'; import type { ScreenshotOptions } from './screenshotter'; -import type { RegisteredListener } from '../utils/eventsHelper'; +import type { RegisteredListener } from './utils/eventsHelper'; import type { ParsedSelector } from '../utils/isomorphic/selectorParser'; import type * as channels from '@protocol/channels'; diff --git a/packages/playwright-core/src/server/har/harRecorder.ts b/packages/playwright-core/src/server/har/harRecorder.ts index 7cde24fa3a..12489f1c4c 100644 --- a/packages/playwright-core/src/server/har/harRecorder.ts +++ b/packages/playwright-core/src/server/har/harRecorder.ts @@ -19,8 +19,8 @@ import * as path from 'path'; import { Artifact } from '../artifact'; import { HarTracer } from './harTracer'; -import { createGuid } from '../../utils'; -import { ManualPromise } from '../../utils/manualPromise'; +import { createGuid } from '../utils/crypto'; +import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { yazl } from '../../zipBundle'; import type { BrowserContext } from '../browserContext'; diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index 0e3a66b938..b1ff48b73d 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -16,8 +16,8 @@ import { assert, calculateSha1, monotonicTime } from '../../utils'; import { getPlaywrightVersion, isTextualMimeType, urlMatches } from '../../utils'; -import { eventsHelper } from '../../utils/eventsHelper'; -import { ManualPromise } from '../../utils/manualPromise'; +import { eventsHelper } from '../utils/eventsHelper'; +import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { mime } from '../../utilsBundle'; import { BrowserContext } from '../browserContext'; import { APIRequestContext } from '../fetch'; @@ -25,7 +25,7 @@ import { Frame } from '../frames'; import { helper } from '../helper'; import * as network from '../network'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type { APIRequestEvent, APIRequestFinishedEvent } from '../fetch'; import type { Page } from '../page'; import type { Worker } from '../page'; diff --git a/packages/playwright-core/src/server/helper.ts b/packages/playwright-core/src/server/helper.ts index ccf9af12b0..e9fec62419 100644 --- a/packages/playwright-core/src/server/helper.ts +++ b/packages/playwright-core/src/server/helper.ts @@ -15,12 +15,12 @@ * limitations under the License. */ -import { debugLogger } from '../utils/debugLogger'; -import { eventsHelper } from '../utils/eventsHelper'; +import { debugLogger } from './utils/debugLogger'; +import { eventsHelper } from './utils/eventsHelper'; import type { Progress } from './progress'; import type * as types from './types'; -import type { RegisteredListener } from '../utils/eventsHelper'; +import type { RegisteredListener } from './utils/eventsHelper'; import type { EventEmitter } from 'events'; diff --git a/packages/playwright-core/src/server/instrumentation.ts b/packages/playwright-core/src/server/instrumentation.ts index b33ec542ec..076008a31e 100644 --- a/packages/playwright-core/src/server/instrumentation.ts +++ b/packages/playwright-core/src/server/instrumentation.ts @@ -16,7 +16,7 @@ import { EventEmitter } from 'events'; -import { createGuid } from '../utils'; +import { createGuid } from './utils/crypto'; import type { Browser } from './browser'; import type { BrowserContext } from './browserContext'; diff --git a/packages/playwright-core/src/server/javascript.ts b/packages/playwright-core/src/server/javascript.ts index 32ba84179e..a3d65f86a8 100644 --- a/packages/playwright-core/src/server/javascript.ts +++ b/packages/playwright-core/src/server/javascript.ts @@ -18,7 +18,7 @@ import { SdkObject } from './instrumentation'; import * as utilityScriptSource from '../generated/utilityScriptSource'; import { isUnderTest } from '../utils'; import { serializeAsCallArgument } from './isomorphic/utilityScriptSerializers'; -import { LongStandingScope } from '../utils/manualPromise'; +import { LongStandingScope } from '../utils/isomorphic/manualPromise'; import type * as dom from './dom'; import type { UtilityScript } from './injected/utilityScript'; diff --git a/packages/playwright-core/src/server/network.ts b/packages/playwright-core/src/server/network.ts index 81e23abd82..dfee496fc2 100644 --- a/packages/playwright-core/src/server/network.ts +++ b/packages/playwright-core/src/server/network.ts @@ -18,7 +18,7 @@ import { assert } from '../utils'; import { BrowserContext } from './browserContext'; import { APIRequestContext } from './fetch'; import { SdkObject } from './instrumentation'; -import { ManualPromise } from '../utils/manualPromise'; +import { ManualPromise } from '../utils/isomorphic/manualPromise'; import type * as contexts from './browserContext'; import type * as frames from './frames'; diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index ac7f6dfd81..d2db2e4211 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -28,13 +28,14 @@ import { parseEvaluationResultValue, source } from './isomorphic/utilityScriptSe import * as js from './javascript'; import { ProgressController } from './progress'; import { Screenshotter, validateScreenshotOptions } from './screenshotter'; -import { TimeoutSettings } from '../common/timeoutSettings'; -import { LongStandingScope, assert, compressCallLog, createGuid, trimStringWithEllipsis } from '../utils'; +import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; +import { LongStandingScope, assert, compressCallLog, trimStringWithEllipsis } from '../utils'; +import { createGuid } from './utils/crypto'; import { asLocator } from '../utils'; import { getComparator } from './utils/comparators'; -import { debugLogger } from '../utils/debugLogger'; +import { debugLogger } from './utils/debugLogger'; import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser'; -import { ManualPromise } from '../utils/manualPromise'; +import { ManualPromise } from '../utils/isomorphic/manualPromise'; import type { Artifact } from './artifact'; import type * as dom from './dom'; diff --git a/packages/playwright-core/src/server/pipeTransport.ts b/packages/playwright-core/src/server/pipeTransport.ts index 67bf126324..ba811e606e 100644 --- a/packages/playwright-core/src/server/pipeTransport.ts +++ b/packages/playwright-core/src/server/pipeTransport.ts @@ -16,7 +16,7 @@ */ import { makeWaitForNextTask } from '../utils'; -import { debugLogger } from '../utils/debugLogger'; +import { debugLogger } from './utils/debugLogger'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from './transport'; diff --git a/packages/playwright-core/src/server/progress.ts b/packages/playwright-core/src/server/progress.ts index 1b18089233..30db144fbb 100644 --- a/packages/playwright-core/src/server/progress.ts +++ b/packages/playwright-core/src/server/progress.ts @@ -16,11 +16,11 @@ import { TimeoutError } from './errors'; import { assert, monotonicTime } from '../utils'; -import { ManualPromise } from '../utils/manualPromise'; +import { ManualPromise } from '../utils/isomorphic/manualPromise'; import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation'; import type { Progress as CommonProgress } from '../common/progress'; -import type { LogName } from '../utils/debugLogger'; +import type { LogName } from './utils/debugLogger'; export interface Progress extends CommonProgress { metadata: CallMetadata; diff --git a/packages/playwright-core/src/server/protocolError.ts b/packages/playwright-core/src/server/protocolError.ts index 1f999b7c64..9ef856c2eb 100644 --- a/packages/playwright-core/src/server/protocolError.ts +++ b/packages/playwright-core/src/server/protocolError.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { rewriteErrorMessage } from '../utils/stackTrace'; +import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace'; export class ProtocolError extends Error { type: 'error' | 'closed' | 'crashed'; diff --git a/packages/playwright-core/src/server/recorder/contextRecorder.ts b/packages/playwright-core/src/server/recorder/contextRecorder.ts index 5cc3e9abae..cede7bb28a 100644 --- a/packages/playwright-core/src/server/recorder/contextRecorder.ts +++ b/packages/playwright-core/src/server/recorder/contextRecorder.ts @@ -19,7 +19,7 @@ import { EventEmitter } from 'events'; import { RecorderCollection } from './recorderCollection'; import * as recorderSource from '../../generated/pollingRecorderSource'; import { eventsHelper, monotonicTime, quoteCSSAttributeValue } from '../../utils'; -import { raceAgainstDeadline } from '../../utils/timeoutRunner'; +import { raceAgainstDeadline } from '../../utils/isomorphic/timeoutRunner'; import { BrowserContext } from '../browserContext'; import { languageSet } from '../codegen/languages'; import { Frame } from '../frames'; diff --git a/packages/playwright-core/src/server/recorder/recorderCollection.ts b/packages/playwright-core/src/server/recorder/recorderCollection.ts index 1780502d7d..8c7b344b8e 100644 --- a/packages/playwright-core/src/server/recorder/recorderCollection.ts +++ b/packages/playwright-core/src/server/recorder/recorderCollection.ts @@ -18,8 +18,8 @@ import { EventEmitter } from 'events'; import { performAction } from './recorderRunner'; import { collapseActions } from './recorderUtils'; -import { isUnderTest } from '../../utils/debug'; -import { monotonicTime } from '../../utils/time'; +import { isUnderTest } from '../../utils/isomorphic/debug'; +import { monotonicTime } from '../../utils/isomorphic/time'; import type { Signal } from '../../../../recorder/src/actions'; import type { Frame } from '../frames'; diff --git a/packages/playwright-core/src/server/registry/browserFetcher.ts b/packages/playwright-core/src/server/registry/browserFetcher.ts index e7ffda483f..6a84f2573a 100644 --- a/packages/playwright-core/src/server/registry/browserFetcher.ts +++ b/packages/playwright-core/src/server/registry/browserFetcher.ts @@ -20,10 +20,10 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { debugLogger } from '../../utils/debugLogger'; -import { ManualPromise } from '../../utils/manualPromise'; -import { getUserAgent } from '../../utils/userAgent'; -import { colors, progress as ProgressBar } from '../../utilsBundle'; +import { debugLogger } from '../utils/debugLogger'; +import { ManualPromise } from '../../utils/isomorphic/manualPromise'; +import { getUserAgent } from '../utils/userAgent'; +import { progress as ProgressBar, colors } from '../../utilsBundle'; import { existsAsync } from '../utils/fileUtils'; import { browserDirectoryToMarkerFilePath } from '.'; diff --git a/packages/playwright-core/src/server/registry/dependencies.ts b/packages/playwright-core/src/server/registry/dependencies.ts index e405d75c6f..5cf4a097b1 100644 --- a/packages/playwright-core/src/server/registry/dependencies.ts +++ b/packages/playwright-core/src/server/registry/dependencies.ts @@ -21,9 +21,9 @@ import * as path from 'path'; import { deps } from './nativeDeps'; import { wrapInASCIIBox } from '../utils/ascii'; -import { hostPlatform, isOfficiallySupportedPlatform } from '../../utils/hostPlatform'; +import { hostPlatform, isOfficiallySupportedPlatform } from '../utils/hostPlatform'; import { spawnAsync } from '../utils/spawnAsync'; -import { getPlaywrightVersion } from '../../utils/userAgent'; +import { getPlaywrightVersion } from '../utils/userAgent'; import { buildPlaywrightCLICommand, registry } from '.'; diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index 87804f06dd..e0282120fb 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -25,16 +25,16 @@ import { dockerVersion, readDockerVersionSync, transformCommandsForRoot } from ' import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies'; import { calculateSha1, getAsBooleanFromENV, getFromENV, getPackageManagerExecCommand } from '../../utils'; import { wrapInASCIIBox } from '../utils/ascii'; -import { debugLogger } from '../../utils/debugLogger'; -import { hostPlatform, isOfficiallySupportedPlatform } from '../../utils/hostPlatform'; +import { debugLogger } from '../utils/debugLogger'; +import { hostPlatform, isOfficiallySupportedPlatform } from '../utils/hostPlatform'; import { fetchData } from '../utils/network'; import { spawnAsync } from '../utils/spawnAsync'; -import { getEmbedderName } from '../../utils/userAgent'; +import { getEmbedderName } from '../utils/userAgent'; import { lockfile } from '../../utilsBundle'; import { canAccessFile, existsAsync, removeFolders } from '../utils/fileUtils'; import type { DependencyGroup } from './dependencies'; -import type { HostPlatform } from '../../utils/hostPlatform'; +import type { HostPlatform } from '../utils/hostPlatform'; export { writeDockerVersion } from './dependencies'; diff --git a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts index ef6348aab2..bda904f47a 100644 --- a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts +++ b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts @@ -17,7 +17,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { ManualPromise } from '../../utils/manualPromise'; +import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { httpRequest } from '../utils/network'; import { extract } from '../../zipBundle'; diff --git a/packages/playwright-core/src/server/screenshotter.ts b/packages/playwright-core/src/server/screenshotter.ts index 88870b8900..b32f51a6de 100644 --- a/packages/playwright-core/src/server/screenshotter.ts +++ b/packages/playwright-core/src/server/screenshotter.ts @@ -17,7 +17,7 @@ import { helper } from './helper'; import { assert } from '../utils'; -import { MultiMap } from '../utils/multimap'; +import { MultiMap } from '../utils/isomorphic/multimap'; import type * as dom from './dom'; import type { Frame } from './frames'; diff --git a/packages/playwright-core/src/server/selectors.ts b/packages/playwright-core/src/server/selectors.ts index 1d1eece9cc..886108b972 100644 --- a/packages/playwright-core/src/server/selectors.ts +++ b/packages/playwright-core/src/server/selectors.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createGuid } from '../utils'; +import { createGuid } from './utils/crypto'; import { InvalidSelectorError, parseSelector, stringifySelector, visitAllSelectorParts } from '../utils/isomorphic/selectorParser'; import type { ParsedSelector } from '../utils/isomorphic/selectorParser'; diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index b1943ca123..f769f60d74 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -24,7 +24,7 @@ import { SocksProxy } from './utils/socksProxy'; import { ManualPromise, escapeHTML, generateSelfSignedCertificate, rewriteErrorMessage } from '../utils'; import { verifyClientCertificates } from './browserContext'; import { createProxyAgent } from './fetch'; -import { debugLogger } from '../utils/debugLogger'; +import { debugLogger } from './utils/debugLogger'; import { createSocket, createTLSSocket } from './utils/happyEyeballs'; import type * as types from './types'; diff --git a/packages/playwright-core/src/server/trace/recorder/DEPS.list b/packages/playwright-core/src/server/trace/recorder/DEPS.list index 11a449d26d..bf35323ecc 100644 --- a/packages/playwright-core/src/server/trace/recorder/DEPS.list +++ b/packages/playwright-core/src/server/trace/recorder/DEPS.list @@ -5,6 +5,7 @@ ../../../protocol/ ../../../utils/ ../../../utilsBundle.ts +../../../utils/isomorphic/ ../../../zipBundle.ts ../../dispatchers/dispatcher.ts ../../utils diff --git a/packages/playwright-core/src/server/trace/recorder/snapshotter.ts b/packages/playwright-core/src/server/trace/recorder/snapshotter.ts index 1ad2fdf1ca..f4e288d077 100644 --- a/packages/playwright-core/src/server/trace/recorder/snapshotter.ts +++ b/packages/playwright-core/src/server/trace/recorder/snapshotter.ts @@ -15,15 +15,16 @@ */ import { frameSnapshotStreamer } from './snapshotterInjected'; -import { calculateSha1, createGuid, monotonicTime } from '../../../utils'; -import { debugLogger } from '../../../utils/debugLogger'; -import { eventsHelper } from '../../../utils/eventsHelper'; +import { monotonicTime } from '../../../utils/isomorphic/time'; +import { calculateSha1, createGuid } from '../../utils/crypto'; +import { debugLogger } from '../../utils/debugLogger'; +import { eventsHelper } from '../../utils/eventsHelper'; import { mime } from '../../../utilsBundle'; import { BrowserContext } from '../../browserContext'; import { Page } from '../../page'; import type { SnapshotData } from './snapshotterInjected'; -import type { RegisteredListener } from '../../../utils/eventsHelper'; +import type { RegisteredListener } from '../../utils/eventsHelper'; import type { Frame } from '../../frames'; import type { FrameSnapshot } from '@trace/snapshot'; diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index a3dedc8485..309867f3b0 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -20,7 +20,10 @@ import * as path from 'path'; import { Snapshotter } from './snapshotter'; import { commandsWithTracingSnapshots } from '../../../protocol/debug'; -import { assert, createGuid, eventsHelper, monotonicTime } from '../../../utils'; +import { assert } from '../../../utils/isomorphic/debug'; +import { monotonicTime } from '../../../utils/isomorphic/time'; +import { eventsHelper } from '../../utils/eventsHelper'; +import { createGuid } from '../../utils/crypto'; import { Artifact } from '../../artifact'; import { BrowserContext } from '../../browserContext'; import { Dispatcher } from '../../dispatchers/dispatcher'; diff --git a/packages/playwright-core/src/server/utils/DEPS.list b/packages/playwright-core/src/server/utils/DEPS.list index 53cad9639a..685e00df46 100644 --- a/packages/playwright-core/src/server/utils/DEPS.list +++ b/packages/playwright-core/src/server/utils/DEPS.list @@ -1,9 +1,10 @@ [*] +../../common ../../utils +../../utils/isomorphic ../../utilsBundle.ts ../../zipBundle.ts [comparators.ts] ./image_tools ../../third_party/pixelmatch - diff --git a/packages/playwright-core/src/server/utils/comparators.ts b/packages/playwright-core/src/server/utils/comparators.ts index 072133d833..06b6bdc6ff 100644 --- a/packages/playwright-core/src/server/utils/comparators.ts +++ b/packages/playwright-core/src/server/utils/comparators.ts @@ -18,8 +18,8 @@ import { compare } from './image_tools/compare'; // @ts-ignore import pixelmatch from '../../third_party/pixelmatch'; -import { colors, jpegjs } from '../../utilsBundle'; -import { diff } from '../../utilsBundle'; +import { jpegjs } from '../../utilsBundle'; +import { colors, diff } from '../../utilsBundle'; import { PNG } from '../../utilsBundle'; export type ImageComparatorOptions = { threshold?: number, maxDiffPixels?: number, maxDiffPixelRatio?: number, comparator?: string }; diff --git a/packages/playwright-core/src/utils/crypto.ts b/packages/playwright-core/src/server/utils/crypto.ts similarity index 99% rename from packages/playwright-core/src/utils/crypto.ts rename to packages/playwright-core/src/server/utils/crypto.ts index c59b84caf0..7189f38b00 100644 --- a/packages/playwright-core/src/utils/crypto.ts +++ b/packages/playwright-core/src/server/utils/crypto.ts @@ -16,7 +16,7 @@ import * as crypto from 'crypto'; -import { assert } from './debug'; +import { assert } from '../../utils/isomorphic/debug'; export function createGuid(): string { return crypto.randomBytes(16).toString('hex'); diff --git a/packages/playwright-core/src/utils/debugLogger.ts b/packages/playwright-core/src/server/utils/debugLogger.ts similarity index 98% rename from packages/playwright-core/src/utils/debugLogger.ts rename to packages/playwright-core/src/server/utils/debugLogger.ts index 24b0a1dbf8..8713b3cf7e 100644 --- a/packages/playwright-core/src/utils/debugLogger.ts +++ b/packages/playwright-core/src/server/utils/debugLogger.ts @@ -16,7 +16,7 @@ import * as fs from 'fs'; -import { debug } from '../utilsBundle'; +import { debug } from '../../utilsBundle'; const debugLoggerColorMap = { 'api': 45, // cyan diff --git a/packages/playwright-core/src/utils/env.ts b/packages/playwright-core/src/server/utils/env.ts similarity index 100% rename from packages/playwright-core/src/utils/env.ts rename to packages/playwright-core/src/server/utils/env.ts diff --git a/packages/playwright-core/src/utils/eventsHelper.ts b/packages/playwright-core/src/server/utils/eventsHelper.ts similarity index 100% rename from packages/playwright-core/src/utils/eventsHelper.ts rename to packages/playwright-core/src/server/utils/eventsHelper.ts diff --git a/packages/playwright-core/src/utils/expectUtils.ts b/packages/playwright-core/src/server/utils/expectUtils.ts similarity index 94% rename from packages/playwright-core/src/utils/expectUtils.ts rename to packages/playwright-core/src/server/utils/expectUtils.ts index f39af23cee..6d7a2a1e3d 100644 --- a/packages/playwright-core/src/utils/expectUtils.ts +++ b/packages/playwright-core/src/server/utils/expectUtils.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { isRegExp, isString } from './rtti'; +import { isRegExp, isString } from '../../utils/isomorphic/rtti'; import type { ExpectedTextValue } from '@protocol/channels'; diff --git a/packages/playwright-core/src/server/utils/fileUtils.ts b/packages/playwright-core/src/server/utils/fileUtils.ts index 5f196963b7..0a0a95d2a1 100644 --- a/packages/playwright-core/src/server/utils/fileUtils.ts +++ b/packages/playwright-core/src/server/utils/fileUtils.ts @@ -17,7 +17,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { ManualPromise } from '../../utils/manualPromise'; +import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { yazl } from '../../zipBundle'; import type { EventEmitter } from 'events'; diff --git a/packages/playwright-core/src/server/utils/happyEyeballs.ts b/packages/playwright-core/src/server/utils/happyEyeballs.ts index ba6188f97c..da3481f4c1 100644 --- a/packages/playwright-core/src/server/utils/happyEyeballs.ts +++ b/packages/playwright-core/src/server/utils/happyEyeballs.ts @@ -20,9 +20,9 @@ import * as https from 'https'; import * as net from 'net'; import * as tls from 'tls'; -import { assert } from '../../utils/debug'; -import { ManualPromise } from '../../utils/manualPromise'; -import { monotonicTime } from '../../utils/time'; +import { assert } from '../../utils/isomorphic/debug'; +import { ManualPromise } from '../../utils/isomorphic/manualPromise'; +import { monotonicTime } from '../../utils/isomorphic/time'; // Implementation(partial) of Happy Eyeballs 2 algorithm described in // https://www.rfc-editor.org/rfc/rfc8305 diff --git a/packages/playwright-core/src/utils/hostPlatform.ts b/packages/playwright-core/src/server/utils/hostPlatform.ts similarity index 100% rename from packages/playwright-core/src/utils/hostPlatform.ts rename to packages/playwright-core/src/server/utils/hostPlatform.ts diff --git a/packages/playwright-core/src/server/utils/httpServer.ts b/packages/playwright-core/src/server/utils/httpServer.ts index ee170eca33..0c9f0fe25a 100644 --- a/packages/playwright-core/src/server/utils/httpServer.ts +++ b/packages/playwright-core/src/server/utils/httpServer.ts @@ -18,9 +18,9 @@ import * as fs from 'fs'; import * as path from 'path'; import { mime, wsServer } from '../../utilsBundle'; -import { createGuid } from '../../utils/crypto'; -import { assert } from '../../utils/debug'; -import { ManualPromise } from '../../utils/manualPromise'; +import { createGuid } from './crypto'; +import { assert } from '../../utils/isomorphic/debug'; +import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { createHttpServer } from './network'; import type http from 'http'; diff --git a/packages/playwright-core/src/utils/linuxUtils.ts b/packages/playwright-core/src/server/utils/linuxUtils.ts similarity index 73% rename from packages/playwright-core/src/utils/linuxUtils.ts rename to packages/playwright-core/src/server/utils/linuxUtils.ts index c51d32f9d5..3c85ebe091 100644 --- a/packages/playwright-core/src/utils/linuxUtils.ts +++ b/packages/playwright-core/src/server/utils/linuxUtils.ts @@ -23,26 +23,6 @@ let osRelease: { version: string, } | undefined; -export async function getLinuxDistributionInfo(): Promise<{ id: string, version: string } | undefined> { - if (process.platform !== 'linux') - return undefined; - if (!osRelease && !didFailToReadOSRelease) { - try { - // List of /etc/os-release values for different distributions could be - // found here: https://gist.github.com/aslushnikov/8ceddb8288e4cf9db3039c02e0f4fb75 - const osReleaseText = await fs.promises.readFile('/etc/os-release', 'utf8'); - const fields = parseOSReleaseText(osReleaseText); - osRelease = { - id: fields.get('id') ?? '', - version: fields.get('version_id') ?? '', - }; - } catch (e) { - didFailToReadOSRelease = true; - } - } - return osRelease; -} - export function getLinuxDistributionInfoSync(): { id: string, version: string } | undefined { if (process.platform !== 'linux') return undefined; diff --git a/packages/playwright-core/src/utils/platform.ts b/packages/playwright-core/src/server/utils/nodePlatform.ts similarity index 57% rename from packages/playwright-core/src/utils/platform.ts rename to packages/playwright-core/src/server/utils/nodePlatform.ts index e682ad46b0..d4b8b5d7ad 100644 --- a/packages/playwright-core/src/utils/platform.ts +++ b/packages/playwright-core/src/server/utils/nodePlatform.ts @@ -14,36 +14,39 @@ * limitations under the License. */ +import * as crypto from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; import * as util from 'util'; -export type Platform = { - fs: () => typeof fs; - path: () => typeof path; - inspectCustom: symbol | undefined; - ws?: (url: string) => WebSocket; -}; - -export const emptyPlatform: Platform = { - fs: () => { - throw new Error('File system is not available'); - }, - - path: () => { - throw new Error('Path module is not available'); - }, - - inspectCustom: undefined, -}; +import { colors } from '../../utilsBundle'; +import { Platform } from '../../common/platform'; +import { debugLogger } from './debugLogger'; export const nodePlatform: Platform = { - fs: () => fs, - path: () => path, - inspectCustom: util.inspect.custom, -}; + calculateSha1: (text: string) => { + const sha1 = crypto.createHash('sha1'); + sha1.update(text); + return Promise.resolve(sha1.digest('hex')); + }, -export const webPlatform: Platform = { - ...emptyPlatform, - ws: (url: string) => new WebSocket(url), + colors, + + createGuid: () => crypto.randomBytes(16).toString('hex'), + + fs: () => fs, + + inspectCustom: util.inspect.custom, + + isLogEnabled(name: 'api' | 'channel') { + return debugLogger.isEnabled(name); + }, + + log(name: 'api' | 'channel', message: string | Error | object) { + debugLogger.log(name, message); + }, + + path: () => path, + + pathSeparator: path.sep }; diff --git a/packages/playwright-core/src/server/utils/socksProxy.ts b/packages/playwright-core/src/server/utils/socksProxy.ts index f5eb9eec1a..c0fe0f126a 100644 --- a/packages/playwright-core/src/server/utils/socksProxy.ts +++ b/packages/playwright-core/src/server/utils/socksProxy.ts @@ -17,8 +17,9 @@ import EventEmitter from 'events'; import * as net from 'net'; -import { assert, createGuid, } from '../../utils'; -import { debugLogger } from '../../utils/debugLogger'; +import { assert } from '../../utils/isomorphic/debug'; +import { createGuid } from './crypto'; +import { debugLogger } from './debugLogger'; import { createSocket } from './happyEyeballs'; import type { AddressInfo } from 'net'; diff --git a/packages/playwright-core/src/utils/userAgent.ts b/packages/playwright-core/src/server/utils/userAgent.ts similarity index 98% rename from packages/playwright-core/src/utils/userAgent.ts rename to packages/playwright-core/src/server/utils/userAgent.ts index 7f3e3d2275..9676a5bae5 100644 --- a/packages/playwright-core/src/utils/userAgent.ts +++ b/packages/playwright-core/src/server/utils/userAgent.ts @@ -77,6 +77,6 @@ export function getEmbedderName(): { embedderName: string, embedderVersion: stri } export function getPlaywrightVersion(majorMinorOnly = false): string { - const version = process.env.PW_VERSION_OVERRIDE || require('./../../package.json').version; + const version = process.env.PW_VERSION_OVERRIDE || require('./../../../package.json').version; return majorMinorOnly ? version.split('.').slice(0, 2).join('.') : version; } diff --git a/packages/playwright-core/src/server/utils/wsServer.ts b/packages/playwright-core/src/server/utils/wsServer.ts index b223802a1b..68362f14de 100644 --- a/packages/playwright-core/src/server/utils/wsServer.ts +++ b/packages/playwright-core/src/server/utils/wsServer.ts @@ -16,7 +16,7 @@ import { createHttpServer } from './network'; import { wsServer } from '../../utilsBundle'; -import { debugLogger } from '../../utils/debugLogger'; +import { debugLogger } from './debugLogger'; import type { WebSocket, WebSocketServer } from '../../utilsBundle'; import type http from 'http'; diff --git a/packages/playwright-core/src/server/webkit/wkConnection.ts b/packages/playwright-core/src/server/webkit/wkConnection.ts index 0012d8702f..ada50a413b 100644 --- a/packages/playwright-core/src/server/webkit/wkConnection.ts +++ b/packages/playwright-core/src/server/webkit/wkConnection.ts @@ -18,13 +18,13 @@ import { EventEmitter } from 'events'; import { assert } from '../../utils'; -import { debugLogger } from '../../utils/debugLogger'; +import { debugLogger } from '../utils/debugLogger'; import { helper } from '../helper'; import { ProtocolError } from '../protocolError'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import type { Protocol } from './protocol'; -import type { RecentLogsCollector } from '../../utils/debugLogger'; +import type { RecentLogsCollector } from '../utils/debugLogger'; import type { ProtocolLogger } from '../types'; diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index e80031d388..f9e9517fb2 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -17,10 +17,12 @@ import * as path from 'path'; -import { assert, createGuid, debugAssert, headersArrayToObject } from '../../utils'; -import { eventsHelper } from '../../utils/eventsHelper'; -import { hostPlatform } from '../../utils/hostPlatform'; -import { splitErrorMessage } from '../../utils/stackTrace'; +import { assert, debugAssert } from '../../utils'; +import { headersArrayToObject } from '../../utils/isomorphic/headers'; +import { createGuid } from '../utils/crypto'; +import { eventsHelper } from '../utils/eventsHelper'; +import { hostPlatform } from '../utils/hostPlatform'; +import { splitErrorMessage } from '../../utils/isomorphic/stackTrace'; import { PNG, jpegjs } from '../../utilsBundle'; import { BrowserContext } from '../browserContext'; import * as dialog from '../dialog'; @@ -37,11 +39,11 @@ import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './wkInput'; import { WKInterceptableRequest, WKRouteImpl } from './wkInterceptableRequest'; import { WKProvisionalPage } from './wkProvisionalPage'; import { WKWorkers } from './wkWorkers'; -import { debugLogger } from '../../utils/debugLogger'; +import { debugLogger } from '../utils/debugLogger'; import type { Protocol } from './protocol'; import type { WKBrowserContext } from './wkBrowser'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type * as accessibility from '../accessibility'; import type * as frames from '../frames'; import type { JSHandle } from '../javascript'; diff --git a/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts b/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts index 2958f4597c..63d4cc6f0a 100644 --- a/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts +++ b/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts @@ -15,12 +15,12 @@ */ import { assert } from '../../utils'; -import { eventsHelper } from '../../utils/eventsHelper'; +import { eventsHelper } from '../utils/eventsHelper'; import type { Protocol } from './protocol'; import type { WKSession } from './wkConnection'; import type { WKPage } from './wkPage'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type * as network from '../network'; export class WKProvisionalPage { diff --git a/packages/playwright-core/src/server/webkit/wkWorkers.ts b/packages/playwright-core/src/server/webkit/wkWorkers.ts index 644c64a27f..719d8df6be 100644 --- a/packages/playwright-core/src/server/webkit/wkWorkers.ts +++ b/packages/playwright-core/src/server/webkit/wkWorkers.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { eventsHelper } from '../../utils/eventsHelper'; +import { eventsHelper } from '../utils/eventsHelper'; import { Worker } from '../page'; import { WKSession } from './wkConnection'; import { WKExecutionContext } from './wkExecutionContext'; import type { Protocol } from './protocol'; -import type { RegisteredListener } from '../../utils/eventsHelper'; +import type { RegisteredListener } from '../utils/eventsHelper'; import type { Page } from '../page'; import type * as types from '../types'; diff --git a/packages/playwright-core/src/utils.ts b/packages/playwright-core/src/utils.ts index 930acae092..1a0c446c6b 100644 --- a/packages/playwright-core/src/utils.ts +++ b/packages/playwright-core/src/utils.ts @@ -14,38 +14,42 @@ * limitations under the License. */ -export * from './utils/crypto'; -export * from './utils/debug'; -export * from './utils/debugLogger'; -export * from './utils/env'; -export * from './utils/eventsHelper'; -export * from './utils/expectUtils'; -export * from './utils/headers'; -export * from './utils/hostPlatform'; -export * from './utils/manualPromise'; +export * from './utils/isomorphic/colors'; +export * from './utils/isomorphic/debug'; 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/stringUtils'; +export * from './utils/isomorphic/time'; +export * from './utils/isomorphic/timeoutRunner'; export * from './utils/isomorphic/urlMatch'; -export * from './utils/multimap'; -export * from './utils/rtti'; -export * from './utils/semaphore'; -export * from './utils/stackTrace'; + +export * from './utils/isomorphic/headers'; +export * from './utils/isomorphic/semaphore'; +export * from './utils/isomorphic/stackTrace'; export * from './utils/task'; -export * from './utils/time'; -export * from './utils/timeoutRunner'; -export * from './utils/traceUtils'; -export * from './utils/userAgent'; export * from './utils/zipFile'; export * from './utils/zones'; -export * from './server/utils/socksProxy'; -export * from './server/utils/processLauncher'; export * from './server/utils/ascii'; export * from './server/utils/comparators'; +export * from './server/utils/crypto'; +export * from './server/utils/debugLogger'; +export * from './server/utils/env'; +export * from './server/utils/eventsHelper'; +export * from './server/utils/expectUtils'; export * from './server/utils/fileUtils'; +export * from './server/utils/hostPlatform'; export * from './server/utils/httpServer'; export * from './server/utils/network'; +export * from './server/utils/nodePlatform'; +export * from './server/utils/processLauncher'; export * from './server/utils/profiler'; -export * from './server/utils/wsServer'; +export * from './server/utils/socksProxy'; export * from './server/utils/spawnAsync'; +export * from './server/utils/userAgent'; +export * from './server/utils/wsServer'; + +export { colors } from './utilsBundle'; diff --git a/packages/playwright-core/src/utils/DEPS.list b/packages/playwright-core/src/utils/DEPS.list index 0e2297dfdb..8a14701d4d 100644 --- a/packages/playwright-core/src/utils/DEPS.list +++ b/packages/playwright-core/src/utils/DEPS.list @@ -1,4 +1,6 @@ [*] ./ +./isomorphic ../utilsBundle.ts ../zipBundle.ts +../utils/isomorphic \ No newline at end of file diff --git a/packages/playwright-core/src/utils/isomorphic/colors.ts b/packages/playwright-core/src/utils/isomorphic/colors.ts new file mode 100644 index 0000000000..7be0955ad3 --- /dev/null +++ b/packages/playwright-core/src/utils/isomorphic/colors.ts @@ -0,0 +1,66 @@ +/** + * 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 const webColors = { + enabled: true, + reset: (text: string) => applyStyle(0, 0, text), + + bold: (text: string) => applyStyle(1, 22, text), + dim: (text: string) => applyStyle(2, 22, text), + italic: (text: string) => applyStyle(3, 23, text), + underline: (text: string) => applyStyle(4, 24, text), + inverse: (text: string) => applyStyle(7, 27, text), + hidden: (text: string) => applyStyle(8, 28, text), + strikethrough: (text: string) => applyStyle(9, 29, text), + + black: (text: string) => applyStyle(30, 39, text), + red: (text: string) => applyStyle(31, 39, text), + green: (text: string) => applyStyle(32, 39, text), + yellow: (text: string) => applyStyle(33, 39, text), + blue: (text: string) => applyStyle(34, 39, text), + magenta: (text: string) => applyStyle(35, 39, text), + cyan: (text: string) => applyStyle(36, 39, text), + white: (text: string) => applyStyle(37, 39, text), + gray: (text: string) => applyStyle(90, 39, text), + grey: (text: string) => applyStyle(90, 39, text), +}; + +export type Colors = typeof webColors; + +export const noColors: Colors = { + enabled: false, + reset: t => t, + bold: t => t, + dim: t => t, + italic: t => t, + underline: t => t, + inverse: t => t, + hidden: t => t, + strikethrough: t => t, + black: t => t, + red: t => t, + green: t => t, + yellow: t => t, + blue: t => t, + magenta: t => t, + cyan: t => t, + white: t => t, + gray: t => t, + grey: t => t, +}; + + +const applyStyle = (open: number, close: number, text: string) => `\u001b[${open}m${text}\u001b[${close}m`; diff --git a/packages/playwright-core/src/utils/debug.ts b/packages/playwright-core/src/utils/isomorphic/debug.ts similarity index 83% rename from packages/playwright-core/src/utils/debug.ts rename to packages/playwright-core/src/utils/isomorphic/debug.ts index 2e199b3a31..1eec988aa9 100644 --- a/packages/playwright-core/src/utils/debug.ts +++ b/packages/playwright-core/src/utils/isomorphic/debug.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { getFromENV } from './env'; - export function assert(value: any, message?: string): asserts value { if (!value) throw new Error(message || 'Assertion error'); @@ -26,13 +24,18 @@ export function debugAssert(value: any, message?: string): asserts value { throw new Error(message); } -const debugEnv = getFromENV('PWDEBUG') || ''; +let _debugMode: string | undefined; + +export function setDebugMode(mode: string) { + _debugMode = mode; +} + export function debugMode() { - if (debugEnv === 'console') + if (_debugMode === 'console') return 'console'; - if (debugEnv === '0' || debugEnv === 'false') + if (_debugMode === '0' || _debugMode === 'false') return ''; - return debugEnv ? 'inspector' : ''; + return _debugMode ? 'inspector' : ''; } let _isUnderTest = !!process.env.PWTEST_UNDER_TEST; diff --git a/packages/playwright-core/src/utils/headers.ts b/packages/playwright-core/src/utils/isomorphic/headers.ts similarity index 100% rename from packages/playwright-core/src/utils/headers.ts rename to packages/playwright-core/src/utils/isomorphic/headers.ts diff --git a/packages/playwright-core/src/utils/manualPromise.ts b/packages/playwright-core/src/utils/isomorphic/manualPromise.ts similarity index 92% rename from packages/playwright-core/src/utils/manualPromise.ts rename to packages/playwright-core/src/utils/isomorphic/manualPromise.ts index 467b8de0db..a5034e05ec 100644 --- a/packages/playwright-core/src/utils/manualPromise.ts +++ b/packages/playwright-core/src/utils/isomorphic/manualPromise.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { captureRawStack } from './stackTrace'; - export class ManualPromise extends Promise { private _resolve!: (t: T) => void; private _reject!: (e: Error) => void; @@ -118,3 +116,12 @@ 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/multimap.ts b/packages/playwright-core/src/utils/isomorphic/multimap.ts similarity index 100% rename from packages/playwright-core/src/utils/multimap.ts rename to packages/playwright-core/src/utils/isomorphic/multimap.ts diff --git a/packages/playwright-core/src/utils/rtti.ts b/packages/playwright-core/src/utils/isomorphic/rtti.ts similarity index 95% rename from packages/playwright-core/src/utils/rtti.ts rename to packages/playwright-core/src/utils/isomorphic/rtti.ts index a18d3a450a..0bcaef0748 100644 --- a/packages/playwright-core/src/utils/rtti.ts +++ b/packages/playwright-core/src/utils/isomorphic/rtti.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -export { isString } from './isomorphic/stringUtils'; +export { isString } from './stringUtils'; export function isRegExp(obj: any): obj is RegExp { return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]'; diff --git a/packages/playwright-core/src/utils/semaphore.ts b/packages/playwright-core/src/utils/isomorphic/semaphore.ts similarity index 100% rename from packages/playwright-core/src/utils/semaphore.ts rename to packages/playwright-core/src/utils/isomorphic/semaphore.ts diff --git a/packages/playwright-core/src/utils/sequence.ts b/packages/playwright-core/src/utils/isomorphic/sequence.ts similarity index 100% rename from packages/playwright-core/src/utils/sequence.ts rename to packages/playwright-core/src/utils/isomorphic/sequence.ts diff --git a/packages/playwright-core/src/utils/stackTrace.ts b/packages/playwright-core/src/utils/isomorphic/stackTrace.ts similarity index 81% rename from packages/playwright-core/src/utils/stackTrace.ts rename to packages/playwright-core/src/utils/isomorphic/stackTrace.ts index fb97115035..26b79c5c27 100644 --- a/packages/playwright-core/src/utils/stackTrace.ts +++ b/packages/playwright-core/src/utils/isomorphic/stackTrace.ts @@ -14,18 +14,14 @@ * limitations under the License. */ -import * as path from 'path'; - -import { colors } from '../utilsBundle'; import { findRepeatedSubsequences } from './sequence'; -import { StackUtils } from './stackUtils'; +import { parseStackFrame } from './stackUtils'; import type { StackFrame } from '@protocol/channels'; +import type { Platform } from '../../common/platform'; -const stackUtils = new StackUtils(); - -export function parseStackTraceLine(line: string): StackFrame | null { - const frame = stackUtils.parseLine(line); +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:'))) @@ -49,12 +45,15 @@ export function rewriteErrorMessage(e: E, newMessage: string): return e; } -const CORE_DIR = path.resolve(__dirname, '..', '..'); +let coreDir: string | undefined; -const internalStackPrefixes = [ - CORE_DIR, -]; -export const addInternalStackPrefix = (prefix: string) => internalStackPrefixes.push(prefix); +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[]; @@ -67,7 +66,7 @@ export function captureRawStack(): RawStack { return stack.split('\n'); } -export function captureLibraryStackTrace(): { frames: StackFrame[], apiName: string } { +export function captureLibraryStackTrace(pathSeparator: string): { frames: StackFrame[], apiName: string } { const stack = captureRawStack(); type ParsedFrame = { @@ -76,10 +75,10 @@ export function captureLibraryStackTrace(): { frames: StackFrame[], apiName: str isPlaywrightLibrary: boolean; }; let parsedFrames = stack.map(line => { - const frame = parseStackTraceLine(line); + const frame = parseStackTraceLine(line, pathSeparator); if (!frame || !frame.file) return null; - const isPlaywrightLibrary = frame.file.startsWith(CORE_DIR); + const isPlaywrightLibrary = !!coreDir && frame.file.startsWith(coreDir); const parsed: ParsedFrame = { frame, frameText: line, @@ -113,7 +112,7 @@ export function captureLibraryStackTrace(): { frames: StackFrame[], apiName: str parsedFrames = parsedFrames.filter(f => { if (process.env.PWDEBUGIMPL) return true; - if (internalStackPrefixes.some(prefix => f.frame.file.startsWith(prefix))) + if (playwrightStackPrefixes.some(prefix => f.frame.file.startsWith(prefix))) return false; return true; }); @@ -135,11 +134,6 @@ export function stringifyStackFrames(frames: StackFrame[]): string[] { return stackLines; } -export function captureLibraryStackText() { - const parsed = captureLibraryStackTrace(); - return stringifyStackFrames(parsed.frames).join('\n'); -} - export function splitErrorMessage(message: string): { name: string, message: string } { const separationIdx = message.indexOf(':'); return { @@ -148,12 +142,12 @@ export function splitErrorMessage(message: string): { name: string, message: str }; } -export function formatCallLog(log: string[] | undefined): string { +export function formatCallLog(platform: Platform, log: string[] | undefined): string { if (!log || !log.some(l => !!l)) return ''; return ` Call log: -${colors.dim(log.join('\n'))} +${platform.colors.dim(log.join('\n'))} `; } diff --git a/packages/playwright-core/src/utils/stackUtils.ts b/packages/playwright-core/src/utils/isomorphic/stackUtils.ts similarity index 53% rename from packages/playwright-core/src/utils/stackUtils.ts rename to packages/playwright-core/src/utils/isomorphic/stackUtils.ts index 0be4675a86..20be8081af 100644 --- a/packages/playwright-core/src/utils/stackUtils.ts +++ b/packages/playwright-core/src/utils/isomorphic/stackUtils.ts @@ -19,8 +19,6 @@ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import * as url from 'url'; - type StackData = { line?: number; column?: number; @@ -35,90 +33,88 @@ type StackData = { evalFile?: string | undefined; }; -export class StackUtils { - parseLine(line: string) { - const match = line && line.match(re); - if (!match) - return null; +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 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 = {}; + const res: StackData = {}; - if (lnum) - res.line = Number(lnum); + if (lnum) + res.line = Number(lnum); - if (col) - res.column = Number(col); + 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 (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); - - 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; } + + 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) { +function setFile(result: StackData, filename: string, pathSeparator: string) { if (filename) { if (filename.startsWith('file://')) - filename = url.fileURLToPath(filename); + filename = fileURLToPath(filename, pathSeparator); result.file = filename; } } @@ -149,3 +145,14 @@ const re = new RegExp('^' + ); 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/utils/isomorphic/time.ts b/packages/playwright-core/src/utils/isomorphic/time.ts new file mode 100644 index 0000000000..625fd18805 --- /dev/null +++ b/packages/playwright-core/src/utils/isomorphic/time.ts @@ -0,0 +1,19 @@ +/** + * 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 monotonicTime(): number { + return (performance.now() * 1000 | 0) / 1000; +} diff --git a/packages/playwright-core/src/utils/timeoutRunner.ts b/packages/playwright-core/src/utils/isomorphic/timeoutRunner.ts similarity index 98% rename from packages/playwright-core/src/utils/timeoutRunner.ts rename to packages/playwright-core/src/utils/isomorphic/timeoutRunner.ts index e8f128b9f8..e8016ddb49 100644 --- a/packages/playwright-core/src/utils/timeoutRunner.ts +++ b/packages/playwright-core/src/utils/isomorphic/timeoutRunner.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { monotonicTime } from '../utils'; +import { monotonicTime } from './time'; export async function raceAgainstDeadline(cb: () => Promise, deadline: number): Promise<{ result: T, timedOut: false } | { timedOut: true }> { let timer: NodeJS.Timeout | undefined; diff --git a/packages/playwright-core/src/common/timeoutSettings.ts b/packages/playwright-core/src/utils/isomorphic/timeoutSettings.ts similarity index 98% rename from packages/playwright-core/src/common/timeoutSettings.ts rename to packages/playwright-core/src/utils/isomorphic/timeoutSettings.ts index 65c8be1ecf..02c02a33b5 100644 --- a/packages/playwright-core/src/common/timeoutSettings.ts +++ b/packages/playwright-core/src/utils/isomorphic/timeoutSettings.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { debugMode } from '../utils'; +import { debugMode } from './debug'; export const DEFAULT_TIMEOUT = 30000; export const DEFAULT_LAUNCH_TIMEOUT = 3 * 60 * 1000; // 3 minutes diff --git a/packages/playwright-core/src/utils/isomorphic/traceUtils.ts b/packages/playwright-core/src/utils/isomorphic/traceUtils.ts index edb1e1c660..f077cc5c4b 100644 --- a/packages/playwright-core/src/utils/isomorphic/traceUtils.ts +++ b/packages/playwright-core/src/utils/isomorphic/traceUtils.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { StackFrame } from '@protocol/channels'; +import type { ClientSideCallMetadata, StackFrame } from '@protocol/channels'; export type SerializedStackFrame = [number, number, number, string]; export type SerializedStack = [number, SerializedStackFrame[]]; @@ -33,3 +33,24 @@ export function parseClientSideCallMetadata(data: SerializedClientSideCallMetada } return result; } + +export function serializeClientSideCallMetadata(metadatas: ClientSideCallMetadata[]): SerializedClientSideCallMetadata { + const fileNames = new Map(); + const stacks: SerializedStack[] = []; + for (const m of metadatas) { + if (!m.stack || !m.stack.length) + continue; + const stack: SerializedStackFrame[] = []; + for (const frame of m.stack) { + let ordinal = fileNames.get(frame.file); + if (typeof ordinal !== 'number') { + ordinal = fileNames.size; + fileNames.set(frame.file, ordinal); + } + const stackFrame: SerializedStackFrame = [ordinal, frame.line || 0, frame.column || 0, frame.function || '']; + stack.push(stackFrame); + } + stacks.push([m.id, stack]); + } + return { files: [...fileNames.keys()], stacks }; +} diff --git a/packages/playwright-core/src/protocol/transport.ts b/packages/playwright-core/src/utils/pipeTransport.ts similarity index 95% rename from packages/playwright-core/src/protocol/transport.ts rename to packages/playwright-core/src/utils/pipeTransport.ts index e647100c63..64b1d80e8f 100644 --- a/packages/playwright-core/src/protocol/transport.ts +++ b/packages/playwright-core/src/utils/pipeTransport.ts @@ -14,18 +14,18 @@ * limitations under the License. */ -import { makeWaitForNextTask } from '../utils'; +import { makeWaitForNextTask } from './task'; -export interface WritableStream { +interface WritableStream { write(data: Buffer): void; } -export interface ReadableStream { +interface ReadableStream { on(event: 'data', callback: (b: Buffer) => void): void; on(event: 'close', callback: () => void): void; } -export interface ClosableStream { +interface ClosableStream { close(): void; } diff --git a/packages/playwright-core/src/utils/time.ts b/packages/playwright-core/src/utils/time.ts deleted file mode 100644 index d00ee62e82..0000000000 --- a/packages/playwright-core/src/utils/time.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// The `process.hrtime()` returns a time from some arbitrary -// date in the past; on certain systems, this is the time from the system boot. -// The `monotonicTime()` converts this to milliseconds. -// -// For a Linux server with uptime of 36 days, the `monotonicTime()` value -// will be 36 * 86400 * 1000 = 3_110_400_000, which is larger than -// the maximum value that `setTimeout` accepts as an argument: 2_147_483_647. -// -// To make the `monotonicTime()` a reasonable value, we anchor -// it to the time of the first import of this utility. -const initialTime = process.hrtime(); - -export function monotonicTime(): number { - const [seconds, nanoseconds] = process.hrtime(initialTime); - return seconds * 1000 + (nanoseconds / 1000 | 0) / 1000; -} diff --git a/packages/playwright-core/src/utils/traceUtils.ts b/packages/playwright-core/src/utils/traceUtils.ts deleted file mode 100644 index c6ecc7988b..0000000000 --- a/packages/playwright-core/src/utils/traceUtils.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { SerializedClientSideCallMetadata, SerializedStack, SerializedStackFrame } from './isomorphic/traceUtils'; -import type { ClientSideCallMetadata } from '@protocol/channels'; - -export function serializeClientSideCallMetadata(metadatas: ClientSideCallMetadata[]): SerializedClientSideCallMetadata { - const fileNames = new Map(); - const stacks: SerializedStack[] = []; - for (const m of metadatas) { - if (!m.stack || !m.stack.length) - continue; - const stack: SerializedStackFrame[] = []; - for (const frame of m.stack) { - let ordinal = fileNames.get(frame.file); - if (typeof ordinal !== 'number') { - ordinal = fileNames.size; - fileNames.set(frame.file, ordinal); - } - const stackFrame: SerializedStackFrame = [ordinal, frame.line || 0, frame.column || 0, frame.function || '']; - stack.push(stackFrame); - } - stacks.push([m.id, stack]); - } - return { files: [...fileNames.keys()], stacks }; -} diff --git a/packages/playwright/src/common/config.ts b/packages/playwright/src/common/config.ts index b0bc394873..59d1973578 100644 --- a/packages/playwright/src/common/config.ts +++ b/packages/playwright/src/common/config.ts @@ -78,7 +78,7 @@ export class FullConfigInternal { const privateConfiguration = (userConfig as any)['@playwright/test']; this.plugins = (privateConfiguration?.plugins || []).map((p: any) => ({ factory: p })); this.singleTSConfigPath = pathResolve(configDir, userConfig.tsconfig); - this.populateGitInfo = takeFirst(userConfig.populateGitInfo, false); + this.populateGitInfo = takeFirst(userConfig.populateGitInfo, defaultPopulateGitInfo); this.globalSetups = (Array.isArray(userConfig.globalSetup) ? userConfig.globalSetup : [userConfig.globalSetup]).map(s => resolveScript(s, configDir)).filter(script => script !== undefined); this.globalTeardowns = (Array.isArray(userConfig.globalTeardown) ? userConfig.globalTeardown : [userConfig.globalTeardown]).map(s => resolveScript(s, configDir)).filter(script => script !== undefined); @@ -301,6 +301,7 @@ function resolveScript(id: string | undefined, rootDir: string): string | undefi export const defaultGrep = /.*/; export const defaultReporter = process.env.CI ? 'dot' : 'list'; +const defaultPopulateGitInfo = process.env.GITHUB_ACTIONS === 'true'; const configInternalSymbol = Symbol('configInternalSymbol'); diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 9b0fb456ab..a8d0cc0387 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -726,7 +726,7 @@ class ArtifactsRecorder { return; (tracing as any)[this._startedCollectingArtifacts] = true; if (this._testInfo._tracing.traceOptions() && (tracing as any)[kTracingStarted]) - await tracing.stopChunk({ path: this._testInfo._tracing.generateNextTraceRecordingPath() }); + await tracing.stopChunk({ path: this._testInfo._tracing.maybeGenerateNextTraceRecordingPath() }); } } diff --git a/packages/playwright/src/matchers/matcherHint.ts b/packages/playwright/src/matchers/matcherHint.ts index 1dfa1ceb4a..316ecad685 100644 --- a/packages/playwright/src/matchers/matcherHint.ts +++ b/packages/playwright/src/matchers/matcherHint.ts @@ -15,7 +15,7 @@ */ import { stringifyStackFrames } from 'playwright-core/lib/utils'; -import { colors } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; import type { ExpectMatcherState } from '../../types/test'; import type { StackFrame } from '@protocol/channels'; diff --git a/packages/playwright/src/matchers/matchers.ts b/packages/playwright/src/matchers/matchers.ts index 2890feaac6..d37e3285f4 100644 --- a/packages/playwright/src/matchers/matchers.ts +++ b/packages/playwright/src/matchers/matchers.ts @@ -15,7 +15,7 @@ */ import { isRegExp, isString, isTextualMimeType, pollAgainstDeadline, serializeExpectedTextValues } from 'playwright-core/lib/utils'; -import { colors } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; import { callLogText, expectTypes } from '../util'; import { toBeTruthy } from './toBeTruthy'; diff --git a/packages/playwright/src/matchers/toHaveURL.ts b/packages/playwright/src/matchers/toHaveURL.ts index c56441fee5..efc0ebd5f2 100644 --- a/packages/playwright/src/matchers/toHaveURL.ts +++ b/packages/playwright/src/matchers/toHaveURL.ts @@ -15,7 +15,7 @@ */ import { constructURLBasedOnBaseURL, urlMatches } from 'playwright-core/lib/utils'; -import { colors } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; import { printReceivedStringContainExpectedResult, printReceivedStringContainExpectedSubstring } from './expect'; import { matcherHint } from './matcherHint'; diff --git a/packages/playwright/src/matchers/toMatchSnapshot.ts b/packages/playwright/src/matchers/toMatchSnapshot.ts index 220c948887..dd54e61aeb 100644 --- a/packages/playwright/src/matchers/toMatchSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchSnapshot.ts @@ -18,7 +18,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { compareBuffersOrStrings, getComparator, isString, sanitizeForFilePath } from 'playwright-core/lib/utils'; -import { colors } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; import { mime } from 'playwright-core/lib/utilsBundle'; import { diff --git a/packages/playwright/src/matchers/toMatchText.ts b/packages/playwright/src/matchers/toMatchText.ts index 961937eb5b..142013f1b5 100644 --- a/packages/playwright/src/matchers/toMatchText.ts +++ b/packages/playwright/src/matchers/toMatchText.ts @@ -15,7 +15,7 @@ */ -import { colors } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; import { callLogText, expectTypes } from '../util'; import { diff --git a/packages/playwright/src/plugins/webServerPlugin.ts b/packages/playwright/src/plugins/webServerPlugin.ts index 62583c303e..4bb1bef0db 100644 --- a/packages/playwright/src/plugins/webServerPlugin.ts +++ b/packages/playwright/src/plugins/webServerPlugin.ts @@ -17,7 +17,8 @@ import * as net from 'net'; import * as path from 'path'; import { launchProcess, isURLAvailable, monotonicTime, raceAgainstDeadline } from 'playwright-core/lib/utils'; -import { colors, debug } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; +import { debug } from 'playwright-core/lib/utilsBundle'; import type { TestRunnerPlugin } from '.'; import type { FullConfig } from '../../types/testReporter'; diff --git a/packages/playwright/src/reporters/base.ts b/packages/playwright/src/reporters/base.ts index 156f9755db..1748cac9b9 100644 --- a/packages/playwright/src/reporters/base.ts +++ b/packages/playwright/src/reporters/base.ts @@ -18,18 +18,19 @@ import * as path from 'path'; import { getPackageManagerExecCommand } from 'playwright-core/lib/utils'; import { parseStackTraceLine } from 'playwright-core/lib/utils'; -import { colors as realColors, ms as milliseconds } from 'playwright-core/lib/utilsBundle'; +import { ms as milliseconds } from 'playwright-core/lib/utilsBundle'; +import { colors as realColors, noColors } from 'playwright-core/lib/utils'; import { resolveReporterOutputPath } from '../util'; import { getEastAsianWidth } from '../utilsBundle'; import type { ReporterV2 } from './reporterV2'; import type { FullConfig, FullResult, Location, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter'; +import type { Colors } from '@isomorphic/colors'; + export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' }; export const kOutputSymbol = Symbol('output'); -type Colors = typeof realColors; - type ErrorDetails = { message: string; location?: Location; @@ -53,48 +54,6 @@ export type Screen = { ttyWidth: number; }; -export const noColors: Colors = { - bold: (t: string) => t, - cyan: (t: string) => t, - dim: (t: string) => t, - gray: (t: string) => t, - green: (t: string) => t, - red: (t: string) => t, - yellow: (t: string) => t, - black: (t: string) => t, - blue: (t: string) => t, - magenta: (t: string) => t, - white: (t: string) => t, - grey: (t: string) => t, - bgBlack: (t: string) => t, - bgRed: (t: string) => t, - bgGreen: (t: string) => t, - bgYellow: (t: string) => t, - bgBlue: (t: string) => t, - bgMagenta: (t: string) => t, - bgCyan: (t: string) => t, - bgWhite: (t: string) => t, - strip: (t: string) => t, - stripColors: (t: string) => t, - reset: (t: string) => t, - italic: (t: string) => t, - underline: (t: string) => t, - inverse: (t: string) => t, - hidden: (t: string) => t, - strikethrough: (t: string) => t, - rainbow: (t: string) => t, - zebra: (t: string) => t, - america: (t: string) => t, - trap: (t: string) => t, - random: (t: string) => t, - zalgo: (t: string) => t, - - enabled: false, - enable: () => {}, - disable: () => {}, - setTheme: () => {}, -}; - // Output goes to terminal. export const terminalScreen: Screen = (() => { let isTTY = !!process.stdout.isTTY; @@ -563,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); + const frame = parseStackTraceLine(line, path.sep); if (!frame || !frame.file) continue; if (belongsToNodeModules(frame.file)) diff --git a/packages/playwright/src/reporters/github.ts b/packages/playwright/src/reporters/github.ts index e3677dd3ff..7c65c00b3c 100644 --- a/packages/playwright/src/reporters/github.ts +++ b/packages/playwright/src/reporters/github.ts @@ -16,9 +16,10 @@ import * as path from 'path'; +import { noColors } from 'playwright-core/lib/utils'; import { ms as milliseconds } from 'playwright-core/lib/utilsBundle'; -import { TerminalReporter, formatResultFailure, formatRetry, noColors, stripAnsiEscapes } from './base'; +import { TerminalReporter, formatResultFailure, formatRetry, stripAnsiEscapes } from './base'; import type { FullResult, TestCase, TestError } from '../../types/testReporter'; diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index 3390a71746..a86a758d48 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -19,7 +19,8 @@ import * as path from 'path'; import { Transform } from 'stream'; import { HttpServer, MultiMap, assert, calculateSha1, getPackageManagerExecCommand, copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath, toPosixPath } from 'playwright-core/lib/utils'; -import { colors, open } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; +import { open } from 'playwright-core/lib/utilsBundle'; import { mime } from 'playwright-core/lib/utilsBundle'; import { yazl } from 'playwright-core/lib/zipBundle'; diff --git a/packages/playwright/src/runner/dispatcher.ts b/packages/playwright/src/runner/dispatcher.ts index 084deba11d..cf0c7144c0 100644 --- a/packages/playwright/src/runner/dispatcher.ts +++ b/packages/playwright/src/runner/dispatcher.ts @@ -15,7 +15,7 @@ */ import { ManualPromise, eventsHelper } from 'playwright-core/lib/utils'; -import { colors } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; import { addSuggestedRebaseline } from './rebase'; import { WorkerHost } from './workerHost'; diff --git a/packages/playwright/src/runner/rebase.ts b/packages/playwright/src/runner/rebase.ts index c2d9f96d18..ef7d49f013 100644 --- a/packages/playwright/src/runner/rebase.ts +++ b/packages/playwright/src/runner/rebase.ts @@ -19,7 +19,8 @@ import * as path from 'path'; import { MultiMap } from 'playwright-core/lib/utils'; -import { colors, diff } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; +import { diff } from 'playwright-core/lib/utilsBundle'; import { filterProjects } from './projectUtils'; import { babelParse, traverse, types } from '../transform/babelBundle'; diff --git a/packages/playwright/src/runner/taskRunner.ts b/packages/playwright/src/runner/taskRunner.ts index 12185aa951..ea0561faaa 100644 --- a/packages/playwright/src/runner/taskRunner.ts +++ b/packages/playwright/src/runner/taskRunner.ts @@ -15,7 +15,8 @@ */ import { ManualPromise, monotonicTime } from 'playwright-core/lib/utils'; -import { colors, debug } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; +import { debug } from 'playwright-core/lib/utilsBundle'; import { SigIntWatcher } from './sigIntWatcher'; diff --git a/packages/playwright/src/runner/watchMode.ts b/packages/playwright/src/runner/watchMode.ts index c9768c47ba..ad819853fc 100644 --- a/packages/playwright/src/runner/watchMode.ts +++ b/packages/playwright/src/runner/watchMode.ts @@ -20,7 +20,7 @@ import { EventEmitter } from 'stream'; import { PlaywrightServer } from 'playwright-core/lib/remote/playwrightServer'; import { ManualPromise, createGuid, eventsHelper, getPackageManagerExecCommand } from 'playwright-core/lib/utils'; -import { colors } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; import { separator, terminalScreen } from '../reporters/base'; import { enquirer } from '../utilsBundle'; diff --git a/packages/playwright/src/util.ts b/packages/playwright/src/util.ts index aa575710e5..b80a3f387d 100644 --- a/packages/playwright/src/util.ts +++ b/packages/playwright/src/util.ts @@ -19,10 +19,9 @@ import * as path from 'path'; import * as url from 'url'; import util from 'util'; -import { sanitizeForFilePath } from 'playwright-core/lib/utils'; -import { calculateSha1, formatCallLog, isRegExp, isString, stringifyStackFrames } from 'playwright-core/lib/utils'; -import { parseStackTraceLine } from 'playwright-core/lib/utils'; +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 type { Location } from './../types/testReporter'; import type { TestInfoErrorImpl } from './common/ipc'; @@ -57,7 +56,7 @@ export function filterStackFile(file: string) { export function filteredStackTrace(rawStack: RawStack): StackFrame[] { const frames: StackFrame[] = []; for (const line of rawStack) { - const frame = parseStackTraceLine(line); + const frame = parseStackTraceLine(line, path.sep); if (!frame || !frame.file) continue; if (!filterStackFile(frame.file)) @@ -226,7 +225,7 @@ export function getContainedPath(parentPath: string, subPath: string = ''): stri export const debugTest = debug('pw:test'); -export const callLogText = formatCallLog; +export const callLogText = (log: string[] | undefined) => formatCallLog(nodePlatform, log); const folderToPackageJsonPath = new Map(); diff --git a/packages/playwright/src/worker/testTracing.ts b/packages/playwright/src/worker/testTracing.ts index a90c006616..ba215c8792 100644 --- a/packages/playwright/src/worker/testTracing.ts +++ b/packages/playwright/src/worker/testTracing.ts @@ -46,6 +46,7 @@ export class TestTracing { private _artifactsDir: string; private _tracesDir: string; private _contextCreatedEvent: trace.ContextCreatedTraceEvent; + private _didFinishTestFunctionAndAfterEachHooks = false; constructor(testInfo: TestInfoImpl, artifactsDir: string) { this._testInfo = testInfo; @@ -113,6 +114,10 @@ export class TestTracing { } } + didFinishTestFunctionAndAfterEachHooks() { + this._didFinishTestFunctionAndAfterEachHooks = true; + } + artifactsDir() { return this._artifactsDir; } @@ -133,7 +138,7 @@ export class TestTracing { return `${this._testInfo.testId}${retrySuffix}${ordinalSuffix}`; } - generateNextTraceRecordingPath() { + private _generateNextTraceRecordingPath() { const file = path.join(this._artifactsDir, createGuid() + '.zip'); this._temporaryTraceFiles.push(file); return file; @@ -143,6 +148,22 @@ export class TestTracing { return this._options; } + maybeGenerateNextTraceRecordingPath() { + // Forget about traces that should be saved on failure, when no failure happened + // during the test and beforeEach/afterEach hooks. + // This avoids downloading traces over the wire when not really needed. + if (this._didFinishTestFunctionAndAfterEachHooks && this._shouldAbandonTrace()) + return; + return this._generateNextTraceRecordingPath(); + } + + private _shouldAbandonTrace() { + if (!this._options) + return true; + const testFailed = this._testInfo.status !== this._testInfo.expectedStatus; + return !testFailed && (this._options.mode === 'retain-on-failure' || this._options.mode === 'retain-on-first-failure'); + } + async stopIfNeeded() { if (!this._options) return; @@ -151,10 +172,7 @@ export class TestTracing { if (error) throw error; - const testFailed = this._testInfo.status !== this._testInfo.expectedStatus; - const shouldAbandonTrace = !testFailed && (this._options.mode === 'retain-on-failure' || this._options.mode === 'retain-on-first-failure'); - - if (shouldAbandonTrace) { + if (this._shouldAbandonTrace()) { for (const file of this._temporaryTraceFiles) await fs.promises.unlink(file).catch(() => {}); return; @@ -213,7 +231,7 @@ export class TestTracing { await new Promise(f => { zipFile.end(undefined, () => { - zipFile.outputStream.pipe(fs.createWriteStream(this.generateNextTraceRecordingPath())).on('close', f); + zipFile.outputStream.pipe(fs.createWriteStream(this._generateNextTraceRecordingPath())).on('close', f); }); }); diff --git a/packages/playwright/src/worker/timeoutManager.ts b/packages/playwright/src/worker/timeoutManager.ts index 7018dafcc9..65e97c9f36 100644 --- a/packages/playwright/src/worker/timeoutManager.ts +++ b/packages/playwright/src/worker/timeoutManager.ts @@ -15,7 +15,7 @@ */ import { ManualPromise, monotonicTime } from 'playwright-core/lib/utils'; -import { colors } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; import type { Location } from '../../types/testReporter'; diff --git a/packages/playwright/src/worker/workerMain.ts b/packages/playwright/src/worker/workerMain.ts index 5188614271..e09579d0e0 100644 --- a/packages/playwright/src/worker/workerMain.ts +++ b/packages/playwright/src/worker/workerMain.ts @@ -15,7 +15,7 @@ */ import { ManualPromise, gracefullyCloseAll, removeFolders } from 'playwright-core/lib/utils'; -import { colors } from 'playwright-core/lib/utilsBundle'; +import { colors } from 'playwright-core/lib/utils'; import { deserializeConfig } from '../common/configLoader'; import { setCurrentTestInfo, setIsWorkerProcess } from '../common/globals'; @@ -399,6 +399,8 @@ export class WorkerMain extends ProcessRunner { firstAfterHooksError = firstAfterHooksError ?? error; } + testInfo._tracing.didFinishTestFunctionAndAfterEachHooks(); + try { // Teardown test-scoped fixtures. Attribute to 'test' so that users understand // they should probably increase the test timeout to fix this issue. diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index ab23b480b2..a31bb858d5 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -1367,6 +1367,8 @@ interface TestConfig { * * This information will appear in the HTML and JSON reports and is available in the Reporter API. * + * On Github Actions, this feature is enabled by default. + * * **Usage** * * ```js diff --git a/packages/web/src/components/prompts.ts b/packages/web/src/components/prompts.ts index 8ecc78e9c6..d446dfa17d 100644 --- a/packages/web/src/components/prompts.ts +++ b/packages/web/src/components/prompts.ts @@ -19,9 +19,18 @@ function stripAnsiEscapes(str: string): string { return str.replace(ansiRegex, ''); } +function enumerate(items: string[]) { + if (items.length === 0) + return ''; + if (items.length === 1) + return items[0]; + return items.slice(0, -1).join(', ') + ' and ' + items[items.length - 1]; +} + export function fixTestPrompt(error: string, diff?: string, pageSnapshot?: string) { + const includedData = ['the error', diff && 'a code diff', pageSnapshot && 'a snapshot of the page'].filter((v): v is string => Boolean(v)); const promptParts = [ - `My Playwright test failed. What's going wrong?`, + `My Playwright test failed, what's going wrong? I've included ${enumerate(includedData)} below.`, `Please give me a suggestion how to fix it, and then explain what went wrong. Be very concise and apply Playwright best practices.`, `Don't include many headings in your output. Make sure what you're saying is correct, and take into account whether there might be a bug in the app.`, 'Here is the error:', diff --git a/tests/library/browsertype-connect.spec.ts b/tests/library/browsertype-connect.spec.ts index e4118e489f..1298e38b5e 100644 --- a/tests/library/browsertype-connect.spec.ts +++ b/tests/library/browsertype-connect.spec.ts @@ -19,7 +19,7 @@ import fs from 'fs'; import type http from 'http'; import type net from 'net'; import * as path from 'path'; -import { getUserAgent, getPlaywrightVersion } from '../../packages/playwright-core/lib/utils/userAgent'; +import { getUserAgent, getPlaywrightVersion } from '../../packages/playwright-core/lib/server/utils/userAgent'; import WebSocket from 'ws'; import { expect, playwrightTest } from '../config/browserTest'; import { parseTrace, suppressCertificateWarning } from '../config/utils'; @@ -258,7 +258,9 @@ for (const kind of ['launchServer', 'run-server'] as const) { }).catch(() => {}) ]); expect(request.headers['user-agent']).toBe(getUserAgent()); - expect(request.headers['x-playwright-browser']).toBe(browserName); + // _bidiFirefox and _bidiChromium are initialized with 'bidi' as browser name. + const bidiAwareBrowserName = browserName.startsWith('_bidi') ? 'bidi' : browserName; + expect(request.headers['x-playwright-browser']).toBe(bidiAwareBrowserName); expect(request.headers['foo']).toBe('bar'); }); diff --git a/tests/library/capabilities.spec.ts b/tests/library/capabilities.spec.ts index 29328f3f80..8b065834ab 100644 --- a/tests/library/capabilities.spec.ts +++ b/tests/library/capabilities.spec.ts @@ -17,7 +17,7 @@ import os from 'os'; import url from 'url'; import { contextTest as it, expect } from '../config/browserTest'; -import { hostPlatform } from '../../packages/playwright-core/src/utils/hostPlatform'; +import { hostPlatform } from '../../packages/playwright-core/src/server/utils/hostPlatform'; it('SharedArrayBuffer should work @smoke', async function({ contextFactory, httpsServer }) { const context = await contextFactory({ ignoreHTTPSErrors: true }); diff --git a/tests/library/chromium/connect-over-cdp.spec.ts b/tests/library/chromium/connect-over-cdp.spec.ts index f31f60baf0..3191403be8 100644 --- a/tests/library/chromium/connect-over-cdp.spec.ts +++ b/tests/library/chromium/connect-over-cdp.spec.ts @@ -18,7 +18,7 @@ import { playwrightTest as test, expect } from '../../config/browserTest'; import http from 'http'; import fs from 'fs'; -import { getUserAgent } from '../../../packages/playwright-core/lib/utils/userAgent'; +import { getUserAgent } from '../../../packages/playwright-core/lib/server/utils/userAgent'; import { suppressCertificateWarning } from '../../config/utils'; test.skip(({ mode }) => mode === 'service2'); diff --git a/tests/library/debug-controller.spec.ts b/tests/library/debug-controller.spec.ts index b8163296f7..7481e04246 100644 --- a/tests/library/debug-controller.spec.ts +++ b/tests/library/debug-controller.spec.ts @@ -16,7 +16,7 @@ import { expect, playwrightTest as baseTest } from '../config/browserTest'; import { PlaywrightServer } from '../../packages/playwright-core/lib/remote/playwrightServer'; -import { createGuid } from '../../packages/playwright-core/lib/utils/crypto'; +import { createGuid } from '../../packages/playwright-core/lib/server/utils/crypto'; import { Backend } from '../config/debugControllerBackend'; import type { Browser, BrowserContext } from '@playwright/test'; import type * as channels from '@protocol/channels'; diff --git a/tests/library/events/check-listener-leaks.spec.ts b/tests/library/events/check-listener-leaks.spec.ts index d9b51f27ef..5a328d720f 100644 --- a/tests/library/events/check-listener-leaks.spec.ts +++ b/tests/library/events/check-listener-leaks.spec.ts @@ -22,7 +22,7 @@ import events from 'events'; import { EventEmitter } from '../../../packages/playwright-core/lib/client/eventEmitter'; -import { setUnderTest } from '../../../packages/playwright-core/lib/utils/debug'; +import { setUnderTest } from '../../../packages/playwright-core/lib/utils/isomorphic/debug'; import { test, expect } from '@playwright/test'; import * as common from './utils'; diff --git a/tests/library/events/remove-all-listeners-wait.spec.ts b/tests/library/events/remove-all-listeners-wait.spec.ts index 1f8dcc8dd0..e5b4bf2dc5 100644 --- a/tests/library/events/remove-all-listeners-wait.spec.ts +++ b/tests/library/events/remove-all-listeners-wait.spec.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { ManualPromise } from '../../../packages/playwright-core/lib/utils/manualPromise'; +import { ManualPromise } from '../../../packages/playwright-core/lib/utils/isomorphic/manualPromise'; import { EventEmitter } from '../../../packages/playwright-core/lib/client/eventEmitter'; import { test, expect } from '@playwright/test'; diff --git a/tests/library/global-fetch.spec.ts b/tests/library/global-fetch.spec.ts index 0ad73bb9b6..9a402ed152 100644 --- a/tests/library/global-fetch.spec.ts +++ b/tests/library/global-fetch.spec.ts @@ -16,7 +16,7 @@ import os from 'os'; import * as util from 'util'; -import { getPlaywrightVersion } from '../../packages/playwright-core/lib/utils/userAgent'; +import { getPlaywrightVersion } from '../../packages/playwright-core/lib/server/utils/userAgent'; import { expect, playwrightTest as base } from '../config/browserTest'; import { kTargetClosedErrorMessage } from 'tests/config/errors'; diff --git a/tests/library/inspector/inspectorTest.ts b/tests/library/inspector/inspectorTest.ts index fa13420fc3..c2ed97d50e 100644 --- a/tests/library/inspector/inspectorTest.ts +++ b/tests/library/inspector/inspectorTest.ts @@ -22,7 +22,7 @@ import type { Source } from '../../../packages/recorder/src/recorderTypes'; import type { CommonFixtures, TestChildProcess } from '../../config/commonFixtures'; import { stripAnsi } from '../../config/utils'; import { expect } from '@playwright/test'; -import { nodePlatform } from '../../../packages/playwright-core/lib/utils/platform'; +import { nodePlatform } from '../../../packages/playwright-core/lib/server/utils/nodePlatform'; export { expect } from '@playwright/test'; type CLITestArgs = { diff --git a/tests/library/modernizr.spec.ts b/tests/library/modernizr.spec.ts index c0a06f3714..0a1b124301 100644 --- a/tests/library/modernizr.spec.ts +++ b/tests/library/modernizr.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { hostPlatform } from '../../packages/playwright-core/src/utils/hostPlatform'; +import { hostPlatform } from '../../packages/playwright-core/src/server/utils/hostPlatform'; import { browserTest as it, expect } from '../config/browserTest'; import fs from 'fs'; import os from 'os'; diff --git a/tests/library/unit/sequence.spec.ts b/tests/library/unit/sequence.spec.ts index 624722753e..e0decd4d84 100644 --- a/tests/library/unit/sequence.spec.ts +++ b/tests/library/unit/sequence.spec.ts @@ -16,7 +16,7 @@ import { test as it, expect } from '@playwright/test'; -import { findRepeatedSubsequences } from '../../../packages/playwright-core/lib/utils/sequence'; +import { findRepeatedSubsequences } from '../../../packages/playwright-core/lib/utils/isomorphic/sequence'; it('should return an empty array when the input is empty', () => { const input = []; diff --git a/tests/page/interception.spec.ts b/tests/page/interception.spec.ts index eef67e8b37..9447a80fcd 100644 --- a/tests/page/interception.spec.ts +++ b/tests/page/interception.spec.ts @@ -126,7 +126,6 @@ it('should intercept worker requests when enabled after worker creation', { }, async ({ page, server, isAndroid, browserName, browserMajorVersion }) => { it.skip(isAndroid); it.skip(browserName === 'chromium' && browserMajorVersion < 130, 'fixed in Chromium 130'); - it.fixme(browserName === 'chromium', 'requires PlzDedicatedWorker to be enabled'); await page.goto(server.EMPTY_PAGE); server.setRoute('/data_for_worker', (req, res) => res.end('failed to intercept')); diff --git a/tests/page/page-filechooser.spec.ts b/tests/page/page-filechooser.spec.ts new file mode 100644 index 0000000000..7fc8cf5500 --- /dev/null +++ b/tests/page/page-filechooser.spec.ts @@ -0,0 +1,368 @@ +/** + * Copyright 2017 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 { test, expect } from './pageTest'; +import { attachFrame } from '../config/utils'; + +import fs from 'fs'; +import formidable from 'formidable'; + +test('should upload multiple large files', async ({ page, server, isAndroid, isWebView2, mode }, testInfo) => { + test.skip(isAndroid); + test.skip(isWebView2); + test.skip(mode.startsWith('service')); + test.slow(); + + const filesCount = 10; + await page.goto(server.PREFIX + '/input/fileupload-multi.html'); + const uploadFile = testInfo.outputPath('50MB_1.zip'); + const str = 'A'.repeat(1024); + const stream = fs.createWriteStream(uploadFile); + // 49 is close to the actual limit + for (let i = 0; i < 49 * 1024; i++) { + await new Promise((fulfill, reject) => { + stream.write(str, err => { + if (err) + reject(err); + else + fulfill(); + }); + }); + } + await new Promise(f => stream.end(f)); + const input = page.locator('input[type="file"]'); + const uploadFiles = [uploadFile]; + for (let i = 2; i <= filesCount; i++) { + const dstFile = testInfo.outputPath(`50MB_${i}.zip`); + fs.copyFileSync(uploadFile, dstFile); + uploadFiles.push(dstFile); + } + const fileChooserPromise = page.waitForEvent('filechooser'); + await input.click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles(uploadFiles); + const filesLen = await page.evaluate('document.getElementsByTagName("input")[0].files.length'); + expect(fileChooser.isMultiple()).toBe(true); + expect(filesLen).toEqual(filesCount); + await Promise.all(uploadFiles.map(path => fs.promises.unlink(path))); +}); + +test('should emit event once', async ({ page, server }) => { + await page.setContent(``); + const [chooser] = await Promise.all([ + new Promise(f => page.once('filechooser', f)), + page.click('input'), + ]); + expect(chooser).toBeTruthy(); +}); + +test('should emit event via prepend', async ({ page, server }) => { + await page.setContent(``); + const [chooser] = await Promise.all([ + new Promise(f => page.prependListener('filechooser', f)), + page.click('input'), + ]); + expect(chooser).toBeTruthy(); +}); + +test('should emit event for iframe', async ({ page, server }) => { + const frame = await attachFrame(page, 'frame1', server.EMPTY_PAGE); + await frame.setContent(``); + const [chooser] = await Promise.all([ + new Promise(f => page.once('filechooser', f)), + frame.click('input'), + ]); + expect(chooser).toBeTruthy(); +}); + +test('should emit event on/off', async ({ page, server }) => { + await page.setContent(``); + const [chooser] = await Promise.all([ + new Promise(f => { + const listener = chooser => { + page.off('filechooser', listener); + f(chooser); + }; + page.on('filechooser', listener); + }), + page.click('input'), + ]); + expect(chooser).toBeTruthy(); +}); + +test('should emit event addListener/removeListener', async ({ page, server }) => { + await page.setContent(``); + const [chooser] = await Promise.all([ + new Promise(f => { + const listener = chooser => { + page.removeListener('filechooser', listener); + f(chooser); + }; + page.addListener('filechooser', listener); + }), + page.click('input'), + ]); + expect(chooser).toBeTruthy(); +}); + +test('should work when file input is attached to DOM', async ({ page, server }) => { + await page.setContent(``); + const [chooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(chooser).toBeTruthy(); +}); + +test('should work when file input is not attached to DOM', async ({ page, asset }) => { + const [, content] = await Promise.all([ + page.waitForEvent('filechooser').then(chooser => chooser.setFiles(asset('file-to-upload.txt'))), + page.evaluate(async () => { + const el = document.createElement('input'); + el.type = 'file'; + el.click(); + await new Promise(x => el.oninput = x); + const reader = new FileReader(); + const promise = new Promise(fulfill => reader.onload = fulfill); + reader.readAsText(el.files[0]); + return promise.then(() => reader.result); + }), + ]); + expect(content).toBe('contents of the file'); +}); + +test('should not throw when filechooser belongs to iframe', async ({ page, server, browserName }) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = page.mainFrame().childFrames()[0]; + await frame.setContent(` +
Click me
+ + `); + await Promise.all([ + page.waitForEvent('filechooser'), + frame.click('div') + ]); + await page.waitForFunction(() => (window as any).__done); +}); + +test('should not throw when frame is detached immediately', async ({ page, server }) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + const frame = page.mainFrame().childFrames()[0]; + await frame.setContent(` +
Click me
+ + `); + page.on('filechooser', () => {}); // To ensure we handle file choosers. + await frame.click('div'); + await page.waitForFunction(() => (window as any).__done); +}); + +test('should respect timeout', async ({ page, playwright }) => { + let error = null; + await page.waitForEvent('filechooser', { timeout: 1 }).catch(e => error = e); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); +}); + +test('should respect default timeout when there is no custom timeout', async ({ page, playwright }) => { + page.setDefaultTimeout(1); + let error = null; + await page.waitForEvent('filechooser').catch(e => error = e); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); +}); + +test('should prioritize exact timeout over default timeout', async ({ page, playwright }) => { + page.setDefaultTimeout(0); + let error = null; + await page.waitForEvent('filechooser', { timeout: 1 }).catch(e => error = e); + expect(error).toBeInstanceOf(playwright.errors.TimeoutError); +}); + +test('should work with no timeout', async ({ page, server }) => { + const [chooser] = await Promise.all([ + page.waitForEvent('filechooser', { timeout: 0 }), + page.evaluate(() => window.builtinSetTimeout(() => { + const el = document.createElement('input'); + el.type = 'file'; + el.click(); + }, 50)) + ]); + expect(chooser).toBeTruthy(); +}); + +test('should return the same file chooser when there are many watchdogs simultaneously', async ({ page, server }) => { + await page.setContent(``); + const [fileChooser1, fileChooser2] = await Promise.all([ + page.waitForEvent('filechooser'), + page.waitForEvent('filechooser'), + page.$eval('input', input => input.click()), + ]); + expect(fileChooser1 === fileChooser2).toBe(true); +}); + +test('should accept single file', async ({ page, asset }) => { + await page.setContent(``); + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(fileChooser.page()).toBe(page); + expect(fileChooser.element()).toBeTruthy(); + await fileChooser.setFiles(asset('file-to-upload.txt')); + expect(await page.$eval('input', input => input.files.length)).toBe(1); + expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt'); +}); + +// @see https://github.com/microsoft/playwright/issues/4704 +test('should not trim big uploaded files', async ({ page, server }) => { + + let files: Record; + server.setRoute('/upload', async (req, res) => { + const form = new formidable.IncomingForm(); + form.parse(req, function(err, fields, f) { + files = f as Record; + res.end(); + }); + }); + await page.goto(server.EMPTY_PAGE); + + const DATA_SIZE = Math.pow(2, 20); + await Promise.all([ + page.evaluate(async size => { + const body = new FormData(); + body.set('file', new Blob([new Uint8Array(size)])); + await fetch('/upload', { method: 'POST', body }); + }, DATA_SIZE), + server.waitForRequest('/upload'), + ]); + expect(files.file.size).toBe(DATA_SIZE); +}); + +test('should be able to read selected file', async ({ page, asset }) => { + await page.setContent(``); + const [, content] = await Promise.all([ + page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles(asset('file-to-upload.txt'))), + page.$eval('input', async picker => { + picker.click(); + await new Promise(x => picker.oninput = x); + const reader = new FileReader(); + const promise = new Promise(fulfill => reader.onload = fulfill); + reader.readAsText(picker.files[0]); + return promise.then(() => reader.result); + }), + ]); + expect(content).toBe('contents of the file'); +}); + +test('should be able to reset selected files with empty file list', async ({ page, asset }) => { + await page.setContent(``); + const [, fileLength1] = await Promise.all([ + page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles(asset('file-to-upload.txt'))), + page.$eval('input', async picker => { + picker.click(); + await new Promise(x => picker.oninput = x); + return picker.files.length; + }), + ]); + expect(fileLength1).toBe(1); + const [, fileLength2] = await Promise.all([ + page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles([])), + page.$eval('input', async picker => { + picker.click(); + await new Promise(x => picker.oninput = x); + return picker.files.length; + }), + ]); + expect(fileLength2).toBe(0); +}); + +test('should work for single file pick', async ({ page, server }) => { + await page.setContent(``); + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(fileChooser.isMultiple()).toBe(false); +}); + +test('should work for "multiple"', async ({ page, server }) => { + await page.setContent(``); + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(fileChooser.isMultiple()).toBe(true); +}); + +test('should work for "webkitdirectory"', async ({ page, server }) => { + await page.setContent(``); + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(fileChooser.isMultiple()).toBe(true); +}); + +test('should emit event after navigation', async ({ page, server, browserName, browserMajorVersion }) => { + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/11375' }); + test.skip(browserName === 'chromium' && browserMajorVersion < 99); + + const logs = []; + page.on('filechooser', () => logs.push('filechooser')); + await page.goto(server.PREFIX + '/empty.html'); + await page.setContent(``); + await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + await page.setContent(``); + await Promise.all([ + page.waitForEvent('filechooser'), + page.click('input'), + ]); + expect(logs).toEqual(['filechooser', 'filechooser']); +}); + +test('should trigger listener added before navigation', async ({ page, server, browserMajorVersion, isElectron }) => { + test.skip(isElectron && browserMajorVersion <= 98); + // Add listener before cross process navigation. + const chooserPromise = new Promise(f => page.once('filechooser', f)); + await page.goto(server.PREFIX + '/empty.html'); + await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + await page.setContent(``); + const [chooser] = await Promise.all([ + chooserPromise, + page.click('input'), + ]); + expect(chooser).toBeTruthy(); +}); diff --git a/tests/page/page-leaks.spec.ts b/tests/page/page-leaks.spec.ts index abd33e651e..90f78d83a9 100644 --- a/tests/page/page-leaks.spec.ts +++ b/tests/page/page-leaks.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { MultiMap } from '../../packages/playwright-core/lib/utils/multimap'; +import { MultiMap } from '../../packages/playwright-core/lib/utils/isomorphic/multimap'; import { test, expect } from './pageTest'; function leakedJSHandles(): string { diff --git a/tests/page/page-listeners.spec.ts b/tests/page/page-listeners.spec.ts index c22be64553..495ecf36c7 100644 --- a/tests/page/page-listeners.spec.ts +++ b/tests/page/page-listeners.spec.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { ManualPromise } from '../../packages/playwright-core/lib/utils/manualPromise'; +import { ManualPromise } from '../../packages/playwright-core/lib/utils/isomorphic/manualPromise'; import { test as it, expect } from './pageTest'; // This test is mostly for type checking, the actual tests are in the library/events. diff --git a/tests/page/page-set-input-files.spec.ts b/tests/page/page-set-input-files.spec.ts index eaf1316f5c..9129a6ee4f 100644 --- a/tests/page/page-set-input-files.spec.ts +++ b/tests/page/page-set-input-files.spec.ts @@ -15,14 +15,13 @@ * limitations under the License. */ -import { test as it, expect } from './pageTest'; -import { attachFrame } from '../config/utils'; +import { test, expect } from './pageTest'; import path from 'path'; import fs from 'fs'; import formidable from 'formidable'; -it('should upload the file', async ({ page, server, asset }) => { +test('should upload the file', async ({ page, server, asset }) => { await page.goto(server.PREFIX + '/input/fileupload.html'); const filePath = path.relative(process.cwd(), asset('file-to-upload.txt')); const input = await page.$('input'); @@ -36,13 +35,13 @@ it('should upload the file', async ({ page, server, asset }) => { }, input)).toBe('contents of the file'); }); -it('should upload a folder', async ({ page, server, browserName, headless, browserMajorVersion, isAndroid, macVersion, isMac }) => { - it.skip(isAndroid); - it.skip(browserName === 'webkit' && isMac && macVersion <= 12, 'WebKit on macOS-12 is frozen'); +test('should upload a folder', async ({ page, server, browserName, headless, browserMajorVersion, isAndroid, macVersion, isMac }) => { + test.skip(isAndroid); + test.skip(browserName === 'webkit' && isMac && macVersion <= 12, 'WebKit on macOS-12 is frozen'); await page.goto(server.PREFIX + '/input/folderupload.html'); const input = await page.$('input'); - const dir = path.join(it.info().outputDir, 'file-upload-test'); + const dir = path.join(test.info().outputDir, 'file-upload-test'); { await fs.promises.mkdir(dir, { recursive: true }); await fs.promises.writeFile(path.join(dir, 'file1.txt'), 'file1 content'); @@ -69,13 +68,13 @@ it('should upload a folder', async ({ page, server, browserName, headless, brows } }); -it('should upload a folder and throw for multiple directories', async ({ page, server, isAndroid, browserName, macVersion, isMac }) => { - it.skip(isAndroid); - it.skip(browserName === 'webkit' && isMac && macVersion <= 12, 'WebKit on macOS-12 is frozen'); +test('should upload a folder and throw for multiple directories', async ({ page, server, isAndroid, browserName, macVersion, isMac }) => { + test.skip(isAndroid); + test.skip(browserName === 'webkit' && isMac && macVersion <= 12, 'WebKit on macOS-12 is frozen'); await page.goto(server.PREFIX + '/input/folderupload.html'); const input = await page.$('input'); - const dir = path.join(it.info().outputDir, 'file-upload-test'); + const dir = path.join(test.info().outputDir, 'file-upload-test'); { await fs.promises.mkdir(path.join(dir, 'folder1'), { recursive: true }); await fs.promises.writeFile(path.join(dir, 'folder1', 'file1.txt'), 'file1 content'); @@ -88,13 +87,13 @@ it('should upload a folder and throw for multiple directories', async ({ page, s ])).rejects.toThrow('Multiple directories are not supported'); }); -it('should throw if a directory and files are passed', async ({ page, server, isAndroid, browserName, macVersion, isMac }) => { - it.skip(isAndroid); - it.skip(browserName === 'webkit' && isMac && macVersion <= 12, 'WebKit on macOS-12 is frozen'); +test('should throw if a directory and files are passed', async ({ page, server, isAndroid, browserName, macVersion, isMac }) => { + test.skip(isAndroid); + test.skip(browserName === 'webkit' && isMac && macVersion <= 12, 'WebKit on macOS-12 is frozen'); await page.goto(server.PREFIX + '/input/folderupload.html'); const input = await page.$('input'); - const dir = path.join(it.info().outputDir, 'file-upload-test'); + const dir = path.join(test.info().outputDir, 'file-upload-test'); { await fs.promises.mkdir(path.join(dir, 'folder1'), { recursive: true }); await fs.promises.writeFile(path.join(dir, 'folder1', 'file1.txt'), 'file1 content'); @@ -105,13 +104,13 @@ it('should throw if a directory and files are passed', async ({ page, server, is ])).rejects.toThrow('File paths must be all files or a single directory'); }); -it('should throw when uploading a folder in a normal file upload input', async ({ page, server, isAndroid, browserName, macVersion, isMac }) => { - it.skip(isAndroid); - it.skip(browserName === 'webkit' && isMac && macVersion <= 12, 'WebKit on macOS-12 is frozen'); +test('should throw when uploading a folder in a normal file upload input', async ({ page, server, isAndroid, browserName, macVersion, isMac }) => { + test.skip(isAndroid); + test.skip(browserName === 'webkit' && isMac && macVersion <= 12, 'WebKit on macOS-12 is frozen'); await page.goto(server.PREFIX + '/input/fileupload.html'); const input = await page.$('input'); - const dir = path.join(it.info().outputDir, 'file-upload-test'); + const dir = path.join(test.info().outputDir, 'file-upload-test'); { await fs.promises.mkdir(path.join(dir), { recursive: true }); await fs.promises.writeFile(path.join(dir, 'file1.txt'), 'file1 content'); @@ -119,17 +118,17 @@ it('should throw when uploading a folder in a normal file upload input', async ( await expect(input.setInputFiles(dir)).rejects.toThrow('File input does not support directories, pass individual files instead'); }); -it('should throw when uploading a file in a directory upload input', async ({ page, server, isAndroid, asset, browserName, macVersion, isMac }) => { - it.skip(isAndroid); - it.skip(browserName === 'webkit' && isMac && macVersion <= 12, 'WebKit on macOS-12 is frozen'); +test('should throw when uploading a file in a directory upload input', async ({ page, server, isAndroid, asset, browserName, macVersion, isMac }) => { + test.skip(isAndroid); + test.skip(browserName === 'webkit' && isMac && macVersion <= 12, 'WebKit on macOS-12 is frozen'); await page.goto(server.PREFIX + '/input/folderupload.html'); const input = await page.$('input'); await expect(input.setInputFiles(asset('file to upload.txt'))).rejects.toThrow('[webkitdirectory] input requires passing a path to a directory'); }); -it('should upload a file after popup', async ({ page, server, asset }) => { - it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29923' }); +test('should upload a file after popup', async ({ page, server, asset }) => { + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29923' }); await page.goto(server.PREFIX + '/input/fileupload.html'); { const [popup] = await Promise.all([ @@ -144,11 +143,11 @@ it('should upload a file after popup', async ({ page, server, asset }) => { expect(await page.evaluate(e => e.files[0].name, input)).toBe('file-to-upload.txt'); }); -it('should upload large file', async ({ page, server, isAndroid, isWebView2, mode }, testInfo) => { - it.skip(isAndroid); - it.skip(isWebView2); - it.skip(mode.startsWith('service')); - it.slow(); +test('should upload large file', async ({ page, server, isAndroid, isWebView2, mode }, testInfo) => { + test.skip(isAndroid); + test.skip(isWebView2); + test.skip(mode.startsWith('service')); + test.slow(); await page.goto(server.PREFIX + '/input/fileupload.html'); const uploadFile = testInfo.outputPath('200MB.zip'); @@ -194,7 +193,7 @@ it('should upload large file', async ({ page, server, isAndroid, isWebView2, mod await Promise.all([uploadFile, file1.filepath].map(fs.promises.unlink)); }); -it('should throw an error if the file does not exist', async ({ page, server, asset }) => { +test('should throw an error if the file does not exist', async ({ page, server, asset }) => { await page.goto(server.PREFIX + '/input/fileupload.html'); const input = await page.$('input'); const error = await input.setInputFiles('i actually do not exist.txt').catch(e => e); @@ -202,51 +201,11 @@ it('should throw an error if the file does not exist', async ({ page, server, as expect(error.message).toContain('i actually do not exist.txt'); }); -it('should upload multiple large files', async ({ page, server, isAndroid, isWebView2, mode }, testInfo) => { - it.skip(isAndroid); - it.skip(isWebView2); - it.skip(mode.startsWith('service')); - it.slow(); - - const filesCount = 10; - await page.goto(server.PREFIX + '/input/fileupload-multi.html'); - const uploadFile = testInfo.outputPath('50MB_1.zip'); - const str = 'A'.repeat(1024); - const stream = fs.createWriteStream(uploadFile); - // 49 is close to the actual limit - for (let i = 0; i < 49 * 1024; i++) { - await new Promise((fulfill, reject) => { - stream.write(str, err => { - if (err) - reject(err); - else - fulfill(); - }); - }); - } - await new Promise(f => stream.end(f)); - const input = page.locator('input[type="file"]'); - const uploadFiles = [uploadFile]; - for (let i = 2; i <= filesCount; i++) { - const dstFile = testInfo.outputPath(`50MB_${i}.zip`); - fs.copyFileSync(uploadFile, dstFile); - uploadFiles.push(dstFile); - } - const fileChooserPromise = page.waitForEvent('filechooser'); - await input.click(); - const fileChooser = await fileChooserPromise; - await fileChooser.setFiles(uploadFiles); - const filesLen = await page.evaluate('document.getElementsByTagName("input")[0].files.length'); - expect(fileChooser.isMultiple()).toBe(true); - expect(filesLen).toEqual(filesCount); - await Promise.all(uploadFiles.map(path => fs.promises.unlink(path))); -}); - -it('should upload large file with relative path', async ({ page, server, isAndroid, isWebView2, mode }, testInfo) => { - it.skip(isAndroid); - it.skip(isWebView2); - it.skip(mode.startsWith('service')); - it.slow(); +test('should upload large file with relative path', async ({ page, server, isAndroid, isWebView2, mode }, testInfo) => { + test.skip(isAndroid); + test.skip(isWebView2); + test.skip(mode.startsWith('service')); + test.slow(); await page.goto(server.PREFIX + '/input/fileupload.html'); const uploadFile = testInfo.outputPath('200MB.zip'); @@ -294,8 +253,8 @@ it('should upload large file with relative path', async ({ page, server, isAndro await Promise.all([uploadFile, file1.filepath].map(fs.promises.unlink)); }); -it('should upload the file with spaces in name', async ({ page, server, asset }) => { - it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/17451' }); +test('should upload the file with spaces in name', async ({ page, server, asset }) => { + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/17451' }); await page.goto(server.PREFIX + '/input/fileupload.html'); const filePath = path.relative(process.cwd(), asset('file to upload.txt')); const input = await page.$('input'); @@ -310,14 +269,14 @@ it('should upload the file with spaces in name', async ({ page, server, asset }) }); -it('should work @smoke', async ({ page, asset }) => { +test('should work @smoke', async ({ page, asset }) => { await page.setContent(``); await page.setInputFiles('input', asset('file-to-upload.txt')); expect(await page.$eval('input', input => input.files.length)).toBe(1); expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt'); }); -it('should set from memory', async ({ page }) => { +test('should set from memory', async ({ page }) => { await page.setContent(``); await page.setInputFiles('input', { name: 'test.txt', @@ -328,133 +287,7 @@ it('should set from memory', async ({ page }) => { expect(await page.$eval('input', input => input.files[0].name)).toBe('test.txt'); }); -it('should emit event once', async ({ page, server }) => { - await page.setContent(``); - const [chooser] = await Promise.all([ - new Promise(f => page.once('filechooser', f)), - page.click('input'), - ]); - expect(chooser).toBeTruthy(); -}); - -it('should emit event via prepend', async ({ page, server }) => { - await page.setContent(``); - const [chooser] = await Promise.all([ - new Promise(f => page.prependListener('filechooser', f)), - page.click('input'), - ]); - expect(chooser).toBeTruthy(); -}); - -it('should emit event for iframe', async ({ page, server }) => { - const frame = await attachFrame(page, 'frame1', server.EMPTY_PAGE); - await frame.setContent(``); - const [chooser] = await Promise.all([ - new Promise(f => page.once('filechooser', f)), - frame.click('input'), - ]); - expect(chooser).toBeTruthy(); -}); - -it('should emit event on/off', async ({ page, server }) => { - await page.setContent(``); - const [chooser] = await Promise.all([ - new Promise(f => { - const listener = chooser => { - page.off('filechooser', listener); - f(chooser); - }; - page.on('filechooser', listener); - }), - page.click('input'), - ]); - expect(chooser).toBeTruthy(); -}); - -it('should emit event addListener/removeListener', async ({ page, server }) => { - await page.setContent(``); - const [chooser] = await Promise.all([ - new Promise(f => { - const listener = chooser => { - page.removeListener('filechooser', listener); - f(chooser); - }; - page.addListener('filechooser', listener); - }), - page.click('input'), - ]); - expect(chooser).toBeTruthy(); -}); - -it('should work when file input is attached to DOM', async ({ page, server }) => { - await page.setContent(``); - const [chooser] = await Promise.all([ - page.waitForEvent('filechooser'), - page.click('input'), - ]); - expect(chooser).toBeTruthy(); -}); - -it('should work when file input is not attached to DOM', async ({ page, asset }) => { - const [, content] = await Promise.all([ - page.waitForEvent('filechooser').then(chooser => chooser.setFiles(asset('file-to-upload.txt'))), - page.evaluate(async () => { - const el = document.createElement('input'); - el.type = 'file'; - el.click(); - await new Promise(x => el.oninput = x); - const reader = new FileReader(); - const promise = new Promise(fulfill => reader.onload = fulfill); - reader.readAsText(el.files[0]); - return promise.then(() => reader.result); - }), - ]); - expect(content).toBe('contents of the file'); -}); - -it('should not throw when filechooser belongs to iframe', async ({ page, server, browserName }) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = page.mainFrame().childFrames()[0]; - await frame.setContent(` -
Click me
- - `); - await Promise.all([ - page.waitForEvent('filechooser'), - frame.click('div') - ]); - await page.waitForFunction(() => (window as any).__done); -}); - -it('should not throw when frame is detached immediately', async ({ page, server }) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - const frame = page.mainFrame().childFrames()[0]; - await frame.setContent(` -
Click me
- - `); - page.on('filechooser', () => {}); // To ensure we handle file choosers. - await frame.click('div'); - await page.waitForFunction(() => (window as any).__done); -}); - -it('should work with CSP', async ({ page, server, asset }) => { +test('should work with CSP', async ({ page, server, asset }) => { server.setCSP('/empty.html', 'default-src "none"'); await page.goto(server.EMPTY_PAGE); await page.setContent(``); @@ -463,62 +296,7 @@ it('should work with CSP', async ({ page, server, asset }) => { expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt'); }); -it('should respect timeout', async ({ page, playwright }) => { - let error = null; - await page.waitForEvent('filechooser', { timeout: 1 }).catch(e => error = e); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); -}); - -it('should respect default timeout when there is no custom timeout', async ({ page, playwright }) => { - page.setDefaultTimeout(1); - let error = null; - await page.waitForEvent('filechooser').catch(e => error = e); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); -}); - -it('should prioritize exact timeout over default timeout', async ({ page, playwright }) => { - page.setDefaultTimeout(0); - let error = null; - await page.waitForEvent('filechooser', { timeout: 1 }).catch(e => error = e); - expect(error).toBeInstanceOf(playwright.errors.TimeoutError); -}); - -it('should work with no timeout', async ({ page, server }) => { - const [chooser] = await Promise.all([ - page.waitForEvent('filechooser', { timeout: 0 }), - page.evaluate(() => window.builtinSetTimeout(() => { - const el = document.createElement('input'); - el.type = 'file'; - el.click(); - }, 50)) - ]); - expect(chooser).toBeTruthy(); -}); - -it('should return the same file chooser when there are many watchdogs simultaneously', async ({ page, server }) => { - await page.setContent(``); - const [fileChooser1, fileChooser2] = await Promise.all([ - page.waitForEvent('filechooser'), - page.waitForEvent('filechooser'), - page.$eval('input', input => input.click()), - ]); - expect(fileChooser1 === fileChooser2).toBe(true); -}); - -it('should accept single file', async ({ page, asset }) => { - await page.setContent(``); - const [fileChooser] = await Promise.all([ - page.waitForEvent('filechooser'), - page.click('input'), - ]); - expect(fileChooser.page()).toBe(page); - expect(fileChooser.element()).toBeTruthy(); - await fileChooser.setFiles(asset('file-to-upload.txt')); - expect(await page.$eval('input', input => input.files.length)).toBe(1); - expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt'); -}); - -it('should detect mime type', async ({ page, server, asset }) => { +test('should detect mime type', async ({ page, server, asset }) => { let files: Record; server.setRoute('/upload', async (req, res) => { @@ -553,7 +331,7 @@ it('should detect mime type', async ({ page, server, asset }) => { }); // @see https://github.com/microsoft/playwright/issues/4704 -it('should not trim big uploaded files', async ({ page, server }) => { +test('should not trim big uploaded files', async ({ page, server }) => { let files: Record; server.setRoute('/upload', async (req, res) => { @@ -577,59 +355,7 @@ it('should not trim big uploaded files', async ({ page, server }) => { expect(files.file.size).toBe(DATA_SIZE); }); -it('should be able to read selected file', async ({ page, asset }) => { - await page.setContent(``); - const [, content] = await Promise.all([ - page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles(asset('file-to-upload.txt'))), - page.$eval('input', async picker => { - picker.click(); - await new Promise(x => picker.oninput = x); - const reader = new FileReader(); - const promise = new Promise(fulfill => reader.onload = fulfill); - reader.readAsText(picker.files[0]); - return promise.then(() => reader.result); - }), - ]); - expect(content).toBe('contents of the file'); -}); - -it('should be able to reset selected files with empty file list', async ({ page, asset }) => { - await page.setContent(``); - const [, fileLength1] = await Promise.all([ - page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles(asset('file-to-upload.txt'))), - page.$eval('input', async picker => { - picker.click(); - await new Promise(x => picker.oninput = x); - return picker.files.length; - }), - ]); - expect(fileLength1).toBe(1); - const [, fileLength2] = await Promise.all([ - page.waitForEvent('filechooser').then(fileChooser => fileChooser.setFiles([])), - page.$eval('input', async picker => { - picker.click(); - await new Promise(x => picker.oninput = x); - return picker.files.length; - }), - ]); - expect(fileLength2).toBe(0); -}); - -it('should not accept multiple files for single-file input', async ({ page, asset }) => { - await page.setContent(``); - const [fileChooser] = await Promise.all([ - page.waitForEvent('filechooser'), - page.click('input'), - ]); - let error = null; - await fileChooser.setFiles([ - asset('file-to-upload.txt'), - asset('pptr.png') - ]).catch(e => error = e); - expect(error).not.toBe(null); -}); - -it('should emit input and change events', async ({ page, asset }) => { +test('should emit input and change events', async ({ page, asset }) => { const events = []; await page.exposeFunction('eventHandled', e => events.push(e)); await page.setContent(` @@ -644,8 +370,8 @@ it('should emit input and change events', async ({ page, asset }) => { expect(events[1].type).toBe('change'); }); -it('input event.composed should be true and cross shadow dom boundary', async ({ page, server, asset }) => { - it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28726' }); +test('input event.composed should be true and cross shadow dom boundary', async ({ page, server, asset }) => { + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28726' }); await page.goto(server.EMPTY_PAGE); await page.setContent(`