Merge branch 'microsoft:main' into cnm_dev

This commit is contained in:
Neel A Deshmukh 2025-02-13 18:01:29 -08:00 committed by GitHub
commit 075f07dd59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
136 changed files with 1219 additions and 607 deletions

View file

@ -39,7 +39,7 @@
}, },
{ {
"name": "webkit", "name": "webkit",
"revision": "2132", "revision": "2134",
"installByDefault": true, "installByDefault": true,
"revisionOverrides": { "revisionOverrides": {
"debian11-x64": "2105", "debian11-x64": "2105",

View file

@ -167,6 +167,7 @@
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.1.90" "node": ">=0.1.90"
} }

View file

@ -8,10 +8,13 @@
** **
[inprocess.ts] [inprocess.ts]
common/
utils/ utils/
server/utils
[outofprocess.ts] [outofprocess.ts]
client/ client/
common/
protocol/ protocol/
utils/ utils/
utils/isomorphic utils/isomorphic

View file

@ -21,7 +21,7 @@ import { helper } from './server/helper';
import { serverSideCallMetadata } from './server/instrumentation'; import { serverSideCallMetadata } from './server/instrumentation';
import { createPlaywright } from './server/playwright'; import { createPlaywright } from './server/playwright';
import { createGuid } from './server/utils/crypto'; import { createGuid } from './server/utils/crypto';
import { rewriteErrorMessage } from './utils/stackTrace'; import { rewriteErrorMessage } from './utils/isomorphic/stackTrace';
import { ws } from './utilsBundle'; import { ws } from './utilsBundle';
import type { BrowserServer, BrowserServerLauncher } from './client/browserType'; import type { BrowserServer, BrowserServerLauncher } from './client/browserType';

View file

@ -19,7 +19,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as playwright from '../..'; import * as playwright from '../..';
import { PipeTransport } from '../utils/pipeTransport'; import { PipeTransport } from '../server/utils/pipeTransport';
import { PlaywrightServer } from '../remote/playwrightServer'; import { PlaywrightServer } from '../remote/playwrightServer';
import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from '../server'; import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from '../server';
import { gracefullyProcessExitDoNotHang } from '../server/utils/processLauncher'; import { gracefullyProcessExitDoNotHang } from '../server/utils/processLauncher';

View file

@ -1,5 +1,4 @@
[*] [*]
../common/ ../common/
../protocol/ ../protocol/
../utils/** ../utils/isomorphic
../utilsBundle.ts

View file

@ -14,23 +14,23 @@
* limitations under the License. * limitations under the License.
*/ */
import { EventEmitter } from 'events'; import { EventEmitter } from './eventEmitter';
import { BrowserContext, prepareBrowserContextParams } from './browserContext'; import { BrowserContext, prepareBrowserContextParams } from './browserContext';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { TargetClosedError, isTargetClosedError } from './errors'; import { TargetClosedError, isTargetClosedError } from './errors';
import { Events } from './events'; import { Events } from './events';
import { Waiter } from './waiter'; import { Waiter } from './waiter';
import { TimeoutSettings } from '../common/timeoutSettings'; import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
import { isRegExp, isString } from '../utils/isomorphic/rtti'; import { isRegExp, isString } from '../utils/isomorphic/rtti';
import { monotonicTime } from '../utils/isomorphic/time'; import { monotonicTime } from '../utils/isomorphic/time';
import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner'; import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner';
import { connectOverWebSocket } from './webSocket';
import type { Page } from './page'; import type { Page } from './page';
import type * as types from './types'; import type * as types from './types';
import type * as api from '../../types/types'; import type * as api from '../../types/types';
import type { AndroidServerLauncherImpl } from '../androidServerImpl'; import type { AndroidServerLauncherImpl } from '../androidServerImpl';
import type { Platform } from '../utils/platform'; import type { Platform } from '../common/platform';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
type Direction = 'down' | 'up' | 'left' | 'right'; type Direction = 'down' | 'up' | 'left' | 'right';
@ -69,9 +69,8 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
return await this._wrapApiCall(async () => { return await this._wrapApiCall(async () => {
const deadline = options.timeout ? monotonicTime() + options.timeout : 0; const deadline = options.timeout ? monotonicTime() + options.timeout : 0;
const headers = { 'x-playwright-browser': 'android', ...options.headers }; const headers = { 'x-playwright-browser': 'android', ...options.headers };
const localUtils = this._connection.localUtils();
const connectParams: channels.LocalUtilsConnectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout }; const connectParams: channels.LocalUtilsConnectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout };
const connection = await localUtils.connect(connectParams); const connection = await connectOverWebSocket(this._connection, connectParams);
let device: AndroidDevice; let device: AndroidDevice;
connection.on('close', () => { connection.on('close', () => {

View file

@ -16,7 +16,7 @@
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { Stream } from './stream'; import { Stream } from './stream';
import { mkdirIfNeeded } from '../utils/fileUtils'; import { mkdirIfNeeded } from './fileUtils';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import type { Readable } from 'stream'; import type { Readable } from 'stream';

View file

@ -20,7 +20,7 @@ import { CDPSession } from './cdpSession';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { isTargetClosedError } from './errors'; import { isTargetClosedError } from './errors';
import { Events } from './events'; import { Events } from './events';
import { mkdirIfNeeded } from '../utils/fileUtils'; import { mkdirIfNeeded } from './fileUtils';
import type { BrowserType } from './browserType'; import type { BrowserType } from './browserType';
import type { Page } from './page'; import type { Page } from './page';

View file

@ -34,19 +34,19 @@ import { Tracing } from './tracing';
import { Waiter } from './waiter'; import { Waiter } from './waiter';
import { WebError } from './webError'; import { WebError } from './webError';
import { Worker } from './worker'; import { Worker } from './worker';
import { TimeoutSettings } from '../common/timeoutSettings'; import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
import { mkdirIfNeeded } from '../utils/fileUtils'; import { mkdirIfNeeded } from './fileUtils';
import { headersObjectToArray } from '../utils/isomorphic/headers'; import { headersObjectToArray } from '../utils/isomorphic/headers';
import { urlMatchesEqual } from '../utils/isomorphic/urlMatch'; import { urlMatchesEqual } from '../utils/isomorphic/urlMatch';
import { isRegExp, isString } from '../utils/isomorphic/rtti'; import { isRegExp, isString } from '../utils/isomorphic/rtti';
import { rewriteErrorMessage } from '../utils/stackTrace'; import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
import type { BrowserType } from './browserType'; import type { BrowserType } from './browserType';
import type { BrowserContextOptions, Headers, LaunchOptions, StorageState, WaitForEventOptions } from './types'; import type { BrowserContextOptions, Headers, LaunchOptions, StorageState, WaitForEventOptions } from './types';
import type * as structs from '../../types/structs'; import type * as structs from '../../types/structs';
import type * as api from '../../types/types'; import type * as api from '../../types/types';
import type { URLMatch } from '../utils/isomorphic/urlMatch'; import type { URLMatch } from '../utils/isomorphic/urlMatch';
import type { Platform } from '../utils/platform'; import type { Platform } from '../common/platform';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext { export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
@ -338,7 +338,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
} }
async route(url: URLMatch, handler: network.RouteHandlerCallback, options: { times?: number } = {}): Promise<void> { async route(url: URLMatch, handler: network.RouteHandlerCallback, options: { times?: number } = {}): Promise<void> {
this._routes.unshift(new network.RouteHandler(this._options.baseURL, url, handler, options.times)); this._routes.unshift(new network.RouteHandler(this._platform, this._options.baseURL, url, handler, options.times));
await this._updateInterceptionPatterns(); await this._updateInterceptionPatterns();
} }
@ -361,11 +361,14 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
} }
async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full' } = {}): Promise<void> { async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full' } = {}): Promise<void> {
const localUtils = this._connection.localUtils();
if (!localUtils)
throw new Error('Route from har is not supported in thin clients');
if (options.update) { if (options.update) {
await this._recordIntoHAR(har, null, options); await this._recordIntoHAR(har, null, options);
return; return;
} }
const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url }); const harRouter = await HarRouter.create(localUtils, har, options.notFound || 'abort', { urlMatch: options.url });
this._harRouters.push(harRouter); this._harRouters.push(harRouter);
await harRouter.addContextRoute(this); await harRouter.addContextRoute(this);
} }
@ -484,8 +487,11 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
const isCompressed = harParams.content === 'attach' || harParams.path.endsWith('.zip'); const isCompressed = harParams.content === 'attach' || harParams.path.endsWith('.zip');
const needCompressed = harParams.path.endsWith('.zip'); const needCompressed = harParams.path.endsWith('.zip');
if (isCompressed && !needCompressed) { if (isCompressed && !needCompressed) {
const localUtils = this._connection.localUtils();
if (!localUtils)
throw new Error('Uncompressed har is not supported in thin clients');
await artifact.saveAs(harParams.path + '.tmp'); await artifact.saveAs(harParams.path + '.tmp');
await this._connection.localUtils().harUnzip({ zipFile: harParams.path + '.tmp', harFile: harParams.path }); await localUtils.harUnzip({ zipFile: harParams.path + '.tmp', harFile: harParams.path });
} else { } else {
await artifact.saveAs(harParams.path); await artifact.saveAs(harParams.path);
} }

View file

@ -14,17 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
import path from 'path';
import { Browser } from './browser'; import { Browser } from './browser';
import { BrowserContext, prepareBrowserContextParams } from './browserContext'; import { BrowserContext, prepareBrowserContextParams } from './browserContext';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { envObjectToArray } from './clientHelper'; import { envObjectToArray } from './clientHelper';
import { Events } from './events'; import { Events } from './events';
import { assert } from '../utils/debug'; import { assert } from '../utils/isomorphic/debug';
import { headersObjectToArray } from '../utils/isomorphic/headers'; import { headersObjectToArray } from '../utils/isomorphic/headers';
import { monotonicTime } from '../utils/isomorphic/time'; import { monotonicTime } from '../utils/isomorphic/time';
import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner'; import { raceAgainstDeadline } from '../utils/isomorphic/timeoutRunner';
import { connectOverWebSocket } from './webSocket';
import type { Playwright } from './playwright'; import type { Playwright } from './playwright';
import type { ConnectOptions, LaunchOptions, LaunchPersistentContextOptions, LaunchServerOptions, Logger } from './types'; import type { ConnectOptions, LaunchOptions, LaunchPersistentContextOptions, LaunchServerOptions, Logger } from './types';
@ -100,7 +99,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs), ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? envObjectToArray(options.env) : undefined, env: options.env ? envObjectToArray(options.env) : undefined,
channel: options.channel, channel: options.channel,
userDataDir: (path.isAbsolute(userDataDir) || !userDataDir) ? userDataDir : path.resolve(userDataDir), userDataDir: (this._platform.path().isAbsolute(userDataDir) || !userDataDir) ? userDataDir : this._platform.path().resolve(userDataDir),
}; };
return await this._wrapApiCall(async () => { return await this._wrapApiCall(async () => {
const result = await this._channel.launchPersistentContext(persistentParams); const result = await this._channel.launchPersistentContext(persistentParams);
@ -124,7 +123,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
return await this._wrapApiCall(async () => { return await this._wrapApiCall(async () => {
const deadline = params.timeout ? monotonicTime() + params.timeout : 0; const deadline = params.timeout ? monotonicTime() + params.timeout : 0;
const headers = { 'x-playwright-browser': this.name(), ...params.headers }; const headers = { 'x-playwright-browser': this.name(), ...params.headers };
const localUtils = this._connection.localUtils();
const connectParams: channels.LocalUtilsConnectParams = { const connectParams: channels.LocalUtilsConnectParams = {
wsEndpoint: params.wsEndpoint, wsEndpoint: params.wsEndpoint,
headers, headers,
@ -134,7 +132,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
}; };
if ((params as any).__testHookRedirectPortForwarding) if ((params as any).__testHookRedirectPortForwarding)
connectParams.socksProxyRedirectPortForTest = (params as any).__testHookRedirectPortForwarding; connectParams.socksProxyRedirectPortForTest = (params as any).__testHookRedirectPortForwarding;
const connection = await localUtils.connect(connectParams); const connection = await connectOverWebSocket(this._connection, connectParams);
let browser: Browser; let browser: Browser;
connection.on('close', () => { connection.on('close', () => {
// Emulate all pages, contexts and the browser closing upon disconnect. // Emulate all pages, contexts and the browser closing upon disconnect.

View file

@ -16,16 +16,14 @@
import { EventEmitter } from './eventEmitter'; import { EventEmitter } from './eventEmitter';
import { ValidationError, maybeFindValidator } from '../protocol/validator'; import { ValidationError, maybeFindValidator } from '../protocol/validator';
import { isUnderTest } from '../utils/debug'; import { isUnderTest } from '../utils/isomorphic/debug';
import { debugLogger } from '../utils/debugLogger'; import { captureLibraryStackTrace, stringifyStackFrames } from '../utils/isomorphic/stackTrace';
import { captureLibraryStackTrace, stringifyStackFrames } from '../utils/stackTrace';
import { zones } from '../utils/zones';
import type { ClientInstrumentation } from './clientInstrumentation'; import type { ClientInstrumentation } from './clientInstrumentation';
import type { Connection } from './connection'; import type { Connection } from './connection';
import type { Logger } from './types'; import type { Logger } from './types';
import type { ValidatorContext } from '../protocol/validator'; import type { ValidatorContext } from '../protocol/validator';
import type { Platform } from '../utils/platform'; import type { Platform } from '../common/platform';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
type Listener = (...args: any[]) => void; type Listener = (...args: any[]) => void;
@ -158,7 +156,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
apiZone.params = params; apiZone.params = params;
apiZone.reported = true; apiZone.reported = true;
this._instrumentation.onApiCallBegin(apiZone); this._instrumentation.onApiCallBegin(apiZone);
logApiCall(this._logger, `=> ${apiZone.apiName} started`); logApiCall(this._platform, this._logger, `=> ${apiZone.apiName} started`);
return await this._connection.sendMessageToServer(this, prop, validatedParams, apiZone.apiName, apiZone.frames, apiZone.stepId); return await this._connection.sendMessageToServer(this, prop, validatedParams, apiZone.apiName, apiZone.frames, apiZone.stepId);
} }
// Since this api call is either internal, or has already been reported/traced once, // Since this api call is either internal, or has already been reported/traced once,
@ -177,19 +175,19 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
async _wrapApiCall<R>(func: (apiZone: ApiZone) => Promise<R>, isInternal?: boolean): Promise<R> { async _wrapApiCall<R>(func: (apiZone: ApiZone) => Promise<R>, isInternal?: boolean): Promise<R> {
const logger = this._logger; const logger = this._logger;
const existingApiZone = zones.zoneData<ApiZone>('apiZone'); const existingApiZone = this._platform.zones.current().data<ApiZone>();
if (existingApiZone) if (existingApiZone)
return await func(existingApiZone); return await func(existingApiZone);
if (isInternal === undefined) if (isInternal === undefined)
isInternal = this._isInternalType; isInternal = this._isInternalType;
const stackTrace = captureLibraryStackTrace(); const stackTrace = captureLibraryStackTrace(this._platform.pathSeparator);
const apiZone: ApiZone = { apiName: stackTrace.apiName, frames: stackTrace.frames, isInternal, reported: false, userData: undefined, stepId: undefined }; const apiZone: ApiZone = { apiName: stackTrace.apiName, frames: stackTrace.frames, isInternal, reported: false, userData: undefined, stepId: undefined };
try { try {
const result = await zones.run('apiZone', apiZone, async () => await func(apiZone)); const result = await this._platform.zones.current().push(apiZone).run(async () => await func(apiZone));
if (!isInternal) { if (!isInternal) {
logApiCall(logger, `<= ${apiZone.apiName} succeeded`); logApiCall(this._platform, logger, `<= ${apiZone.apiName} succeeded`);
this._instrumentation.onApiCallEnd(apiZone); this._instrumentation.onApiCallEnd(apiZone);
} }
return result; return result;
@ -204,7 +202,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
e.stack = ''; e.stack = '';
if (!isInternal) { if (!isInternal) {
apiZone.error = e; apiZone.error = e;
logApiCall(logger, `<= ${apiZone.apiName} failed`); logApiCall(this._platform, logger, `<= ${apiZone.apiName} failed`);
this._instrumentation.onApiCallEnd(apiZone); this._instrumentation.onApiCallEnd(apiZone);
} }
throw e; throw e;
@ -227,10 +225,10 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
} }
} }
function logApiCall(logger: Logger | undefined, message: string) { function logApiCall(platform: Platform, logger: Logger | undefined, message: string) {
if (logger && logger.isEnabled('api', 'info')) if (logger && logger.isEnabled('api', 'info'))
logger.log('api', 'info', message, [], { color: 'cyan' }); logger.log('api', 'info', message, [], { color: 'cyan' });
debugLogger.log('api', message); platform.log('api', message);
} }
function tChannelImplToWire(names: '*' | string[], arg: any, path: string, context: ValidatorContext) { function tChannelImplToWire(names: '*' | string[], arg: any, path: string, context: ValidatorContext) {

View file

@ -18,7 +18,7 @@
import { isString } from '../utils/isomorphic/rtti'; import { isString } from '../utils/isomorphic/rtti';
import type * as types from './types'; import type * as types from './types';
import type { Platform } from '../utils/platform'; import type { Platform } from '../common/platform';
export function envObjectToArray(env: types.Env): { name: string, value: string }[] { export function envObjectToArray(env: types.Env): { name: string, value: string }[] {
const result: { name: string, value: string }[] = []; const result: { name: string, value: string }[] = [];

View file

@ -14,8 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { EventEmitter } from 'events';
import { EventEmitter } from './eventEmitter';
import { Android, AndroidDevice, AndroidSocket } from './android'; import { Android, AndroidDevice, AndroidSocket } from './android';
import { Artifact } from './artifact'; import { Artifact } from './artifact';
import { Browser } from './browser'; import { Browser } from './browser';
@ -42,14 +42,12 @@ import { Tracing } from './tracing';
import { Worker } from './worker'; import { Worker } from './worker';
import { WritableStream } from './writableStream'; import { WritableStream } from './writableStream';
import { ValidationError, findValidator } from '../protocol/validator'; import { ValidationError, findValidator } from '../protocol/validator';
import { debugLogger } from '../utils/debugLogger'; import { formatCallLog, rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
import { formatCallLog, rewriteErrorMessage } from '../utils/stackTrace';
import { zones } from '../utils/zones';
import type { ClientInstrumentation } from './clientInstrumentation'; import type { ClientInstrumentation } from './clientInstrumentation';
import type { HeadersArray } from './types'; import type { HeadersArray } from './types';
import type { ValidatorContext } from '../protocol/validator'; import type { ValidatorContext } from '../protocol/validator';
import type { Platform } from '../utils/platform'; import type { Platform } from '../common/platform';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
class Root extends ChannelOwner<channels.RootChannel> { class Root extends ChannelOwner<channels.RootChannel> {
@ -110,8 +108,8 @@ export class Connection extends EventEmitter {
return this._rawBuffers; return this._rawBuffers;
} }
localUtils(): LocalUtils { localUtils(): LocalUtils | undefined {
return this._localUtils!; return this._localUtils;
} }
async initializePlaywright(): Promise<Playwright> { async initializePlaywright(): Promise<Playwright> {
@ -139,9 +137,9 @@ export class Connection extends EventEmitter {
const type = object._type; const type = object._type;
const id = ++this._lastId; const id = ++this._lastId;
const message = { id, guid, method, params }; const message = { id, guid, method, params };
if (debugLogger.isEnabled('channel')) { if (this.platform.isLogEnabled('channel')) {
// Do not include metadata in debug logs to avoid noise. // Do not include metadata in debug logs to avoid noise.
debugLogger.log('channel', 'SEND> ' + JSON.stringify(message)); this.platform.log('channel', 'SEND> ' + JSON.stringify(message));
} }
const location = frames[0] ? { file: frames[0].file, line: frames[0].line, column: frames[0].column } : undefined; const location = frames[0] ? { file: frames[0].file, line: frames[0].line, column: frames[0].column } : undefined;
const metadata: channels.Metadata = { apiName, location, internal: !apiName, stepId }; const metadata: channels.Metadata = { apiName, location, internal: !apiName, stepId };
@ -149,7 +147,7 @@ export class Connection extends EventEmitter {
this._localUtils?.addStackToTracingNoReply({ callData: { stack: frames, id } }).catch(() => {}); this._localUtils?.addStackToTracingNoReply({ callData: { stack: frames, id } }).catch(() => {});
// We need to exit zones before calling into the server, otherwise // We need to exit zones before calling into the server, otherwise
// when we receive events from the server, we would be in an API zone. // when we receive events from the server, we would be in an API zone.
zones.empty().run(() => this.onmessage({ ...message, metadata })); this.platform.zones.empty.run(() => this.onmessage({ ...message, metadata }));
return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject, apiName, type, method })); return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject, apiName, type, method }));
} }
@ -159,15 +157,15 @@ export class Connection extends EventEmitter {
const { id, guid, method, params, result, error, log } = message as any; const { id, guid, method, params, result, error, log } = message as any;
if (id) { if (id) {
if (debugLogger.isEnabled('channel')) if (this.platform.isLogEnabled('channel'))
debugLogger.log('channel', '<RECV ' + JSON.stringify(message)); this.platform.log('channel', '<RECV ' + JSON.stringify(message));
const callback = this._callbacks.get(id); const callback = this._callbacks.get(id);
if (!callback) if (!callback)
throw new Error(`Cannot find command to respond: ${id}`); throw new Error(`Cannot find command to respond: ${id}`);
this._callbacks.delete(id); this._callbacks.delete(id);
if (error && !result) { if (error && !result) {
const parsedError = parseError(error); const parsedError = parseError(error);
rewriteErrorMessage(parsedError, parsedError.message + formatCallLog(log)); rewriteErrorMessage(parsedError, parsedError.message + formatCallLog(this.platform, log));
callback.reject(parsedError); callback.reject(parsedError);
} else { } else {
const validator = findValidator(callback.type, callback.method, 'Result'); const validator = findValidator(callback.type, callback.method, 'Result');
@ -176,8 +174,8 @@ export class Connection extends EventEmitter {
return; return;
} }
if (debugLogger.isEnabled('channel')) if (this.platform.isLogEnabled('channel'))
debugLogger.log('channel', '<EVENT ' + JSON.stringify(message)); this.platform.log('channel', '<EVENT ' + JSON.stringify(message));
if (method === '__create__') { if (method === '__create__') {
this._createRemoteObject(guid, params.type, params.guid, params.initializer); this._createRemoteObject(guid, params.type, params.guid, params.initializer);
return; return;

View file

@ -18,7 +18,7 @@ import { JSHandle } from './jsHandle';
import { Page } from './page'; import { Page } from './page';
import type * as api from '../../types/types'; import type * as api from '../../types/types';
import type { Platform } from '../utils/platform'; import type { Platform } from '../common/platform';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
type ConsoleMessageLocation = channels.BrowserContextConsoleEvent['location']; type ConsoleMessageLocation = channels.BrowserContextConsoleEvent['location'];

View file

@ -22,7 +22,7 @@ import { TargetClosedError, isTargetClosedError } from './errors';
import { Events } from './events'; import { Events } from './events';
import { JSHandle, parseResult, serializeArgument } from './jsHandle'; import { JSHandle, parseResult, serializeArgument } from './jsHandle';
import { Waiter } from './waiter'; import { Waiter } from './waiter';
import { TimeoutSettings } from '../common/timeoutSettings'; import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
import type { Page } from './page'; import type { Page } from './page';
import type { BrowserContextOptions, Env, Headers, WaitForEventOptions } from './types'; import type { BrowserContextOptions, Env, Headers, WaitForEventOptions } from './types';

View file

@ -19,11 +19,11 @@ import { promisify } from 'util';
import { Frame } from './frame'; import { Frame } from './frame';
import { JSHandle, parseResult, serializeArgument } from './jsHandle'; import { JSHandle, parseResult, serializeArgument } from './jsHandle';
import { assert } from '../utils/debug'; import { assert } from '../utils/isomorphic/debug';
import { fileUploadSizeLimit, mkdirIfNeeded } from '../utils/fileUtils'; import { fileUploadSizeLimit, mkdirIfNeeded } from './fileUtils';
import { isString } from '../utils/isomorphic/rtti'; import { isString } from '../utils/isomorphic/rtti';
import { mime } from '../utilsBundle';
import { WritableStream } from './writableStream'; import { WritableStream } from './writableStream';
import { getMimeTypeForPath } from '../utils/isomorphic/mimeType';
import type { BrowserContext } from './browserContext'; import type { BrowserContext } from './browserContext';
import type { ChannelOwner } from './channelOwner'; import type { ChannelOwner } from './channelOwner';
@ -31,7 +31,7 @@ import type { Locator } from './locator';
import type { FilePayload, Rect, SelectOption, SelectOptionOptions } from './types'; import type { FilePayload, Rect, SelectOption, SelectOptionOptions } from './types';
import type * as structs from '../../types/structs'; import type * as structs from '../../types/structs';
import type * as api from '../../types/types'; import type * as api from '../../types/types';
import type { Platform } from '../utils/platform'; import type { Platform } from '../common/platform';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
const pipelineAsync = promisify(pipeline); const pipelineAsync = promisify(pipeline);
@ -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 { export function determineScreenshotType(options: { path?: string, type?: 'png' | 'jpeg' }): 'png' | 'jpeg' | undefined {
if (options.path) { if (options.path) {
const mimeType = mime.getType(options.path); const mimeType = getMimeTypeForPath(options.path);
if (mimeType === 'image/png') if (mimeType === 'image/png')
return 'png'; return 'png';
else if (mimeType === 'image/jpeg') else if (mimeType === 'image/jpeg')

View file

@ -22,9 +22,7 @@
* USE OR OTHER DEALINGS IN THE SOFTWARE. * USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { EventEmitter as OriginalEventEmitter } from 'events'; import { isUnderTest } from '../utils/isomorphic/debug';
import { isUnderTest } from '../utils/debug';
import type { EventEmitter as EventEmitterType } from 'events'; import type { EventEmitter as EventEmitterType } from 'events';
@ -32,6 +30,12 @@ type EventType = string | symbol;
type Listener = (...args: any[]) => any; type Listener = (...args: any[]) => any;
type EventMap = Record<EventType, Listener | Listener[]>; type EventMap = Record<EventType, Listener | Listener[]>;
let defaultMaxListenersProvider = () => 10;
export function setDefaultMaxListenersProvider(provider: () => number) {
defaultMaxListenersProvider = provider;
}
export class EventEmitter implements EventEmitterType { export class EventEmitter implements EventEmitterType {
private _events: EventMap | undefined = undefined; private _events: EventMap | undefined = undefined;
@ -58,7 +62,7 @@ export class EventEmitter implements EventEmitterType {
} }
getMaxListeners(): number { getMaxListeners(): number {
return this._maxListeners === undefined ? OriginalEventEmitter.defaultMaxListeners : this._maxListeners; return this._maxListeners === undefined ? defaultMaxListenersProvider() : this._maxListeners;
} }
emit(type: EventType, ...args: any[]): boolean { emit(type: EventType, ...args: any[]): boolean {

View file

@ -19,8 +19,8 @@ import { ChannelOwner } from './channelOwner';
import { TargetClosedError, isTargetClosedError } from './errors'; import { TargetClosedError, isTargetClosedError } from './errors';
import { RawHeaders } from './network'; import { RawHeaders } from './network';
import { Tracing } from './tracing'; import { Tracing } from './tracing';
import { assert } from '../utils/debug'; import { assert } from '../utils/isomorphic/debug';
import { mkdirIfNeeded } from '../utils/fileUtils'; import { mkdirIfNeeded } from './fileUtils';
import { headersObjectToArray } from '../utils/isomorphic/headers'; import { headersObjectToArray } from '../utils/isomorphic/headers';
import { isString } from '../utils/isomorphic/rtti'; import { isString } from '../utils/isomorphic/rtti';
@ -29,7 +29,7 @@ import type { ClientCertificate, FilePayload, Headers, SetStorageState, StorageS
import type { Serializable } from '../../types/structs'; import type { Serializable } from '../../types/structs';
import type * as api from '../../types/types'; import type * as api from '../../types/types';
import type { HeadersArray, NameValue } from '../common/types'; import type { HeadersArray, NameValue } from '../common/types';
import type { Platform } from '../utils/platform'; import type { Platform } from '../common/platform';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import type * as fs from 'fs'; import type * as fs from 'fs';

View file

@ -14,17 +14,12 @@
* limitations under the License. * 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 const fileUploadSizeLimit = 50 * 1024 * 1024;
export async function mkdirIfNeeded(platform: Platform, filePath: string) { export async function mkdirIfNeeded(platform: Platform, filePath: string) {
// This will harmlessly throw on windows if the dirname is the root directory. // This will harmlessly throw on windows if the dirname is the root directory.
await platform.fs().promises.mkdir(platform.path().dirname(filePath), { recursive: true }).catch(() => {}); await platform.fs().promises.mkdir(platform.path().dirname(filePath), { recursive: true }).catch(() => {});
} }
export async function removeFolders(platform: Platform, dirs: string[]): Promise<Error[]> {
return await Promise.all(dirs.map((dir: string) =>
platform.fs().promises.rm(dir, { recursive: true, force: true, maxRetries: 10 }).catch(e => e)
));
}

View file

@ -15,8 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { EventEmitter } from 'events'; import { EventEmitter } from './eventEmitter';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { addSourceUrlToScript } from './clientHelper'; import { addSourceUrlToScript } from './clientHelper';
import { ElementHandle, convertInputFiles, convertSelectOptionValues } from './elementHandle'; import { ElementHandle, convertInputFiles, convertSelectOptionValues } from './elementHandle';
@ -26,7 +25,7 @@ import { FrameLocator, Locator, testIdAttributeName } from './locator';
import * as network from './network'; import * as network from './network';
import { kLifecycleEvents } from './types'; import { kLifecycleEvents } from './types';
import { Waiter } from './waiter'; import { Waiter } from './waiter';
import { assert } from '../utils/debug'; import { assert } from '../utils/isomorphic/debug';
import { getByAltTextSelector, getByLabelSelector, getByPlaceholderSelector, getByRoleSelector, getByTestIdSelector, getByTextSelector, getByTitleSelector } from '../utils/isomorphic/locatorUtils'; import { getByAltTextSelector, getByLabelSelector, getByPlaceholderSelector, getByRoleSelector, getByTestIdSelector, getByTextSelector, getByTitleSelector } from '../utils/isomorphic/locatorUtils';
import { urlMatches } from '../utils/isomorphic/urlMatch'; import { urlMatches } from '../utils/isomorphic/urlMatch';

View file

@ -14,8 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
import { debugLogger } from '../utils/debugLogger';
import type { BrowserContext } from './browserContext'; import type { BrowserContext } from './browserContext';
import type { LocalUtils } from './localUtils'; import type { LocalUtils } from './localUtils';
import type { Route } from './network'; import type { Route } from './network';
@ -57,7 +55,7 @@ export class HarRouter {
}); });
if (response.action === 'redirect') { if (response.action === 'redirect') {
debugLogger.log('api', `HAR: ${route.request().url()} redirected to ${response.redirectURL}`); route._platform.log('api', `HAR: ${route.request().url()} redirected to ${response.redirectURL}`);
await route._redirectNavigationRequest(response.redirectURL!); await route._redirectNavigationRequest(response.redirectURL!);
return; return;
} }
@ -79,7 +77,7 @@ export class HarRouter {
} }
if (response.action === 'error') if (response.action === 'error')
debugLogger.log('api', 'HAR: ' + response.message!); route._platform.log('api', 'HAR: ' + response.message!);
// Report the error, but fall through to the default handler. // Report the error, but fall through to the default handler.
if (this._notFoundAction === 'abort') { if (this._notFoundAction === 'abort') {

View file

@ -15,12 +15,8 @@
*/ */
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { Connection } from './connection';
import * as localUtils from '../utils/localUtils';
import type { HeadersArray, Size } from './types'; import type { Size } from './types';
import type { HarBackend } from '../utils/harBackend';
import type { Platform } from '../utils/platform';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
type DeviceDescriptor = { type DeviceDescriptor = {
@ -35,8 +31,6 @@ type Devices = { [name: string]: DeviceDescriptor };
export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> { export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
readonly devices: Devices; readonly devices: Devices;
private _harBackends = new Map<string, HarBackend>();
private _stackSessions = new Map<string, localUtils.StackSession>();
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.LocalUtilsInitializer) { constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.LocalUtilsInitializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
@ -47,132 +41,34 @@ export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
} }
async zip(params: channels.LocalUtilsZipParams): Promise<void> { async zip(params: channels.LocalUtilsZipParams): Promise<void> {
return await localUtils.zip(this._platform, this._stackSessions, params); return await this._channel.zip(params);
} }
async harOpen(params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> { async harOpen(params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> {
return await localUtils.harOpen(this._platform, this._harBackends, params); return await this._channel.harOpen(params);
} }
async harLookup(params: channels.LocalUtilsHarLookupParams): Promise<channels.LocalUtilsHarLookupResult> { async harLookup(params: channels.LocalUtilsHarLookupParams): Promise<channels.LocalUtilsHarLookupResult> {
return await localUtils.harLookup(this._harBackends, params); return await this._channel.harLookup(params);
} }
async harClose(params: channels.LocalUtilsHarCloseParams): Promise<void> { async harClose(params: channels.LocalUtilsHarCloseParams): Promise<void> {
return await localUtils.harClose(this._harBackends, params); return await this._channel.harClose(params);
} }
async harUnzip(params: channels.LocalUtilsHarUnzipParams): Promise<void> { async harUnzip(params: channels.LocalUtilsHarUnzipParams): Promise<void> {
return await localUtils.harUnzip(params); return await this._channel.harUnzip(params);
} }
async tracingStarted(params: channels.LocalUtilsTracingStartedParams): Promise<channels.LocalUtilsTracingStartedResult> { async tracingStarted(params: channels.LocalUtilsTracingStartedParams): Promise<channels.LocalUtilsTracingStartedResult> {
return await localUtils.tracingStarted(this._stackSessions, params); return await this._channel.tracingStarted(params);
} }
async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams): Promise<void> { async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
return await localUtils.traceDiscarded(this._platform, this._stackSessions, params); return await this._channel.traceDiscarded(params);
} }
async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> { async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> {
return await localUtils.addStackToTracingNoReply(this._stackSessions, params); return await this._channel.addStackToTracingNoReply(params);
}
async connect(params: channels.LocalUtilsConnectParams): Promise<Connection> {
const transport = this._platform.ws ? new WebSocketTransport(this._platform) : new JsonPipeTransport(this);
const connectHeaders = await transport.connect(params);
const connection = new Connection(this, this._platform, this._instrumentation, connectHeaders);
connection.markAsRemote();
connection.on('close', () => transport.close());
let closeError: string | undefined;
const onTransportClosed = (reason?: string) => {
connection.close(reason || closeError);
};
transport.onClose(reason => onTransportClosed(reason));
connection.onmessage = message => transport.send(message).catch(() => onTransportClosed());
transport.onMessage(message => {
try {
connection!.dispatch(message);
} catch (e) {
closeError = String(e);
transport.close();
}
});
return connection;
}
}
interface Transport {
connect(params: channels.LocalUtilsConnectParams): Promise<HeadersArray>;
send(message: any): Promise<void>;
onMessage(callback: (message: object) => void): void;
onClose(callback: (reason?: string) => void): void;
close(): Promise<void>;
}
class JsonPipeTransport implements Transport {
private _pipe: channels.JsonPipeChannel | undefined;
private _owner: ChannelOwner<channels.LocalUtilsChannel>;
constructor(owner: ChannelOwner<channels.LocalUtilsChannel>) {
this._owner = owner;
}
async connect(params: channels.LocalUtilsConnectParams) {
const { pipe, headers: connectHeaders } = await this._owner._wrapApiCall(async () => {
return await this._owner._channel.connect(params);
}, /* isInternal */ true);
this._pipe = pipe;
return connectHeaders;
}
async send(message: object) {
this._owner._wrapApiCall(async () => {
await this._pipe!.send({ message });
}, /* isInternal */ true);
}
onMessage(callback: (message: object) => void) {
this._pipe!.on('message', ({ message }) => callback(message));
}
onClose(callback: (reason?: string) => void) {
this._pipe!.on('closed', ({ reason }) => callback(reason));
}
async close() {
await this._owner._wrapApiCall(async () => {
await this._pipe!.close().catch(() => {});
}, /* isInternal */ true);
}
}
class WebSocketTransport implements Transport {
private _platform: Platform;
private _ws: WebSocket | undefined;
constructor(platform: Platform) {
this._platform = platform;
}
async connect(params: channels.LocalUtilsConnectParams) {
this._ws = this._platform.ws!(params.wsEndpoint);
return [];
}
async send(message: object) {
this._ws!.send(JSON.stringify(message));
}
onMessage(callback: (message: object) => void) {
this._ws!.addEventListener('message', event => callback(JSON.parse(event.data)));
}
onClose(callback: (reason?: string) => void) {
this._ws!.addEventListener('close', () => callback());
}
async close() {
this._ws!.close();
} }
} }

View file

@ -14,8 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
import { URLSearchParams } from 'url';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { isTargetClosedError } from './errors'; import { isTargetClosedError } from './errors';
import { Events } from './events'; import { Events } from './events';
@ -23,15 +21,14 @@ import { APIResponse } from './fetch';
import { Frame } from './frame'; import { Frame } from './frame';
import { Waiter } from './waiter'; import { Waiter } from './waiter';
import { Worker } from './worker'; import { Worker } from './worker';
import { assert } from '../utils/debug'; import { assert } from '../utils/isomorphic/debug';
import { headersObjectToArray } from '../utils/isomorphic/headers'; import { headersObjectToArray } from '../utils/isomorphic/headers';
import { urlMatches } from '../utils/isomorphic/urlMatch'; import { urlMatches } from '../utils/isomorphic/urlMatch';
import { LongStandingScope, ManualPromise } from '../utils/isomorphic/manualPromise'; import { LongStandingScope, ManualPromise } from '../utils/isomorphic/manualPromise';
import { MultiMap } from '../utils/isomorphic/multimap'; import { MultiMap } from '../utils/isomorphic/multimap';
import { isRegExp, isString } from '../utils/isomorphic/rtti'; import { isRegExp, isString } from '../utils/isomorphic/rtti';
import { rewriteErrorMessage } from '../utils/stackTrace'; import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
import { zones } from '../utils/zones'; import { getMimeTypeForPath } from '../utils/isomorphic/mimeType';
import { mime } from '../utilsBundle';
import type { BrowserContext } from './browserContext'; import type { BrowserContext } from './browserContext';
import type { Page } from './page'; import type { Page } from './page';
@ -40,8 +37,8 @@ import type { Serializable } from '../../types/structs';
import type * as api from '../../types/types'; import type * as api from '../../types/types';
import type { HeadersArray } from '../common/types'; import type { HeadersArray } from '../common/types';
import type { URLMatch } from '../utils/isomorphic/urlMatch'; import type { URLMatch } from '../utils/isomorphic/urlMatch';
import type { Zone } from '../utils/zones';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import type { Platform, Zone } from '../common/platform';
export type NetworkCookie = { export type NetworkCookie = {
name: string, name: string,
@ -414,7 +411,7 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
else if (options.json) else if (options.json)
headers['content-type'] = 'application/json'; headers['content-type'] = 'application/json';
else if (options.path) else if (options.path)
headers['content-type'] = mime.getType(options.path) || 'application/octet-stream'; headers['content-type'] = getMimeTypeForPath(options.path) || 'application/octet-stream';
if (length && !('content-length' in headers)) if (length && !('content-length' in headers))
headers['content-length'] = String(length); headers['content-length'] = String(length);
@ -821,14 +818,14 @@ export class RouteHandler {
readonly handler: RouteHandlerCallback; readonly handler: RouteHandlerCallback;
private _ignoreException: boolean = false; private _ignoreException: boolean = false;
private _activeInvocations: Set<{ complete: Promise<void>, route: Route }> = new Set(); private _activeInvocations: Set<{ complete: Promise<void>, route: Route }> = new Set();
private _svedZone: Zone; private _savedZone: Zone;
constructor(baseURL: string | undefined, url: URLMatch, handler: RouteHandlerCallback, times: number = Number.MAX_SAFE_INTEGER) { constructor(platform: Platform, baseURL: string | undefined, url: URLMatch, handler: RouteHandlerCallback, times: number = Number.MAX_SAFE_INTEGER) {
this._baseURL = baseURL; this._baseURL = baseURL;
this._times = times; this._times = times;
this.url = url; this.url = url;
this.handler = handler; this.handler = handler;
this._svedZone = zones.current().without('apiZone'); this._savedZone = platform.zones.current().pop();
} }
static prepareInterceptionPatterns(handlers: RouteHandler[]) { static prepareInterceptionPatterns(handlers: RouteHandler[]) {
@ -852,7 +849,7 @@ export class RouteHandler {
} }
public async handle(route: Route): Promise<boolean> { public async handle(route: Route): Promise<boolean> {
return await this._svedZone.run(async () => this._handleImpl(route)); return await this._savedZone.run(async () => this._handleImpl(route));
} }
private async _handleImpl(route: Route): Promise<boolean> { private async _handleImpl(route: Route): Promise<boolean> {

View file

@ -33,9 +33,9 @@ import { Response, Route, RouteHandler, WebSocket, WebSocketRoute, WebSocketRou
import { Video } from './video'; import { Video } from './video';
import { Waiter } from './waiter'; import { Waiter } from './waiter';
import { Worker } from './worker'; import { Worker } from './worker';
import { TimeoutSettings } from '../common/timeoutSettings'; import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
import { assert } from '../utils/debug'; import { assert } from '../utils/isomorphic/debug';
import { mkdirIfNeeded } from '../utils/fileUtils'; import { mkdirIfNeeded } from './fileUtils';
import { headersObjectToArray } from '../utils/isomorphic/headers'; import { headersObjectToArray } from '../utils/isomorphic/headers';
import { trimStringWithEllipsis } from '../utils/isomorphic/stringUtils'; import { trimStringWithEllipsis } from '../utils/isomorphic/stringUtils';
import { urlMatches, urlMatchesEqual } from '../utils/isomorphic/urlMatch'; import { urlMatches, urlMatchesEqual } from '../utils/isomorphic/urlMatch';
@ -520,16 +520,19 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
} }
async route(url: URLMatch, handler: RouteHandlerCallback, options: { times?: number } = {}): Promise<void> { async route(url: URLMatch, handler: RouteHandlerCallback, options: { times?: number } = {}): Promise<void> {
this._routes.unshift(new RouteHandler(this._browserContext._options.baseURL, url, handler, options.times)); this._routes.unshift(new RouteHandler(this._platform, this._browserContext._options.baseURL, url, handler, options.times));
await this._updateInterceptionPatterns(); await this._updateInterceptionPatterns();
} }
async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full'} = {}): Promise<void> { async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full'} = {}): Promise<void> {
const localUtils = this._connection.localUtils();
if (!localUtils)
throw new Error('Route from har is not supported in thin clients');
if (options.update) { if (options.update) {
await this._browserContext._recordIntoHAR(har, this, options); await this._browserContext._recordIntoHAR(har, this, options);
return; return;
} }
const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url }); const harRouter = await HarRouter.create(localUtils, har, options.notFound || 'abort', { urlMatch: options.url });
this._harRouters.push(harRouter); this._harRouters.push(harRouter);
await harRouter.addPageRoute(this); await harRouter.addPageRoute(this);
} }
@ -796,7 +799,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
} }
async pause(_options?: { __testHookKeepTestTimeout: boolean }) { async pause(_options?: { __testHookKeepTestTimeout: boolean }) {
if (require('inspector').url()) if (this._platform.isDebuggerAttached())
return; return;
const defaultNavigationTimeout = this._browserContext._timeoutSettings.defaultNavigationTimeout(); const defaultNavigationTimeout = this._browserContext._timeoutSettings.defaultNavigationTimeout();
const defaultTimeout = this._browserContext._timeoutSettings.defaultTimeout(); const defaultTimeout = this._browserContext._timeoutSettings.defaultTimeout();

View file

@ -17,19 +17,25 @@
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { evaluationScript } from './clientHelper'; import { evaluationScript } from './clientHelper';
import { setTestIdAttribute, testIdAttributeName } from './locator'; import { setTestIdAttribute, testIdAttributeName } from './locator';
import { nodePlatform } from '../utils/platform'; import { emptyPlatform } from '../common/platform';
import type { SelectorEngine } from './types'; import type { SelectorEngine } from './types';
import type * as api from '../../types/types'; import type * as api from '../../types/types';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import type { Platform } from '../common/platform';
let platform = emptyPlatform;
export function setPlatformForSelectors(p: Platform) {
platform = p;
}
export class Selectors implements api.Selectors { export class Selectors implements api.Selectors {
private _channels = new Set<SelectorsOwner>(); private _channels = new Set<SelectorsOwner>();
private _registrations: channels.SelectorsRegisterParams[] = []; private _registrations: channels.SelectorsRegisterParams[] = [];
async register(name: string, script: string | (() => SelectorEngine) | { path?: string, content?: string }, options: { contentScript?: boolean } = {}): Promise<void> { async register(name: string, script: string | (() => SelectorEngine) | { path?: string, content?: string }, options: { contentScript?: boolean } = {}): Promise<void> {
const source = await evaluationScript(nodePlatform, script, undefined, false); const source = await evaluationScript(platform, script, undefined, false);
const params = { ...options, name, source }; const params = { ...options, name, source };
for (const channel of this._channels) for (const channel of this._channels)
await channel._channel.register(params); await channel._channel.register(params);

View file

@ -69,8 +69,8 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
this._isTracing = true; this._isTracing = true;
this._connection.setIsTracing(true); this._connection.setIsTracing(true);
} }
const result = await this._connection.localUtils().tracingStarted({ tracesDir: this._tracesDir, traceName }); const result = await this._connection.localUtils()?.tracingStarted({ tracesDir: this._tracesDir, traceName });
this._stacksId = result.stacksId; this._stacksId = result?.stacksId;
} }
async stopChunk(options: { path?: string } = {}) { async stopChunk(options: { path?: string } = {}) {
@ -89,15 +89,19 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
// Not interested in artifacts. // Not interested in artifacts.
await this._channel.tracingStopChunk({ mode: 'discard' }); await this._channel.tracingStopChunk({ mode: 'discard' });
if (this._stacksId) if (this._stacksId)
await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId }); await this._connection.localUtils()!.traceDiscarded({ stacksId: this._stacksId });
return; return;
} }
const localUtils = this._connection.localUtils();
if (!localUtils)
throw new Error('Cannot save trace in thin clients');
const isLocal = !this._connection.isRemote(); const isLocal = !this._connection.isRemote();
if (isLocal) { if (isLocal) {
const result = await this._channel.tracingStopChunk({ mode: 'entries' }); const result = await this._channel.tracingStopChunk({ mode: 'entries' });
await this._connection.localUtils().zip({ zipFile: filePath, entries: result.entries!, mode: 'write', stacksId: this._stacksId, includeSources: this._includeSources }); await localUtils.zip({ zipFile: filePath, entries: result.entries!, mode: 'write', stacksId: this._stacksId, includeSources: this._includeSources });
return; return;
} }
@ -106,7 +110,7 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
// The artifact may be missing if the browser closed while stopping tracing. // The artifact may be missing if the browser closed while stopping tracing.
if (!result.artifact) { if (!result.artifact) {
if (this._stacksId) if (this._stacksId)
await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId }); await localUtils.traceDiscarded({ stacksId: this._stacksId });
return; return;
} }
@ -115,7 +119,7 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
await artifact.saveAs(filePath); await artifact.saveAs(filePath);
await artifact.delete(); await artifact.delete();
await this._connection.localUtils().zip({ zipFile: filePath, entries: [], mode: 'append', stacksId: this._stacksId, includeSources: this._includeSources }); await localUtils.zip({ zipFile: filePath, entries: [], mode: 'append', stacksId: this._stacksId, includeSources: this._includeSources });
} }
_resetStackCounter() { _resetStackCounter() {

View file

@ -15,13 +15,12 @@
*/ */
import { TimeoutError } from './errors'; import { TimeoutError } from './errors';
import { rewriteErrorMessage } from '../utils/stackTrace'; import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
import { zones } from '../utils/zones';
import type { ChannelOwner } from './channelOwner'; import type { ChannelOwner } from './channelOwner';
import type { Zone } from '../utils/zones';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import type { EventEmitter } from 'events'; import type { EventEmitter } from 'events';
import type { Zone } from '../common/platform';
export class Waiter { export class Waiter {
private _dispose: (() => void)[]; private _dispose: (() => void)[];
@ -36,7 +35,7 @@ export class Waiter {
constructor(channelOwner: ChannelOwner<channels.EventTargetChannel>, event: string) { constructor(channelOwner: ChannelOwner<channels.EventTargetChannel>, event: string) {
this._waitId = channelOwner._platform.createGuid(); this._waitId = channelOwner._platform.createGuid();
this._channelOwner = channelOwner; this._channelOwner = channelOwner;
this._savedZone = zones.current().without('apiZone'); this._savedZone = channelOwner._platform.zones.current().pop();
this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'before', event } }).catch(() => {}); this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'before', event } }).catch(() => {});
this._dispose = [ this._dispose = [

View file

@ -0,0 +1,116 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChannelOwner } from './channelOwner';
import { Connection } from './connection';
import type { HeadersArray } from './types';
import type * as channels from '@protocol/channels';
export async function connectOverWebSocket(parentConnection: Connection, params: channels.LocalUtilsConnectParams): Promise<Connection> {
const localUtils = parentConnection.localUtils();
const transport = localUtils ? new JsonPipeTransport(localUtils) : new WebSocketTransport();
const connectHeaders = await transport.connect(params);
const connection = new Connection(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<HeadersArray>;
send(message: any): Promise<void>;
onMessage(callback: (message: object) => void): void;
onClose(callback: (reason?: string) => void): void;
close(): Promise<void>;
}
class JsonPipeTransport implements Transport {
private _pipe: channels.JsonPipeChannel | undefined;
private _owner: ChannelOwner<channels.LocalUtilsChannel>;
constructor(owner: ChannelOwner<channels.LocalUtilsChannel>) {
this._owner = owner;
}
async connect(params: channels.LocalUtilsConnectParams) {
const { pipe, headers: connectHeaders } = await this._owner._wrapApiCall(async () => {
return await this._owner._channel.connect(params);
}, /* isInternal */ true);
this._pipe = pipe;
return connectHeaders;
}
async send(message: object) {
this._owner._wrapApiCall(async () => {
await this._pipe!.send({ message });
}, /* isInternal */ true);
}
onMessage(callback: (message: object) => void) {
this._pipe!.on('message', ({ message }) => callback(message));
}
onClose(callback: (reason?: string) => void) {
this._pipe!.on('closed', ({ reason }) => callback(reason));
}
async close() {
await this._owner._wrapApiCall(async () => {
await this._pipe!.close().catch(() => {});
}, /* isInternal */ true);
}
}
class WebSocketTransport implements Transport {
private _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();
}
}

View file

@ -1,4 +1,2 @@
[*] [*]
../utils/ ../utils/isomorphic/
../utilsBundle.ts
../zipBundle.ts

View file

@ -0,0 +1,125 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { webColors, noColors } from '../utils/isomorphic/colors';
import type * as fs from 'fs';
import type * as path from 'path';
import type { Colors } from '../utils/isomorphic/colors';
export type Zone = {
push(data: unknown): Zone;
pop(): Zone;
run<R>(func: () => R): R;
data<T>(): T | undefined;
};
const noopZone: Zone = {
push: () => noopZone,
pop: () => noopZone,
run: func => func(),
data: () => undefined,
};
export type Platform = {
name: 'node' | 'web' | 'empty';
calculateSha1(text: string): Promise<string>;
colors: Colors;
createGuid: () => string;
fs: () => typeof fs;
inspectCustom: symbol | undefined;
isDebuggerAttached(): boolean;
isLogEnabled(name: 'api' | 'channel'): boolean;
log(name: 'api' | 'channel', message: string | Error | object): void;
path: () => typeof path;
pathSeparator: string;
zones: { empty: Zone, current: () => Zone; };
};
export const webPlatform: Platform = {
name: 'web',
calculateSha1: async (text: string) => {
const bytes = new TextEncoder().encode(text);
const hashBuffer = await window.crypto.subtle.digest('SHA-1', bytes);
return Array.from(new Uint8Array(hashBuffer), b => b.toString(16).padStart(2, '0')).join('');
},
colors: webColors,
createGuid: () => {
return Array.from(window.crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join('');
},
fs: () => {
throw new Error('File system is not available');
},
inspectCustom: undefined,
isDebuggerAttached: () => false,
isLogEnabled(name: 'api' | 'channel') {
return false;
},
log(name: 'api' | 'channel', message: string | Error | object) {},
path: () => {
throw new Error('Path module is not available');
},
pathSeparator: '/',
zones: { empty: noopZone, current: () => noopZone },
};
export const emptyPlatform: Platform = {
name: 'empty',
calculateSha1: async () => {
throw new Error('Not implemented');
},
colors: noColors,
createGuid: () => {
throw new Error('Not implemented');
},
fs: () => {
throw new Error('Not implemented');
},
inspectCustom: undefined,
isDebuggerAttached: () => false,
isLogEnabled(name: 'api' | 'channel') {
return false;
},
log(name: 'api' | 'channel', message: string | Error | object) { },
path: () => {
throw new Error('Function not implemented.');
},
pathSeparator: '/',
zones: { empty: noopZone, current: () => noopZone },
};

View file

@ -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;
}

View file

@ -14,17 +14,32 @@
* limitations under the License. * limitations under the License.
*/ */
import * as path from 'path';
import { EventEmitter } from 'events';
import { AndroidServerLauncherImpl } from './androidServerImpl'; import { AndroidServerLauncherImpl } from './androidServerImpl';
import { BrowserServerLauncherImpl } from './browserServerImpl'; import { BrowserServerLauncherImpl } from './browserServerImpl';
import { Connection } from './client/connection'; import { Connection } from './client/connection';
import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from './server'; import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from './server';
import { setLibraryStackPrefix } from './utils/isomorphic/stackTrace';
import { setDebugMode } from './utils/isomorphic/debug';
import { getFromENV } from './server/utils/env';
import { nodePlatform } from './server/utils/nodePlatform';
import { setPlatformForSelectors } from './client/selectors';
import { setDefaultMaxListenersProvider } from './client/eventEmitter';
import type { Playwright as PlaywrightAPI } from './client/playwright'; import type { Playwright as PlaywrightAPI } from './client/playwright';
import type { Language } from './utils'; import type { Language } from './utils';
import type { Platform } from './utils/platform'; import type { Platform } from './common/platform';
export function createInProcessPlaywright(platform: Platform): PlaywrightAPI { export function createInProcessPlaywright(platform: Platform): PlaywrightAPI {
const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript' }); const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript' });
setDebugMode(getFromENV('PWDEBUG') || '');
setPlatformForSelectors(nodePlatform);
setDefaultMaxListenersProvider(() => EventEmitter.defaultMaxListeners);
setLibraryStackPrefix(path.join(__dirname, '..'));
const clientConnection = new Connection(undefined, platform, undefined, []); const clientConnection = new Connection(undefined, platform, undefined, []);
clientConnection.useRawBuffers(); clientConnection.useRawBuffers();

View file

@ -15,6 +15,6 @@
*/ */
import { createInProcessPlaywright } from './inProcessFactory'; import { createInProcessPlaywright } from './inProcessFactory';
import { nodePlatform } from './utils/platform'; import { nodePlatform } from './server/utils/nodePlatform';
module.exports = createInProcessPlaywright(nodePlatform); module.exports = createInProcessPlaywright(nodePlatform);

View file

@ -18,9 +18,9 @@ import * as childProcess from 'child_process';
import * as path from 'path'; import * as path from 'path';
import { Connection } from './client/connection'; import { Connection } from './client/connection';
import { PipeTransport } from './utils/pipeTransport'; import { PipeTransport } from './server/utils/pipeTransport';
import { ManualPromise } from './utils/isomorphic/manualPromise'; import { ManualPromise } from './utils/isomorphic/manualPromise';
import { nodePlatform } from './utils/platform'; import { nodePlatform } from './server/utils/nodePlatform';
import type { Playwright } from './client/playwright'; import type { Playwright } from './client/playwright';

View file

@ -1,4 +1,3 @@
[*] [*]
../common/ ../common/
../utils/ ../utils/isomorphic

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { isUnderTest } from '../utils'; import { isUnderTest } from '../utils/isomorphic/debug';
export class ValidationError extends Error {} export class ValidationError extends Error {}
export type Validator = (arg: any, path: string, context: ValidatorContext) => any; export type Validator = (arg: any, path: string, context: ValidatorContext) => any;

View file

@ -23,7 +23,7 @@ import { serverSideCallMetadata } from '../server/instrumentation';
import { assert, isUnderTest } from '../utils'; import { assert, isUnderTest } from '../utils';
import { startProfiling, stopProfiling } from '../server/utils/profiler'; import { startProfiling, stopProfiling } from '../server/utils/profiler';
import { monotonicTime } from '../utils'; import { monotonicTime } from '../utils';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from '../server/utils/debugLogger';
import type { DispatcherScope, Playwright } from '../server'; import type { DispatcherScope, Playwright } from '../server';
import type { LaunchOptions } from '../server/types'; import type { LaunchOptions } from '../server/types';

View file

@ -16,11 +16,11 @@
import { PlaywrightConnection } from './playwrightConnection'; import { PlaywrightConnection } from './playwrightConnection';
import { createPlaywright } from '../server/playwright'; import { createPlaywright } from '../server/playwright';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from '../server/utils/debugLogger';
import { Semaphore } from '../utils/isomorphic/semaphore'; import { Semaphore } from '../utils/isomorphic/semaphore';
import { WSServer } from '../server/utils/wsServer'; import { WSServer } from '../server/utils/wsServer';
import { wrapInASCIIBox } from '../server/utils/ascii'; import { wrapInASCIIBox } from '../server/utils/ascii';
import { getPlaywrightVersion } from '../utils/userAgent'; import { getPlaywrightVersion } from '../server/utils/userAgent';
import type { ClientType } from './playwrightConnection'; import type { ClientType } from './playwrightConnection';
import type { SocksProxy } from '../server/utils/socksProxy'; import type { SocksProxy } from '../server/utils/socksProxy';

View file

@ -3,6 +3,7 @@
../../common/ ../../common/
../../protocol/ ../../protocol/
../../utils/ ../../utils/
../../utils/isomorphic/
../../utilsBundle.ts ../../utilsBundle.ts
../chromium/ ../chromium/
../utils ../utils

View file

@ -19,13 +19,13 @@ import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { TimeoutSettings } from '../../common/timeoutSettings'; import { TimeoutSettings } from '../../utils/isomorphic/timeoutSettings';
import { PipeTransport } from '../../utils/pipeTransport'; import { PipeTransport } from '../utils/pipeTransport';
import { createGuid } from '../utils/crypto'; import { createGuid } from '../utils/crypto';
import { isUnderTest } from '../../utils/debug'; import { isUnderTest } from '../../utils/isomorphic/debug';
import { getPackageManagerExecCommand } from '../../utils/env'; import { getPackageManagerExecCommand } from '../utils/env';
import { makeWaitForNextTask } from '../../utils/task'; import { makeWaitForNextTask } from '../utils/task';
import { RecentLogsCollector } from '../../utils/debugLogger'; import { RecentLogsCollector } from '../utils/debugLogger';
import { debug } from '../../utilsBundle'; import { debug } from '../../utilsBundle';
import { wsReceiver, wsSender } from '../../utilsBundle'; import { wsReceiver, wsSender } from '../../utilsBundle';
import { validateBrowserContextOptions } from '../browserContext'; import { validateBrowserContextOptions } from '../browserContext';

View file

@ -17,7 +17,7 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import * as net from 'net'; import * as net from 'net';
import { assert } from '../../utils/debug'; import { assert } from '../../utils/isomorphic/debug';
import { createGuid } from '../utils/crypto'; import { createGuid } from '../utils/crypto';
import { debug } from '../../utilsBundle'; import { debug } from '../../utilsBundle';

View file

@ -16,11 +16,11 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { debugLogger } from '../../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { helper } from '../helper'; import { helper } from '../helper';
import { ProtocolError } from '../protocolError'; import { ProtocolError } from '../protocolError';
import type { RecentLogsCollector } from '../../utils/debugLogger'; import type { RecentLogsCollector } from '../utils/debugLogger';
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import type { ProtocolLogger } from '../types'; import type { ProtocolLogger } from '../types';
import type * as bidiCommands from './third_party/bidiCommands'; import type * as bidiCommands from './third_party/bidiCommands';

View file

@ -16,6 +16,7 @@
import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers'; import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
import * as js from '../javascript'; import * as js from '../javascript';
import * as dom from '../dom';
import { BidiDeserializer } from './third_party/bidiDeserializer'; import { BidiDeserializer } from './third_party/bidiDeserializer';
import * as bidi from './third_party/bidiProtocol'; import * as bidi from './third_party/bidiProtocol';
import { BidiSerializer } from './third_party/bidiSerializer'; import { BidiSerializer } from './third_party/bidiSerializer';
@ -137,7 +138,45 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
}); });
} }
async rawCallFunction(functionDeclaration: string, arg: bidi.Script.LocalValue): Promise<bidi.Script.RemoteValue> {
async nodeIdForElementHandle(handle: dom.ElementHandle): Promise<bidi.Script.SharedReference> {
const shared = await this._remoteValueForReference({ handle: handle._objectId });
// TODO: store sharedId in the handle.
if (!('sharedId' in shared))
throw new Error('Element is not a node');
return {
sharedId: shared.sharedId!,
};
}
async remoteObjectForNodeId(nodeId: bidi.Script.SharedReference): Promise<js.RemoteObject> {
const result = await this._remoteValueForReference(nodeId);
if ('handle' in result)
return { objectId: result.handle!, ...result };
throw new Error('Can\'t get remote object for nodeId');
}
async contentFrameIdForFrame(handle: dom.ElementHandle) {
const contentWindow = await this._rawCallFunction('e => e.contentWindow', { handle: handle._objectId });
if (contentWindow?.type === 'window')
return contentWindow.value.context;
return null;
}
async frameIdForWindowHandle(handle: js.JSHandle): Promise<string | null> {
if (!handle._objectId)
throw new Error('JSHandle is not a DOM node handle');
const contentWindow = await this._remoteValueForReference({ handle: handle._objectId });
if (contentWindow.type === 'window')
return contentWindow.value.context;
return null;
}
private async _remoteValueForReference(reference: bidi.Script.RemoteReference) {
return await this._rawCallFunction('e => e', reference);
}
private async _rawCallFunction(functionDeclaration: string, arg: bidi.Script.LocalValue): Promise<bidi.Script.RemoteValue> {
const response = await this._session.send('script.callFunction', { const response = await this._session.send('script.callFunction', {
functionDeclaration, functionDeclaration,
target: this._target, target: this._target,

View file

@ -17,7 +17,7 @@
import * as bidiMapper from 'chromium-bidi/lib/cjs/bidiMapper/BidiMapper'; import * as bidiMapper from 'chromium-bidi/lib/cjs/bidiMapper/BidiMapper';
import * as bidiCdpConnection from 'chromium-bidi/lib/cjs/cdp/CdpConnection'; import * as bidiCdpConnection from 'chromium-bidi/lib/cjs/cdp/CdpConnection';
import { debugLogger } from '../../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import type { ChromiumBidi } from 'chromium-bidi/lib/cjs/protocol/protocol'; import type { ChromiumBidi } from 'chromium-bidi/lib/cjs/protocol/protocol';

View file

@ -413,13 +413,10 @@ export class BidiPage implements PageDelegate {
async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> { async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
const executionContext = toBidiExecutionContext(handle._context); const executionContext = toBidiExecutionContext(handle._context);
const contentWindow = await executionContext.rawCallFunction('e => e.contentWindow', { handle: handle._objectId }); const frameId = await executionContext.contentFrameIdForFrame(handle);
if (contentWindow.type === 'window') { if (!frameId)
const frameId = contentWindow.value.context; return null;
const result = this._page._frameManager.frame(frameId); return this._page._frameManager.frame(frameId);
return result;
}
return null;
} }
async getOwnerFrame(handle: dom.ElementHandle): Promise<string | null> { async getOwnerFrame(handle: dom.ElementHandle): Promise<string | null> {
@ -430,15 +427,8 @@ export class BidiPage implements PageDelegate {
}); });
if (!windowHandle) if (!windowHandle)
return null; return null;
if (!windowHandle._objectId) const executionContext = toBidiExecutionContext(handle._context);
return null; return executionContext.frameIdForWindowHandle(windowHandle);
const executionContext = toBidiExecutionContext(windowHandle._context as dom.FrameExecutionContext);
const contentWindow = await executionContext.rawCallFunction('e => e', { handle: windowHandle._objectId });
if (contentWindow.type === 'window') {
const frameId = contentWindow.value.context;
return frameId;
}
return null;
} }
isElementHandle(remoteObject: bidi.Script.RemoteValue): boolean { isElementHandle(remoteObject: bidi.Script.RemoteValue): boolean {
@ -530,34 +520,26 @@ export class BidiPage implements PageDelegate {
} }
async setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void> { async setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void> {
throw new Error('Setting FilePayloads is not supported in Bidi.'); await handle.evaluateInUtility(([injected, node, files]) =>
injected.setInputFiles(node, files), files);
} }
async setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, paths: string[]): Promise<void> { async setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, paths: string[]): Promise<void> {
const fromContext = toBidiExecutionContext(handle._context); const fromContext = toBidiExecutionContext(handle._context);
const shared = await fromContext.rawCallFunction('x => x', { handle: handle._objectId });
// TODO: store sharedId in the handle.
if (!('sharedId' in shared))
throw new Error('Element is not a node');
const sharedId = shared.sharedId!;
await this._session.send('input.setFiles', { await this._session.send('input.setFiles', {
context: this._session.sessionId, context: this._session.sessionId,
element: { sharedId }, element: await fromContext.nodeIdForElementHandle(handle),
files: paths, files: paths,
}); });
} }
async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>> { async adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>> {
const fromContext = toBidiExecutionContext(handle._context); const fromContext = toBidiExecutionContext(handle._context);
const shared = await fromContext.rawCallFunction('x => x', { handle: handle._objectId }); const nodeId = await fromContext.nodeIdForElementHandle(handle);
// TODO: store sharedId in the handle.
if (!('sharedId' in shared))
throw new Error('Element is not a node');
const sharedId = shared.sharedId!;
const executionContext = toBidiExecutionContext(to); const executionContext = toBidiExecutionContext(to);
const result = await executionContext.rawCallFunction('x => x', { sharedId }); const objectId = await executionContext.remoteObjectForNodeId(nodeId);
if ('handle' in result) if (objectId)
return to.createHandle({ objectId: result.handle!, ...result }) as dom.ElementHandle<T>; return to.createHandle(objectId) as dom.ElementHandle<T>;
throw new Error('Failed to adopt element handle.'); throw new Error('Failed to adopt element handle.');
} }

View file

@ -24,7 +24,7 @@ import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor';
import type { CallMetadata } from './instrumentation'; import type { CallMetadata } from './instrumentation';
import type * as types from './types'; import type * as types from './types';
import type { ProxySettings } from './types'; import type { ProxySettings } from './types';
import type { RecentLogsCollector } from '../utils/debugLogger'; import type { RecentLogsCollector } from './utils/debugLogger';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import type { ChildProcess } from 'child_process'; import type { ChildProcess } from 'child_process';

View file

@ -18,9 +18,9 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { TimeoutSettings } from '../common/timeoutSettings'; import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
import { createGuid } from './utils/crypto'; import { createGuid } from './utils/crypto';
import { debugMode } from '../utils/debug'; import { debugMode } from '../utils/isomorphic/debug';
import { Clock } from './clock'; import { Clock } from './clock';
import { Debugger } from './debugger'; import { Debugger } from './debugger';
import { BrowserContextAPIRequestContext } from './fetch'; import { BrowserContextAPIRequestContext } from './fetch';

View file

@ -19,7 +19,7 @@ import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { normalizeProxySettings, validateBrowserContextOptions } from './browserContext'; import { normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
import { DEFAULT_TIMEOUT, TimeoutSettings } from '../common/timeoutSettings'; import { DEFAULT_TIMEOUT, TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
import { ManualPromise, assert, debugMode } from '../utils'; import { ManualPromise, assert, debugMode } from '../utils';
import { existsAsync } from './utils/fileUtils'; import { existsAsync } from './utils/fileUtils';
import { helper } from './helper'; import { helper } from './helper';
@ -31,7 +31,7 @@ import { isProtocolError } from './protocolError';
import { registry } from './registry'; import { registry } from './registry';
import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor'; import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor';
import { WebSocketTransport } from './transport'; import { WebSocketTransport } from './transport';
import { RecentLogsCollector } from '../utils/debugLogger'; import { RecentLogsCollector } from './utils/debugLogger';
import type { Browser, BrowserOptions, BrowserProcess } from './browser'; import type { Browser, BrowserOptions, BrowserProcess } from './browser';
import type { BrowserContext } from './browserContext'; import type { BrowserContext } from './browserContext';

View file

@ -22,13 +22,13 @@ import * as path from 'path';
import { chromiumSwitches } from './chromiumSwitches'; import { chromiumSwitches } from './chromiumSwitches';
import { CRBrowser } from './crBrowser'; import { CRBrowser } from './crBrowser';
import { kBrowserCloseMessageId } from './crConnection'; import { kBrowserCloseMessageId } from './crConnection';
import { TimeoutSettings } from '../../common/timeoutSettings'; import { TimeoutSettings } from '../../utils/isomorphic/timeoutSettings';
import { debugMode, headersArrayToObject, headersObjectToArray, } from '../../utils'; import { debugMode, headersArrayToObject, headersObjectToArray, } from '../../utils';
import { wrapInASCIIBox } from '../utils/ascii'; import { wrapInASCIIBox } from '../utils/ascii';
import { RecentLogsCollector } from '../../utils/debugLogger'; import { RecentLogsCollector } from '../utils/debugLogger';
import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { ManualPromise } from '../../utils/isomorphic/manualPromise';
import { fetchData } from '../utils/network'; import { fetchData } from '../utils/network';
import { getUserAgent } from '../../utils/userAgent'; import { getUserAgent } from '../utils/userAgent';
import { validateBrowserContextOptions } from '../browserContext'; import { validateBrowserContextOptions } from '../browserContext';
import { BrowserType, kNoXServerRunningError } from '../browserType'; import { BrowserType, kNoXServerRunningError } from '../browserType';
import { BrowserReadyState } from '../browserType'; import { BrowserReadyState } from '../browserType';

View file

@ -17,7 +17,7 @@
import * as path from 'path'; import * as path from 'path';
import { assert } from '../../utils/debug'; import { assert } from '../../utils/isomorphic/debug';
import { createGuid } from '../utils/crypto'; import { createGuid } from '../utils/crypto';
import { Artifact } from '../artifact'; import { Artifact } from '../artifact';
import { Browser } from '../browser'; import { Browser } from '../browser';

View file

@ -18,14 +18,14 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { assert, eventsHelper } from '../../utils'; import { assert, eventsHelper } from '../../utils';
import { debugLogger } from '../../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { helper } from '../helper'; import { helper } from '../helper';
import { ProtocolError } from '../protocolError'; import { ProtocolError } from '../protocolError';
import type { RegisteredListener } from '../../utils'; import type { RegisteredListener } from '../../utils';
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import type { Protocol } from './protocol'; import type { Protocol } from './protocol';
import type { RecentLogsCollector } from '../../utils/debugLogger'; import type { RecentLogsCollector } from '../utils/debugLogger';
import type { ProtocolLogger } from '../types'; import type { ProtocolLogger } from '../types';

View file

@ -16,7 +16,7 @@
*/ */
import { getExceptionMessage, releaseObject } from './crProtocolHelper'; import { getExceptionMessage, releaseObject } from './crProtocolHelper';
import { rewriteErrorMessage } from '../../utils/stackTrace'; import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace';
import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers'; import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
import * as js from '../javascript'; import * as js from '../javascript';
import { isSessionClosedError } from '../protocolError'; import { isSessionClosedError } from '../protocolError';

View file

@ -17,10 +17,10 @@
import * as path from 'path'; import * as path from 'path';
import { assert } from '../../utils/debug'; import { assert } from '../../utils/isomorphic/debug';
import { createGuid } from '../utils/crypto'; import { createGuid } from '../utils/crypto';
import { eventsHelper } from '../utils/eventsHelper'; import { eventsHelper } from '../utils/eventsHelper';
import { rewriteErrorMessage } from '../../utils/stackTrace'; import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace';
import * as dialog from '../dialog'; import * as dialog from '../dialog';
import * as dom from '../dom'; import * as dom from '../dom';
import * as frames from '../frames'; import * as frames from '../frames';

View file

@ -17,7 +17,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import { splitErrorMessage } from '../../utils/stackTrace'; import { splitErrorMessage } from '../../utils/isomorphic/stackTrace';
import { mkdirIfNeeded } from '../utils/fileUtils'; import { mkdirIfNeeded } from '../utils/fileUtils';
import type { CRSession } from './crConnection'; import type { CRSession } from './crConnection';

View file

@ -16,9 +16,8 @@
import { Dispatcher } from './dispatcher'; import { Dispatcher } from './dispatcher';
import { SdkObject } from '../../server/instrumentation'; import { SdkObject } from '../../server/instrumentation';
import * as localUtils from '../../utils/localUtils'; import * as localUtils from '../localUtils';
import { nodePlatform } from '../../utils/platform'; import { getUserAgent } from '../utils/userAgent';
import { getUserAgent } from '../../utils/userAgent';
import { deviceDescriptors as descriptors } from '../deviceDescriptors'; import { deviceDescriptors as descriptors } from '../deviceDescriptors';
import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher'; import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher';
import { Progress, ProgressController } from '../progress'; import { Progress, ProgressController } from '../progress';
@ -26,7 +25,7 @@ import { SocksInterceptor } from '../socksInterceptor';
import { WebSocketTransport } from '../transport'; import { WebSocketTransport } from '../transport';
import { fetchData } from '../utils/network'; import { fetchData } from '../utils/network';
import type { HarBackend } from '../../utils/harBackend'; import type { HarBackend } from '../harBackend';
import type { CallMetadata } from '../instrumentation'; import type { CallMetadata } from '../instrumentation';
import type { Playwright } from '../playwright'; import type { Playwright } from '../playwright';
import type { RootDispatcher } from './dispatcher'; import type { RootDispatcher } from './dispatcher';
@ -50,11 +49,11 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
} }
async zip(params: channels.LocalUtilsZipParams): Promise<void> { async zip(params: channels.LocalUtilsZipParams): Promise<void> {
return await localUtils.zip(nodePlatform, this._stackSessions, params); return await localUtils.zip(this._stackSessions, params);
} }
async harOpen(params: channels.LocalUtilsHarOpenParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarOpenResult> { async harOpen(params: channels.LocalUtilsHarOpenParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarOpenResult> {
return await localUtils.harOpen(nodePlatform, this._harBackends, params); return await localUtils.harOpen(this._harBackends, params);
} }
async harLookup(params: channels.LocalUtilsHarLookupParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarLookupResult> { async harLookup(params: channels.LocalUtilsHarLookupParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarLookupResult> {
@ -74,7 +73,7 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
} }
async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams, metadata?: CallMetadata | undefined): Promise<void> { async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams, metadata?: CallMetadata | undefined): Promise<void> {
return await localUtils.traceDiscarded(nodePlatform, this._stackSessions, params); return await localUtils.traceDiscarded(this._stackSessions, params);
} }
async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams, metadata?: CallMetadata | undefined): Promise<void> { async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams, metadata?: CallMetadata | undefined): Promise<void> {

View file

@ -2,5 +2,6 @@
../ ../
../../common/ ../../common/
../../utils/ ../../utils/
../../utils/isomorphic/
../chromium/ ../chromium/
../utils ../utils

View file

@ -19,10 +19,10 @@ import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as readline from 'readline'; import * as readline from 'readline';
import { TimeoutSettings } from '../../common/timeoutSettings'; import { TimeoutSettings } from '../../utils/isomorphic/timeoutSettings';
import { ManualPromise } from '../../utils'; import { ManualPromise } from '../../utils';
import { wrapInASCIIBox } from '../utils/ascii'; import { wrapInASCIIBox } from '../utils/ascii';
import { RecentLogsCollector } from '../../utils/debugLogger'; import { RecentLogsCollector } from '../utils/debugLogger';
import { eventsHelper } from '../utils/eventsHelper'; import { eventsHelper } from '../utils/eventsHelper';
import { validateBrowserContextOptions } from '../browserContext'; import { validateBrowserContextOptions } from '../browserContext';
import { CRBrowser } from '../chromium/crBrowser'; import { CRBrowser } from '../chromium/crBrowser';

View file

@ -21,10 +21,10 @@ import { TLSSocket } from 'tls';
import * as url from 'url'; import * as url from 'url';
import * as zlib from 'zlib'; import * as zlib from 'zlib';
import { TimeoutSettings } from '../common/timeoutSettings'; import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
import { assert, constructURLBasedOnBaseURL, eventsHelper, monotonicTime } from '../utils'; import { assert, constructURLBasedOnBaseURL, eventsHelper, monotonicTime } from '../utils';
import { createGuid } from './utils/crypto'; import { createGuid } from './utils/crypto';
import { getUserAgent } from '../utils/userAgent'; import { getUserAgent } from './utils/userAgent';
import { HttpsProxyAgent, SocksProxyAgent } from '../utilsBundle'; import { HttpsProxyAgent, SocksProxyAgent } from '../utilsBundle';
import { BrowserContext, verifyClientCertificates } from './browserContext'; import { BrowserContext, verifyClientCertificates } from './browserContext';
import { CookieStore, domainMatches, parseRawCookie } from './cookieStore'; import { CookieStore, domainMatches, parseRawCookie } from './cookieStore';

View file

@ -17,8 +17,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { assert } from '../utils/debug'; import { assert } from '../utils/isomorphic/debug';
import { fileUploadSizeLimit } from '../utils/fileUtils';
import { mime } from '../utilsBundle'; import { mime } from '../utilsBundle';
import type { WritableStreamDispatcher } from './dispatchers/writableStreamDispatcher'; import type { WritableStreamDispatcher } from './dispatchers/writableStreamDispatcher';
@ -27,6 +26,9 @@ import type { Frame } from './frames';
import type * as types from './types'; import type * as types from './types';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
// Keep in sync with the client.
export const fileUploadSizeLimit = 50 * 1024 * 1024;
async function filesExceedUploadLimit(files: string[]) { async function filesExceedUploadLimit(files: string[]) {
const sizes = await Promise.all(files.map(async file => (await fs.promises.stat(file)).size)); const sizes = await Promise.all(files.map(async file => (await fs.promises.stat(file)).size));
return sizes.reduce((total, size) => total + size, 0) >= fileUploadSizeLimit; return sizes.reduce((total, size) => total + size, 0) >= fileUploadSizeLimit;

View file

@ -17,13 +17,13 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { debugLogger } from '../../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { helper } from '../helper'; import { helper } from '../helper';
import { ProtocolError } from '../protocolError'; import { ProtocolError } from '../protocolError';
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import type { Protocol } from './protocol'; import type { Protocol } from './protocol';
import type { RecentLogsCollector } from '../../utils/debugLogger'; import type { RecentLogsCollector } from '../utils/debugLogger';
import type { ProtocolLogger } from '../types'; import type { ProtocolLogger } from '../types';

View file

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { rewriteErrorMessage } from '../../utils/stackTrace'; import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace';
import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers'; import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
import * as js from '../javascript'; import * as js from '../javascript';
import { isSessionClosedError } from '../protocolError'; import { isSessionClosedError } from '../protocolError';

View file

@ -25,8 +25,8 @@ import { FFSession } from './ffConnection';
import { FFExecutionContext } from './ffExecutionContext'; import { FFExecutionContext } from './ffExecutionContext';
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './ffInput'; import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './ffInput';
import { FFNetworkManager } from './ffNetworkManager'; import { FFNetworkManager } from './ffNetworkManager';
import { debugLogger } from '../../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { splitErrorMessage } from '../../utils/stackTrace'; import { splitErrorMessage } from '../../utils/isomorphic/stackTrace';
import { BrowserContext } from '../browserContext'; import { BrowserContext } from '../browserContext';
import { TargetClosedError } from '../errors'; import { TargetClosedError } from '../errors';

View file

@ -29,7 +29,7 @@ import { ProgressController } from './progress';
import * as types from './types'; import * as types from './types';
import { LongStandingScope, asLocator, assert, compressCallLog, constructURLBasedOnBaseURL, makeWaitForNextTask, monotonicTime } from '../utils'; import { LongStandingScope, asLocator, assert, compressCallLog, constructURLBasedOnBaseURL, makeWaitForNextTask, monotonicTime } from '../utils';
import { isSessionClosedError } from './protocolError'; import { isSessionClosedError } from './protocolError';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from './utils/debugLogger';
import { eventsHelper } from './utils/eventsHelper'; import { eventsHelper } from './utils/eventsHelper';
import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser'; import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser';
import { ManualPromise } from '../utils/isomorphic/manualPromise'; import { ManualPromise } from '../utils/isomorphic/manualPromise';

View file

@ -14,11 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
import { ZipFile } from './zipFile'; import * as fs from 'fs';
import * as path from 'path';
import { createGuid } from './utils/crypto';
import { ZipFile } from './utils/zipFile';
import type { HeadersArray } from '../common/types'; import type { HeadersArray } from '../common/types';
import type * as har from '@trace/har'; import type * as har from '@trace/har';
import type { Platform } from './platform';
const redirectStatus = [301, 302, 303, 307, 308]; const redirectStatus = [301, 302, 303, 307, 308];
@ -27,11 +30,9 @@ export class HarBackend {
private _harFile: har.HARFile; private _harFile: har.HARFile;
private _zipFile: ZipFile | null; private _zipFile: ZipFile | null;
private _baseDir: string | null; private _baseDir: string | null;
private _platform: Platform;
constructor(platform: Platform, harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) { constructor(harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) {
this._platform = platform; this.id = createGuid();
this.id = platform.createGuid();
this._harFile = harFile; this._harFile = harFile;
this._baseDir = baseDir; this._baseDir = baseDir;
this._zipFile = zipFile; this._zipFile = zipFile;
@ -79,7 +80,7 @@ export class HarBackend {
if (this._zipFile) if (this._zipFile)
buffer = await this._zipFile.read(file); buffer = await this._zipFile.read(file);
else else
buffer = await this._platform.fs().promises.readFile(this._platform.path().resolve(this._baseDir!, file)); buffer = await fs.promises.readFile(path.resolve(this._baseDir!, file));
} else { } else {
buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8'); buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8');
} }

View file

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from './utils/debugLogger';
import { eventsHelper } from './utils/eventsHelper'; import { eventsHelper } from './utils/eventsHelper';
import type { Progress } from './progress'; import type { Progress } from './progress';

View file

@ -18,15 +18,15 @@ import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { removeFolders } from './fileUtils'; import { calculateSha1 } from './utils/crypto';
import { HarBackend } from './harBackend'; import { HarBackend } from './harBackend';
import { ManualPromise } from './isomorphic/manualPromise'; import { ManualPromise } from '../utils/isomorphic/manualPromise';
import { ZipFile } from './zipFile'; import { ZipFile } from './utils/zipFile';
import { yauzl, yazl } from '../zipBundle'; import { yauzl, yazl } from '../zipBundle';
import { serializeClientSideCallMetadata } from '../utils/isomorphic/traceUtils'; import { serializeClientSideCallMetadata } from '../utils/isomorphic/traceUtils';
import { assert } from '../utils/debug'; 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 channels from '@protocol/channels';
import type * as har from '@trace/har'; import type * as har from '@trace/har';
import type EventEmitter from 'events'; import type EventEmitter from 'events';
@ -39,7 +39,7 @@ export type StackSession = {
callStacks: channels.ClientSideCallMetadata[]; callStacks: channels.ClientSideCallMetadata[];
}; };
export async function zip(platform: Platform, stackSessions: Map<string, StackSession>, params: channels.LocalUtilsZipParams): Promise<void> { export async function zip(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsZipParams): Promise<void> {
const promise = new ManualPromise<void>(); const promise = new ManualPromise<void>();
const zipFile = new yazl.ZipFile(); const zipFile = new yazl.ZipFile();
(zipFile as any as EventEmitter).on('error', error => promise.reject(error)); (zipFile as any as EventEmitter).on('error', error => promise.reject(error));
@ -77,7 +77,7 @@ export async function zip(platform: Platform, stackSessions: Map<string, StackSe
sourceFiles.add(file); sourceFiles.add(file);
} }
for (const sourceFile of sourceFiles) for (const sourceFile of sourceFiles)
addFile(sourceFile, 'resources/src@' + await platform.calculateSha1(sourceFile) + '.txt'); addFile(sourceFile, 'resources/src@' + await calculateSha1(sourceFile) + '.txt');
} }
if (params.mode === 'write') { if (params.mode === 'write') {
@ -89,7 +89,7 @@ export async function zip(platform: Platform, stackSessions: Map<string, StackSe
.on('error', error => promise.reject(error)); .on('error', error => promise.reject(error));
}); });
await promise; await promise;
await deleteStackSession(platform, stackSessions, params.stacksId); await deleteStackSession(stackSessions, params.stacksId);
return; return;
} }
@ -124,20 +124,20 @@ export async function zip(platform: Platform, stackSessions: Map<string, StackSe
}); });
}); });
await promise; await promise;
await deleteStackSession(platform, stackSessions, params.stacksId); await deleteStackSession(stackSessions, params.stacksId);
} }
async function deleteStackSession(platform: Platform, stackSessions: Map<string, StackSession>, stacksId?: string) { async function deleteStackSession(stackSessions: Map<string, StackSession>, stacksId?: string) {
const session = stacksId ? stackSessions.get(stacksId) : undefined; const session = stacksId ? stackSessions.get(stacksId) : undefined;
if (!session) if (!session)
return; return;
await session.writer; await session.writer;
if (session.tmpDir) if (session.tmpDir)
await removeFolders(platform, [session.tmpDir]); await removeFolders([session.tmpDir]);
stackSessions.delete(stacksId!); stackSessions.delete(stacksId!);
} }
export async function harOpen(platform: Platform, harBackends: Map<string, HarBackend>, params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> { export async function harOpen(harBackends: Map<string, HarBackend>, params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> {
let harBackend: HarBackend; let harBackend: HarBackend;
if (params.file.endsWith('.zip')) { if (params.file.endsWith('.zip')) {
const zipFile = new ZipFile(params.file); const zipFile = new ZipFile(params.file);
@ -147,10 +147,10 @@ export async function harOpen(platform: Platform, harBackends: Map<string, HarBa
return { error: 'Specified archive does not have a .har file' }; return { error: 'Specified archive does not have a .har file' };
const har = await zipFile.read(harEntryName); const har = await zipFile.read(harEntryName);
const harFile = JSON.parse(har.toString()) as har.HARFile; const harFile = JSON.parse(har.toString()) as har.HARFile;
harBackend = new HarBackend(platform, harFile, null, zipFile); harBackend = new HarBackend(harFile, null, zipFile);
} else { } else {
const harFile = JSON.parse(await fs.promises.readFile(params.file, 'utf-8')) as har.HARFile; const harFile = JSON.parse(await fs.promises.readFile(params.file, 'utf-8')) as har.HARFile;
harBackend = new HarBackend(platform, harFile, path.dirname(params.file), null); harBackend = new HarBackend(harFile, path.dirname(params.file), null);
} }
harBackends.set(harBackend.id, harBackend); harBackends.set(harBackend.id, harBackend);
return { harId: harBackend.id }; return { harId: harBackend.id };
@ -194,8 +194,8 @@ export async function tracingStarted(stackSessions: Map<string, StackSession>, p
return { stacksId: traceStacksFile }; return { stacksId: traceStacksFile };
} }
export async function traceDiscarded(platform: Platform, stackSessions: Map<string, StackSession>, params: channels.LocalUtilsTraceDiscardedParams): Promise<void> { export async function traceDiscarded(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
await deleteStackSession(platform, stackSessions, params.stacksId); await deleteStackSession(stackSessions, params.stacksId);
} }
export async function addStackToTracingNoReply(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> { export async function addStackToTracingNoReply(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> {

View file

@ -28,12 +28,12 @@ import { parseEvaluationResultValue, source } from './isomorphic/utilityScriptSe
import * as js from './javascript'; import * as js from './javascript';
import { ProgressController } from './progress'; import { ProgressController } from './progress';
import { Screenshotter, validateScreenshotOptions } from './screenshotter'; import { Screenshotter, validateScreenshotOptions } from './screenshotter';
import { TimeoutSettings } from '../common/timeoutSettings'; import { TimeoutSettings } from '../utils/isomorphic/timeoutSettings';
import { LongStandingScope, assert, compressCallLog, trimStringWithEllipsis } from '../utils'; import { LongStandingScope, assert, compressCallLog, trimStringWithEllipsis } from '../utils';
import { createGuid } from './utils/crypto'; import { createGuid } from './utils/crypto';
import { asLocator } from '../utils'; import { asLocator } from '../utils';
import { getComparator } from './utils/comparators'; import { getComparator } from './utils/comparators';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from './utils/debugLogger';
import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser'; import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser';
import { ManualPromise } from '../utils/isomorphic/manualPromise'; import { ManualPromise } from '../utils/isomorphic/manualPromise';

View file

@ -16,7 +16,7 @@
*/ */
import { makeWaitForNextTask } from '../utils'; import { makeWaitForNextTask } from '../utils';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from './utils/debugLogger';
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from './transport'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from './transport';

View file

@ -19,10 +19,14 @@ import { assert, monotonicTime } from '../utils';
import { ManualPromise } from '../utils/isomorphic/manualPromise'; import { ManualPromise } from '../utils/isomorphic/manualPromise';
import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation'; import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation';
import type { Progress as CommonProgress } from '../common/progress'; import type { LogName } from './utils/debugLogger';
import type { LogName } from '../utils/debugLogger';
export interface Progress extends CommonProgress { export interface Progress {
log(message: string): void;
timeUntilDeadline(): number;
isRunning(): boolean;
cleanupWhenAborted(cleanup: () => any): void;
throwIfAborted(): void;
metadata: CallMetadata; metadata: CallMetadata;
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { rewriteErrorMessage } from '../utils/stackTrace'; import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
export class ProtocolError extends Error { export class ProtocolError extends Error {
type: 'error' | 'closed' | 'crashed'; type: 'error' | 'closed' | 'crashed';

View file

@ -18,7 +18,7 @@ import { EventEmitter } from 'events';
import { performAction } from './recorderRunner'; import { performAction } from './recorderRunner';
import { collapseActions } from './recorderUtils'; import { collapseActions } from './recorderUtils';
import { isUnderTest } from '../../utils/debug'; import { isUnderTest } from '../../utils/isomorphic/debug';
import { monotonicTime } from '../../utils/isomorphic/time'; import { monotonicTime } from '../../utils/isomorphic/time';
import type { Signal } from '../../../../recorder/src/actions'; import type { Signal } from '../../../../recorder/src/actions';

View file

@ -20,10 +20,10 @@ import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { debugLogger } from '../../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { ManualPromise } from '../../utils/isomorphic/manualPromise';
import { getUserAgent } from '../../utils/userAgent'; import { getUserAgent } from '../utils/userAgent';
import { colors, progress as ProgressBar } from '../../utilsBundle'; import { progress as ProgressBar, colors } from '../../utilsBundle';
import { existsAsync } from '../utils/fileUtils'; import { existsAsync } from '../utils/fileUtils';
import { browserDirectoryToMarkerFilePath } from '.'; import { browserDirectoryToMarkerFilePath } from '.';

View file

@ -21,9 +21,9 @@ import * as path from 'path';
import { deps } from './nativeDeps'; import { deps } from './nativeDeps';
import { wrapInASCIIBox } from '../utils/ascii'; import { wrapInASCIIBox } from '../utils/ascii';
import { hostPlatform, isOfficiallySupportedPlatform } from '../../utils/hostPlatform'; import { hostPlatform, isOfficiallySupportedPlatform } from '../utils/hostPlatform';
import { spawnAsync } from '../utils/spawnAsync'; import { spawnAsync } from '../utils/spawnAsync';
import { getPlaywrightVersion } from '../../utils/userAgent'; import { getPlaywrightVersion } from '../utils/userAgent';
import { buildPlaywrightCLICommand, registry } from '.'; import { buildPlaywrightCLICommand, registry } from '.';

View file

@ -25,16 +25,16 @@ import { dockerVersion, readDockerVersionSync, transformCommandsForRoot } from '
import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies'; import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies';
import { calculateSha1, getAsBooleanFromENV, getFromENV, getPackageManagerExecCommand } from '../../utils'; import { calculateSha1, getAsBooleanFromENV, getFromENV, getPackageManagerExecCommand } from '../../utils';
import { wrapInASCIIBox } from '../utils/ascii'; import { wrapInASCIIBox } from '../utils/ascii';
import { debugLogger } from '../../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { hostPlatform, isOfficiallySupportedPlatform } from '../../utils/hostPlatform'; import { hostPlatform, isOfficiallySupportedPlatform } from '../utils/hostPlatform';
import { fetchData } from '../utils/network'; import { fetchData } from '../utils/network';
import { spawnAsync } from '../utils/spawnAsync'; import { spawnAsync } from '../utils/spawnAsync';
import { getEmbedderName } from '../../utils/userAgent'; import { getEmbedderName } from '../utils/userAgent';
import { lockfile } from '../../utilsBundle'; import { lockfile } from '../../utilsBundle';
import { canAccessFile, existsAsync, removeFolders } from '../utils/fileUtils'; import { canAccessFile, existsAsync, removeFolders } from '../utils/fileUtils';
import type { DependencyGroup } from './dependencies'; import type { DependencyGroup } from './dependencies';
import type { HostPlatform } from '../../utils/hostPlatform'; import type { HostPlatform } from '../utils/hostPlatform';
export { writeDockerVersion } from './dependencies'; export { writeDockerVersion } from './dependencies';

View file

@ -24,7 +24,7 @@ import { SocksProxy } from './utils/socksProxy';
import { ManualPromise, escapeHTML, generateSelfSignedCertificate, rewriteErrorMessage } from '../utils'; import { ManualPromise, escapeHTML, generateSelfSignedCertificate, rewriteErrorMessage } from '../utils';
import { verifyClientCertificates } from './browserContext'; import { verifyClientCertificates } from './browserContext';
import { createProxyAgent } from './fetch'; import { createProxyAgent } from './fetch';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from './utils/debugLogger';
import { createSocket, createTLSSocket } from './utils/happyEyeballs'; import { createSocket, createTLSSocket } from './utils/happyEyeballs';
import type * as types from './types'; import type * as types from './types';

View file

@ -17,7 +17,7 @@
import { frameSnapshotStreamer } from './snapshotterInjected'; import { frameSnapshotStreamer } from './snapshotterInjected';
import { monotonicTime } from '../../../utils/isomorphic/time'; import { monotonicTime } from '../../../utils/isomorphic/time';
import { calculateSha1, createGuid } from '../../utils/crypto'; import { calculateSha1, createGuid } from '../../utils/crypto';
import { debugLogger } from '../../../utils/debugLogger'; import { debugLogger } from '../../utils/debugLogger';
import { eventsHelper } from '../../utils/eventsHelper'; import { eventsHelper } from '../../utils/eventsHelper';
import { mime } from '../../../utilsBundle'; import { mime } from '../../../utilsBundle';
import { BrowserContext } from '../../browserContext'; import { BrowserContext } from '../../browserContext';

View file

@ -20,7 +20,7 @@ import * as path from 'path';
import { Snapshotter } from './snapshotter'; import { Snapshotter } from './snapshotter';
import { commandsWithTracingSnapshots } from '../../../protocol/debug'; import { commandsWithTracingSnapshots } from '../../../protocol/debug';
import { assert } from '../../../utils/debug'; import { assert } from '../../../utils/isomorphic/debug';
import { monotonicTime } from '../../../utils/isomorphic/time'; import { monotonicTime } from '../../../utils/isomorphic/time';
import { eventsHelper } from '../../utils/eventsHelper'; import { eventsHelper } from '../../utils/eventsHelper';
import { createGuid } from '../../utils/crypto'; import { createGuid } from '../../utils/crypto';

View file

@ -1,4 +1,5 @@
[*] [*]
../../common
../../utils ../../utils
../../utils/isomorphic ../../utils/isomorphic
../../utilsBundle.ts ../../utilsBundle.ts

View file

@ -18,8 +18,8 @@
import { compare } from './image_tools/compare'; import { compare } from './image_tools/compare';
// @ts-ignore // @ts-ignore
import pixelmatch from '../../third_party/pixelmatch'; import pixelmatch from '../../third_party/pixelmatch';
import { colors, jpegjs } from '../../utilsBundle'; import { jpegjs } from '../../utilsBundle';
import { diff } from '../../utilsBundle'; import { colors, diff } from '../../utilsBundle';
import { PNG } from '../../utilsBundle'; import { PNG } from '../../utilsBundle';
export type ImageComparatorOptions = { threshold?: number, maxDiffPixels?: number, maxDiffPixelRatio?: number, comparator?: string }; export type ImageComparatorOptions = { threshold?: number, maxDiffPixels?: number, maxDiffPixelRatio?: number, comparator?: string };

View file

@ -16,7 +16,7 @@
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import { assert } from '../../utils/debug'; import { assert } from '../../utils/isomorphic/debug';
export function createGuid(): string { export function createGuid(): string {
return crypto.randomBytes(16).toString('hex'); return crypto.randomBytes(16).toString('hex');

View file

@ -16,7 +16,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import { debug } from '../utilsBundle'; import { debug } from '../../utilsBundle';
const debugLoggerColorMap = { const debugLoggerColorMap = {
'api': 45, // cyan 'api': 45, // cyan

View file

@ -20,7 +20,7 @@ import * as https from 'https';
import * as net from 'net'; import * as net from 'net';
import * as tls from 'tls'; import * as tls from 'tls';
import { assert } from '../../utils/debug'; import { assert } from '../../utils/isomorphic/debug';
import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { ManualPromise } from '../../utils/isomorphic/manualPromise';
import { monotonicTime } from '../../utils/isomorphic/time'; import { monotonicTime } from '../../utils/isomorphic/time';

View file

@ -19,7 +19,7 @@ import * as path from 'path';
import { mime, wsServer } from '../../utilsBundle'; import { mime, wsServer } from '../../utilsBundle';
import { createGuid } from './crypto'; import { createGuid } from './crypto';
import { assert } from '../../utils/debug'; import { assert } from '../../utils/isomorphic/debug';
import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { ManualPromise } from '../../utils/isomorphic/manualPromise';
import { createHttpServer } from './network'; import { createHttpServer } from './network';

View file

@ -23,26 +23,6 @@ let osRelease: {
version: string, version: string,
} | undefined; } | undefined;
export async function getLinuxDistributionInfo(): Promise<{ id: string, version: string } | undefined> {
if (process.platform !== 'linux')
return undefined;
if (!osRelease && !didFailToReadOSRelease) {
try {
// List of /etc/os-release values for different distributions could be
// found here: https://gist.github.com/aslushnikov/8ceddb8288e4cf9db3039c02e0f4fb75
const osReleaseText = await fs.promises.readFile('/etc/os-release', 'utf8');
const fields = parseOSReleaseText(osReleaseText);
osRelease = {
id: fields.get('id') ?? '',
version: fields.get('version_id') ?? '',
};
} catch (e) {
didFailToReadOSRelease = true;
}
}
return osRelease;
}
export function getLinuxDistributionInfoSync(): { id: string, version: string } | undefined { export function getLinuxDistributionInfoSync(): { id: string, version: string } | undefined {
if (process.platform !== 'linux') if (process.platform !== 'linux')
return undefined; return undefined;

View file

@ -19,51 +19,70 @@ import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as util from 'util'; import * as util from 'util';
export type Platform = { import { colors } from '../../utilsBundle';
calculateSha1(text: string): Promise<string>; import { debugLogger } from './debugLogger';
createGuid: () => string; import { currentZone, emptyZone } from './zones';
fs: () => typeof fs;
inspectCustom: symbol | undefined; import type { Platform, Zone } from '../../common/platform';
path: () => typeof path; import type { Zone as ZoneImpl } from './zones';
ws?: (url: string) => WebSocket;
}; class NodeZone implements Zone {
private _zone: ZoneImpl;
constructor(zone: ZoneImpl) {
this._zone = zone;
}
push<T>(data: T) {
return new NodeZone(this._zone.with('apiZone', data));
}
pop() {
return new NodeZone(this._zone.without('apiZone'));
}
run<R>(func: () => R): R {
return this._zone.run(func);
}
data<T>(): T | undefined {
return this._zone.data('apiZone');
}
}
export const nodePlatform: Platform = { export const nodePlatform: Platform = {
name: 'node',
calculateSha1: (text: string) => { calculateSha1: (text: string) => {
const sha1 = crypto.createHash('sha1'); const sha1 = crypto.createHash('sha1');
sha1.update(text); sha1.update(text);
return Promise.resolve(sha1.digest('hex')); return Promise.resolve(sha1.digest('hex'));
}, },
colors,
createGuid: () => crypto.randomBytes(16).toString('hex'), createGuid: () => crypto.randomBytes(16).toString('hex'),
fs: () => fs, fs: () => fs,
inspectCustom: util.inspect.custom, inspectCustom: util.inspect.custom,
isDebuggerAttached: () => !!require('inspector').url(),
isLogEnabled(name: 'api' | 'channel') {
return debugLogger.isEnabled(name);
},
log(name: 'api' | 'channel', message: string | Error | object) {
debugLogger.log(name, message);
},
path: () => path, path: () => path,
};
pathSeparator: path.sep,
export const webPlatform: Platform = {
calculateSha1: async (text: string) => { zones: {
const bytes = new TextEncoder().encode(text); current: () => new NodeZone(currentZone()),
const hashBuffer = await crypto.subtle.digest('SHA-1', bytes); empty: new NodeZone(emptyZone),
return Array.from(new Uint8Array(hashBuffer), b => b.toString(16).padStart(2, '0')).join(''); }
},
createGuid: () => {
return Array.from(crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join('');
},
fs: () => {
throw new Error('File system is not available');
},
inspectCustom: undefined,
path: () => {
throw new Error('Path module is not available');
},
ws: (url: string) => new WebSocket(url),
}; };

View file

@ -17,9 +17,9 @@
import EventEmitter from 'events'; import EventEmitter from 'events';
import * as net from 'net'; import * as net from 'net';
import { assert } from '../../utils/debug'; import { assert } from '../../utils/isomorphic/debug';
import { createGuid } from './crypto'; import { createGuid } from './crypto';
import { debugLogger } from '../../utils/debugLogger'; import { debugLogger } from './debugLogger';
import { createSocket } from './happyEyeballs'; import { createSocket } from './happyEyeballs';
import type { AddressInfo } from 'net'; import type { AddressInfo } from 'net';

View file

@ -77,6 +77,6 @@ export function getEmbedderName(): { embedderName: string, embedderVersion: stri
} }
export function getPlaywrightVersion(majorMinorOnly = false): string { export function getPlaywrightVersion(majorMinorOnly = false): string {
const version = process.env.PW_VERSION_OVERRIDE || require('./../../package.json').version; const version = process.env.PW_VERSION_OVERRIDE || require('./../../../package.json').version;
return majorMinorOnly ? version.split('.').slice(0, 2).join('.') : version; return majorMinorOnly ? version.split('.').slice(0, 2).join('.') : version;
} }

View file

@ -16,7 +16,7 @@
import { createHttpServer } from './network'; import { createHttpServer } from './network';
import { wsServer } from '../../utilsBundle'; import { wsServer } from '../../utilsBundle';
import { debugLogger } from '../../utils/debugLogger'; import { debugLogger } from './debugLogger';
import type { WebSocket, WebSocketServer } from '../../utilsBundle'; import type { WebSocket, WebSocketServer } from '../../utilsBundle';
import type http from 'http'; import type http from 'http';

View file

@ -14,9 +14,9 @@
* limitations under the License. * 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 { export class ZipFile {
private _fileName: string; private _fileName: string;

View file

@ -18,36 +18,13 @@ import { AsyncLocalStorage } from 'async_hooks';
export type ZoneType = 'apiZone' | 'stepZone'; export type ZoneType = 'apiZone' | 'stepZone';
class ZoneManager { const asyncLocalStorage = new AsyncLocalStorage<Zone | undefined>();
private readonly _asyncLocalStorage = new AsyncLocalStorage<Zone | undefined>();
private readonly _emptyZone = Zone.createEmpty(this._asyncLocalStorage);
run<T, R>(type: ZoneType, data: T, func: () => R): R {
return this.current().with(type, data).run(func);
}
zoneData<T>(type: ZoneType): T | undefined {
return this.current().data(type);
}
current(): Zone {
return this._asyncLocalStorage.getStore() ?? this._emptyZone;
}
empty(): Zone {
return this._emptyZone;
}
}
export class Zone { export class Zone {
private readonly _asyncLocalStorage: AsyncLocalStorage<Zone | undefined>; private readonly _asyncLocalStorage: AsyncLocalStorage<Zone | undefined>;
private readonly _data: ReadonlyMap<ZoneType, unknown>; private readonly _data: ReadonlyMap<ZoneType, unknown>;
static createEmpty(asyncLocalStorage: AsyncLocalStorage<Zone | undefined>) { constructor(asyncLocalStorage: AsyncLocalStorage<Zone | undefined>, store: Map<ZoneType, unknown>) {
return new Zone(asyncLocalStorage, new Map());
}
private constructor(asyncLocalStorage: AsyncLocalStorage<Zone | undefined>, store: Map<ZoneType, unknown>) {
this._asyncLocalStorage = asyncLocalStorage; this._asyncLocalStorage = asyncLocalStorage;
this._data = store; this._data = store;
} }
@ -71,4 +48,8 @@ export class Zone {
} }
} }
export const zones = new ZoneManager(); export const emptyZone = new Zone(asyncLocalStorage, new Map());
export function currentZone(): Zone {
return asyncLocalStorage.getStore() ?? emptyZone;
}

View file

@ -18,13 +18,13 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { assert } from '../../utils'; import { assert } from '../../utils';
import { debugLogger } from '../../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { helper } from '../helper'; import { helper } from '../helper';
import { ProtocolError } from '../protocolError'; import { ProtocolError } from '../protocolError';
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport'; import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import type { Protocol } from './protocol'; import type { Protocol } from './protocol';
import type { RecentLogsCollector } from '../../utils/debugLogger'; import type { RecentLogsCollector } from '../utils/debugLogger';
import type { ProtocolLogger } from '../types'; import type { ProtocolLogger } from '../types';

View file

@ -21,8 +21,8 @@ import { assert, debugAssert } from '../../utils';
import { headersArrayToObject } from '../../utils/isomorphic/headers'; import { headersArrayToObject } from '../../utils/isomorphic/headers';
import { createGuid } from '../utils/crypto'; import { createGuid } from '../utils/crypto';
import { eventsHelper } from '../utils/eventsHelper'; import { eventsHelper } from '../utils/eventsHelper';
import { hostPlatform } from '../../utils/hostPlatform'; import { hostPlatform } from '../utils/hostPlatform';
import { splitErrorMessage } from '../../utils/stackTrace'; import { splitErrorMessage } from '../../utils/isomorphic/stackTrace';
import { PNG, jpegjs } from '../../utilsBundle'; import { PNG, jpegjs } from '../../utilsBundle';
import { BrowserContext } from '../browserContext'; import { BrowserContext } from '../browserContext';
import * as dialog from '../dialog'; import * as dialog from '../dialog';
@ -39,7 +39,7 @@ import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './wkInput';
import { WKInterceptableRequest, WKRouteImpl } from './wkInterceptableRequest'; import { WKInterceptableRequest, WKRouteImpl } from './wkInterceptableRequest';
import { WKProvisionalPage } from './wkProvisionalPage'; import { WKProvisionalPage } from './wkProvisionalPage';
import { WKWorkers } from './wkWorkers'; import { WKWorkers } from './wkWorkers';
import { debugLogger } from '../../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import type { Protocol } from './protocol'; import type { Protocol } from './protocol';
import type { WKBrowserContext } from './wkBrowser'; import type { WKBrowserContext } from './wkBrowser';

View file

@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
export * from './utils/isomorphic/colors';
export * from './utils/isomorphic/debug';
export * from './utils/isomorphic/locatorGenerators'; export * from './utils/isomorphic/locatorGenerators';
export * from './utils/isomorphic/manualPromise'; export * from './utils/isomorphic/manualPromise';
export * from './utils/isomorphic/mimeType'; export * from './utils/isomorphic/mimeType';
@ -24,29 +26,30 @@ export * from './utils/isomorphic/time';
export * from './utils/isomorphic/timeoutRunner'; export * from './utils/isomorphic/timeoutRunner';
export * from './utils/isomorphic/urlMatch'; export * from './utils/isomorphic/urlMatch';
export * from './utils/debug';
export * from './utils/debugLogger';
export * from './utils/env';
export * from './utils/hostPlatform';
export * from './utils/isomorphic/headers'; export * from './utils/isomorphic/headers';
export * from './utils/isomorphic/semaphore'; export * from './utils/isomorphic/semaphore';
export * from './utils/platform'; export * from './utils/isomorphic/stackTrace';
export * from './utils/stackTrace';
export * from './utils/task';
export * from './utils/userAgent';
export * from './utils/zipFile';
export * from './utils/zones';
export * from './server/utils/ascii'; export * from './server/utils/ascii';
export * from './server/utils/comparators'; export * from './server/utils/comparators';
export * from './server/utils/crypto'; export * from './server/utils/crypto';
export * from './server/utils/debugLogger';
export * from './server/utils/env';
export * from './server/utils/eventsHelper'; export * from './server/utils/eventsHelper';
export * from './server/utils/expectUtils'; export * from './server/utils/expectUtils';
export * from './server/utils/fileUtils'; export * from './server/utils/fileUtils';
export * from './server/utils/hostPlatform';
export * from './server/utils/httpServer'; export * from './server/utils/httpServer';
export * from './server/utils/network'; export * from './server/utils/network';
export * from './server/utils/nodePlatform';
export * from './server/utils/processLauncher'; export * from './server/utils/processLauncher';
export * from './server/utils/profiler'; export * from './server/utils/profiler';
export * from './server/utils/socksProxy'; export * from './server/utils/socksProxy';
export * from './server/utils/spawnAsync'; export * from './server/utils/spawnAsync';
export * from './server/utils/task';
export * from './server/utils/userAgent';
export * from './server/utils/wsServer'; export * from './server/utils/wsServer';
export * from './server/utils/zipFile';
export * from './server/utils/zones';
export { colors } from './utilsBundle';

View file

@ -0,0 +1,66 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const webColors = {
enabled: true,
reset: (text: string) => applyStyle(0, 0, text),
bold: (text: string) => applyStyle(1, 22, text),
dim: (text: string) => applyStyle(2, 22, text),
italic: (text: string) => applyStyle(3, 23, text),
underline: (text: string) => applyStyle(4, 24, text),
inverse: (text: string) => applyStyle(7, 27, text),
hidden: (text: string) => applyStyle(8, 28, text),
strikethrough: (text: string) => applyStyle(9, 29, text),
black: (text: string) => applyStyle(30, 39, text),
red: (text: string) => applyStyle(31, 39, text),
green: (text: string) => applyStyle(32, 39, text),
yellow: (text: string) => applyStyle(33, 39, text),
blue: (text: string) => applyStyle(34, 39, text),
magenta: (text: string) => applyStyle(35, 39, text),
cyan: (text: string) => applyStyle(36, 39, text),
white: (text: string) => applyStyle(37, 39, text),
gray: (text: string) => applyStyle(90, 39, text),
grey: (text: string) => applyStyle(90, 39, text),
};
export type Colors = typeof webColors;
export const noColors: Colors = {
enabled: false,
reset: t => t,
bold: t => t,
dim: t => t,
italic: t => t,
underline: t => t,
inverse: t => t,
hidden: t => t,
strikethrough: t => t,
black: t => t,
red: t => t,
green: t => t,
yellow: t => t,
blue: t => t,
magenta: t => t,
cyan: t => t,
white: t => t,
gray: t => t,
grey: t => t,
};
const applyStyle = (open: number, close: number, text: string) => `\u001b[${open}m${text}\u001b[${close}m`;

View file

@ -14,8 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
import { getFromENV } from './env';
export function assert(value: any, message?: string): asserts value { export function assert(value: any, message?: string): asserts value {
if (!value) if (!value)
throw new Error(message || 'Assertion error'); throw new Error(message || 'Assertion error');
@ -26,13 +24,18 @@ export function debugAssert(value: any, message?: string): asserts value {
throw new Error(message); throw new Error(message);
} }
const debugEnv = getFromENV('PWDEBUG') || ''; let _debugMode: string | undefined;
export function setDebugMode(mode: string) {
_debugMode = mode;
}
export function debugMode() { export function debugMode() {
if (debugEnv === 'console') if (_debugMode === 'console')
return 'console'; return 'console';
if (debugEnv === '0' || debugEnv === 'false') if (_debugMode === '0' || _debugMode === 'false')
return ''; return '';
return debugEnv ? 'inspector' : ''; return _debugMode ? 'inspector' : '';
} }
let _isUnderTest = !!process.env.PWTEST_UNDER_TEST; let _isUnderTest = !!process.env.PWTEST_UNDER_TEST;

Some files were not shown because too many files have changed in this diff Show more