diff --git a/packages/playwright-core/src/DEPS.list b/packages/playwright-core/src/DEPS.list index 2f7995ab62d55..018501cfb6ea5 100644 --- a/packages/playwright-core/src/DEPS.list +++ b/packages/playwright-core/src/DEPS.list @@ -7,7 +7,11 @@ [inProcessFactory.ts] ** +[inprocess.ts] +common/ + [outofprocess.ts] client/ protocol/ utils/ +common/ \ No newline at end of file diff --git a/packages/playwright-core/src/cli/DEPS.list b/packages/playwright-core/src/cli/DEPS.list index ab30158290a6d..54eb6a5e49c44 100644 --- a/packages/playwright-core/src/cli/DEPS.list +++ b/packages/playwright-core/src/cli/DEPS.list @@ -4,6 +4,7 @@ ../common ../debug/injected ../generated/ +../server/ ../server/injected/ ../server/trace ../utils diff --git a/packages/playwright-core/src/cli/driver.ts b/packages/playwright-core/src/cli/driver.ts index 0e4cbb5816ac7..cb7ade9d7a254 100644 --- a/packages/playwright-core/src/cli/driver.ts +++ b/packages/playwright-core/src/cli/driver.ts @@ -22,7 +22,7 @@ import * as playwright from '../..'; import { PipeTransport } from '../protocol/transport'; import { PlaywrightServer } from '../remote/playwrightServer'; import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from '../server'; -import { gracefullyProcessExitDoNotHang } from '../utils/processLauncher'; +import { gracefullyProcessExitDoNotHang } from '../server/processLauncher'; import type { BrowserType } from '../client/browserType'; import type { LaunchServerOptions } from '../client/types'; diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts index 2c0a955207520..38b3072f3a677 100644 --- a/packages/playwright-core/src/cli/program.ts +++ b/packages/playwright-core/src/cli/program.ts @@ -21,11 +21,11 @@ import * as os from 'os'; import * as path from 'path'; import * as playwright from '../..'; -import { registry, writeDockerVersion } from '../server'; import { launchBrowserServer, printApiJson, runDriver, runServer } from './driver'; import { isTargetClosedError } from '../client/errors'; +import { gracefullyProcessExitDoNotHang, registry, writeDockerVersion } from '../server'; import { runTraceInBrowser, runTraceViewerApp } from '../server/trace/viewer/traceViewer'; -import { assert, getPackageManagerExecCommand, gracefullyProcessExitDoNotHang, isLikelyNpxGlobal, wrapInASCIIBox } from '../utils'; +import { assert, getPackageManagerExecCommand, isLikelyNpxGlobal, wrapInASCIIBox } from '../utils'; import { dotenv, program } from '../utilsBundle'; import type { Browser } from '../client/browser'; diff --git a/packages/playwright-core/src/cli/programWithTestStub.ts b/packages/playwright-core/src/cli/programWithTestStub.ts index 1c11c14ec2773..08a306f2f0cc8 100644 --- a/packages/playwright-core/src/cli/programWithTestStub.ts +++ b/packages/playwright-core/src/cli/programWithTestStub.ts @@ -16,7 +16,8 @@ /* eslint-disable no-console */ -import { getPackageManager, gracefullyProcessExitDoNotHang } from '../utils'; +import { gracefullyProcessExitDoNotHang } from '../server'; +import { getPackageManager } from '../utils'; import { program } from './program'; export { program } from './program'; diff --git a/packages/playwright-core/src/client/android.ts b/packages/playwright-core/src/client/android.ts index 1f4b89ed8a395..644f3a51e1402 100644 --- a/packages/playwright-core/src/client/android.ts +++ b/packages/playwright-core/src/client/android.ts @@ -15,9 +15,7 @@ */ import { EventEmitter } from 'events'; -import * as fs from 'fs'; -import { isRegExp, isString, monotonicTime } from '../utils'; import { BrowserContext, prepareBrowserContextParams } from './browserContext'; import { ChannelOwner } from './channelOwner'; import { Connection } from './connection'; @@ -25,12 +23,15 @@ 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 type { Page } from './page'; import type * as types from './types'; import type * as api from '../../types/types'; import type { AndroidServerLauncherImpl } from '../androidServerImpl'; +import type { Platform } from '../common/platform'; import type * as channels from '@protocol/channels'; type Direction = 'down' | 'up' | 'left' | 'right'; @@ -73,7 +74,7 @@ export class Android extends ChannelOwner implements ap const connectParams: channels.LocalUtilsConnectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout }; const { pipe } = await localUtils._channel.connect(connectParams); const closePipe = () => pipe.close().catch(() => {}); - const connection = new Connection(localUtils, this._instrumentation); + const connection = new Connection(localUtils, this._platform, this._instrumentation); connection.markAsRemote(); connection.on('close', closePipe); @@ -232,7 +233,7 @@ export class AndroidDevice extends ChannelOwner i async screenshot(options: { path?: string } = {}): Promise { const { binary } = await this._channel.screenshot(); if (options.path) - await fs.promises.writeFile(options.path, binary); + await this._platform.fs().promises.writeFile(options.path, binary); return binary; } @@ -267,15 +268,15 @@ export class AndroidDevice extends ChannelOwner i } async installApk(file: string | Buffer, options?: { args: string[] }): Promise { - await this._channel.installApk({ file: await loadFile(file), args: options && options.args }); + await this._channel.installApk({ file: await loadFile(this._platform, file), args: options && options.args }); } async push(file: string | Buffer, path: string, options?: { mode: number }): Promise { - await this._channel.push({ file: await loadFile(file), path, mode: options ? options.mode : undefined }); + await this._channel.push({ file: await loadFile(this._platform, file), path, mode: options ? options.mode : undefined }); } async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise { - const contextOptions = await prepareBrowserContextParams(options); + const contextOptions = await prepareBrowserContextParams(this._platform, options); const result = await this._channel.launchBrowser(contextOptions); const context = BrowserContext.from(result.context) as BrowserContext; context._setOptions(contextOptions, {}); @@ -321,9 +322,9 @@ export class AndroidSocket extends ChannelOwner i } } -async function loadFile(file: string | Buffer): Promise { +async function loadFile(platform: Platform, file: string | Buffer): Promise { if (isString(file)) - return await fs.promises.readFile(file); + return await platform.fs().promises.readFile(file); return file; } diff --git a/packages/playwright-core/src/client/artifact.ts b/packages/playwright-core/src/client/artifact.ts index 1874ba0c1de98..fc1e9bca12924 100644 --- a/packages/playwright-core/src/client/artifact.ts +++ b/packages/playwright-core/src/client/artifact.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import * as fs from 'fs'; - import { ChannelOwner } from './channelOwner'; import { Stream } from './stream'; import { mkdirIfNeeded } from '../utils/fileUtils'; @@ -42,9 +40,9 @@ export class Artifact extends ChannelOwner { const result = await this._channel.saveAsStream(); const stream = Stream.from(result.stream); - await mkdirIfNeeded(path); + await mkdirIfNeeded(this._platform, path); await new Promise((resolve, reject) => { - stream.stream().pipe(fs.createWriteStream(path)) + stream.stream().pipe(this._platform.fs().createWriteStream(path)) .on('finish' as any, resolve) .on('error' as any, reject); }); diff --git a/packages/playwright-core/src/client/browser.ts b/packages/playwright-core/src/client/browser.ts index 444074ba54949..f7eb649ae3244 100644 --- a/packages/playwright-core/src/client/browser.ts +++ b/packages/playwright-core/src/client/browser.ts @@ -14,15 +14,13 @@ * limitations under the License. */ -import * as fs from 'fs'; - import { Artifact } from './artifact'; import { BrowserContext, prepareBrowserContextParams } from './browserContext'; import { CDPSession } from './cdpSession'; import { ChannelOwner } from './channelOwner'; import { isTargetClosedError } from './errors'; import { Events } from './events'; -import { mkdirIfNeeded } from '../utils'; +import { mkdirIfNeeded } from '../utils/fileUtils'; import type { BrowserType } from './browserType'; import type { Page } from './page'; @@ -83,7 +81,7 @@ export class Browser extends ChannelOwner implements ap async _innerNewContext(options: BrowserContextOptions = {}, forReuse: boolean): Promise { options = { ...this._browserType._playwright._defaultContextOptions, ...options }; - const contextOptions = await prepareBrowserContextParams(options); + const contextOptions = await prepareBrowserContextParams(this._platform, options); const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions); const context = BrowserContext.from(response.context); await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger); @@ -126,8 +124,8 @@ export class Browser extends ChannelOwner implements ap const buffer = await artifact.readIntoBuffer(); await artifact.delete(); if (this._path) { - await mkdirIfNeeded(this._path); - await fs.promises.writeFile(this._path, buffer); + await mkdirIfNeeded(this._platform, this._path); + await this._platform.fs().promises.writeFile(this._path, buffer); this._path = undefined; } return buffer; diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index a8417d896ba48..4fcb1873f8c32 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -15,9 +15,6 @@ * limitations under the License. */ -import * as fs from 'fs'; -import * as path from 'path'; - import { Artifact } from './artifact'; import { Browser } from './browser'; import { CDPSession } from './cdpSession'; @@ -38,14 +35,18 @@ import { Waiter } from './waiter'; import { WebError } from './webError'; import { Worker } from './worker'; import { TimeoutSettings } from '../common/timeoutSettings'; -import { headersObjectToArray, isRegExp, isString, mkdirIfNeeded, urlMatchesEqual } from '../utils'; +import { mkdirIfNeeded } from '../utils/fileUtils'; +import { headersObjectToArray } from '../utils/headers'; +import { urlMatchesEqual } from '../utils/isomorphic/urlMatch'; +import { isRegExp, isString } from '../utils/rtti'; import { rewriteErrorMessage } from '../utils/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'; +import type { Platform } from '../common/platform'; +import type { URLMatch } from '../utils/isomorphic/urlMatch'; import type * as channels from '@protocol/channels'; export class BrowserContext extends ChannelOwner implements api.BrowserContext { @@ -107,7 +108,7 @@ export class BrowserContext extends ChannelOwner this.emit(Events.BrowserContext.ServiceWorker, serviceWorker); }); this._channel.on('console', event => { - const consoleMessage = new ConsoleMessage(event); + const consoleMessage = new ConsoleMessage(this._platform, event); this.emit(Events.BrowserContext.Console, consoleMessage); const page = consoleMessage.page(); if (page) @@ -321,7 +322,7 @@ export class BrowserContext extends ChannelOwner } async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise { - const source = await evaluationScript(script, arg); + const source = await evaluationScript(this._platform, script, arg); await this._channel.addInitScript({ source }); } @@ -431,8 +432,8 @@ export class BrowserContext extends ChannelOwner async storageState(options: { path?: string, indexedDB?: boolean } = {}): Promise { const state = await this._channel.storageState({ indexedDB: options.indexedDB }); if (options.path) { - await mkdirIfNeeded(options.path); - await fs.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8'); + await mkdirIfNeeded(this._platform, options.path); + await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8'); } return state; } @@ -500,11 +501,11 @@ export class BrowserContext extends ChannelOwner } } -async function prepareStorageState(options: BrowserContextOptions): Promise { +async function prepareStorageState(platform: Platform, options: BrowserContextOptions): Promise { if (typeof options.storageState !== 'string') return options.storageState; try { - return JSON.parse(await fs.promises.readFile(options.storageState, 'utf8')); + return JSON.parse(await platform.fs().promises.readFile(options.storageState, 'utf8')); } catch (e) { rewriteErrorMessage(e, `Error reading storage state from ${options.storageState}:\n` + e.message); throw e; @@ -524,7 +525,7 @@ function prepareRecordHarOptions(options: BrowserContextOptions['recordHar']): c }; } -export async function prepareBrowserContextParams(options: BrowserContextOptions): Promise { +export async function prepareBrowserContextParams(platform: Platform, options: BrowserContextOptions): Promise { if (options.videoSize && !options.videosPath) throw new Error(`"videoSize" option requires "videosPath" to be specified`); if (options.extraHTTPHeaders) @@ -534,7 +535,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions viewport: options.viewport === null ? undefined : options.viewport, noDefaultViewport: options.viewport === null, extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined, - storageState: await prepareStorageState(options), + storageState: await prepareStorageState(platform, options), serviceWorkers: options.serviceWorkers, recordHar: prepareRecordHarOptions(options.recordHar), colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme, @@ -542,7 +543,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors, contrast: options.contrast === null ? 'no-override' : options.contrast, acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads), - clientCertificates: await toClientCertificatesProtocol(options.clientCertificates), + clientCertificates: await toClientCertificatesProtocol(platform, options.clientCertificates), }; if (!contextParams.recordVideo && options.videosPath) { contextParams.recordVideo = { @@ -551,7 +552,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions }; } if (contextParams.recordVideo && contextParams.recordVideo.dir) - contextParams.recordVideo.dir = path.resolve(process.cwd(), contextParams.recordVideo.dir); + contextParams.recordVideo.dir = platform.path().resolve(process.cwd(), contextParams.recordVideo.dir); return contextParams; } @@ -563,7 +564,7 @@ function toAcceptDownloadsProtocol(acceptDownloads?: boolean) { return 'deny'; } -export async function toClientCertificatesProtocol(certs?: BrowserContextOptions['clientCertificates']): Promise { +export async function toClientCertificatesProtocol(platform: Platform, certs?: BrowserContextOptions['clientCertificates']): Promise { if (!certs) return undefined; @@ -571,7 +572,7 @@ export async function toClientCertificatesProtocol(certs?: BrowserContextOptions if (value) return value; if (path) - return await fs.promises.readFile(path); + return await platform.fs().promises.readFile(path); }; return await Promise.all(certs.map(async cert => ({ diff --git a/packages/playwright-core/src/client/browserType.ts b/packages/playwright-core/src/client/browserType.ts index 36b9fe4d7ee12..33f1705b9e8a9 100644 --- a/packages/playwright-core/src/client/browserType.ts +++ b/packages/playwright-core/src/client/browserType.ts @@ -20,7 +20,9 @@ import { ChannelOwner } from './channelOwner'; import { envObjectToArray } from './clientHelper'; import { Connection } from './connection'; import { Events } from './events'; -import { assert, headersObjectToArray, monotonicTime } from '../utils'; +import { assert } from '../utils/debug'; +import { headersObjectToArray } from '../utils/headers'; +import { monotonicTime } from '../utils/time'; import { raceAgainstDeadline } from '../utils/timeoutRunner'; import type { Playwright } from './playwright'; @@ -90,7 +92,7 @@ export class BrowserType extends ChannelOwner imple const logger = options.logger || this._playwright._defaultLaunchOptions?.logger; assert(!(options as any).port, 'Cannot specify a port without launching as a server.'); options = { ...this._playwright._defaultLaunchOptions, ...this._playwright._defaultContextOptions, ...options }; - const contextParams = await prepareBrowserContextParams(options); + const contextParams = await prepareBrowserContextParams(this._platform, options); const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = { ...contextParams, ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined, @@ -133,7 +135,7 @@ export class BrowserType extends ChannelOwner imple connectParams.socksProxyRedirectPortForTest = (params as any).__testHookRedirectPortForwarding; const { pipe, headers: connectHeaders } = await localUtils._channel.connect(connectParams); const closePipe = () => pipe.close().catch(() => {}); - const connection = new Connection(localUtils, this._instrumentation); + const connection = new Connection(localUtils, this._platform, this._instrumentation); connection.markAsRemote(); connection.on('close', closePipe); diff --git a/packages/playwright-core/src/client/channelOwner.ts b/packages/playwright-core/src/client/channelOwner.ts index 2624be72829ee..f34d55f389e9d 100644 --- a/packages/playwright-core/src/client/channelOwner.ts +++ b/packages/playwright-core/src/client/channelOwner.ts @@ -16,7 +16,7 @@ import { EventEmitter } from './eventEmitter'; import { ValidationError, maybeFindValidator } from '../protocol/validator'; -import { isUnderTest } from '../utils'; +import { isUnderTest } from '../utils/debug'; import { debugLogger } from '../utils/debugLogger'; import { captureLibraryStackTrace, stringifyStackFrames } from '../utils/stackTrace'; import { zones } from '../utils/zones'; @@ -24,6 +24,7 @@ import { zones } from '../utils/zones'; import type { ClientInstrumentation } from './clientInstrumentation'; import type { Connection } from './connection'; import type { Logger } from './types'; +import type { Platform } from '../common/platform'; import type { ValidatorContext } from '../protocol/validator'; import type * as channels from '@protocol/channels'; @@ -39,6 +40,7 @@ export abstract class ChannelOwner; _logger: Logger | undefined; + readonly _platform: Platform; readonly _instrumentation: ClientInstrumentation; private _eventToSubscriptionMapping: Map = new Map(); private _isInternalType = false; @@ -52,6 +54,7 @@ export abstract class ChannelOwner { +export async function evaluationScript(platform: Platform, fun: Function | string | { path?: string, content?: string }, arg?: any, addSourceUrl: boolean = true): Promise { if (typeof fun === 'function') { const source = fun.toString(); const argString = Object.is(arg, undefined) ? 'undefined' : JSON.stringify(arg); @@ -43,7 +42,7 @@ export async function evaluationScript(fun: Function | string | { path?: string, if (fun.content !== undefined) return fun.content; if (fun.path !== undefined) { - let source = await fs.promises.readFile(fun.path, 'utf8'); + let source = await platform.fs().promises.readFile(fun.path, 'utf8'); if (addSourceUrl) source = addSourceUrlToScript(source, fun.path); return source; diff --git a/packages/playwright-core/src/client/connection.ts b/packages/playwright-core/src/client/connection.ts index 30eba37cf0eb4..d00341c642119 100644 --- a/packages/playwright-core/src/client/connection.ts +++ b/packages/playwright-core/src/client/connection.ts @@ -42,10 +42,12 @@ import { Tracing } from './tracing'; import { Worker } from './worker'; import { WritableStream } from './writableStream'; import { ValidationError, findValidator } from '../protocol/validator'; -import { formatCallLog, rewriteErrorMessage, zones } from '../utils'; import { debugLogger } from '../utils/debugLogger'; +import { formatCallLog, rewriteErrorMessage } from '../utils/stackTrace'; +import { zones } from '../utils/zones'; import type { ClientInstrumentation } from './clientInstrumentation'; +import type { Platform } from '../common/platform'; import type { ValidatorContext } from '../protocol/validator'; import type * as channels from '@protocol/channels'; @@ -78,11 +80,13 @@ export class Connection extends EventEmitter { toImpl: ((client: ChannelOwner) => any) | undefined; private _tracingCount = 0; readonly _instrumentation: ClientInstrumentation; + readonly platform: Platform; - constructor(localUtils: LocalUtils | undefined, instrumentation: ClientInstrumentation | undefined) { + constructor(localUtils: LocalUtils | undefined, platform: Platform, instrumentation: ClientInstrumentation | undefined) { super(); this._instrumentation = instrumentation || createInstrumentation(); this._localUtils = localUtils; + this.platform = platform; this._rootObject = new Root(this); } diff --git a/packages/playwright-core/src/client/consoleMessage.ts b/packages/playwright-core/src/client/consoleMessage.ts index db2ed1a246949..5d215cf2ad8b4 100644 --- a/packages/playwright-core/src/client/consoleMessage.ts +++ b/packages/playwright-core/src/client/consoleMessage.ts @@ -14,12 +14,11 @@ * limitations under the License. */ -import * as util from 'util'; - import { JSHandle } from './jsHandle'; import { Page } from './page'; import type * as api from '../../types/types'; +import type { Platform } from '../common/platform'; import type * as channels from '@protocol/channels'; type ConsoleMessageLocation = channels.BrowserContextConsoleEvent['location']; @@ -29,9 +28,11 @@ export class ConsoleMessage implements api.ConsoleMessage { private _page: Page | null; private _event: channels.BrowserContextConsoleEvent | channels.ElectronApplicationConsoleEvent; - constructor(event: channels.BrowserContextConsoleEvent | channels.ElectronApplicationConsoleEvent) { + constructor(platform: Platform, event: channels.BrowserContextConsoleEvent | channels.ElectronApplicationConsoleEvent) { this._page = ('page' in event && event.page) ? Page.from(event.page) : null; this._event = event; + if (platform.inspectCustom) + (this as any)[platform.inspectCustom] = () => this._inspect(); } page() { @@ -54,7 +55,7 @@ export class ConsoleMessage implements api.ConsoleMessage { return this._event.location; } - [util.inspect.custom]() { + private _inspect() { return this.text(); } } diff --git a/packages/playwright-core/src/client/electron.ts b/packages/playwright-core/src/client/electron.ts index 15551ebce925c..01d194e029969 100644 --- a/packages/playwright-core/src/client/electron.ts +++ b/packages/playwright-core/src/client/electron.ts @@ -53,7 +53,7 @@ export class Electron extends ChannelOwner implements async launch(options: ElectronOptions = {}): Promise { const params: channels.ElectronLaunchParams = { - ...await prepareBrowserContextParams(options), + ...await prepareBrowserContextParams(this._platform, options), env: envObjectToArray(options.env ? options.env : process.env), tracesDir: options.tracesDir, }; @@ -81,7 +81,7 @@ export class ElectronApplication extends ChannelOwner { this.emit(Events.ElectronApplication.Close); }); - this._channel.on('console', event => this.emit(Events.ElectronApplication.Console, new ConsoleMessage(event))); + this._channel.on('console', event => this.emit(Events.ElectronApplication.Console, new ConsoleMessage(this._platform, event))); this._setEventToSubscriptionMapping(new Map([ [Events.ElectronApplication.Console, 'console'], ])); diff --git a/packages/playwright-core/src/client/elementHandle.ts b/packages/playwright-core/src/client/elementHandle.ts index 2883a69dfce34..3e8de03097964 100644 --- a/packages/playwright-core/src/client/elementHandle.ts +++ b/packages/playwright-core/src/client/elementHandle.ts @@ -14,15 +14,14 @@ * limitations under the License. */ -import * as fs from 'fs'; -import * as path from 'path'; import { pipeline } from 'stream'; import { promisify } from 'util'; import { Frame } from './frame'; import { JSHandle, parseResult, serializeArgument } from './jsHandle'; -import { assert, isString } from '../utils'; +import { assert } from '../utils/debug'; import { fileUploadSizeLimit, mkdirIfNeeded } from '../utils/fileUtils'; +import { isString } from '../utils/rtti'; import { mime } from '../utilsBundle'; import { WritableStream } from './writableStream'; @@ -32,6 +31,7 @@ import type { Locator } from './locator'; import type { FilePayload, Rect, SelectOption, SelectOptionOptions } from './types'; import type * as structs from '../../types/structs'; import type * as api from '../../types/types'; +import type { Platform } from '../common/platform'; import type * as channels from '@protocol/channels'; const pipelineAsync = promisify(pipeline); @@ -156,7 +156,7 @@ export class ElementHandle extends JSHandle implements const frame = await this.ownerFrame(); if (!frame) throw new Error('Cannot set input files to detached element'); - const converted = await convertInputFiles(files, frame.page().context()); + const converted = await convertInputFiles(this._platform, files, frame.page().context()); await this._elementChannel.setInputFiles({ ...converted, ...options }); } @@ -204,8 +204,8 @@ export class ElementHandle extends JSHandle implements } const result = await this._elementChannel.screenshot(copy); if (options.path) { - await mkdirIfNeeded(options.path); - await fs.promises.writeFile(options.path, result.binary); + await mkdirIfNeeded(this._platform, options.path); + await this._platform.fs().promises.writeFile(options.path, result.binary); } return result.binary; } @@ -263,18 +263,18 @@ function filePayloadExceedsSizeLimit(payloads: FilePayload[]) { return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= fileUploadSizeLimit; } -async function resolvePathsAndDirectoryForInputFiles(items: string[]): Promise<[string[] | undefined, string | undefined]> { +async function resolvePathsAndDirectoryForInputFiles(platform: Platform, items: string[]): Promise<[string[] | undefined, string | undefined]> { let localPaths: string[] | undefined; let localDirectory: string | undefined; for (const item of items) { - const stat = await fs.promises.stat(item as string); + const stat = await platform.fs().promises.stat(item as string); if (stat.isDirectory()) { if (localDirectory) throw new Error('Multiple directories are not supported'); - localDirectory = path.resolve(item as string); + localDirectory = platform.path().resolve(item as string); } else { localPaths ??= []; - localPaths.push(path.resolve(item as string)); + localPaths.push(platform.path().resolve(item as string)); } } if (localPaths?.length && localDirectory) @@ -282,30 +282,30 @@ async function resolvePathsAndDirectoryForInputFiles(items: string[]): Promise<[ return [localPaths, localDirectory]; } -export async function convertInputFiles(files: string | FilePayload | string[] | FilePayload[], context: BrowserContext): Promise { +export async function convertInputFiles(platform: Platform, files: string | FilePayload | string[] | FilePayload[], context: BrowserContext): Promise { const items: (string | FilePayload)[] = Array.isArray(files) ? files.slice() : [files]; if (items.some(item => typeof item === 'string')) { if (!items.every(item => typeof item === 'string')) throw new Error('File paths cannot be mixed with buffers'); - const [localPaths, localDirectory] = await resolvePathsAndDirectoryForInputFiles(items); + const [localPaths, localDirectory] = await resolvePathsAndDirectoryForInputFiles(platform, items); if (context._connection.isRemote()) { - const files = localDirectory ? (await fs.promises.readdir(localDirectory, { withFileTypes: true, recursive: true })).filter(f => f.isFile()).map(f => path.join(f.path, f.name)) : localPaths!; + const files = localDirectory ? (await platform.fs().promises.readdir(localDirectory, { withFileTypes: true, recursive: true })).filter(f => f.isFile()).map(f => platform.path().join(f.path, f.name)) : localPaths!; const { writableStreams, rootDir } = await context._wrapApiCall(async () => context._channel.createTempFiles({ - rootDirName: localDirectory ? path.basename(localDirectory) : undefined, + rootDirName: localDirectory ? platform.path().basename(localDirectory) : undefined, items: await Promise.all(files.map(async file => { - const lastModifiedMs = (await fs.promises.stat(file)).mtimeMs; + const lastModifiedMs = (await platform.fs().promises.stat(file)).mtimeMs; return { - name: localDirectory ? path.relative(localDirectory, file) : path.basename(file), + name: localDirectory ? platform.path().relative(localDirectory, file) : platform.path().basename(file), lastModifiedMs }; })), }), true); for (let i = 0; i < files.length; i++) { const writable = WritableStream.from(writableStreams[i]); - await pipelineAsync(fs.createReadStream(files[i]), writable.stream()); + await pipelineAsync(platform.fs().createReadStream(files[i]), writable.stream()); } return { directoryStream: rootDir, diff --git a/packages/playwright-core/src/client/errors.ts b/packages/playwright-core/src/client/errors.ts index 4757a6d0f75c6..81f32080b777a 100644 --- a/packages/playwright-core/src/client/errors.ts +++ b/packages/playwright-core/src/client/errors.ts @@ -15,7 +15,7 @@ */ import { parseSerializedValue, serializeValue } from '../protocol/serializers'; -import { isError } from '../utils'; +import { isError } from '../utils/rtti'; import type { SerializedError } from '@protocol/channels'; diff --git a/packages/playwright-core/src/client/eventEmitter.ts b/packages/playwright-core/src/client/eventEmitter.ts index b295f87ebadf9..5fbe3372cba8d 100644 --- a/packages/playwright-core/src/client/eventEmitter.ts +++ b/packages/playwright-core/src/client/eventEmitter.ts @@ -24,7 +24,7 @@ import { EventEmitter as OriginalEventEmitter } from 'events'; -import { isUnderTest } from '../utils'; +import { isUnderTest } from '../utils/debug'; import type { EventEmitter as EventEmitterType } from 'events'; diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index fea6c1a17eefe..314f695ffc59c 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -14,24 +14,24 @@ * limitations under the License. */ -import * as fs from 'fs'; -import * as path from 'path'; -import * as util from 'util'; - -import { assert, headersObjectToArray, isString } from '../utils'; import { toClientCertificatesProtocol } from './browserContext'; import { ChannelOwner } from './channelOwner'; import { TargetClosedError, isTargetClosedError } from './errors'; import { RawHeaders } from './network'; import { Tracing } from './tracing'; +import { assert } from '../utils/debug'; import { mkdirIfNeeded } from '../utils/fileUtils'; +import { headersObjectToArray } from '../utils/headers'; +import { isString } from '../utils/rtti'; import type { Playwright } from './playwright'; import type { ClientCertificate, FilePayload, Headers, SetStorageState, StorageState } from './types'; import type { Serializable } from '../../types/structs'; import type * as api from '../../types/types'; +import type { Platform } from '../common/platform'; import type { HeadersArray, NameValue } from '../common/types'; import type * as channels from '@protocol/channels'; +import type * as fs from 'fs'; export type FetchOptions = { params?: { [key: string]: string | number | boolean; } | URLSearchParams | string, @@ -70,14 +70,14 @@ export class APIRequest implements api.APIRequest { ...options, }; const storageState = typeof options.storageState === 'string' ? - JSON.parse(await fs.promises.readFile(options.storageState, 'utf8')) : + JSON.parse(await this._playwright._platform.fs().promises.readFile(options.storageState, 'utf8')) : options.storageState; const context = APIRequestContext.from((await this._playwright._channel.newRequest({ ...options, extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined, storageState, tracesDir: this._playwright._defaultLaunchOptions?.tracesDir, // We do not expose tracesDir in the API, so do not allow options to accidentally override it. - clientCertificates: await toClientCertificatesProtocol(options.clientCertificates), + clientCertificates: await toClientCertificatesProtocol(this._playwright._platform, options.clientCertificates), })).request); this._contexts.add(context); context._request = this; @@ -232,7 +232,7 @@ export class APIRequestContext extends ChannelOwner { const state = await this._channel.storageState({ indexedDB: options.indexedDB }); if (options.path) { - await mkdirIfNeeded(options.path); - await fs.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8'); + await mkdirIfNeeded(this._platform, options.path); + await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8'); } return state; } } -async function toFormField(name: string, value: string|number|boolean|fs.ReadStream|FilePayload): Promise { +async function toFormField(platform: Platform, name: string, value: string | number | boolean | fs.ReadStream | FilePayload): Promise { + const typeOfValue = typeof value; if (isFilePayload(value)) { const payload = value as FilePayload; if (!Buffer.isBuffer(payload.buffer)) throw new Error(`Unexpected buffer type of 'data.${name}'`); return { name, file: filePayloadToJson(payload) }; - } else if (value instanceof fs.ReadStream) { - return { name, file: await readStreamToJson(value as fs.ReadStream) }; - } else { + } else if (typeOfValue === 'string' || typeOfValue === 'number' || typeOfValue === 'boolean') { return { name, value: String(value) }; + } else { + return { name, file: await readStreamToJson(platform, value as fs.ReadStream) }; } } @@ -307,6 +308,9 @@ export class APIResponse implements api.APIResponse { this._request = context; this._initializer = initializer; this._headers = new RawHeaders(this._initializer.headers); + + if (context._platform.inspectCustom) + (this as any)[context._platform.inspectCustom] = () => this._inspect(); } ok(): boolean { @@ -364,7 +368,7 @@ export class APIResponse implements api.APIResponse { await this._request._channel.disposeAPIResponse({ fetchUid: this._fetchUid() }); } - [util.inspect.custom]() { + private _inspect() { const headers = this.headersArray().map(({ name, value }) => ` ${name}: ${value}`); return `APIResponse: ${this.status()} ${this.statusText()}\n${headers.join('\n')}`; } @@ -389,7 +393,7 @@ function filePayloadToJson(payload: FilePayload): ServerFilePayload { }; } -async function readStreamToJson(stream: fs.ReadStream): Promise { +async function readStreamToJson(platform: Platform, stream: fs.ReadStream): Promise { const buffer = await new Promise((resolve, reject) => { const chunks: Buffer[] = []; stream.on('data', chunk => chunks.push(chunk as Buffer)); @@ -398,7 +402,7 @@ async function readStreamToJson(stream: fs.ReadStream): Promise implements api.Fr async addScriptTag(options: { url?: string, path?: string, content?: string, type?: string } = {}): Promise { const copy = { ...options }; if (copy.path) { - copy.content = (await fs.promises.readFile(copy.path)).toString(); + copy.content = (await this._platform.fs().promises.readFile(copy.path)).toString(); copy.content = addSourceUrlToScript(copy.content, copy.path); } return ElementHandle.from((await this._channel.addScriptTag({ ...copy })).element); @@ -278,7 +277,7 @@ export class Frame extends ChannelOwner implements api.Fr async addStyleTag(options: { url?: string; path?: string; content?: string; } = {}): Promise { const copy = { ...options }; if (copy.path) { - copy.content = (await fs.promises.readFile(copy.path)).toString(); + copy.content = (await this._platform.fs().promises.readFile(copy.path)).toString(); copy.content += '/*# sourceURL=' + copy.path.replace(/\n/g, '') + '*/'; } return ElementHandle.from((await this._channel.addStyleTag({ ...copy })).element); @@ -403,7 +402,7 @@ export class Frame extends ChannelOwner implements api.Fr } async setInputFiles(selector: string, files: string | FilePayload | string[] | FilePayload[], options: channels.FrameSetInputFilesOptions = {}): Promise { - const converted = await convertInputFiles(files, this.page().context()); + const converted = await convertInputFiles(this._platform, files, this.page().context()); await this._channel.setInputFiles({ selector, ...converted, ...options }); } diff --git a/packages/playwright-core/src/client/harRouter.ts b/packages/playwright-core/src/client/harRouter.ts index 3fb2ce0e7b758..35bc03c833018 100644 --- a/packages/playwright-core/src/client/harRouter.ts +++ b/packages/playwright-core/src/client/harRouter.ts @@ -19,8 +19,8 @@ import { debugLogger } from '../utils/debugLogger'; import type { BrowserContext } from './browserContext'; import type { LocalUtils } from './localUtils'; import type { Route } from './network'; -import type { URLMatch } from '../utils'; import type { Page } from './page'; +import type { URLMatch } from '../utils/isomorphic/urlMatch'; type HarNotFoundAction = 'abort' | 'fallback'; diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index af84294d2e8cc..04cfa5fadfd04 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import * as util from 'util'; - -import { asLocator, isString, monotonicTime } from '../utils'; import { ElementHandle } from './elementHandle'; 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 type { Frame } from './frame'; import type { FilePayload, FrameExpectParams, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types'; @@ -64,6 +64,9 @@ export class Locator implements api.Locator { throw new Error(`Inner "hasNot" locator must belong to the same frame.`); this._selector += ` >> internal:has-not=` + JSON.stringify(locator._selector); } + + if (this._frame._platform.inspectCustom) + (this as any)[this._frame._platform.inspectCustom] = () => this._inspect(); } private async _withElement(task: (handle: ElementHandle, timeout?: number) => Promise, timeout?: number): Promise { @@ -291,8 +294,9 @@ export class Locator implements api.Locator { return await this._frame.press(this._selector, key, { strict: true, ...options }); } - async screenshot(options: Omit & { path?: string, mask?: Locator[] } = {}): Promise { - return await this._withElement((h, timeout) => h.screenshot({ ...options, timeout }), options.timeout); + async screenshot(options: Omit & { path?: string, mask?: api.Locator[] } = {}): Promise { + const mask = options.mask as Locator[] | undefined; + return await this._withElement((h, timeout) => h.screenshot({ ...options, mask, timeout }), options.timeout); } async ariaSnapshot(options?: { _id?: boolean, _mode?: 'raw' | 'regex' } & TimeoutOptions): Promise { @@ -370,7 +374,7 @@ export class Locator implements api.Locator { return result; } - [util.inspect.custom]() { + private _inspect() { return this.toString(); } diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index 8667447e8beea..ae7c7bb251dbe 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import * as fs from 'fs'; import { URLSearchParams } from 'url'; import { ChannelOwner } from './channelOwner'; @@ -22,19 +21,26 @@ import { isTargetClosedError } from './errors'; import { Events } from './events'; import { APIResponse } from './fetch'; import { Frame } from './frame'; -import { Worker } from './worker'; -import { MultiMap, assert, headersObjectToArray, isRegExp, isString, rewriteErrorMessage, urlMatches, zones } from '../utils'; import { Waiter } from './waiter'; +import { Worker } from './worker'; +import { assert } from '../utils/debug'; +import { headersObjectToArray } from '../utils/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 { zones } from '../utils/zones'; import { mime } from '../utilsBundle'; -import type { Headers, RemoteAddr, SecurityDetails, WaitForEventOptions } from './types'; -import type { URLMatch, Zone } from '../utils'; import type { BrowserContext } from './browserContext'; import type { Page } from './page'; +import type { Headers, RemoteAddr, SecurityDetails, WaitForEventOptions } from './types'; import type { Serializable } from '../../types/structs'; import type * as api from '../../types/types'; import type { HeadersArray } from '../common/types'; +import type { URLMatch } from '../utils/isomorphic/urlMatch'; +import type { Zone } from '../utils/zones'; import type * as channels from '@protocol/channels'; export type NetworkCookie = { @@ -387,7 +393,7 @@ export class Route extends ChannelOwner implements api.Ro let isBase64 = false; let length = 0; if (options.path) { - const buffer = await fs.promises.readFile(options.path); + const buffer = await this._platform.fs().promises.readFile(options.path); body = buffer.toString('base64'); isBase64 = true; length = buffer.length; diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index 12983f7b62d30..d4bb32ffbd72c 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -15,12 +15,6 @@ * limitations under the License. */ -import * as fs from 'fs'; -import * as path from 'path'; - -import { TargetClosedError, isTargetClosedError, serializeError } from './errors'; -import { TimeoutSettings } from '../common/timeoutSettings'; -import { LongStandingScope, assert, headersObjectToArray, isObject, isRegExp, isString, mkdirIfNeeded, trimStringWithEllipsis, urlMatches, urlMatchesEqual } from '../utils'; import { Accessibility } from './accessibility'; import { Artifact } from './artifact'; import { ChannelOwner } from './channelOwner'; @@ -28,16 +22,25 @@ import { evaluationScript } from './clientHelper'; import { Coverage } from './coverage'; import { Download } from './download'; import { ElementHandle, determineScreenshotType } from './elementHandle'; +import { TargetClosedError, isTargetClosedError, serializeError } from './errors'; import { Events } from './events'; import { FileChooser } from './fileChooser'; import { Frame, verifyLoadState } from './frame'; import { HarRouter } from './harRouter'; import { Keyboard, Mouse, Touchscreen } from './input'; import { JSHandle, assertMaxArguments, parseResult, serializeArgument } from './jsHandle'; -import { Response, Route, RouteHandler, WebSocket, WebSocketRoute, WebSocketRouteHandler, validateHeaders } from './network'; +import { Response, Route, RouteHandler, WebSocket, WebSocketRoute, WebSocketRouteHandler, validateHeaders } from './network'; 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 { 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 type { BrowserContext } from './browserContext'; import type { Clock } from './clock'; @@ -48,8 +51,8 @@ import type { Request, RouteHandlerCallback, WebSocketRouteHandlerCallback } fro import type { FilePayload, Headers, LifecycleEvent, SelectOption, SelectOptionOptions, Size, WaitForEventOptions, WaitForFunctionOptions } from './types'; import type * as structs from '../../types/structs'; import type * as api from '../../types/types'; -import type { URLMatch } from '../utils'; import type { ByRoleOptions } from '../utils/isomorphic/locatorUtils'; +import type { URLMatch } from '../utils/isomorphic/urlMatch'; import type * as channels from '@protocol/channels'; type PDFOptions = Omit & { @@ -512,7 +515,7 @@ export class Page extends ChannelOwner implements api.Page } async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) { - const source = await evaluationScript(script, arg); + const source = await evaluationScript(this._platform, script, arg); await this._channel.addInitScript({ source }); } @@ -590,8 +593,8 @@ export class Page extends ChannelOwner implements api.Page } const result = await this._channel.screenshot(copy); if (options.path) { - await mkdirIfNeeded(options.path); - await fs.promises.writeFile(options.path, result.binary); + await mkdirIfNeeded(this._platform, options.path); + await this._platform.fs().promises.writeFile(options.path, result.binary); } return result.binary; } @@ -820,8 +823,9 @@ export class Page extends ChannelOwner implements api.Page } const result = await this._channel.pdf(transportOptions); if (options.path) { - await fs.promises.mkdir(path.dirname(options.path), { recursive: true }); - await fs.promises.writeFile(options.path, result.pdf); + const platform = this._platform; + await platform.fs().promises.mkdir(platform.path().dirname(options.path), { recursive: true }); + await platform.fs().promises.writeFile(options.path, result.pdf); } return result.pdf; } diff --git a/packages/playwright-core/src/client/selectors.ts b/packages/playwright-core/src/client/selectors.ts index 4f8ee784a8976..d071062ef5f48 100644 --- a/packages/playwright-core/src/client/selectors.ts +++ b/packages/playwright-core/src/client/selectors.ts @@ -17,6 +17,7 @@ import { ChannelOwner } from './channelOwner'; import { evaluationScript } from './clientHelper'; import { setTestIdAttribute, testIdAttributeName } from './locator'; +import { nodePlatform } from '../common/platform'; import type { SelectorEngine } from './types'; import type * as api from '../../types/types'; @@ -28,7 +29,7 @@ export class Selectors implements api.Selectors { private _registrations: channels.SelectorsRegisterParams[] = []; async register(name: string, script: string | (() => SelectorEngine) | { path?: string, content?: string }, options: { contentScript?: boolean } = {}): Promise { - const source = await evaluationScript(script, undefined, false); + const source = await evaluationScript(nodePlatform, 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 f15d75bade0f5..993647dc721a7 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'; +import { ManualPromise } from '../utils/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 6453fb875dab5..90e48108a24c1 100644 --- a/packages/playwright-core/src/client/waiter.ts +++ b/packages/playwright-core/src/client/waiter.ts @@ -15,11 +15,12 @@ */ import { TimeoutError } from './errors'; -import { createGuid, zones } from '../utils'; +import { createGuid } from '../utils/crypto'; import { rewriteErrorMessage } from '../utils/stackTrace'; +import { zones } from '../utils/zones'; -import type { Zone } from '../utils'; import type { ChannelOwner } from './channelOwner'; +import type { Zone } from '../utils/zones'; import type * as channels from '@protocol/channels'; import type { EventEmitter } from 'events'; diff --git a/packages/playwright-core/src/client/worker.ts b/packages/playwright-core/src/client/worker.ts index 36d5bddff3120..ee9c2dcd6ed36 100644 --- a/packages/playwright-core/src/client/worker.ts +++ b/packages/playwright-core/src/client/worker.ts @@ -15,10 +15,10 @@ */ import { ChannelOwner } from './channelOwner'; +import { TargetClosedError } from './errors'; import { Events } from './events'; import { JSHandle, assertMaxArguments, parseResult, serializeArgument } from './jsHandle'; -import { LongStandingScope } from '../utils'; -import { TargetClosedError } from './errors'; +import { LongStandingScope } from '../utils/manualPromise'; import type { BrowserContext } from './browserContext'; import type { Page } from './page'; diff --git a/packages/playwright-core/src/common/platform.ts b/packages/playwright-core/src/common/platform.ts new file mode 100644 index 0000000000000..167887549136c --- /dev/null +++ b/packages/playwright-core/src/common/platform.ts @@ -0,0 +1,43 @@ +/** + * 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 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; +}; + +export const emptyPlatform: Platform = { + fs: () => { + throw new Error('File system is not available'); + }, + + path: () => { + throw new Error('Path module is not available'); + }, + + inspectCustom: undefined, +}; + +export const nodePlatform: Platform = { + fs: () => fs, + path: () => path, + inspectCustom: util.inspect.custom, +}; diff --git a/packages/playwright-core/src/inProcessFactory.ts b/packages/playwright-core/src/inProcessFactory.ts index d6cd8110c24ba..09fabe9b18e3f 100644 --- a/packages/playwright-core/src/inProcessFactory.ts +++ b/packages/playwright-core/src/inProcessFactory.ts @@ -20,12 +20,13 @@ import { Connection } from './client/connection'; import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from './server'; import type { Playwright as PlaywrightAPI } from './client/playwright'; +import type { Platform } from './common/platform'; import type { Language } from './utils'; -export function createInProcessPlaywright(): PlaywrightAPI { +export function createInProcessPlaywright(platform: Platform): PlaywrightAPI { const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript' }); - const clientConnection = new Connection(undefined, undefined); + const clientConnection = new Connection(undefined, platform, undefined); clientConnection.useRawBuffers(); const dispatcherConnection = new DispatcherConnection(true /* local */); diff --git a/packages/playwright-core/src/inprocess.ts b/packages/playwright-core/src/inprocess.ts index 90b1bf499d401..7b5005e5410bd 100644 --- a/packages/playwright-core/src/inprocess.ts +++ b/packages/playwright-core/src/inprocess.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { nodePlatform } from './common/platform'; import { createInProcessPlaywright } from './inProcessFactory'; -module.exports = createInProcessPlaywright(); +module.exports = createInProcessPlaywright(nodePlatform); diff --git a/packages/playwright-core/src/outofprocess.ts b/packages/playwright-core/src/outofprocess.ts index 3d8c43788e0de..274b9343758c9 100644 --- a/packages/playwright-core/src/outofprocess.ts +++ b/packages/playwright-core/src/outofprocess.ts @@ -18,12 +18,12 @@ import * as childProcess from 'child_process'; import * as path from 'path'; import { Connection } from './client/connection'; +import { nodePlatform } from './common/platform'; import { PipeTransport } from './protocol/transport'; import { ManualPromise } from './utils/manualPromise'; import type { Playwright } from './client/playwright'; - export async function start(env: any = {}): Promise<{ playwright: Playwright, stop: () => Promise }> { const client = new PlaywrightClient(env); const playwright = await client._playwright; @@ -48,7 +48,7 @@ class PlaywrightClient { this._driverProcess.unref(); this._driverProcess.stderr!.on('data', data => process.stderr.write(data)); - const connection = new Connection(undefined, undefined); + const connection = new Connection(undefined, nodePlatform, undefined); const transport = new PipeTransport(this._driverProcess.stdin!, this._driverProcess.stdout!); connection.onmessage = message => transport.send(JSON.stringify(message)); transport.onmessage = message => connection.dispatch(JSON.parse(message)); diff --git a/packages/playwright-core/src/server/android/android.ts b/packages/playwright-core/src/server/android/android.ts index d411581eabd9e..47c46ca2cf351 100644 --- a/packages/playwright-core/src/server/android/android.ts +++ b/packages/playwright-core/src/server/android/android.ts @@ -23,15 +23,15 @@ import { TimeoutSettings } from '../../common/timeoutSettings'; import { PipeTransport } from '../../protocol/transport'; import { createGuid, getPackageManagerExecCommand, isUnderTest, makeWaitForNextTask } from '../../utils'; import { RecentLogsCollector } from '../../utils/debugLogger'; -import { removeFolders } from '../../utils/fileUtils'; -import { gracefullyCloseSet } from '../../utils/processLauncher'; import { debug } from '../../utilsBundle'; import { wsReceiver, wsSender } from '../../utilsBundle'; import { validateBrowserContextOptions } from '../browserContext'; import { chromiumSwitches } from '../chromium/chromiumSwitches'; import { CRBrowser } from '../chromium/crBrowser'; +import { removeFolders } from '../fileUtils'; import { helper } from '../helper'; import { SdkObject, serverSideCallMetadata } from '../instrumentation'; +import { gracefullyCloseSet } from '../processLauncher'; import { ProgressController } from '../progress'; import { registry } from '../registry'; diff --git a/packages/playwright-core/src/server/bidi/bidiChromium.ts b/packages/playwright-core/src/server/bidi/bidiChromium.ts index a46f1a017a6ee..8df3460b729df 100644 --- a/packages/playwright-core/src/server/bidi/bidiChromium.ts +++ b/packages/playwright-core/src/server/bidi/bidiChromium.ts @@ -22,9 +22,9 @@ import { BidiBrowser } from './bidiBrowser'; import { kBrowserCloseMessageId } from './bidiConnection'; import { chromiumSwitches } from '../chromium/chromiumSwitches'; -import type { Env } from '../../utils/processLauncher'; import type { BrowserOptions } from '../browser'; import type { SdkObject } from '../instrumentation'; +import type { Env } from '../processLauncher'; import type { ProtocolError } from '../protocolError'; import type { ConnectionTransport } from '../transport'; import type * as types from '../types'; diff --git a/packages/playwright-core/src/server/bidi/bidiFirefox.ts b/packages/playwright-core/src/server/bidi/bidiFirefox.ts index 94d943898ffee..566d2aae9526f 100644 --- a/packages/playwright-core/src/server/bidi/bidiFirefox.ts +++ b/packages/playwright-core/src/server/bidi/bidiFirefox.ts @@ -23,9 +23,9 @@ import { BidiBrowser } from './bidiBrowser'; import { kBrowserCloseMessageId } from './bidiConnection'; import { createProfile } from './third_party/firefoxPrefs'; -import type { Env } from '../../utils/processLauncher'; import type { BrowserOptions } from '../browser'; import type { SdkObject } from '../instrumentation'; +import type { Env } from '../processLauncher'; import type { ProtocolError } from '../protocolError'; import type { ConnectionTransport } from '../transport'; import type * as types from '../types'; diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 69f879ded67d3..f6380e8d665e0 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -23,6 +23,7 @@ import { createGuid, debugMode } from '../utils'; import { Clock } from './clock'; import { Debugger } from './debugger'; import { BrowserContextAPIRequestContext } from './fetch'; +import { mkdirIfNeeded } from './fileUtils'; import { HarRecorder } from './har/harRecorder'; import { helper } from './helper'; import { SdkObject, serverSideCallMetadata } from './instrumentation'; @@ -31,9 +32,8 @@ import * as network from './network'; import { InitScript } from './page'; import { Page, PageBinding } from './page'; import { Recorder } from './recorder'; -import * as storageScript from './storageScript'; -import { mkdirIfNeeded } from '../utils/fileUtils'; import { RecorderApp } from './recorder/recorderApp'; +import * as storageScript from './storageScript'; import * as consoleApiSource from '../generated/consoleApiSource'; import { Tracing } from './trace/recorder/tracing'; diff --git a/packages/playwright-core/src/server/browserType.ts b/packages/playwright-core/src/server/browserType.ts index 62ec63011e961..509fb4cc4f1d6 100644 --- a/packages/playwright-core/src/server/browserType.ts +++ b/packages/playwright-core/src/server/browserType.ts @@ -21,27 +21,27 @@ import * as path from 'path'; import { normalizeProxySettings, validateBrowserContextOptions } from './browserContext'; import { DEFAULT_TIMEOUT, TimeoutSettings } from '../common/timeoutSettings'; import { ManualPromise, debugMode } from '../utils'; +import { existsAsync } from './fileUtils'; import { helper } from './helper'; import { SdkObject } from './instrumentation'; import { PipeTransport } from './pipeTransport'; +import { envArrayToObject, launchProcess } from './processLauncher'; import { ProgressController } from './progress'; import { isProtocolError } from './protocolError'; import { registry } from './registry'; import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor'; import { WebSocketTransport } from './transport'; import { RecentLogsCollector } from '../utils/debugLogger'; -import { existsAsync } from '../utils/fileUtils'; -import { envArrayToObject, launchProcess } from '../utils/processLauncher'; import type { Browser, BrowserOptions, BrowserProcess } from './browser'; import type { BrowserContext } from './browserContext'; import type { CallMetadata } from './instrumentation'; +import type { Env } from './processLauncher'; import type { Progress } from './progress'; import type { ProtocolError } from './protocolError'; import type { BrowserName } from './registry'; import type { ConnectionTransport } from './transport'; import type * as types from './types'; -import type { Env } from '../utils/processLauncher'; import type * as channels from '@protocol/channels'; export const kNoXServerRunningError = 'Looks like you launched a headed browser without having a XServer running.\n' + diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index badbe700bbb41..ad28e5b6a3b19 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -26,10 +26,8 @@ import { TimeoutSettings } from '../../common/timeoutSettings'; import { debugMode, headersArrayToObject, headersObjectToArray, } from '../../utils'; import { wrapInASCIIBox } from '../../utils/ascii'; import { RecentLogsCollector } from '../../utils/debugLogger'; -import { removeFolders } from '../../utils/fileUtils'; import { ManualPromise } from '../../utils/manualPromise'; import { fetchData } from '../../utils/network'; -import { gracefullyCloseSet } from '../../utils/processLauncher'; import { getUserAgent } from '../../utils/userAgent'; import { validateBrowserContextOptions } from '../browserContext'; import { BrowserType, kNoXServerRunningError } from '../browserType'; @@ -39,12 +37,14 @@ import { registry } from '../registry'; import { WebSocketTransport } from '../transport'; import { CRDevTools } from './crDevTools'; import { Browser } from '../browser'; +import { removeFolders } from '../fileUtils'; +import { gracefullyCloseSet } from '../processLauncher'; import { ProgressController } from '../progress'; import type { HTTPRequestParams } from '../../utils/network'; -import type { Env } from '../../utils/processLauncher'; import type { BrowserOptions, BrowserProcess } from '../browser'; import type { CallMetadata, SdkObject } from '../instrumentation'; +import type { Env } from '../processLauncher'; import type { Progress } from '../progress'; import type { ProtocolError } from '../protocolError'; import type { ConnectionTransport, ProtocolRequest } from '../transport'; diff --git a/packages/playwright-core/src/server/chromium/crProtocolHelper.ts b/packages/playwright-core/src/server/chromium/crProtocolHelper.ts index 4852626b935f1..71cf3b5816da0 100644 --- a/packages/playwright-core/src/server/chromium/crProtocolHelper.ts +++ b/packages/playwright-core/src/server/chromium/crProtocolHelper.ts @@ -17,8 +17,8 @@ import * as fs from 'fs'; -import { mkdirIfNeeded } from '../../utils/fileUtils'; import { splitErrorMessage } from '../../utils/stackTrace'; +import { mkdirIfNeeded } from '../fileUtils'; import type { CRSession } from './crConnection'; import type { Protocol } from './protocol'; diff --git a/packages/playwright-core/src/server/chromium/videoRecorder.ts b/packages/playwright-core/src/server/chromium/videoRecorder.ts index e8f96e4129efe..5dfd32569ac11 100644 --- a/packages/playwright-core/src/server/chromium/videoRecorder.ts +++ b/packages/playwright-core/src/server/chromium/videoRecorder.ts @@ -15,9 +15,9 @@ */ import { assert, monotonicTime } from '../../utils'; -import { launchProcess } from '../../utils/processLauncher'; import { serverSideCallMetadata } from '../instrumentation'; import { Page } from '../page'; +import { launchProcess } from '../processLauncher'; import { ProgressController } from '../progress'; import type { Progress } from '../progress'; diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index 601a8338b9d50..c29cbb5a202bb 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -15,13 +15,13 @@ */ import { SdkObject, createInstrumentation, serverSideCallMetadata } from './instrumentation'; +import { gracefullyProcessExitDoNotHang } from './processLauncher'; import { Recorder } from './recorder'; import { asLocator } from '../utils'; import { parseAriaSnapshotUnsafe } from '../utils/isomorphic/ariaSnapshot'; import { yaml } from '../utilsBundle'; import { EmptyRecorderApp } from './recorder/recorderApp'; import { unsafeLocatorOrSelectorAsSelector } from '../utils/isomorphic/locatorParser'; -import { gracefullyProcessExitDoNotHang } from '../utils/processLauncher'; import type { Language } from '../utils'; import type { Browser } from './browser'; diff --git a/packages/playwright-core/src/server/dispatchers/artifactDispatcher.ts b/packages/playwright-core/src/server/dispatchers/artifactDispatcher.ts index d43a846607a3d..d0bc6967d2892 100644 --- a/packages/playwright-core/src/server/dispatchers/artifactDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/artifactDispatcher.ts @@ -18,7 +18,7 @@ import * as fs from 'fs'; import { Dispatcher, existingDispatcher } from './dispatcher'; import { StreamDispatcher } from './streamDispatcher'; -import { mkdirIfNeeded } from '../../utils/fileUtils'; +import { mkdirIfNeeded } from '../fileUtils'; import type { DispatcherScope } from './dispatcher'; import type { Artifact } from '../artifact'; diff --git a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts index d65e3a577545e..666ae1bbd5305 100644 --- a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts @@ -20,7 +20,7 @@ import * as path from 'path'; import { Dispatcher } from './dispatcher'; import { SdkObject } from '../../server/instrumentation'; -import { assert, calculateSha1, createGuid, removeFolders } from '../../utils'; +import { assert, calculateSha1, createGuid } from '../../utils'; import { serializeClientSideCallMetadata } from '../../utils'; import { ManualPromise } from '../../utils/manualPromise'; import { fetchData } from '../../utils/network'; @@ -29,6 +29,7 @@ import { ZipFile } from '../../utils/zipFile'; import { yauzl, yazl } from '../../zipBundle'; import { deviceDescriptors as descriptors } from '../deviceDescriptors'; import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher'; +import { removeFolders } from '../fileUtils'; import { ProgressController } from '../progress'; import { SocksInterceptor } from '../socksInterceptor'; import { WebSocketTransport } from '../transport'; diff --git a/packages/playwright-core/src/server/electron/electron.ts b/packages/playwright-core/src/server/electron/electron.ts index 565fce7fb1073..f20813fc5b954 100644 --- a/packages/playwright-core/src/server/electron/electron.ts +++ b/packages/playwright-core/src/server/electron/electron.ts @@ -23,7 +23,6 @@ import { TimeoutSettings } from '../../common/timeoutSettings'; import { ManualPromise, wrapInASCIIBox } from '../../utils'; import { RecentLogsCollector } from '../../utils/debugLogger'; import { eventsHelper } from '../../utils/eventsHelper'; -import { envArrayToObject, launchProcess } from '../../utils/processLauncher'; import { validateBrowserContextOptions } from '../browserContext'; import { CRBrowser } from '../chromium/crBrowser'; import { CRConnection } from '../chromium/crConnection'; @@ -33,6 +32,7 @@ import { ConsoleMessage } from '../console'; import { helper } from '../helper'; import { SdkObject, serverSideCallMetadata } from '../instrumentation'; import * as js from '../javascript'; +import { envArrayToObject, launchProcess } from '../processLauncher'; import { ProgressController } from '../progress'; import { WebSocketTransport } from '../transport'; diff --git a/packages/playwright-core/src/server/fileUtils.ts b/packages/playwright-core/src/server/fileUtils.ts new file mode 100644 index 0000000000000..b2ac823844d2f --- /dev/null +++ b/packages/playwright-core/src/server/fileUtils.ts @@ -0,0 +1,205 @@ +/** + * 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 fs from 'fs'; +import * as path from 'path'; + +import { ManualPromise } from '../utils/manualPromise'; +import { yazl } from '../zipBundle'; + +import type { EventEmitter } from 'events'; + +export const existsAsync = (path: string): Promise => new Promise(resolve => fs.stat(path, err => resolve(!err))); + +export async function mkdirIfNeeded(filePath: string) { + // This will harmlessly throw on windows if the dirname is the root directory. + await fs.promises.mkdir(path.dirname(filePath), { recursive: true }).catch(() => {}); +} + +export async function removeFolders(dirs: string[]): Promise { + return await Promise.all(dirs.map((dir: string) => + fs.promises.rm(dir, { recursive: true, force: true, maxRetries: 10 }).catch(e => e) + )); +} + +export function canAccessFile(file: string) { + if (!file) + return false; + + try { + fs.accessSync(file); + return true; + } catch (e) { + return false; + } +} + +export async function copyFileAndMakeWritable(from: string, to: string) { + await fs.promises.copyFile(from, to); + await fs.promises.chmod(to, 0o664); +} + +export function sanitizeForFilePath(s: string) { + return s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-'); +} + +export function toPosixPath(aPath: string): string { + return aPath.split(path.sep).join(path.posix.sep); +} + +type NameValue = { name: string, value: string }; +type SerializedFSOperation = { + op: 'mkdir', dir: string, +} | { + op: 'writeFile', file: string, content: string | Buffer, skipIfExists?: boolean, +} | { + op: 'appendFile', file: string, content: string, +} | { + op: 'copyFile', from: string, to: string, +} | { + op: 'zip', entries: NameValue[], zipFileName: string, +}; + +export class SerializedFS { + private _buffers = new Map(); // Should never be accessed from within appendOperation. + private _error: Error | undefined; + private _operations: SerializedFSOperation[] = []; + private _operationsDone: ManualPromise; + + constructor() { + this._operationsDone = new ManualPromise(); + this._operationsDone.resolve(); // No operations scheduled yet. + } + + mkdir(dir: string) { + this._appendOperation({ op: 'mkdir', dir }); + } + + writeFile(file: string, content: string | Buffer, skipIfExists?: boolean) { + this._buffers.delete(file); // No need to flush the buffer since we'll overwrite anyway. + this._appendOperation({ op: 'writeFile', file, content, skipIfExists }); + } + + appendFile(file: string, text: string, flush?: boolean) { + if (!this._buffers.has(file)) + this._buffers.set(file, []); + this._buffers.get(file)!.push(text); + if (flush) + this._flushFile(file); + } + + private _flushFile(file: string) { + const buffer = this._buffers.get(file); + if (buffer === undefined) + return; + const content = buffer.join(''); + this._buffers.delete(file); + this._appendOperation({ op: 'appendFile', file, content }); + } + + copyFile(from: string, to: string) { + this._flushFile(from); + this._buffers.delete(to); // No need to flush the buffer since we'll overwrite anyway. + this._appendOperation({ op: 'copyFile', from, to }); + } + + async syncAndGetError() { + for (const file of this._buffers.keys()) + this._flushFile(file); + await this._operationsDone; + return this._error; + } + + zip(entries: NameValue[], zipFileName: string) { + for (const file of this._buffers.keys()) + this._flushFile(file); + + // Chain the export operation against write operations, + // so that files do not change during the export. + this._appendOperation({ op: 'zip', entries, zipFileName }); + } + + // This method serializes all writes to the trace. + private _appendOperation(op: SerializedFSOperation): void { + const last = this._operations[this._operations.length - 1]; + if (last?.op === 'appendFile' && op.op === 'appendFile' && last.file === op.file) { + // Merge pending appendFile operations for performance. + last.content += op.content; + return; + } + + this._operations.push(op); + if (this._operationsDone.isDone()) + this._performOperations(); + } + + private async _performOperations() { + this._operationsDone = new ManualPromise(); + while (this._operations.length) { + const op = this._operations.shift()!; + // Ignore all operations after the first error. + if (this._error) + continue; + try { + await this._performOperation(op); + } catch (e) { + this._error = e; + } + } + this._operationsDone.resolve(); + } + + private async _performOperation(op: SerializedFSOperation) { + switch (op.op) { + case 'mkdir': { + await fs.promises.mkdir(op.dir, { recursive: true }); + return; + } + case 'writeFile': { + // Note: 'wx' flag only writes when the file does not exist. + // See https://nodejs.org/api/fs.html#file-system-flags. + // This way tracing never have to write the same resource twice. + if (op.skipIfExists) + await fs.promises.writeFile(op.file, op.content, { flag: 'wx' }).catch(() => {}); + else + await fs.promises.writeFile(op.file, op.content); + return; + } + case 'copyFile': { + await fs.promises.copyFile(op.from, op.to); + return; + } + case 'appendFile': { + await fs.promises.appendFile(op.file, op.content); + return; + } + case 'zip': { + const zipFile = new yazl.ZipFile(); + const result = new ManualPromise(); + (zipFile as any as EventEmitter).on('error', error => result.reject(error)); + for (const entry of op.entries) + zipFile.addFile(entry.value, entry.name); + zipFile.end(); + zipFile.outputStream + .pipe(fs.createWriteStream(op.zipFileName)) + .on('close', () => result.resolve()) + .on('error', error => result.reject(error)); + await result; + return; + } + } + } +} diff --git a/packages/playwright-core/src/server/firefox/firefox.ts b/packages/playwright-core/src/server/firefox/firefox.ts index fe55417c8cde8..c3d21446dd2ae 100644 --- a/packages/playwright-core/src/server/firefox/firefox.ts +++ b/packages/playwright-core/src/server/firefox/firefox.ts @@ -24,9 +24,9 @@ import { wrapInASCIIBox } from '../../utils'; import { BrowserType, kNoXServerRunningError } from '../browserType'; import { BrowserReadyState } from '../browserType'; -import type { Env } from '../../utils/processLauncher'; import type { BrowserOptions } from '../browser'; import type { SdkObject } from '../instrumentation'; +import type { Env } from '../processLauncher'; import type { ProtocolError } from '../protocolError'; import type { ConnectionTransport } from '../transport'; import type * as types from '../types'; diff --git a/packages/playwright-core/src/server/index.ts b/packages/playwright-core/src/server/index.ts index e93399007aa2d..1627192370df7 100644 --- a/packages/playwright-core/src/server/index.ts +++ b/packages/playwright-core/src/server/index.ts @@ -31,3 +31,5 @@ export type { Playwright } from './playwright'; export { installRootRedirect, openTraceInBrowser, openTraceViewerApp, runTraceViewerApp, startTraceViewerServer } from './trace/viewer/traceViewer'; export { serverSideCallMetadata } from './instrumentation'; export { SocksProxy } from '../common/socksProxy'; +export * from './fileUtils'; +export * from './processLauncher'; diff --git a/packages/playwright-core/src/utils/processLauncher.ts b/packages/playwright-core/src/server/processLauncher.ts similarity index 99% rename from packages/playwright-core/src/utils/processLauncher.ts rename to packages/playwright-core/src/server/processLauncher.ts index 68176acc8bd46..6f67ebc69958f 100644 --- a/packages/playwright-core/src/utils/processLauncher.ts +++ b/packages/playwright-core/src/server/processLauncher.ts @@ -20,8 +20,7 @@ import * as fs from 'fs'; import * as readline from 'readline'; import { removeFolders } from './fileUtils'; - -import { isUnderTest } from './'; +import { isUnderTest } from '../utils'; export type Env = {[key: string]: string | number | boolean | undefined}; diff --git a/packages/playwright-core/src/server/registry/browserFetcher.ts b/packages/playwright-core/src/server/registry/browserFetcher.ts index f206382b17387..91e9c21f3d6dc 100644 --- a/packages/playwright-core/src/server/registry/browserFetcher.ts +++ b/packages/playwright-core/src/server/registry/browserFetcher.ts @@ -21,10 +21,10 @@ import * as os from 'os'; import * as path from 'path'; import { debugLogger } from '../../utils/debugLogger'; -import { existsAsync } from '../../utils/fileUtils'; import { ManualPromise } from '../../utils/manualPromise'; import { getUserAgent } from '../../utils/userAgent'; import { colors, progress as ProgressBar } from '../../utilsBundle'; +import { existsAsync } from '../fileUtils'; import { browserDirectoryToMarkerFilePath } from '.'; diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index a1aa6a68d2285..74313fb113f05 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -25,12 +25,12 @@ import { dockerVersion, readDockerVersionSync, transformCommandsForRoot } from ' import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies'; import { calculateSha1, getAsBooleanFromENV, getFromENV, getPackageManagerExecCommand, wrapInASCIIBox } from '../../utils'; import { debugLogger } from '../../utils/debugLogger'; -import { canAccessFile, existsAsync, removeFolders } from '../../utils/fileUtils'; import { hostPlatform, isOfficiallySupportedPlatform } from '../../utils/hostPlatform'; import { fetchData } from '../../utils/network'; import { spawnAsync } from '../../utils/spawnAsync'; import { getEmbedderName } from '../../utils/userAgent'; import { lockfile } from '../../utilsBundle'; +import { canAccessFile, existsAsync, removeFolders } from '../fileUtils'; import type { DependencyGroup } from './dependencies'; import type { HostPlatform } from '../../utils/hostPlatform'; diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index a245de16f6877..12fd42d2bcbd4 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -20,11 +20,12 @@ import * as path from 'path'; import { Snapshotter } from './snapshotter'; import { commandsWithTracingSnapshots } from '../../../protocol/debug'; -import { SerializedFS, assert, createGuid, eventsHelper, monotonicTime, removeFolders } from '../../../utils'; +import { assert, createGuid, eventsHelper, monotonicTime } from '../../../utils'; import { Artifact } from '../../artifact'; import { BrowserContext } from '../../browserContext'; import { Dispatcher } from '../../dispatchers/dispatcher'; import { serializeError } from '../../errors'; +import { SerializedFS, removeFolders } from '../../fileUtils'; import { HarTracer } from '../../har/harTracer'; import { SdkObject } from '../../instrumentation'; import { Page } from '../../page'; diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index ad9de86715243..e38062595731a 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -17,7 +17,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import { gracefullyProcessExitDoNotHang, isUnderTest } from '../../../utils'; +import { gracefullyProcessExitDoNotHang } from '../../../server'; +import { isUnderTest } from '../../../utils'; import { HttpServer } from '../../../utils/httpServer'; import { open } from '../../../utilsBundle'; import { serverSideCallMetadata } from '../../instrumentation'; diff --git a/packages/playwright-core/src/server/webkit/webkit.ts b/packages/playwright-core/src/server/webkit/webkit.ts index 69273b8e95102..f92f7b58eeae9 100644 --- a/packages/playwright-core/src/server/webkit/webkit.ts +++ b/packages/playwright-core/src/server/webkit/webkit.ts @@ -22,9 +22,9 @@ import { wrapInASCIIBox } from '../../utils'; import { BrowserType, kNoXServerRunningError } from '../browserType'; import { WKBrowser } from '../webkit/wkBrowser'; -import type { Env } from '../../utils/processLauncher'; import type { BrowserOptions } from '../browser'; import type { SdkObject } from '../instrumentation'; +import type { Env } from '../processLauncher'; import type { ProtocolError } from '../protocolError'; import type { ConnectionTransport } from '../transport'; import type * as types from '../types'; diff --git a/packages/playwright-core/src/utils/fileUtils.ts b/packages/playwright-core/src/utils/fileUtils.ts index 8cfd3d1fe0121..ed9295879df50 100644 --- a/packages/playwright-core/src/utils/fileUtils.ts +++ b/packages/playwright-core/src/utils/fileUtils.ts @@ -14,194 +14,11 @@ * limitations under the License. */ -import * as fs from 'fs'; -import * as path from 'path'; - -import { ManualPromise } from './manualPromise'; -import { yazl } from '../zipBundle'; - -import type { EventEmitter } from 'events'; +import type { Platform } from '../common/platform'; export const fileUploadSizeLimit = 50 * 1024 * 1024; -export const existsAsync = (path: string): Promise => new Promise(resolve => fs.stat(path, err => resolve(!err))); - -export async function mkdirIfNeeded(filePath: string) { +export async function mkdirIfNeeded(platform: Platform, filePath: string) { // This will harmlessly throw on windows if the dirname is the root directory. - await fs.promises.mkdir(path.dirname(filePath), { recursive: true }).catch(() => {}); -} - -export async function removeFolders(dirs: string[]): Promise { - return await Promise.all(dirs.map((dir: string) => - fs.promises.rm(dir, { recursive: true, force: true, maxRetries: 10 }).catch(e => e) - )); -} - -export function canAccessFile(file: string) { - if (!file) - return false; - - try { - fs.accessSync(file); - return true; - } catch (e) { - return false; - } -} - -export async function copyFileAndMakeWritable(from: string, to: string) { - await fs.promises.copyFile(from, to); - await fs.promises.chmod(to, 0o664); -} - -export function sanitizeForFilePath(s: string) { - return s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-'); -} - -export function toPosixPath(aPath: string): string { - return aPath.split(path.sep).join(path.posix.sep); -} - -type NameValue = { name: string, value: string }; -type SerializedFSOperation = { - op: 'mkdir', dir: string, -} | { - op: 'writeFile', file: string, content: string | Buffer, skipIfExists?: boolean, -} | { - op: 'appendFile', file: string, content: string, -} | { - op: 'copyFile', from: string, to: string, -} | { - op: 'zip', entries: NameValue[], zipFileName: string, -}; - -export class SerializedFS { - private _buffers = new Map(); // Should never be accessed from within appendOperation. - private _error: Error | undefined; - private _operations: SerializedFSOperation[] = []; - private _operationsDone: ManualPromise; - - constructor() { - this._operationsDone = new ManualPromise(); - this._operationsDone.resolve(); // No operations scheduled yet. - } - - mkdir(dir: string) { - this._appendOperation({ op: 'mkdir', dir }); - } - - writeFile(file: string, content: string | Buffer, skipIfExists?: boolean) { - this._buffers.delete(file); // No need to flush the buffer since we'll overwrite anyway. - this._appendOperation({ op: 'writeFile', file, content, skipIfExists }); - } - - appendFile(file: string, text: string, flush?: boolean) { - if (!this._buffers.has(file)) - this._buffers.set(file, []); - this._buffers.get(file)!.push(text); - if (flush) - this._flushFile(file); - } - - private _flushFile(file: string) { - const buffer = this._buffers.get(file); - if (buffer === undefined) - return; - const content = buffer.join(''); - this._buffers.delete(file); - this._appendOperation({ op: 'appendFile', file, content }); - } - - copyFile(from: string, to: string) { - this._flushFile(from); - this._buffers.delete(to); // No need to flush the buffer since we'll overwrite anyway. - this._appendOperation({ op: 'copyFile', from, to }); - } - - async syncAndGetError() { - for (const file of this._buffers.keys()) - this._flushFile(file); - await this._operationsDone; - return this._error; - } - - zip(entries: NameValue[], zipFileName: string) { - for (const file of this._buffers.keys()) - this._flushFile(file); - - // Chain the export operation against write operations, - // so that files do not change during the export. - this._appendOperation({ op: 'zip', entries, zipFileName }); - } - - // This method serializes all writes to the trace. - private _appendOperation(op: SerializedFSOperation): void { - const last = this._operations[this._operations.length - 1]; - if (last?.op === 'appendFile' && op.op === 'appendFile' && last.file === op.file) { - // Merge pending appendFile operations for performance. - last.content += op.content; - return; - } - - this._operations.push(op); - if (this._operationsDone.isDone()) - this._performOperations(); - } - - private async _performOperations() { - this._operationsDone = new ManualPromise(); - while (this._operations.length) { - const op = this._operations.shift()!; - // Ignore all operations after the first error. - if (this._error) - continue; - try { - await this._performOperation(op); - } catch (e) { - this._error = e; - } - } - this._operationsDone.resolve(); - } - - private async _performOperation(op: SerializedFSOperation) { - switch (op.op) { - case 'mkdir': { - await fs.promises.mkdir(op.dir, { recursive: true }); - return; - } - case 'writeFile': { - // Note: 'wx' flag only writes when the file does not exist. - // See https://nodejs.org/api/fs.html#file-system-flags. - // This way tracing never have to write the same resource twice. - if (op.skipIfExists) - await fs.promises.writeFile(op.file, op.content, { flag: 'wx' }).catch(() => {}); - else - await fs.promises.writeFile(op.file, op.content); - return; - } - case 'copyFile': { - await fs.promises.copyFile(op.from, op.to); - return; - } - case 'appendFile': { - await fs.promises.appendFile(op.file, op.content); - return; - } - case 'zip': { - const zipFile = new yazl.ZipFile(); - const result = new ManualPromise(); - (zipFile as any as EventEmitter).on('error', error => result.reject(error)); - for (const entry of op.entries) - zipFile.addFile(entry.value, entry.name); - zipFile.end(); - zipFile.outputStream - .pipe(fs.createWriteStream(op.zipFileName)) - .on('close', () => result.resolve()) - .on('error', error => result.reject(error)); - await result; - return; - } - } - } + await platform.fs().promises.mkdir(platform.path().dirname(filePath), { recursive: true }).catch(() => {}); } diff --git a/packages/playwright-core/src/utils/index.ts b/packages/playwright-core/src/utils/index.ts index 0bc7a75b08e5e..03fc46d237edc 100644 --- a/packages/playwright-core/src/utils/index.ts +++ b/packages/playwright-core/src/utils/index.ts @@ -33,7 +33,6 @@ export * from './isomorphic/stringUtils'; export * from './isomorphic/urlMatch'; export * from './multimap'; export * from './network'; -export * from './processLauncher'; export * from './profiler'; export * from './rtti'; export * from './semaphore'; diff --git a/packages/playwright/src/common/configLoader.ts b/packages/playwright/src/common/configLoader.ts index da02b83c7523d..31f5ffc269fb6 100644 --- a/packages/playwright/src/common/configLoader.ts +++ b/packages/playwright/src/common/configLoader.ts @@ -17,7 +17,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import { gracefullyProcessExitDoNotHang, isRegExp } from 'playwright-core/lib/utils'; +import { gracefullyProcessExitDoNotHang } from 'playwright-core/lib/server'; +import { isRegExp } from 'playwright-core/lib/utils'; import { requireOrImport, setSingleTSConfig, setTransformConfig } from '../transform/transform'; import { errorWithFile, fileIsModule } from '../util'; diff --git a/packages/playwright/src/common/suiteUtils.ts b/packages/playwright/src/common/suiteUtils.ts index d229e62732007..1422494b91a16 100644 --- a/packages/playwright/src/common/suiteUtils.ts +++ b/packages/playwright/src/common/suiteUtils.ts @@ -16,7 +16,8 @@ import * as path from 'path'; -import { calculateSha1, toPosixPath } from 'playwright-core/lib/utils'; +import { toPosixPath } from 'playwright-core/lib/server'; +import { calculateSha1 } from 'playwright-core/lib/utils'; import { createFileMatcher } from '../util'; diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts index 1ad577787179f..fdc791d0735c3 100644 --- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts @@ -18,7 +18,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import { escapeTemplateString, isString, sanitizeForFilePath } from 'playwright-core/lib/utils'; +import { sanitizeForFilePath } from 'playwright-core/lib/server'; +import { escapeTemplateString, isString } from 'playwright-core/lib/utils'; import { kNoElementsFoundError, matcherHint } from './matcherHint'; import { EXPECTED_COLOR } from '../common/expectBundle'; diff --git a/packages/playwright/src/matchers/toMatchSnapshot.ts b/packages/playwright/src/matchers/toMatchSnapshot.ts index 220c9488871b1..4fcd49a0cdf7a 100644 --- a/packages/playwright/src/matchers/toMatchSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchSnapshot.ts @@ -17,7 +17,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import { compareBuffersOrStrings, getComparator, isString, sanitizeForFilePath } from 'playwright-core/lib/utils'; +import { sanitizeForFilePath } from 'playwright-core/lib/server'; +import { compareBuffersOrStrings, getComparator, isString } from 'playwright-core/lib/utils'; import { colors } from 'playwright-core/lib/utilsBundle'; import { mime } from 'playwright-core/lib/utilsBundle'; diff --git a/packages/playwright/src/plugins/webServerPlugin.ts b/packages/playwright/src/plugins/webServerPlugin.ts index 5f30a58e95808..e3103faaa8568 100644 --- a/packages/playwright/src/plugins/webServerPlugin.ts +++ b/packages/playwright/src/plugins/webServerPlugin.ts @@ -16,7 +16,8 @@ import * as net from 'net'; import * as path from 'path'; -import { isURLAvailable, launchProcess, monotonicTime, raceAgainstDeadline } from 'playwright-core/lib/utils'; +import { launchProcess } from 'playwright-core/lib/server'; +import { isURLAvailable, monotonicTime, raceAgainstDeadline } from 'playwright-core/lib/utils'; import { colors, debug } from 'playwright-core/lib/utilsBundle'; import type { TestRunnerPlugin } from '.'; diff --git a/packages/playwright/src/program.ts b/packages/playwright/src/program.ts index 5100e06f37c03..a759e6f366a5b 100644 --- a/packages/playwright/src/program.ts +++ b/packages/playwright/src/program.ts @@ -20,7 +20,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { program } from 'playwright-core/lib/cli/program'; -import { gracefullyProcessExitDoNotHang, startProfiling, stopProfiling } from 'playwright-core/lib/utils'; +import { gracefullyProcessExitDoNotHang } from 'playwright-core/lib/server'; +import { startProfiling, stopProfiling } from 'playwright-core/lib/utils'; import { builtInReporters, defaultReporter, defaultTimeout } from './common/config'; import { loadConfigFromFileRestartIfNeeded, loadEmptyConfigForMergeReports, resolveConfigLocation } from './common/configLoader'; diff --git a/packages/playwright/src/reporters/blob.ts b/packages/playwright/src/reporters/blob.ts index 4f913ba452a4b..434ab8f798305 100644 --- a/packages/playwright/src/reporters/blob.ts +++ b/packages/playwright/src/reporters/blob.ts @@ -18,7 +18,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { Readable } from 'stream'; -import { ManualPromise, calculateSha1, createGuid, getUserAgent, removeFolders, sanitizeForFilePath } from 'playwright-core/lib/utils'; +import { removeFolders, sanitizeForFilePath } from 'playwright-core/lib/server'; +import { ManualPromise, calculateSha1, createGuid, getUserAgent } from 'playwright-core/lib/utils'; import { mime } from 'playwright-core/lib/utilsBundle'; import { yazl } from 'playwright-core/lib/zipBundle'; diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index 2600b51797b63..5583bb731638c 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -18,8 +18,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { Transform } from 'stream'; -import { MultiMap, getPackageManagerExecCommand } from 'playwright-core/lib/utils'; -import { HttpServer, assert, calculateSha1, copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath, toPosixPath } from 'playwright-core/lib/utils'; +import { copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath, toPosixPath } from 'playwright-core/lib/server'; +import { HttpServer, MultiMap, assert, calculateSha1, getPackageManagerExecCommand } from 'playwright-core/lib/utils'; import { colors, 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/reporters/json.ts b/packages/playwright/src/reporters/json.ts index 377f4f3b88e7c..cd3ec41b31d0b 100644 --- a/packages/playwright/src/reporters/json.ts +++ b/packages/playwright/src/reporters/json.ts @@ -17,7 +17,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import { MultiMap, toPosixPath } from 'playwright-core/lib/utils'; +import { toPosixPath } from 'playwright-core/lib/server'; +import { MultiMap } from 'playwright-core/lib/utils'; import { formatError, nonTerminalScreen, prepareErrorStack, resolveOutputFile } from './base'; import { getProjectId } from '../common/config'; diff --git a/packages/playwright/src/runner/tasks.ts b/packages/playwright/src/runner/tasks.ts index 4a1056f6a8e5f..77b1f34a52eff 100644 --- a/packages/playwright/src/runner/tasks.ts +++ b/packages/playwright/src/runner/tasks.ts @@ -18,7 +18,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { promisify } from 'util'; -import { monotonicTime, removeFolders } from 'playwright-core/lib/utils'; +import { removeFolders } from 'playwright-core/lib/server'; +import { monotonicTime } from 'playwright-core/lib/utils'; import { debug } from 'playwright-core/lib/utilsBundle'; import { Dispatcher } from './dispatcher'; @@ -26,12 +27,12 @@ import { FailureTracker } from './failureTracker'; import { collectProjectsAndTestFiles, createRootSuite, loadFileSuites, loadGlobalHook } from './loadUtils'; import { buildDependentProjects, buildTeardownToSetupsMap, filterProjects } from './projectUtils'; import { applySuggestedRebaselines, clearSuggestedRebaselines } from './rebase'; -import { Suite } from '../common/test'; -import { createTestGroups } from '../runner/testGroups'; -import { removeDirAndLogToConsole } from '../util'; import { TaskRunner } from './taskRunner'; import { detectChangedTestFiles } from './vcs'; +import { Suite } from '../common/test'; +import { createTestGroups } from '../runner/testGroups'; import { cacheDir } from '../transform/compilationCache'; +import { removeDirAndLogToConsole } from '../util'; import type { TestGroup } from '../runner/testGroups'; import type { Matcher } from '../util'; diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index 14fa5796d4ada..d1a2c26166ee0 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -17,8 +17,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import { installRootRedirect, openTraceInBrowser, openTraceViewerApp, registry, startTraceViewerServer } from 'playwright-core/lib/server'; -import { ManualPromise, gracefullyProcessExitDoNotHang, isUnderTest } from 'playwright-core/lib/utils'; +import { gracefullyProcessExitDoNotHang, installRootRedirect, openTraceInBrowser, openTraceViewerApp, registry, startTraceViewerServer } from 'playwright-core/lib/server'; +import { ManualPromise, isUnderTest } from 'playwright-core/lib/utils'; import { open } from 'playwright-core/lib/utilsBundle'; import { createErrorCollectingReporter, createReporterForTestServer, createReporters } from './reporters'; diff --git a/packages/playwright/src/runner/workerHost.ts b/packages/playwright/src/runner/workerHost.ts index d92093e72b2da..e6c97e45a6824 100644 --- a/packages/playwright/src/runner/workerHost.ts +++ b/packages/playwright/src/runner/workerHost.ts @@ -17,7 +17,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { removeFolders } from 'playwright-core/lib/utils'; +import { removeFolders } from 'playwright-core/lib/server'; import { ProcessHost } from './processHost'; import { stdioChunkToParams } from '../common/ipc'; diff --git a/packages/playwright/src/util.ts b/packages/playwright/src/util.ts index 74911a85cb524..d0a63e8e6e665 100644 --- a/packages/playwright/src/util.ts +++ b/packages/playwright/src/util.ts @@ -19,8 +19,8 @@ import * as path from 'path'; import * as url from 'url'; import util from 'util'; -import { formatCallLog } from 'playwright-core/lib/utils'; -import { calculateSha1, isRegExp, isString, sanitizeForFilePath, stringifyStackFrames } from 'playwright-core/lib/utils'; +import { sanitizeForFilePath } from 'playwright-core/lib/server'; +import { calculateSha1, formatCallLog, isRegExp, isString, stringifyStackFrames } from 'playwright-core/lib/utils'; import { debug, mime, minimatch, parseStackTraceLine } from 'playwright-core/lib/utilsBundle'; import type { Location } from './../types/testReporter'; diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index 5e964af5e0b05..002c126487435 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -17,7 +17,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import { captureRawStack, monotonicTime, sanitizeForFilePath, stringifyStackFrames, zones } from 'playwright-core/lib/utils'; +import { sanitizeForFilePath } from 'playwright-core/lib/server'; +import { captureRawStack, monotonicTime, stringifyStackFrames, zones } from 'playwright-core/lib/utils'; import { TimeoutManager, TimeoutManagerError, kMaxDeadline } from './timeoutManager'; import { debugTest, filteredStackTrace, formatLocation, getContainedPath, normalizeAndSaveAttachment, trimLongString, windowsFilesystemFriendlyLength } from '../util'; diff --git a/packages/playwright/src/worker/testTracing.ts b/packages/playwright/src/worker/testTracing.ts index a90c006616fc3..73ae364591b4c 100644 --- a/packages/playwright/src/worker/testTracing.ts +++ b/packages/playwright/src/worker/testTracing.ts @@ -17,7 +17,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import { ManualPromise, SerializedFS, calculateSha1, createGuid, monotonicTime } from 'playwright-core/lib/utils'; +import { SerializedFS } from 'playwright-core/lib/server'; +import { ManualPromise, calculateSha1, createGuid, monotonicTime } from 'playwright-core/lib/utils'; import { yauzl, yazl } from 'playwright-core/lib/zipBundle'; import { filteredStackTrace } from '../util'; diff --git a/packages/playwright/src/worker/workerMain.ts b/packages/playwright/src/worker/workerMain.ts index 5188614271a3e..a813b0308cae0 100644 --- a/packages/playwright/src/worker/workerMain.ts +++ b/packages/playwright/src/worker/workerMain.ts @@ -14,7 +14,9 @@ * limitations under the License. */ -import { ManualPromise, gracefullyCloseAll, removeFolders } from 'playwright-core/lib/utils'; +import { removeFolders } from 'playwright-core/lib/server'; +import { gracefullyCloseAll } from 'playwright-core/lib/server'; +import { ManualPromise } from 'playwright-core/lib/utils'; import { colors } from 'playwright-core/lib/utilsBundle'; import { deserializeConfig } from '../common/configLoader'; diff --git a/tests/config/browserTest.ts b/tests/config/browserTest.ts index 9eba4f30d197a..ac996eebcbec3 100644 --- a/tests/config/browserTest.ts +++ b/tests/config/browserTest.ts @@ -16,15 +16,17 @@ import * as fs from 'fs'; import * as os from 'os'; -import type { PageTestFixtures, PageWorkerFixtures } from '../page/pageTestApi'; import * as path from 'path'; -import type { BrowserContext, BrowserContextOptions, BrowserType, Page } from 'playwright-core'; -import { removeFolders } from '../../packages/playwright-core/lib/utils/fileUtils'; import { baseTest } from './baseTest'; -import { type RemoteServerOptions, type PlaywrightServer, RunServer, RemoteServer } from './remoteServer'; -import type { Log } from '../../packages/trace/src/har'; +import { RunServer, RemoteServer } from './remoteServer'; +import { removeFolders } from '../../packages/playwright-core/lib/server/fileUtils'; import { parseHar } from '../config/utils'; import { createSkipTestPredicate } from '../bidi/expectationUtil'; + +import type { PageTestFixtures, PageWorkerFixtures } from '../page/pageTestApi'; +import type { RemoteServerOptions, PlaywrightServer } from './remoteServer'; +import type { BrowserContext, BrowserContextOptions, BrowserType, Page } from 'playwright-core'; +import type { Log } from '../../packages/trace/src/har'; import type { TestInfo } from '@playwright/test'; export type BrowserTestWorkerFixtures = PageWorkerFixtures & { diff --git a/tests/installation/globalSetup.ts b/tests/installation/globalSetup.ts index 7714f35226e74..bf2b7df3e4604 100644 --- a/tests/installation/globalSetup.ts +++ b/tests/installation/globalSetup.ts @@ -17,7 +17,7 @@ import path from 'path'; import fs from 'fs'; import { spawnAsync } from '../../packages/playwright-core/lib/utils/spawnAsync'; -import { removeFolders } from '../../packages/playwright-core/lib/utils/fileUtils'; +import { removeFolders } from '../../packages/playwright-core/lib/server/fileUtils'; import { TMP_WORKSPACES } from './npmTest'; const PACKAGE_BUILDER_SCRIPT = path.join(__dirname, '..', '..', 'utils', 'pack_package.js'); diff --git a/tests/installation/npmTest.ts b/tests/installation/npmTest.ts index 4e39fb8c982a6..128e7a7ab6528 100644 --- a/tests/installation/npmTest.ts +++ b/tests/installation/npmTest.ts @@ -22,7 +22,7 @@ import debugLogger from 'debug'; import { Registry } from './registry'; import type { CommonFixtures, CommonWorkerFixtures } from '../config/commonFixtures'; import { commonFixtures } from '../config/commonFixtures'; -import { removeFolders } from '../../packages/playwright-core/lib/utils/fileUtils'; +import { removeFolders } from '../../packages/playwright-core/lib/server/fileUtils'; import { spawnAsync } from '../../packages/playwright-core/lib/utils/spawnAsync'; import type { SpawnOptions } from 'child_process'; diff --git a/tests/library/inspector/inspectorTest.ts b/tests/library/inspector/inspectorTest.ts index f5e3631b3a670..6d47148690a66 100644 --- a/tests/library/inspector/inspectorTest.ts +++ b/tests/library/inspector/inspectorTest.ts @@ -22,6 +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/common/platform'; export { expect } from '@playwright/test'; type CLITestArgs = { @@ -46,7 +47,7 @@ const codegenLang2Id: Map = new Map([ ]); const codegenLangId2lang = new Map([...codegenLang2Id.entries()].map(([lang, langId]) => [langId, lang])); -const playwrightToAutomateInspector = require('../../../packages/playwright-core/lib/inProcessFactory').createInProcessPlaywright(); +const playwrightToAutomateInspector = require('../../../packages/playwright-core/lib/inProcessFactory').createInProcessPlaywright(nodePlatform); export const test = contextTest.extend({ recorderPageGetter: async ({ context, toImpl, mode }, run, testInfo) => {