From 163aacf4b68a9155a6a9fe2474d4bf2a9c130b10 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 13 Feb 2025 16:15:11 -0800 Subject: [PATCH] chore: allow client operation w/o local utils (#34790) --- packages/playwright-core/src/client/DEPS.list | 3 +- .../playwright-core/src/client/android.ts | 4 +- .../playwright-core/src/client/artifact.ts | 2 +- .../playwright-core/src/client/browser.ts | 2 +- .../src/client/browserContext.ts | 12 +- .../playwright-core/src/client/browserType.ts | 4 +- .../playwright-core/src/client/connection.ts | 4 +- .../src/client/elementHandle.ts | 6 +- packages/playwright-core/src/client/fetch.ts | 2 +- .../src/{common => client}/fileUtils.ts | 9 +- .../playwright-core/src/client/localUtils.ts | 122 +---- .../playwright-core/src/client/network.ts | 4 +- packages/playwright-core/src/client/page.ts | 7 +- .../playwright-core/src/client/tracing.ts | 16 +- .../playwright-core/src/client/webSocket.ts | 116 +++++ packages/playwright-core/src/common/DEPS.list | 3 - .../playwright-core/src/common/platform.ts | 19 +- .../playwright-core/src/common/progress.ts | 23 - .../playwright-core/src/protocol/DEPS.list | 3 +- .../src/protocol/validatorPrimitives.ts | 2 +- .../dispatchers/localUtilsDispatcher.ts | 11 +- .../src/server/fileUploadUtils.ts | 4 +- .../src/{common => server}/harBackend.ts | 17 +- .../src/{common => server}/localUtils.ts | 28 +- .../playwright-core/src/server/progress.ts | 8 +- .../src/server/utils/nodePlatform.ts | 6 +- .../src/{ => server}/utils/zipFile.ts | 4 +- packages/playwright-core/src/utils.ts | 2 +- .../src/utils/isomorphic/mimeType.ts | 427 +++++++++++++++++- tests/config/utils.ts | 2 +- 30 files changed, 646 insertions(+), 226 deletions(-) rename packages/playwright-core/src/{common => client}/fileUtils.ts (75%) create mode 100644 packages/playwright-core/src/client/webSocket.ts delete mode 100644 packages/playwright-core/src/common/progress.ts rename packages/playwright-core/src/{common => server}/harBackend.ts (92%) rename packages/playwright-core/src/{common => server}/localUtils.ts (84%) rename packages/playwright-core/src/{ => server}/utils/zipFile.ts (95%) diff --git a/packages/playwright-core/src/client/DEPS.list b/packages/playwright-core/src/client/DEPS.list index 4be2917927..e886cdbe63 100644 --- a/packages/playwright-core/src/client/DEPS.list +++ b/packages/playwright-core/src/client/DEPS.list @@ -1,5 +1,4 @@ [*] ../common/ ../protocol/ -../utils/** -../utilsBundle.ts +../utils/isomorphic diff --git a/packages/playwright-core/src/client/android.ts b/packages/playwright-core/src/client/android.ts index f0ca11848f..3aedc5c4ac 100644 --- a/packages/playwright-core/src/client/android.ts +++ b/packages/playwright-core/src/client/android.ts @@ -25,6 +25,7 @@ import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; import { isRegExp, isString } from '../utils/isomorphic/rtti'; import { monotonicTime } from '../utils/isomorphic/time'; import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner'; +import { connectOverWebSocket } from './webSocket'; import type { Page } from './page'; import type * as types from './types'; @@ -69,9 +70,8 @@ export class Android extends ChannelOwner implements ap return await this._wrapApiCall(async () => { const deadline = options.timeout ? monotonicTime() + options.timeout : 0; const headers = { 'x-playwright-browser': 'android', ...options.headers }; - const localUtils = this._connection.localUtils(); const connectParams: channels.LocalUtilsConnectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout }; - const connection = await localUtils.connect(connectParams); + const connection = await connectOverWebSocket(this._connection, connectParams); let device: AndroidDevice; connection.on('close', () => { diff --git a/packages/playwright-core/src/client/artifact.ts b/packages/playwright-core/src/client/artifact.ts index 815c7a358b..7c6fd947cf 100644 --- a/packages/playwright-core/src/client/artifact.ts +++ b/packages/playwright-core/src/client/artifact.ts @@ -16,7 +16,7 @@ import { ChannelOwner } from './channelOwner'; import { Stream } from './stream'; -import { mkdirIfNeeded } from '../common/fileUtils'; +import { mkdirIfNeeded } from './fileUtils'; import type * as channels from '@protocol/channels'; import type { Readable } from 'stream'; diff --git a/packages/playwright-core/src/client/browser.ts b/packages/playwright-core/src/client/browser.ts index 88140621fe..a12bf108a8 100644 --- a/packages/playwright-core/src/client/browser.ts +++ b/packages/playwright-core/src/client/browser.ts @@ -20,7 +20,7 @@ import { CDPSession } from './cdpSession'; import { ChannelOwner } from './channelOwner'; import { isTargetClosedError } from './errors'; import { Events } from './events'; -import { mkdirIfNeeded } from '../common/fileUtils'; +import { mkdirIfNeeded } from './fileUtils'; import type { BrowserType } from './browserType'; import type { Page } from './page'; diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index d03cfcdeca..21f5a200b7 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -35,7 +35,7 @@ import { Waiter } from './waiter'; import { WebError } from './webError'; import { Worker } from './worker'; import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; -import { mkdirIfNeeded } from '../common/fileUtils'; +import { mkdirIfNeeded } from './fileUtils'; import { headersObjectToArray } from '../utils/isomorphic/headers'; import { urlMatchesEqual } from '../utils/isomorphic/urlMatch'; import { isRegExp, isString } from '../utils/isomorphic/rtti'; @@ -361,11 +361,14 @@ export class BrowserContext extends ChannelOwner } async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full' } = {}): Promise { + const localUtils = this._connection.localUtils(); + if (!localUtils) + throw new Error('Route from har is not supported in thin clients'); if (options.update) { await this._recordIntoHAR(har, null, options); return; } - const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url }); + const harRouter = await HarRouter.create(localUtils, har, options.notFound || 'abort', { urlMatch: options.url }); this._harRouters.push(harRouter); await harRouter.addContextRoute(this); } @@ -484,8 +487,11 @@ export class BrowserContext extends ChannelOwner const isCompressed = harParams.content === 'attach' || harParams.path.endsWith('.zip'); const needCompressed = harParams.path.endsWith('.zip'); if (isCompressed && !needCompressed) { + const localUtils = this._connection.localUtils(); + if (!localUtils) + throw new Error('Uncompressed har is not supported in thin clients'); await artifact.saveAs(harParams.path + '.tmp'); - await this._connection.localUtils().harUnzip({ zipFile: harParams.path + '.tmp', harFile: harParams.path }); + await localUtils.harUnzip({ zipFile: harParams.path + '.tmp', harFile: harParams.path }); } else { await artifact.saveAs(harParams.path); } diff --git a/packages/playwright-core/src/client/browserType.ts b/packages/playwright-core/src/client/browserType.ts index 06bae0419f..39a9a3f1cc 100644 --- a/packages/playwright-core/src/client/browserType.ts +++ b/packages/playwright-core/src/client/browserType.ts @@ -25,6 +25,7 @@ import { assert } from '../utils/isomorphic/debug'; import { headersObjectToArray } from '../utils/isomorphic/headers'; import { monotonicTime } from '../utils/isomorphic/time'; import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner'; +import { connectOverWebSocket } from './webSocket'; import type { Playwright } from './playwright'; import type { ConnectOptions, LaunchOptions, LaunchPersistentContextOptions, LaunchServerOptions, Logger } from './types'; @@ -124,7 +125,6 @@ export class BrowserType extends ChannelOwner imple return await this._wrapApiCall(async () => { const deadline = params.timeout ? monotonicTime() + params.timeout : 0; const headers = { 'x-playwright-browser': this.name(), ...params.headers }; - const localUtils = this._connection.localUtils(); const connectParams: channels.LocalUtilsConnectParams = { wsEndpoint: params.wsEndpoint, headers, @@ -134,7 +134,7 @@ export class BrowserType extends ChannelOwner imple }; if ((params as any).__testHookRedirectPortForwarding) connectParams.socksProxyRedirectPortForTest = (params as any).__testHookRedirectPortForwarding; - const connection = await localUtils.connect(connectParams); + const connection = await connectOverWebSocket(this._connection, connectParams); let browser: Browser; connection.on('close', () => { // Emulate all pages, contexts and the browser closing upon disconnect. diff --git a/packages/playwright-core/src/client/connection.ts b/packages/playwright-core/src/client/connection.ts index 963b13a77f..929acff25f 100644 --- a/packages/playwright-core/src/client/connection.ts +++ b/packages/playwright-core/src/client/connection.ts @@ -108,8 +108,8 @@ export class Connection extends EventEmitter { return this._rawBuffers; } - localUtils(): LocalUtils { - return this._localUtils!; + localUtils(): LocalUtils | undefined { + return this._localUtils; } async initializePlaywright(): Promise { diff --git a/packages/playwright-core/src/client/elementHandle.ts b/packages/playwright-core/src/client/elementHandle.ts index 206fc33084..b1ef721ce3 100644 --- a/packages/playwright-core/src/client/elementHandle.ts +++ b/packages/playwright-core/src/client/elementHandle.ts @@ -20,10 +20,10 @@ import { promisify } from 'util'; import { Frame } from './frame'; import { JSHandle, parseResult, serializeArgument } from './jsHandle'; import { assert } from '../utils/isomorphic/debug'; -import { fileUploadSizeLimit, mkdirIfNeeded } from '../common/fileUtils'; +import { fileUploadSizeLimit, mkdirIfNeeded } from './fileUtils'; import { isString } from '../utils/isomorphic/rtti'; -import { mime } from '../utilsBundle'; import { WritableStream } from './writableStream'; +import { getMimeTypeForPath } from '../utils/isomorphic/mimeType'; import type { BrowserContext } from './browserContext'; import type { ChannelOwner } from './channelOwner'; @@ -327,7 +327,7 @@ export async function convertInputFiles(platform: Platform, files: string | File export function determineScreenshotType(options: { path?: string, type?: 'png' | 'jpeg' }): 'png' | 'jpeg' | undefined { if (options.path) { - const mimeType = mime.getType(options.path); + const mimeType = getMimeTypeForPath(options.path); if (mimeType === 'image/png') return 'png'; else if (mimeType === 'image/jpeg') diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index 8e02bc34a2..41070b8665 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -20,7 +20,7 @@ import { TargetClosedError, isTargetClosedError } from './errors'; import { RawHeaders } from './network'; import { Tracing } from './tracing'; import { assert } from '../utils/isomorphic/debug'; -import { mkdirIfNeeded } from '../common/fileUtils'; +import { mkdirIfNeeded } from './fileUtils'; import { headersObjectToArray } from '../utils/isomorphic/headers'; import { isString } from '../utils/isomorphic/rtti'; diff --git a/packages/playwright-core/src/common/fileUtils.ts b/packages/playwright-core/src/client/fileUtils.ts similarity index 75% rename from packages/playwright-core/src/common/fileUtils.ts rename to packages/playwright-core/src/client/fileUtils.ts index 261b72d84a..0d21b195c0 100644 --- a/packages/playwright-core/src/common/fileUtils.ts +++ b/packages/playwright-core/src/client/fileUtils.ts @@ -14,17 +14,12 @@ * limitations under the License. */ -import type { Platform } from './platform'; +import type { Platform } from '../common/platform'; +// Keep in sync with the server. export const fileUploadSizeLimit = 50 * 1024 * 1024; export async function mkdirIfNeeded(platform: Platform, filePath: string) { // This will harmlessly throw on windows if the dirname is the root directory. await platform.fs().promises.mkdir(platform.path().dirname(filePath), { recursive: true }).catch(() => {}); } - -export async function removeFolders(platform: Platform, dirs: string[]): Promise { - return await Promise.all(dirs.map((dir: string) => - platform.fs().promises.rm(dir, { recursive: true, force: true, maxRetries: 10 }).catch(e => e) - )); -} diff --git a/packages/playwright-core/src/client/localUtils.ts b/packages/playwright-core/src/client/localUtils.ts index ad66de6814..5ba982251f 100644 --- a/packages/playwright-core/src/client/localUtils.ts +++ b/packages/playwright-core/src/client/localUtils.ts @@ -15,12 +15,8 @@ */ import { ChannelOwner } from './channelOwner'; -import { Connection } from './connection'; -import * as localUtils from '../common/localUtils'; -import type { HeadersArray, Size } from './types'; -import type { HarBackend } from '../common/harBackend'; -import type { Platform } from '../common/platform'; +import type { Size } from './types'; import type * as channels from '@protocol/channels'; type DeviceDescriptor = { @@ -35,8 +31,6 @@ type Devices = { [name: string]: DeviceDescriptor }; export class LocalUtils extends ChannelOwner { readonly devices: Devices; - private _harBackends = new Map(); - private _stackSessions = new Map(); constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.LocalUtilsInitializer) { super(parent, type, guid, initializer); @@ -47,132 +41,34 @@ export class LocalUtils extends ChannelOwner { } async zip(params: channels.LocalUtilsZipParams): Promise { - return await localUtils.zip(this._platform, this._stackSessions, params); + return await this._channel.zip(params); } async harOpen(params: channels.LocalUtilsHarOpenParams): Promise { - return await localUtils.harOpen(this._platform, this._harBackends, params); + return await this._channel.harOpen(params); } async harLookup(params: channels.LocalUtilsHarLookupParams): Promise { - return await localUtils.harLookup(this._harBackends, params); + return await this._channel.harLookup(params); } async harClose(params: channels.LocalUtilsHarCloseParams): Promise { - return await localUtils.harClose(this._harBackends, params); + return await this._channel.harClose(params); } async harUnzip(params: channels.LocalUtilsHarUnzipParams): Promise { - return await localUtils.harUnzip(params); + return await this._channel.harUnzip(params); } async tracingStarted(params: channels.LocalUtilsTracingStartedParams): Promise { - return await localUtils.tracingStarted(this._stackSessions, params); + return await this._channel.tracingStarted(params); } async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams): Promise { - return await localUtils.traceDiscarded(this._platform, this._stackSessions, params); + return await this._channel.traceDiscarded(params); } async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise { - return await localUtils.addStackToTracingNoReply(this._stackSessions, params); - } - - async connect(params: channels.LocalUtilsConnectParams): Promise { - const transport = this._platform.ws ? new WebSocketTransport(this._platform) : new JsonPipeTransport(this); - const connectHeaders = await transport.connect(params); - const connection = new Connection(this, this._platform, this._instrumentation, connectHeaders); - connection.markAsRemote(); - connection.on('close', () => transport.close()); - - let closeError: string | undefined; - const onTransportClosed = (reason?: string) => { - connection.close(reason || closeError); - }; - transport.onClose(reason => onTransportClosed(reason)); - connection.onmessage = message => transport.send(message).catch(() => onTransportClosed()); - transport.onMessage(message => { - try { - connection!.dispatch(message); - } catch (e) { - closeError = String(e); - transport.close(); - } - }); - return connection; - } -} -interface Transport { - connect(params: channels.LocalUtilsConnectParams): Promise; - send(message: any): Promise; - onMessage(callback: (message: object) => void): void; - onClose(callback: (reason?: string) => void): void; - close(): Promise; -} - -class JsonPipeTransport implements Transport { - private _pipe: channels.JsonPipeChannel | undefined; - private _owner: ChannelOwner; - - constructor(owner: ChannelOwner) { - this._owner = owner; - } - - async connect(params: channels.LocalUtilsConnectParams) { - const { pipe, headers: connectHeaders } = await this._owner._wrapApiCall(async () => { - return await this._owner._channel.connect(params); - }, /* isInternal */ true); - this._pipe = pipe; - return connectHeaders; - } - - async send(message: object) { - this._owner._wrapApiCall(async () => { - await this._pipe!.send({ message }); - }, /* isInternal */ true); - } - - onMessage(callback: (message: object) => void) { - this._pipe!.on('message', ({ message }) => callback(message)); - } - - onClose(callback: (reason?: string) => void) { - this._pipe!.on('closed', ({ reason }) => callback(reason)); - } - - async close() { - await this._owner._wrapApiCall(async () => { - await this._pipe!.close().catch(() => {}); - }, /* isInternal */ true); - } -} - -class WebSocketTransport implements Transport { - private _platform: Platform; - private _ws: WebSocket | undefined; - - constructor(platform: Platform) { - this._platform = platform; - } - - async connect(params: channels.LocalUtilsConnectParams) { - this._ws = this._platform.ws!(params.wsEndpoint); - return []; - } - - async send(message: object) { - this._ws!.send(JSON.stringify(message)); - } - - onMessage(callback: (message: object) => void) { - this._ws!.addEventListener('message', event => callback(JSON.parse(event.data))); - } - - onClose(callback: (reason?: string) => void) { - this._ws!.addEventListener('close', () => callback()); - } - - async close() { - this._ws!.close(); + return await this._channel.addStackToTracingNoReply(params); } } diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index 3770ca1320..ff992e7ae7 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -30,7 +30,7 @@ import { LongStandingScope, ManualPromise } from '../utils/isomorphic/manualProm import { MultiMap } from '../utils/isomorphic/multimap'; import { isRegExp, isString } from '../utils/isomorphic/rtti'; import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace'; -import { mime } from '../utilsBundle'; +import { getMimeTypeForPath } from '../utils/isomorphic/mimeType'; import type { BrowserContext } from './browserContext'; import type { Page } from './page'; @@ -413,7 +413,7 @@ export class Route extends ChannelOwner implements api.Ro else if (options.json) headers['content-type'] = 'application/json'; else if (options.path) - headers['content-type'] = mime.getType(options.path) || 'application/octet-stream'; + headers['content-type'] = getMimeTypeForPath(options.path) || 'application/octet-stream'; if (length && !('content-length' in headers)) headers['content-length'] = String(length); diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index 062217937c..43c43ee50c 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -35,7 +35,7 @@ import { Waiter } from './waiter'; import { Worker } from './worker'; import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings'; import { assert } from '../utils/isomorphic/debug'; -import { mkdirIfNeeded } from '../common/fileUtils'; +import { mkdirIfNeeded } from './fileUtils'; import { headersObjectToArray } from '../utils/isomorphic/headers'; import { trimStringWithEllipsis } from '../utils/isomorphic/stringUtils'; import { urlMatches, urlMatchesEqual } from '../utils/isomorphic/urlMatch'; @@ -525,11 +525,14 @@ export class Page extends ChannelOwner implements api.Page } async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full'} = {}): Promise { + const localUtils = this._connection.localUtils(); + if (!localUtils) + throw new Error('Route from har is not supported in thin clients'); if (options.update) { await this._browserContext._recordIntoHAR(har, this, options); return; } - const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url }); + const harRouter = await HarRouter.create(localUtils, har, options.notFound || 'abort', { urlMatch: options.url }); this._harRouters.push(harRouter); await harRouter.addPageRoute(this); } diff --git a/packages/playwright-core/src/client/tracing.ts b/packages/playwright-core/src/client/tracing.ts index 61124107fa..5b80f30a0a 100644 --- a/packages/playwright-core/src/client/tracing.ts +++ b/packages/playwright-core/src/client/tracing.ts @@ -69,8 +69,8 @@ export class Tracing extends ChannelOwner implements ap this._isTracing = true; this._connection.setIsTracing(true); } - const result = await this._connection.localUtils().tracingStarted({ tracesDir: this._tracesDir, traceName }); - this._stacksId = result.stacksId; + const result = await this._connection.localUtils()?.tracingStarted({ tracesDir: this._tracesDir, traceName }); + this._stacksId = result?.stacksId; } async stopChunk(options: { path?: string } = {}) { @@ -89,15 +89,19 @@ export class Tracing extends ChannelOwner implements ap // Not interested in artifacts. await this._channel.tracingStopChunk({ mode: 'discard' }); if (this._stacksId) - await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId }); + await this._connection.localUtils()!.traceDiscarded({ stacksId: this._stacksId }); return; } + const localUtils = this._connection.localUtils(); + if (!localUtils) + throw new Error('Cannot save trace in thin clients'); + const isLocal = !this._connection.isRemote(); if (isLocal) { const result = await this._channel.tracingStopChunk({ mode: 'entries' }); - await this._connection.localUtils().zip({ zipFile: filePath, entries: result.entries!, mode: 'write', stacksId: this._stacksId, includeSources: this._includeSources }); + await localUtils.zip({ zipFile: filePath, entries: result.entries!, mode: 'write', stacksId: this._stacksId, includeSources: this._includeSources }); return; } @@ -106,7 +110,7 @@ export class Tracing extends ChannelOwner implements ap // The artifact may be missing if the browser closed while stopping tracing. if (!result.artifact) { if (this._stacksId) - await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId }); + await localUtils.traceDiscarded({ stacksId: this._stacksId }); return; } @@ -115,7 +119,7 @@ export class Tracing extends ChannelOwner implements ap await artifact.saveAs(filePath); await artifact.delete(); - await this._connection.localUtils().zip({ zipFile: filePath, entries: [], mode: 'append', stacksId: this._stacksId, includeSources: this._includeSources }); + await localUtils.zip({ zipFile: filePath, entries: [], mode: 'append', stacksId: this._stacksId, includeSources: this._includeSources }); } _resetStackCounter() { diff --git a/packages/playwright-core/src/client/webSocket.ts b/packages/playwright-core/src/client/webSocket.ts new file mode 100644 index 0000000000..cef07a4dfa --- /dev/null +++ b/packages/playwright-core/src/client/webSocket.ts @@ -0,0 +1,116 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ChannelOwner } from './channelOwner'; +import { Connection } from './connection'; + +import type { HeadersArray } from './types'; +import type * as channels from '@protocol/channels'; + +export async function connectOverWebSocket(parentConnection: Connection, params: channels.LocalUtilsConnectParams): Promise { + const localUtils = parentConnection.localUtils(); + const transport = localUtils ? new JsonPipeTransport(localUtils) : new WebSocketTransport(); + const connectHeaders = await transport.connect(params); + const connection = new Connection(localUtils, parentConnection.platform, parentConnection._instrumentation, connectHeaders); + connection.markAsRemote(); + connection.on('close', () => transport.close()); + + let closeError: string | undefined; + const onTransportClosed = (reason?: string) => { + connection.close(reason || closeError); + }; + transport.onClose(reason => onTransportClosed(reason)); + connection.onmessage = message => transport.send(message).catch(() => onTransportClosed()); + transport.onMessage(message => { + try { + connection!.dispatch(message); + } catch (e) { + closeError = String(e); + transport.close(); + } + }); + return connection; +} + +interface Transport { + connect(params: channels.LocalUtilsConnectParams): Promise; + send(message: any): Promise; + onMessage(callback: (message: object) => void): void; + onClose(callback: (reason?: string) => void): void; + close(): Promise; +} + +class JsonPipeTransport implements Transport { + private _pipe: channels.JsonPipeChannel | undefined; + private _owner: ChannelOwner; + + constructor(owner: ChannelOwner) { + this._owner = owner; + } + + async connect(params: channels.LocalUtilsConnectParams) { + const { pipe, headers: connectHeaders } = await this._owner._wrapApiCall(async () => { + return await this._owner._channel.connect(params); + }, /* isInternal */ true); + this._pipe = pipe; + return connectHeaders; + } + + async send(message: object) { + this._owner._wrapApiCall(async () => { + await this._pipe!.send({ message }); + }, /* isInternal */ true); + } + + onMessage(callback: (message: object) => void) { + this._pipe!.on('message', ({ message }) => callback(message)); + } + + onClose(callback: (reason?: string) => void) { + this._pipe!.on('closed', ({ reason }) => callback(reason)); + } + + async close() { + await this._owner._wrapApiCall(async () => { + await this._pipe!.close().catch(() => {}); + }, /* isInternal */ true); + } +} + +class WebSocketTransport implements Transport { + private _ws: WebSocket | undefined; + + async connect(params: channels.LocalUtilsConnectParams) { + this._ws = new window.WebSocket(params.wsEndpoint); + return []; + } + + async send(message: object) { + this._ws!.send(JSON.stringify(message)); + } + + onMessage(callback: (message: object) => void) { + this._ws!.addEventListener('message', event => callback(JSON.parse(event.data))); + } + + onClose(callback: (reason?: string) => void) { + this._ws!.addEventListener('close', () => callback()); + } + + async close() { + this._ws!.close(); + } +} diff --git a/packages/playwright-core/src/common/DEPS.list b/packages/playwright-core/src/common/DEPS.list index a25dd41c36..686b88087b 100644 --- a/packages/playwright-core/src/common/DEPS.list +++ b/packages/playwright-core/src/common/DEPS.list @@ -1,5 +1,2 @@ [*] -../utils/ ../utils/isomorphic/ -../utilsBundle.ts -../zipBundle.ts diff --git a/packages/playwright-core/src/common/platform.ts b/packages/playwright-core/src/common/platform.ts index bb73ec6d31..330b5d6323 100644 --- a/packages/playwright-core/src/common/platform.ts +++ b/packages/playwright-core/src/common/platform.ts @@ -14,12 +14,10 @@ * limitations under the License. */ -import * as crypto from 'crypto'; -import * as fs from 'fs'; -import * as path from 'path'; - import { webColors, noColors } from '../utils/isomorphic/colors'; +import type * as fs from 'fs'; +import type * as path from 'path'; import type { Colors } from '../utils/isomorphic/colors'; export type Zone = { @@ -37,6 +35,8 @@ const noopZone: Zone = { }; export type Platform = { + name: 'node' | 'web' | 'empty'; + calculateSha1(text: string): Promise; colors: Colors; createGuid: () => string; @@ -46,21 +46,22 @@ export type Platform = { log(name: 'api' | 'channel', message: string | Error | object): void; path: () => typeof path; pathSeparator: string; - ws?: (url: string) => WebSocket; zones: { empty: Zone, current: () => Zone; }; }; export const webPlatform: Platform = { + name: 'web', + calculateSha1: async (text: string) => { const bytes = new TextEncoder().encode(text); - const hashBuffer = await crypto.subtle.digest('SHA-1', bytes); + const hashBuffer = await window.crypto.subtle.digest('SHA-1', bytes); return Array.from(new Uint8Array(hashBuffer), b => b.toString(16).padStart(2, '0')).join(''); }, colors: webColors, createGuid: () => { - return Array.from(crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join(''); + return Array.from(window.crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join(''); }, fs: () => { @@ -82,12 +83,12 @@ export const webPlatform: Platform = { pathSeparator: '/', - ws: (url: string) => new WebSocket(url), - zones: { empty: noopZone, current: () => noopZone }, }; export const emptyPlatform: Platform = { + name: 'empty', + calculateSha1: async () => { throw new Error('Not implemented'); }, diff --git a/packages/playwright-core/src/common/progress.ts b/packages/playwright-core/src/common/progress.ts deleted file mode 100644 index f09670e823..0000000000 --- a/packages/playwright-core/src/common/progress.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export interface Progress { - log(message: string): void; - timeUntilDeadline(): number; - isRunning(): boolean; - cleanupWhenAborted(cleanup: () => any): void; - throwIfAborted(): void; -} diff --git a/packages/playwright-core/src/protocol/DEPS.list b/packages/playwright-core/src/protocol/DEPS.list index bf64b324f1..dbdeafe86c 100644 --- a/packages/playwright-core/src/protocol/DEPS.list +++ b/packages/playwright-core/src/protocol/DEPS.list @@ -1,4 +1,3 @@ [*] ../common/ -../utils/ - +../utils/isomorphic diff --git a/packages/playwright-core/src/protocol/validatorPrimitives.ts b/packages/playwright-core/src/protocol/validatorPrimitives.ts index 9d4614512b..f57a63acea 100644 --- a/packages/playwright-core/src/protocol/validatorPrimitives.ts +++ b/packages/playwright-core/src/protocol/validatorPrimitives.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { isUnderTest } from '../utils'; +import { isUnderTest } from '../utils/isomorphic/debug'; export class ValidationError extends Error {} export type Validator = (arg: any, path: string, context: ValidatorContext) => any; diff --git a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts index b582aa8788..1c74b7258c 100644 --- a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts @@ -16,8 +16,7 @@ import { Dispatcher } from './dispatcher'; import { SdkObject } from '../../server/instrumentation'; -import * as localUtils from '../../common/localUtils'; -import { nodePlatform } from '../utils/nodePlatform'; +import * as localUtils from '../localUtils'; import { getUserAgent } from '../utils/userAgent'; import { deviceDescriptors as descriptors } from '../deviceDescriptors'; import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher'; @@ -26,7 +25,7 @@ import { SocksInterceptor } from '../socksInterceptor'; import { WebSocketTransport } from '../transport'; import { fetchData } from '../utils/network'; -import type { HarBackend } from '../../common/harBackend'; +import type { HarBackend } from '../harBackend'; import type { CallMetadata } from '../instrumentation'; import type { Playwright } from '../playwright'; import type { RootDispatcher } from './dispatcher'; @@ -50,11 +49,11 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels. } async zip(params: channels.LocalUtilsZipParams): Promise { - return await localUtils.zip(nodePlatform, this._stackSessions, params); + return await localUtils.zip(this._stackSessions, params); } async harOpen(params: channels.LocalUtilsHarOpenParams, metadata: CallMetadata): Promise { - return await localUtils.harOpen(nodePlatform, this._harBackends, params); + return await localUtils.harOpen(this._harBackends, params); } async harLookup(params: channels.LocalUtilsHarLookupParams, metadata: CallMetadata): Promise { @@ -74,7 +73,7 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels. } async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams, metadata?: CallMetadata | undefined): Promise { - return await localUtils.traceDiscarded(nodePlatform, this._stackSessions, params); + return await localUtils.traceDiscarded(this._stackSessions, params); } async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams, metadata?: CallMetadata | undefined): Promise { diff --git a/packages/playwright-core/src/server/fileUploadUtils.ts b/packages/playwright-core/src/server/fileUploadUtils.ts index 908e7644ac..5a15617c8b 100644 --- a/packages/playwright-core/src/server/fileUploadUtils.ts +++ b/packages/playwright-core/src/server/fileUploadUtils.ts @@ -18,7 +18,6 @@ import * as fs from 'fs'; import * as path from 'path'; import { assert } from '../utils/isomorphic/debug'; -import { fileUploadSizeLimit } from '../common/fileUtils'; import { mime } from '../utilsBundle'; import type { WritableStreamDispatcher } from './dispatchers/writableStreamDispatcher'; @@ -27,6 +26,9 @@ import type { Frame } from './frames'; import type * as types from './types'; import type * as channels from '@protocol/channels'; +// Keep in sync with the client. +export const fileUploadSizeLimit = 50 * 1024 * 1024; + async function filesExceedUploadLimit(files: string[]) { const sizes = await Promise.all(files.map(async file => (await fs.promises.stat(file)).size)); return sizes.reduce((total, size) => total + size, 0) >= fileUploadSizeLimit; diff --git a/packages/playwright-core/src/common/harBackend.ts b/packages/playwright-core/src/server/harBackend.ts similarity index 92% rename from packages/playwright-core/src/common/harBackend.ts rename to packages/playwright-core/src/server/harBackend.ts index f59b7de5f1..0814be9c5f 100644 --- a/packages/playwright-core/src/common/harBackend.ts +++ b/packages/playwright-core/src/server/harBackend.ts @@ -14,11 +14,14 @@ * limitations under the License. */ -import { ZipFile } from '../utils/zipFile'; +import * as fs from 'fs'; +import * as path from 'path'; -import type { HeadersArray } from './types'; +import { createGuid } from './utils/crypto'; +import { ZipFile } from './utils/zipFile'; + +import type { HeadersArray } from '../common/types'; import type * as har from '@trace/har'; -import type { Platform } from './platform'; const redirectStatus = [301, 302, 303, 307, 308]; @@ -27,11 +30,9 @@ export class HarBackend { private _harFile: har.HARFile; private _zipFile: ZipFile | null; private _baseDir: string | null; - private _platform: Platform; - constructor(platform: Platform, harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) { - this._platform = platform; - this.id = platform.createGuid(); + constructor(harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) { + this.id = createGuid(); this._harFile = harFile; this._baseDir = baseDir; this._zipFile = zipFile; @@ -79,7 +80,7 @@ export class HarBackend { if (this._zipFile) buffer = await this._zipFile.read(file); else - buffer = await this._platform.fs().promises.readFile(this._platform.path().resolve(this._baseDir!, file)); + buffer = await fs.promises.readFile(path.resolve(this._baseDir!, file)); } else { buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8'); } diff --git a/packages/playwright-core/src/common/localUtils.ts b/packages/playwright-core/src/server/localUtils.ts similarity index 84% rename from packages/playwright-core/src/common/localUtils.ts rename to packages/playwright-core/src/server/localUtils.ts index 5020d82b75..1f5f0064b5 100644 --- a/packages/playwright-core/src/common/localUtils.ts +++ b/packages/playwright-core/src/server/localUtils.ts @@ -18,15 +18,15 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { removeFolders } from './fileUtils'; +import { calculateSha1 } from './utils/crypto'; import { HarBackend } from './harBackend'; import { ManualPromise } from '../utils/isomorphic/manualPromise'; -import { ZipFile } from '../utils/zipFile'; +import { ZipFile } from './utils/zipFile'; import { yauzl, yazl } from '../zipBundle'; import { serializeClientSideCallMetadata } from '../utils/isomorphic/traceUtils'; import { assert } from '../utils/isomorphic/debug'; +import { removeFolders } from './utils/fileUtils'; -import type { Platform } from './platform'; import type * as channels from '@protocol/channels'; import type * as har from '@trace/har'; import type EventEmitter from 'events'; @@ -39,7 +39,7 @@ export type StackSession = { callStacks: channels.ClientSideCallMetadata[]; }; -export async function zip(platform: Platform, stackSessions: Map, params: channels.LocalUtilsZipParams): Promise { +export async function zip(stackSessions: Map, params: channels.LocalUtilsZipParams): Promise { const promise = new ManualPromise(); const zipFile = new yazl.ZipFile(); (zipFile as any as EventEmitter).on('error', error => promise.reject(error)); @@ -77,7 +77,7 @@ export async function zip(platform: Platform, stackSessions: Map promise.reject(error)); }); await promise; - await deleteStackSession(platform, stackSessions, params.stacksId); + await deleteStackSession(stackSessions, params.stacksId); return; } @@ -124,20 +124,20 @@ export async function zip(platform: Platform, stackSessions: Map, stacksId?: string) { +async function deleteStackSession(stackSessions: Map, stacksId?: string) { const session = stacksId ? stackSessions.get(stacksId) : undefined; if (!session) return; await session.writer; if (session.tmpDir) - await removeFolders(platform, [session.tmpDir]); + await removeFolders([session.tmpDir]); stackSessions.delete(stacksId!); } -export async function harOpen(platform: Platform, harBackends: Map, params: channels.LocalUtilsHarOpenParams): Promise { +export async function harOpen(harBackends: Map, params: channels.LocalUtilsHarOpenParams): Promise { let harBackend: HarBackend; if (params.file.endsWith('.zip')) { const zipFile = new ZipFile(params.file); @@ -147,10 +147,10 @@ export async function harOpen(platform: Platform, harBackends: Map, p return { stacksId: traceStacksFile }; } -export async function traceDiscarded(platform: Platform, stackSessions: Map, params: channels.LocalUtilsTraceDiscardedParams): Promise { - await deleteStackSession(platform, stackSessions, params.stacksId); +export async function traceDiscarded(stackSessions: Map, params: channels.LocalUtilsTraceDiscardedParams): Promise { + await deleteStackSession(stackSessions, params.stacksId); } export async function addStackToTracingNoReply(stackSessions: Map, params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise { diff --git a/packages/playwright-core/src/server/progress.ts b/packages/playwright-core/src/server/progress.ts index 30db144fbb..16dcc6fb1f 100644 --- a/packages/playwright-core/src/server/progress.ts +++ b/packages/playwright-core/src/server/progress.ts @@ -19,10 +19,14 @@ import { assert, monotonicTime } from '../utils'; import { ManualPromise } from '../utils/isomorphic/manualPromise'; import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation'; -import type { Progress as CommonProgress } from '../common/progress'; import type { LogName } from './utils/debugLogger'; -export interface Progress extends CommonProgress { +export interface Progress { + log(message: string): void; + timeUntilDeadline(): number; + isRunning(): boolean; + cleanupWhenAborted(cleanup: () => any): void; + throwIfAborted(): void; metadata: CallMetadata; } diff --git a/packages/playwright-core/src/server/utils/nodePlatform.ts b/packages/playwright-core/src/server/utils/nodePlatform.ts index 49f6b23ffe..1c2a4dad0c 100644 --- a/packages/playwright-core/src/server/utils/nodePlatform.ts +++ b/packages/playwright-core/src/server/utils/nodePlatform.ts @@ -45,16 +45,14 @@ class NodeZone implements Zone { return this._zone.run(func); } - runIgnoreCurrent(func: () => R): R { - return emptyZone.run(func); - } - data(): T | undefined { return this._zone.data('apiZone'); } } export const nodePlatform: Platform = { + name: 'node', + calculateSha1: (text: string) => { const sha1 = crypto.createHash('sha1'); sha1.update(text); diff --git a/packages/playwright-core/src/utils/zipFile.ts b/packages/playwright-core/src/server/utils/zipFile.ts similarity index 95% rename from packages/playwright-core/src/utils/zipFile.ts rename to packages/playwright-core/src/server/utils/zipFile.ts index 1f70c5e1bc..43b5a3430c 100644 --- a/packages/playwright-core/src/utils/zipFile.ts +++ b/packages/playwright-core/src/server/utils/zipFile.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { yauzl } from '../zipBundle'; +import { yauzl } from '../../zipBundle'; -import type { Entry, UnzipFile } from '../zipBundle'; +import type { Entry, UnzipFile } from '../../zipBundle'; export class ZipFile { private _fileName: string; diff --git a/packages/playwright-core/src/utils.ts b/packages/playwright-core/src/utils.ts index 839aa390a8..dc5f284955 100644 --- a/packages/playwright-core/src/utils.ts +++ b/packages/playwright-core/src/utils.ts @@ -29,7 +29,6 @@ export * from './utils/isomorphic/urlMatch'; export * from './utils/isomorphic/headers'; export * from './utils/isomorphic/semaphore'; export * from './utils/isomorphic/stackTrace'; -export * from './utils/zipFile'; export * from './server/utils/ascii'; export * from './server/utils/comparators'; @@ -50,6 +49,7 @@ export * from './server/utils/spawnAsync'; export * from './server/utils/task'; export * from './server/utils/userAgent'; export * from './server/utils/wsServer'; +export * from './server/utils/zipFile'; export * from './server/utils/zones'; export { colors } from './utilsBundle'; diff --git a/packages/playwright-core/src/utils/isomorphic/mimeType.ts b/packages/playwright-core/src/utils/isomorphic/mimeType.ts index 407d935281..45ac92d645 100644 --- a/packages/playwright-core/src/utils/isomorphic/mimeType.ts +++ b/packages/playwright-core/src/utils/isomorphic/mimeType.ts @@ -1,14 +1,14 @@ /** * Copyright (c) Microsoft Corporation. * - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * 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, + * 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. @@ -21,3 +21,426 @@ export function isJsonMimeType(mimeType: string) { export function isTextualMimeType(mimeType: string) { return !!mimeType.match(/^(text\/.*?|application\/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image\/svg(\+xml)?|application\/.*?(\+json|\+xml))(;\s*charset=.*)?$/); } +export function getMimeTypeForPath(path: string): string | null { + const dotIndex = path.lastIndexOf('.'); + if (dotIndex === -1) + return null; + const extension = path.substring(dotIndex + 1); + return types.get(extension) || null; +} + +const types: Map = new Map([ + ['ez', 'application/andrew-inset'], + ['aw', 'application/applixware'], + ['atom', 'application/atom+xml'], + ['atomcat', 'application/atomcat+xml'], + ['atomdeleted', 'application/atomdeleted+xml'], + ['atomsvc', 'application/atomsvc+xml'], + ['dwd', 'application/atsc-dwd+xml'], + ['held', 'application/atsc-held+xml'], + ['rsat', 'application/atsc-rsat+xml'], + ['bdoc', 'application/bdoc'], + ['xcs', 'application/calendar+xml'], + ['ccxml', 'application/ccxml+xml'], + ['cdfx', 'application/cdfx+xml'], + ['cdmia', 'application/cdmi-capability'], + ['cdmic', 'application/cdmi-container'], + ['cdmid', 'application/cdmi-domain'], + ['cdmio', 'application/cdmi-object'], + ['cdmiq', 'application/cdmi-queue'], + ['cu', 'application/cu-seeme'], + ['mpd', 'application/dash+xml'], + ['davmount', 'application/davmount+xml'], + ['dbk', 'application/docbook+xml'], + ['dssc', 'application/dssc+der'], + ['xdssc', 'application/dssc+xml'], + ['ecma', 'application/ecmascript'], + ['es', 'application/ecmascript'], + ['emma', 'application/emma+xml'], + ['emotionml', 'application/emotionml+xml'], + ['epub', 'application/epub+zip'], + ['exi', 'application/exi'], + ['exp', 'application/express'], + ['fdt', 'application/fdt+xml'], + ['pfr', 'application/font-tdpfr'], + ['geojson', 'application/geo+json'], + ['gml', 'application/gml+xml'], + ['gpx', 'application/gpx+xml'], + ['gxf', 'application/gxf'], + ['gz', 'application/gzip'], + ['hjson', 'application/hjson'], + ['stk', 'application/hyperstudio'], + ['ink', 'application/inkml+xml'], + ['inkml', 'application/inkml+xml'], + ['ipfix', 'application/ipfix'], + ['its', 'application/its+xml'], + ['ear', 'application/java-archive'], + ['jar', 'application/java-archive'], + ['war', 'application/java-archive'], + ['ser', 'application/java-serialized-object'], + ['class', 'application/java-vm'], + ['js', 'application/javascript'], + ['mjs', 'application/javascript'], + ['json', 'application/json'], + ['map', 'application/json'], + ['json5', 'application/json5'], + ['jsonml', 'application/jsonml+json'], + ['jsonld', 'application/ld+json'], + ['lgr', 'application/lgr+xml'], + ['lostxml', 'application/lost+xml'], + ['hqx', 'application/mac-binhex40'], + ['cpt', 'application/mac-compactpro'], + ['mads', 'application/mads+xml'], + ['webmanifest', 'application/manifest+json'], + ['mrc', 'application/marc'], + ['mrcx', 'application/marcxml+xml'], + ['ma', 'application/mathematica'], + ['mb', 'application/mathematica'], + ['nb', 'application/mathematica'], + ['mathml', 'application/mathml+xml'], + ['mbox', 'application/mbox'], + ['mscml', 'application/mediaservercontrol+xml'], + ['metalink', 'application/metalink+xml'], + ['meta4', 'application/metalink4+xml'], + ['mets', 'application/mets+xml'], + ['maei', 'application/mmt-aei+xml'], + ['musd', 'application/mmt-usd+xml'], + ['mods', 'application/mods+xml'], + ['m21', 'application/mp21'], + ['mp21', 'application/mp21'], + ['m4p', 'application/mp4'], + ['mp4s', 'application/mp4'], + ['doc', 'application/msword'], + ['dot', 'application/msword'], + ['mxf', 'application/mxf'], + ['nq', 'application/n-quads'], + ['nt', 'application/n-triples'], + ['cjs', 'application/node'], + ['bin', 'application/octet-stream'], + ['bpk', 'application/octet-stream'], + ['buffer', 'application/octet-stream'], + ['deb', 'application/octet-stream'], + ['deploy', 'application/octet-stream'], + ['dist', 'application/octet-stream'], + ['distz', 'application/octet-stream'], + ['dll', 'application/octet-stream'], + ['dmg', 'application/octet-stream'], + ['dms', 'application/octet-stream'], + ['dump', 'application/octet-stream'], + ['elc', 'application/octet-stream'], + ['exe', 'application/octet-stream'], + ['img', 'application/octet-stream'], + ['iso', 'application/octet-stream'], + ['lrf', 'application/octet-stream'], + ['mar', 'application/octet-stream'], + ['msi', 'application/octet-stream'], + ['msm', 'application/octet-stream'], + ['msp', 'application/octet-stream'], + ['pkg', 'application/octet-stream'], + ['so', 'application/octet-stream'], + ['oda', 'application/oda'], + ['opf', 'application/oebps-package+xml'], + ['ogx', 'application/ogg'], + ['omdoc', 'application/omdoc+xml'], + ['onepkg', 'application/onenote'], + ['onetmp', 'application/onenote'], + ['onetoc', 'application/onenote'], + ['onetoc2', 'application/onenote'], + ['oxps', 'application/oxps'], + ['relo', 'application/p2p-overlay+xml'], + ['xer', 'application/patch-ops-error+xml'], + ['pdf', 'application/pdf'], + ['pgp', 'application/pgp-encrypted'], + ['asc', 'application/pgp-signature'], + ['sig', 'application/pgp-signature'], + ['prf', 'application/pics-rules'], + ['p10', 'application/pkcs10'], + ['p7c', 'application/pkcs7-mime'], + ['p7m', 'application/pkcs7-mime'], + ['p7s', 'application/pkcs7-signature'], + ['p8', 'application/pkcs8'], + ['ac', 'application/pkix-attr-cert'], + ['cer', 'application/pkix-cert'], + ['crl', 'application/pkix-crl'], + ['pkipath', 'application/pkix-pkipath'], + ['pki', 'application/pkixcmp'], + ['pls', 'application/pls+xml'], + ['ai', 'application/postscript'], + ['eps', 'application/postscript'], + ['ps', 'application/postscript'], + ['provx', 'application/provenance+xml'], + ['pskcxml', 'application/pskc+xml'], + ['raml', 'application/raml+yaml'], + ['owl', 'application/rdf+xml'], + ['rdf', 'application/rdf+xml'], + ['rif', 'application/reginfo+xml'], + ['rnc', 'application/relax-ng-compact-syntax'], + ['rl', 'application/resource-lists+xml'], + ['rld', 'application/resource-lists-diff+xml'], + ['rs', 'application/rls-services+xml'], + ['rapd', 'application/route-apd+xml'], + ['sls', 'application/route-s-tsid+xml'], + ['rusd', 'application/route-usd+xml'], + ['gbr', 'application/rpki-ghostbusters'], + ['mft', 'application/rpki-manifest'], + ['roa', 'application/rpki-roa'], + ['rsd', 'application/rsd+xml'], + ['rss', 'application/rss+xml'], + ['rtf', 'application/rtf'], + ['sbml', 'application/sbml+xml'], + ['scq', 'application/scvp-cv-request'], + ['scs', 'application/scvp-cv-response'], + ['spq', 'application/scvp-vp-request'], + ['spp', 'application/scvp-vp-response'], + ['sdp', 'application/sdp'], + ['senmlx', 'application/senml+xml'], + ['sensmlx', 'application/sensml+xml'], + ['setpay', 'application/set-payment-initiation'], + ['setreg', 'application/set-registration-initiation'], + ['shf', 'application/shf+xml'], + ['sieve', 'application/sieve'], + ['siv', 'application/sieve'], + ['smi', 'application/smil+xml'], + ['smil', 'application/smil+xml'], + ['rq', 'application/sparql-query'], + ['srx', 'application/sparql-results+xml'], + ['gram', 'application/srgs'], + ['grxml', 'application/srgs+xml'], + ['sru', 'application/sru+xml'], + ['ssdl', 'application/ssdl+xml'], + ['ssml', 'application/ssml+xml'], + ['swidtag', 'application/swid+xml'], + ['tei', 'application/tei+xml'], + ['teicorpus', 'application/tei+xml'], + ['tfi', 'application/thraud+xml'], + ['tsd', 'application/timestamped-data'], + ['toml', 'application/toml'], + ['trig', 'application/trig'], + ['ttml', 'application/ttml+xml'], + ['ubj', 'application/ubjson'], + ['rsheet', 'application/urc-ressheet+xml'], + ['td', 'application/urc-targetdesc+xml'], + ['vxml', 'application/voicexml+xml'], + ['wasm', 'application/wasm'], + ['wgt', 'application/widget'], + ['hlp', 'application/winhlp'], + ['wsdl', 'application/wsdl+xml'], + ['wspolicy', 'application/wspolicy+xml'], + ['xaml', 'application/xaml+xml'], + ['xav', 'application/xcap-att+xml'], + ['xca', 'application/xcap-caps+xml'], + ['xdf', 'application/xcap-diff+xml'], + ['xel', 'application/xcap-el+xml'], + ['xns', 'application/xcap-ns+xml'], + ['xenc', 'application/xenc+xml'], + ['xht', 'application/xhtml+xml'], + ['xhtml', 'application/xhtml+xml'], + ['xlf', 'application/xliff+xml'], + ['rng', 'application/xml'], + ['xml', 'application/xml'], + ['xsd', 'application/xml'], + ['xsl', 'application/xml'], + ['dtd', 'application/xml-dtd'], + ['xop', 'application/xop+xml'], + ['xpl', 'application/xproc+xml'], + ['*xsl', 'application/xslt+xml'], + ['xslt', 'application/xslt+xml'], + ['xspf', 'application/xspf+xml'], + ['mxml', 'application/xv+xml'], + ['xhvml', 'application/xv+xml'], + ['xvm', 'application/xv+xml'], + ['xvml', 'application/xv+xml'], + ['yang', 'application/yang'], + ['yin', 'application/yin+xml'], + ['zip', 'application/zip'], + ['*3gpp', 'audio/3gpp'], + ['adp', 'audio/adpcm'], + ['amr', 'audio/amr'], + ['au', 'audio/basic'], + ['snd', 'audio/basic'], + ['kar', 'audio/midi'], + ['mid', 'audio/midi'], + ['midi', 'audio/midi'], + ['rmi', 'audio/midi'], + ['mxmf', 'audio/mobile-xmf'], + ['*mp3', 'audio/mp3'], + ['m4a', 'audio/mp4'], + ['mp4a', 'audio/mp4'], + ['m2a', 'audio/mpeg'], + ['m3a', 'audio/mpeg'], + ['mp2', 'audio/mpeg'], + ['mp2a', 'audio/mpeg'], + ['mp3', 'audio/mpeg'], + ['mpga', 'audio/mpeg'], + ['oga', 'audio/ogg'], + ['ogg', 'audio/ogg'], + ['opus', 'audio/ogg'], + ['spx', 'audio/ogg'], + ['s3m', 'audio/s3m'], + ['sil', 'audio/silk'], + ['wav', 'audio/wav'], + ['*wav', 'audio/wave'], + ['weba', 'audio/webm'], + ['xm', 'audio/xm'], + ['ttc', 'font/collection'], + ['otf', 'font/otf'], + ['ttf', 'font/ttf'], + ['woff', 'font/woff'], + ['woff2', 'font/woff2'], + ['exr', 'image/aces'], + ['apng', 'image/apng'], + ['avif', 'image/avif'], + ['bmp', 'image/bmp'], + ['cgm', 'image/cgm'], + ['drle', 'image/dicom-rle'], + ['emf', 'image/emf'], + ['fits', 'image/fits'], + ['g3', 'image/g3fax'], + ['gif', 'image/gif'], + ['heic', 'image/heic'], + ['heics', 'image/heic-sequence'], + ['heif', 'image/heif'], + ['heifs', 'image/heif-sequence'], + ['hej2', 'image/hej2k'], + ['hsj2', 'image/hsj2'], + ['ief', 'image/ief'], + ['jls', 'image/jls'], + ['jp2', 'image/jp2'], + ['jpg2', 'image/jp2'], + ['jpe', 'image/jpeg'], + ['jpeg', 'image/jpeg'], + ['jpg', 'image/jpeg'], + ['jph', 'image/jph'], + ['jhc', 'image/jphc'], + ['jpm', 'image/jpm'], + ['jpf', 'image/jpx'], + ['jpx', 'image/jpx'], + ['jxr', 'image/jxr'], + ['jxra', 'image/jxra'], + ['jxrs', 'image/jxrs'], + ['jxs', 'image/jxs'], + ['jxsc', 'image/jxsc'], + ['jxsi', 'image/jxsi'], + ['jxss', 'image/jxss'], + ['ktx', 'image/ktx'], + ['ktx2', 'image/ktx2'], + ['png', 'image/png'], + ['sgi', 'image/sgi'], + ['svg', 'image/svg+xml'], + ['svgz', 'image/svg+xml'], + ['t38', 'image/t38'], + ['tif', 'image/tiff'], + ['tiff', 'image/tiff'], + ['tfx', 'image/tiff-fx'], + ['webp', 'image/webp'], + ['wmf', 'image/wmf'], + ['disposition-notification', 'message/disposition-notification'], + ['u8msg', 'message/global'], + ['u8dsn', 'message/global-delivery-status'], + ['u8mdn', 'message/global-disposition-notification'], + ['u8hdr', 'message/global-headers'], + ['eml', 'message/rfc822'], + ['mime', 'message/rfc822'], + ['3mf', 'model/3mf'], + ['gltf', 'model/gltf+json'], + ['glb', 'model/gltf-binary'], + ['iges', 'model/iges'], + ['igs', 'model/iges'], + ['mesh', 'model/mesh'], + ['msh', 'model/mesh'], + ['silo', 'model/mesh'], + ['mtl', 'model/mtl'], + ['obj', 'model/obj'], + ['stpx', 'model/step+xml'], + ['stpz', 'model/step+zip'], + ['stpxz', 'model/step-xml+zip'], + ['stl', 'model/stl'], + ['vrml', 'model/vrml'], + ['wrl', 'model/vrml'], + ['*x3db', 'model/x3d+binary'], + ['x3dbz', 'model/x3d+binary'], + ['x3db', 'model/x3d+fastinfoset'], + ['*x3dv', 'model/x3d+vrml'], + ['x3dvz', 'model/x3d+vrml'], + ['x3d', 'model/x3d+xml'], + ['x3dz', 'model/x3d+xml'], + ['x3dv', 'model/x3d-vrml'], + ['appcache', 'text/cache-manifest'], + ['manifest', 'text/cache-manifest'], + ['ics', 'text/calendar'], + ['ifb', 'text/calendar'], + ['coffee', 'text/coffeescript'], + ['litcoffee', 'text/coffeescript'], + ['css', 'text/css'], + ['csv', 'text/csv'], + ['htm', 'text/html'], + ['html', 'text/html'], + ['shtml', 'text/html'], + ['jade', 'text/jade'], + ['jsx', 'text/jsx'], + ['less', 'text/less'], + ['markdown', 'text/markdown'], + ['md', 'text/markdown'], + ['mml', 'text/mathml'], + ['mdx', 'text/mdx'], + ['n3', 'text/n3'], + ['conf', 'text/plain'], + ['def', 'text/plain'], + ['in', 'text/plain'], + ['ini', 'text/plain'], + ['list', 'text/plain'], + ['log', 'text/plain'], + ['text', 'text/plain'], + ['txt', 'text/plain'], + ['rtx', 'text/richtext'], + ['*rtf', 'text/rtf'], + ['sgm', 'text/sgml'], + ['sgml', 'text/sgml'], + ['shex', 'text/shex'], + ['slim', 'text/slim'], + ['slm', 'text/slim'], + ['spdx', 'text/spdx'], + ['styl', 'text/stylus'], + ['stylus', 'text/stylus'], + ['tsv', 'text/tab-separated-values'], + ['man', 'text/troff'], + ['me', 'text/troff'], + ['ms', 'text/troff'], + ['roff', 'text/troff'], + ['t', 'text/troff'], + ['tr', 'text/troff'], + ['ttl', 'text/turtle'], + ['uri', 'text/uri-list'], + ['uris', 'text/uri-list'], + ['urls', 'text/uri-list'], + ['vcard', 'text/vcard'], + ['vtt', 'text/vtt'], + ['*xml', 'text/xml'], + ['yaml', 'text/yaml'], + ['yml', 'text/yaml'], + ['3gp', 'video/3gpp'], + ['3gpp', 'video/3gpp'], + ['3g2', 'video/3gpp2'], + ['h261', 'video/h261'], + ['h263', 'video/h263'], + ['h264', 'video/h264'], + ['m4s', 'video/iso.segment'], + ['jpgv', 'video/jpeg'], + ['jpm', 'video/jpm'], + ['jpgm', 'video/jpm'], + ['mj2', 'video/mj2'], + ['mjp2', 'video/mj2'], + ['ts', 'video/mp2t'], + ['mp4', 'video/mp4'], + ['mp4v', 'video/mp4'], + ['mpg4', 'video/mp4'], + ['m1v', 'video/mpeg'], + ['m2v', 'video/mpeg'], + ['mpe', 'video/mpeg'], + ['mpeg', 'video/mpeg'], + ['mpg', 'video/mpeg'], + ['ogv', 'video/ogg'], + ['mov', 'video/quicktime'], + ['qt', 'video/quicktime'], + ['webm', 'video/webm'] +]); diff --git a/tests/config/utils.ts b/tests/config/utils.ts index 3743e97d80..3ab51c9290 100644 --- a/tests/config/utils.ts +++ b/tests/config/utils.ts @@ -15,7 +15,7 @@ */ import type { Frame, Page } from 'playwright-core'; -import { ZipFile } from '../../packages/playwright-core/lib/utils/zipFile'; +import { ZipFile } from '../../packages/playwright-core/lib/server/utils/zipFile'; import type { TraceModelBackend } from '../../packages/trace-viewer/src/sw/traceModel'; import type { StackFrame } from '../../packages/protocol/src/channels'; import { parseClientSideCallMetadata } from '../../packages/playwright-core/lib/utils/isomorphic/traceUtils';