Skip to content

Commit

Permalink
chore: introduce platform for client (1) (#34683)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman authored Feb 10, 2025
1 parent 0672f1c commit 5d500dd
Show file tree
Hide file tree
Showing 75 changed files with 499 additions and 381 deletions.
4 changes: 4 additions & 0 deletions packages/playwright-core/src/DEPS.list
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
[inProcessFactory.ts]
**

[inprocess.ts]
common/

[outofprocess.ts]
client/
protocol/
utils/
common/
1 change: 1 addition & 0 deletions packages/playwright-core/src/cli/DEPS.list
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
../common
../debug/injected
../generated/
../server/
../server/injected/
../server/trace
../utils
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/cli/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import * as playwright from '../..';
import { PipeTransport } from '../protocol/transport';
import { PlaywrightServer } from '../remote/playwrightServer';
import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from '../server';
import { gracefullyProcessExitDoNotHang } from '../utils/processLauncher';
import { gracefullyProcessExitDoNotHang } from '../server/processLauncher';

import type { BrowserType } from '../client/browserType';
import type { LaunchServerOptions } from '../client/types';
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/cli/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import * as os from 'os';
import * as path from 'path';

import * as playwright from '../..';
import { registry, writeDockerVersion } from '../server';
import { launchBrowserServer, printApiJson, runDriver, runServer } from './driver';
import { isTargetClosedError } from '../client/errors';
import { gracefullyProcessExitDoNotHang, registry, writeDockerVersion } from '../server';
import { runTraceInBrowser, runTraceViewerApp } from '../server/trace/viewer/traceViewer';
import { assert, getPackageManagerExecCommand, gracefullyProcessExitDoNotHang, isLikelyNpxGlobal, wrapInASCIIBox } from '../utils';
import { assert, getPackageManagerExecCommand, isLikelyNpxGlobal, wrapInASCIIBox } from '../utils';
import { dotenv, program } from '../utilsBundle';

import type { Browser } from '../client/browser';
Expand Down
3 changes: 2 additions & 1 deletion packages/playwright-core/src/cli/programWithTestStub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

/* eslint-disable no-console */

import { getPackageManager, gracefullyProcessExitDoNotHang } from '../utils';
import { gracefullyProcessExitDoNotHang } from '../server';
import { getPackageManager } from '../utils';
import { program } from './program';
export { program } from './program';

Expand Down
19 changes: 10 additions & 9 deletions packages/playwright-core/src/client/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,23 @@
*/

import { EventEmitter } from 'events';
import * as fs from 'fs';

import { isRegExp, isString, monotonicTime } from '../utils';
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { Connection } from './connection';
import { TargetClosedError, isTargetClosedError } from './errors';
import { Events } from './events';
import { Waiter } from './waiter';
import { TimeoutSettings } from '../common/timeoutSettings';
import { isRegExp, isString } from '../utils/rtti';
import { monotonicTime } from '../utils/time';
import { raceAgainstDeadline } from '../utils/timeoutRunner';

import type { Page } from './page';
import type * as types from './types';
import type * as api from '../../types/types';
import type { AndroidServerLauncherImpl } from '../androidServerImpl';
import type { Platform } from '../common/platform';
import type * as channels from '@protocol/channels';

type Direction = 'down' | 'up' | 'left' | 'right';
Expand Down Expand Up @@ -73,7 +74,7 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
const connectParams: channels.LocalUtilsConnectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout };
const { pipe } = await localUtils._channel.connect(connectParams);
const closePipe = () => pipe.close().catch(() => {});
const connection = new Connection(localUtils, this._instrumentation);
const connection = new Connection(localUtils, this._platform, this._instrumentation);
connection.markAsRemote();
connection.on('close', closePipe);

Expand Down Expand Up @@ -232,7 +233,7 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
async screenshot(options: { path?: string } = {}): Promise<Buffer> {
const { binary } = await this._channel.screenshot();
if (options.path)
await fs.promises.writeFile(options.path, binary);
await this._platform.fs().promises.writeFile(options.path, binary);
return binary;
}

Expand Down Expand Up @@ -267,15 +268,15 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
}

async installApk(file: string | Buffer, options?: { args: string[] }): Promise<void> {
await this._channel.installApk({ file: await loadFile(file), args: options && options.args });
await this._channel.installApk({ file: await loadFile(this._platform, file), args: options && options.args });
}

async push(file: string | Buffer, path: string, options?: { mode: number }): Promise<void> {
await this._channel.push({ file: await loadFile(file), path, mode: options ? options.mode : undefined });
await this._channel.push({ file: await loadFile(this._platform, file), path, mode: options ? options.mode : undefined });
}

async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise<BrowserContext> {
const contextOptions = await prepareBrowserContextParams(options);
const contextOptions = await prepareBrowserContextParams(this._platform, options);
const result = await this._channel.launchBrowser(contextOptions);
const context = BrowserContext.from(result.context) as BrowserContext;
context._setOptions(contextOptions, {});
Expand Down Expand Up @@ -321,9 +322,9 @@ export class AndroidSocket extends ChannelOwner<channels.AndroidSocketChannel> i
}
}

async function loadFile(file: string | Buffer): Promise<Buffer> {
async function loadFile(platform: Platform, file: string | Buffer): Promise<Buffer> {
if (isString(file))
return await fs.promises.readFile(file);
return await platform.fs().promises.readFile(file);
return file;
}

Expand Down
6 changes: 2 additions & 4 deletions packages/playwright-core/src/client/artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
* limitations under the License.
*/

import * as fs from 'fs';

import { ChannelOwner } from './channelOwner';
import { Stream } from './stream';
import { mkdirIfNeeded } from '../utils/fileUtils';
Expand All @@ -42,9 +40,9 @@ export class Artifact extends ChannelOwner<channels.ArtifactChannel> {

const result = await this._channel.saveAsStream();
const stream = Stream.from(result.stream);
await mkdirIfNeeded(path);
await mkdirIfNeeded(this._platform, path);
await new Promise((resolve, reject) => {
stream.stream().pipe(fs.createWriteStream(path))
stream.stream().pipe(this._platform.fs().createWriteStream(path))
.on('finish' as any, resolve)
.on('error' as any, reject);
});
Expand Down
10 changes: 4 additions & 6 deletions packages/playwright-core/src/client/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@
* limitations under the License.
*/

import * as fs from 'fs';

import { Artifact } from './artifact';
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
import { CDPSession } from './cdpSession';
import { ChannelOwner } from './channelOwner';
import { isTargetClosedError } from './errors';
import { Events } from './events';
import { mkdirIfNeeded } from '../utils';
import { mkdirIfNeeded } from '../utils/fileUtils';

import type { BrowserType } from './browserType';
import type { Page } from './page';
Expand Down Expand Up @@ -83,7 +81,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap

async _innerNewContext(options: BrowserContextOptions = {}, forReuse: boolean): Promise<BrowserContext> {
options = { ...this._browserType._playwright._defaultContextOptions, ...options };
const contextOptions = await prepareBrowserContextParams(options);
const contextOptions = await prepareBrowserContextParams(this._platform, options);
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
const context = BrowserContext.from(response.context);
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
Expand Down Expand Up @@ -126,8 +124,8 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
const buffer = await artifact.readIntoBuffer();
await artifact.delete();
if (this._path) {
await mkdirIfNeeded(this._path);
await fs.promises.writeFile(this._path, buffer);
await mkdirIfNeeded(this._platform, this._path);
await this._platform.fs().promises.writeFile(this._path, buffer);
this._path = undefined;
}
return buffer;
Expand Down
35 changes: 18 additions & 17 deletions packages/playwright-core/src/client/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
* limitations under the License.
*/

import * as fs from 'fs';
import * as path from 'path';

import { Artifact } from './artifact';
import { Browser } from './browser';
import { CDPSession } from './cdpSession';
Expand All @@ -38,14 +35,18 @@ import { Waiter } from './waiter';
import { WebError } from './webError';
import { Worker } from './worker';
import { TimeoutSettings } from '../common/timeoutSettings';
import { headersObjectToArray, isRegExp, isString, mkdirIfNeeded, urlMatchesEqual } from '../utils';
import { mkdirIfNeeded } from '../utils/fileUtils';
import { headersObjectToArray } from '../utils/headers';
import { urlMatchesEqual } from '../utils/isomorphic/urlMatch';
import { isRegExp, isString } from '../utils/rtti';
import { rewriteErrorMessage } from '../utils/stackTrace';

import type { BrowserType } from './browserType';
import type { BrowserContextOptions, Headers, LaunchOptions, StorageState, WaitForEventOptions } from './types';
import type * as structs from '../../types/structs';
import type * as api from '../../types/types';
import type { URLMatch } from '../utils';
import type { Platform } from '../common/platform';
import type { URLMatch } from '../utils/isomorphic/urlMatch';
import type * as channels from '@protocol/channels';

export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
Expand Down Expand Up @@ -107,7 +108,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
this.emit(Events.BrowserContext.ServiceWorker, serviceWorker);
});
this._channel.on('console', event => {
const consoleMessage = new ConsoleMessage(event);
const consoleMessage = new ConsoleMessage(this._platform, event);
this.emit(Events.BrowserContext.Console, consoleMessage);
const page = consoleMessage.page();
if (page)
Expand Down Expand Up @@ -321,7 +322,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
}

async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void> {
const source = await evaluationScript(script, arg);
const source = await evaluationScript(this._platform, script, arg);
await this._channel.addInitScript({ source });
}

Expand Down Expand Up @@ -431,8 +432,8 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
async storageState(options: { path?: string, indexedDB?: boolean } = {}): Promise<StorageState> {
const state = await this._channel.storageState({ indexedDB: options.indexedDB });
if (options.path) {
await mkdirIfNeeded(options.path);
await fs.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
await mkdirIfNeeded(this._platform, options.path);
await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
}
return state;
}
Expand Down Expand Up @@ -500,11 +501,11 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
}
}

async function prepareStorageState(options: BrowserContextOptions): Promise<channels.BrowserNewContextParams['storageState']> {
async function prepareStorageState(platform: Platform, options: BrowserContextOptions): Promise<channels.BrowserNewContextParams['storageState']> {
if (typeof options.storageState !== 'string')
return options.storageState;
try {
return JSON.parse(await fs.promises.readFile(options.storageState, 'utf8'));
return JSON.parse(await platform.fs().promises.readFile(options.storageState, 'utf8'));
} catch (e) {
rewriteErrorMessage(e, `Error reading storage state from ${options.storageState}:\n` + e.message);
throw e;
Expand All @@ -524,7 +525,7 @@ function prepareRecordHarOptions(options: BrowserContextOptions['recordHar']): c
};
}

export async function prepareBrowserContextParams(options: BrowserContextOptions): Promise<channels.BrowserNewContextParams> {
export async function prepareBrowserContextParams(platform: Platform, options: BrowserContextOptions): Promise<channels.BrowserNewContextParams> {
if (options.videoSize && !options.videosPath)
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
if (options.extraHTTPHeaders)
Expand All @@ -534,15 +535,15 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions
viewport: options.viewport === null ? undefined : options.viewport,
noDefaultViewport: options.viewport === null,
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
storageState: await prepareStorageState(options),
storageState: await prepareStorageState(platform, options),
serviceWorkers: options.serviceWorkers,
recordHar: prepareRecordHarOptions(options.recordHar),
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion,
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors,
contrast: options.contrast === null ? 'no-override' : options.contrast,
acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
clientCertificates: await toClientCertificatesProtocol(options.clientCertificates),
clientCertificates: await toClientCertificatesProtocol(platform, options.clientCertificates),
};
if (!contextParams.recordVideo && options.videosPath) {
contextParams.recordVideo = {
Expand All @@ -551,7 +552,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions
};
}
if (contextParams.recordVideo && contextParams.recordVideo.dir)
contextParams.recordVideo.dir = path.resolve(process.cwd(), contextParams.recordVideo.dir);
contextParams.recordVideo.dir = platform.path().resolve(process.cwd(), contextParams.recordVideo.dir);
return contextParams;
}

Expand All @@ -563,15 +564,15 @@ function toAcceptDownloadsProtocol(acceptDownloads?: boolean) {
return 'deny';
}

export async function toClientCertificatesProtocol(certs?: BrowserContextOptions['clientCertificates']): Promise<channels.PlaywrightNewRequestParams['clientCertificates']> {
export async function toClientCertificatesProtocol(platform: Platform, certs?: BrowserContextOptions['clientCertificates']): Promise<channels.PlaywrightNewRequestParams['clientCertificates']> {
if (!certs)
return undefined;

const bufferizeContent = async (value?: Buffer, path?: string): Promise<Buffer | undefined> => {
if (value)
return value;
if (path)
return await fs.promises.readFile(path);
return await platform.fs().promises.readFile(path);
};

return await Promise.all(certs.map(async cert => ({
Expand Down
8 changes: 5 additions & 3 deletions packages/playwright-core/src/client/browserType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import { ChannelOwner } from './channelOwner';
import { envObjectToArray } from './clientHelper';
import { Connection } from './connection';
import { Events } from './events';
import { assert, headersObjectToArray, monotonicTime } from '../utils';
import { assert } from '../utils/debug';
import { headersObjectToArray } from '../utils/headers';
import { monotonicTime } from '../utils/time';
import { raceAgainstDeadline } from '../utils/timeoutRunner';

import type { Playwright } from './playwright';
Expand Down Expand Up @@ -90,7 +92,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
assert(!(options as any).port, 'Cannot specify a port without launching as a server.');
options = { ...this._playwright._defaultLaunchOptions, ...this._playwright._defaultContextOptions, ...options };
const contextParams = await prepareBrowserContextParams(options);
const contextParams = await prepareBrowserContextParams(this._platform, options);
const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = {
...contextParams,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
Expand Down Expand Up @@ -133,7 +135,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
connectParams.socksProxyRedirectPortForTest = (params as any).__testHookRedirectPortForwarding;
const { pipe, headers: connectHeaders } = await localUtils._channel.connect(connectParams);
const closePipe = () => pipe.close().catch(() => {});
const connection = new Connection(localUtils, this._instrumentation);
const connection = new Connection(localUtils, this._platform, this._instrumentation);
connection.markAsRemote();
connection.on('close', closePipe);

Expand Down
5 changes: 4 additions & 1 deletion packages/playwright-core/src/client/channelOwner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@

import { EventEmitter } from './eventEmitter';
import { ValidationError, maybeFindValidator } from '../protocol/validator';
import { isUnderTest } from '../utils';
import { isUnderTest } from '../utils/debug';
import { debugLogger } from '../utils/debugLogger';
import { captureLibraryStackTrace, stringifyStackFrames } from '../utils/stackTrace';
import { zones } from '../utils/zones';

import type { ClientInstrumentation } from './clientInstrumentation';
import type { Connection } from './connection';
import type { Logger } from './types';
import type { Platform } from '../common/platform';
import type { ValidatorContext } from '../protocol/validator';
import type * as channels from '@protocol/channels';

Expand All @@ -39,6 +40,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
readonly _channel: T;
readonly _initializer: channels.InitializerTraits<T>;
_logger: Logger | undefined;
readonly _platform: Platform;
readonly _instrumentation: ClientInstrumentation;
private _eventToSubscriptionMapping: Map<string, string> = new Map();
private _isInternalType = false;
Expand All @@ -52,6 +54,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
this._guid = guid;
this._parent = parent instanceof ChannelOwner ? parent : undefined;
this._instrumentation = this._connection._instrumentation;
this._platform = this._connection.platform;

this._connection._objects.set(guid, this);
if (this._parent) {
Expand Down
Loading

0 comments on commit 5d500dd

Please sign in to comment.