chore: do not use process in client (#34816)

This commit is contained in:
Pavel Feldman 2025-02-15 19:49:30 -08:00 committed by GitHub
parent 3606a434fe
commit f70f92d5cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 72 additions and 33 deletions

View File

@ -177,7 +177,7 @@ const noBooleanCompareRules = {
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 2, '@typescript-eslint/no-unnecessary-boolean-literal-compare': 2,
}; };
const noRestrictedGlobalsRules = { const noWebGlobalsRules = {
'no-restricted-globals': [ 'no-restricted-globals': [
'error', 'error',
{ 'name': 'window' }, { 'name': 'window' },
@ -186,6 +186,13 @@ const noRestrictedGlobalsRules = {
], ],
}; };
const noNodeGlobalsRules = {
'no-restricted-globals': [
'error',
{ 'name': 'process' },
],
};
const importOrderRules = { const importOrderRules = {
'import/order': [2, { 'import/order': [2, {
'groups': ['builtin', 'external', 'internal', ['parent', 'sibling'], 'index', 'type'], 'groups': ['builtin', 'external', 'internal', ['parent', 'sibling'], 'index', 'type'],
@ -249,7 +256,19 @@ export default [{
files: ['packages/playwright-core/src/server/injected/**/*.ts'], files: ['packages/playwright-core/src/server/injected/**/*.ts'],
languageOptions: languageOptionsWithTsConfig, languageOptions: languageOptionsWithTsConfig,
rules: { rules: {
...noRestrictedGlobalsRules, ...noWebGlobalsRules,
...noFloatingPromisesRules,
...noBooleanCompareRules,
}
}, {
files: [
'packages/playwright-core/src/client/**/*.ts',
'packages/playwright-core/src/protocol/**/*.ts',
'packages/playwright-core/src/utils/**/*.ts',
],
languageOptions: languageOptionsWithTsConfig,
rules: {
...noNodeGlobalsRules,
...noFloatingPromisesRules, ...noFloatingPromisesRules,
...noBooleanCompareRules, ...noBooleanCompareRules,
} }

View File

@ -23,9 +23,9 @@ import * as path from 'path';
import * as playwright from '../..'; import * as playwright from '../..';
import { launchBrowserServer, printApiJson, runDriver, runServer } from './driver'; import { launchBrowserServer, printApiJson, runDriver, runServer } from './driver';
import { registry, writeDockerVersion } from '../server'; import { registry, writeDockerVersion } from '../server';
import { gracefullyProcessExitDoNotHang } from '../utils'; import { gracefullyProcessExitDoNotHang, isLikelyNpxGlobal } from '../utils';
import { runTraceInBrowser, runTraceViewerApp } from '../server/trace/viewer/traceViewer'; import { runTraceInBrowser, runTraceViewerApp } from '../server/trace/viewer/traceViewer';
import { assert, getPackageManagerExecCommand, isLikelyNpxGlobal } from '../utils'; import { assert, getPackageManagerExecCommand } from '../utils';
import { wrapInASCIIBox } from '../server/utils/ascii'; import { wrapInASCIIBox } from '../server/utils/ascii';
import { dotenv, program } from '../utilsBundle'; import { dotenv, program } from '../utilsBundle';

View File

@ -51,7 +51,9 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
setDefaultTimeout(timeout: number) { setDefaultTimeout(timeout: number) {
this._timeoutSettings.setDefaultTimeout(timeout); this._timeoutSettings.setDefaultTimeout(timeout);
this._channel.setDefaultTimeoutNoReply({ timeout }); this._wrapApiCall(async () => {
await this._channel.setDefaultTimeoutNoReply({ timeout });
}, true).catch(() => {});
} }
async devices(options: { port?: number } = {}): Promise<AndroidDevice[]> { async devices(options: { port?: number } = {}): Promise<AndroidDevice[]> {
@ -133,7 +135,9 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
setDefaultTimeout(timeout: number) { setDefaultTimeout(timeout: number) {
this._timeoutSettings.setDefaultTimeout(timeout); this._timeoutSettings.setDefaultTimeout(timeout);
this._channel.setDefaultTimeoutNoReply({ timeout }); this._wrapApiCall(async () => {
await this._channel.setDefaultTimeoutNoReply({ timeout });
}, true).catch(() => {});
} }
serial(): string { serial(): string {

View File

@ -246,15 +246,15 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
setDefaultNavigationTimeout(timeout: number | undefined) { setDefaultNavigationTimeout(timeout: number | undefined) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout); this._timeoutSettings.setDefaultNavigationTimeout(timeout);
this._wrapApiCall(async () => { this._wrapApiCall(async () => {
this._channel.setDefaultNavigationTimeoutNoReply({ timeout }).catch(() => {}); await this._channel.setDefaultNavigationTimeoutNoReply({ timeout });
}, true); }, true).catch(() => {});
} }
setDefaultTimeout(timeout: number | undefined) { setDefaultTimeout(timeout: number | undefined) {
this._timeoutSettings.setDefaultTimeout(timeout); this._timeoutSettings.setDefaultTimeout(timeout);
this._wrapApiCall(async () => { this._wrapApiCall(async () => {
this._channel.setDefaultTimeoutNoReply({ timeout }).catch(() => {}); await this._channel.setDefaultTimeoutNoReply({ timeout });
}, true); }, true).catch(() => {});
} }
browser(): Browser | null { browser(): Browser | null {
@ -559,7 +559,7 @@ export async function prepareBrowserContextParams(platform: Platform, options: B
}; };
} }
if (contextParams.recordVideo && contextParams.recordVideo.dir) if (contextParams.recordVideo && contextParams.recordVideo.dir)
contextParams.recordVideo.dir = platform.path().resolve(process.cwd(), contextParams.recordVideo.dir); contextParams.recordVideo.dir = platform.path().resolve(contextParams.recordVideo.dir);
return contextParams; return contextParams;
} }

View File

@ -199,7 +199,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
} }
return result; return result;
} catch (e) { } catch (e) {
const innerError = ((process.env.PWDEBUGIMPL || this._platform.isUnderTest()) && e.stack) ? '\n<inner error>\n' + e.stack : ''; const innerError = ((this._platform.showInternalStackFrames() || this._platform.isUnderTest()) && e.stack) ? '\n<inner error>\n' + e.stack : '';
if (apiZone.apiName && !apiZone.apiName.includes('<anonymous>')) if (apiZone.apiName && !apiZone.apiName.includes('<anonymous>'))
e.message = apiZone.apiName + ': ' + e.message; e.message = apiZone.apiName + ': ' + e.message;
const stackFrames = '\n' + stringifyStackFrames(stackTrace.frames).join('\n') + innerError; const stackFrames = '\n' + stringifyStackFrames(stackTrace.frames).join('\n') + innerError;

View File

@ -28,7 +28,7 @@ export function captureLibraryStackTrace(platform: Platform): { frames: StackFra
isPlaywrightLibrary: boolean; isPlaywrightLibrary: boolean;
}; };
let parsedFrames = stack.map(line => { let parsedFrames = stack.map(line => {
const frame = parseStackFrame(line, platform.pathSeparator); const frame = parseStackFrame(line, platform.pathSeparator, platform.showInternalStackFrames());
if (!frame || !frame.file) if (!frame || !frame.file)
return null; return null;
const isPlaywrightLibrary = !!platform.coreDir && frame.file.startsWith(platform.coreDir); const isPlaywrightLibrary = !!platform.coreDir && frame.file.startsWith(platform.coreDir);
@ -62,10 +62,8 @@ export function captureLibraryStackTrace(platform: Platform): { frames: StackFra
} }
// This is for the inspector so that it did not include the test runner stack frames. // This is for the inspector so that it did not include the test runner stack frames.
const filterPrefixes = platform.coreDir ? [platform.coreDir, ...platform.boxedStackPrefixes()] : platform.boxedStackPrefixes(); const filterPrefixes = platform.boxedStackPrefixes();
parsedFrames = parsedFrames.filter(f => { parsedFrames = parsedFrames.filter(f => {
if (process.env.PWDEBUGIMPL)
return true;
if (filterPrefixes.some(prefix => f.frame.file.startsWith(prefix))) if (filterPrefixes.some(prefix => f.frame.file.startsWith(prefix)))
return false; return false;
return true; return true;

View File

@ -54,7 +54,7 @@ export class Electron extends ChannelOwner<channels.ElectronChannel> implements
async launch(options: ElectronOptions = {}): Promise<ElectronApplication> { async launch(options: ElectronOptions = {}): Promise<ElectronApplication> {
const params: channels.ElectronLaunchParams = { const params: channels.ElectronLaunchParams = {
...await prepareBrowserContextParams(this._platform, options), ...await prepareBrowserContextParams(this._platform, options),
env: envObjectToArray(options.env ? options.env : process.env), env: envObjectToArray(options.env ? options.env : this._platform.env),
tracesDir: options.tracesDir, tracesDir: options.tracesDir,
}; };
const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication); const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);

View File

@ -277,15 +277,15 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
setDefaultNavigationTimeout(timeout: number) { setDefaultNavigationTimeout(timeout: number) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout); this._timeoutSettings.setDefaultNavigationTimeout(timeout);
this._wrapApiCall(async () => { this._wrapApiCall(async () => {
this._channel.setDefaultNavigationTimeoutNoReply({ timeout }).catch(() => {}); await this._channel.setDefaultNavigationTimeoutNoReply({ timeout });
}, true); }, true).catch(() => {});
} }
setDefaultTimeout(timeout: number) { setDefaultTimeout(timeout: number) {
this._timeoutSettings.setDefaultTimeout(timeout); this._timeoutSettings.setDefaultTimeout(timeout);
this._wrapApiCall(async () => { this._wrapApiCall(async () => {
this._channel.setDefaultTimeoutNoReply({ timeout }).catch(() => {}); await this._channel.setDefaultTimeoutNoReply({ timeout });
}, true); }, true).catch(() => {});
} }
private _forceVideo(): Video { private _forceVideo(): Video {

View File

@ -45,6 +45,7 @@ export type Platform = {
coreDir?: string; coreDir?: string;
createGuid: () => string; createGuid: () => string;
defaultMaxListeners: () => number; defaultMaxListeners: () => number;
env: Record<string, string | undefined>;
fs: () => typeof fs; fs: () => typeof fs;
inspectCustom: symbol | undefined; inspectCustom: symbol | undefined;
isDebugMode: () => boolean; isDebugMode: () => boolean;
@ -54,6 +55,7 @@ export type Platform = {
log: (name: 'api' | 'channel', message: string | Error | object) => void; log: (name: 'api' | 'channel', message: string | Error | object) => void;
path: () => typeof path; path: () => typeof path;
pathSeparator: string; pathSeparator: string;
showInternalStackFrames: () => boolean,
streamFile: (path: string, writable: Writable) => Promise<void>, streamFile: (path: string, writable: Writable) => Promise<void>,
streamReadable: (channel: channels.StreamChannel) => Readable, streamReadable: (channel: channels.StreamChannel) => Readable,
streamWritable: (channel: channels.WritableStreamChannel) => Writable, streamWritable: (channel: channels.WritableStreamChannel) => Writable,
@ -77,6 +79,8 @@ export const emptyPlatform: Platform = {
defaultMaxListeners: () => 10, defaultMaxListeners: () => 10,
env: {},
fs: () => { fs: () => {
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },
@ -101,6 +105,8 @@ export const emptyPlatform: Platform = {
pathSeparator: '/', pathSeparator: '/',
showInternalStackFrames: () => false,
streamFile(path: string, writable: Writable): Promise<void> { streamFile(path: string, writable: Writable): Promise<void> {
throw new Error('Streams are not available'); throw new Error('Streams are not available');
}, },

View File

@ -96,8 +96,8 @@ export class Waiter {
log(s: string) { log(s: string) {
this._logs.push(s); this._logs.push(s);
this._channelOwner._wrapApiCall(async () => { this._channelOwner._wrapApiCall(async () => {
await this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'log', message: s } }).catch(() => {}); await this._channelOwner._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'log', message: s } });
}, true); }, true).catch(() => {});
} }
private _rejectOn(promise: Promise<any>, dispose?: () => void) { private _rejectOn(promise: Promise<any>, dispose?: () => void) {

View File

@ -39,7 +39,7 @@ export async function connectOverWebSocket(parentConnection: Connection, params:
connection!.dispatch(message); connection!.dispatch(message);
} catch (e) { } catch (e) {
closeError = String(e); closeError = String(e);
transport.close(); transport.close().catch(() => {});
} }
}); });
return connection; return connection;
@ -70,7 +70,7 @@ class JsonPipeTransport implements Transport {
} }
async send(message: object) { async send(message: object) {
this._owner._wrapApiCall(async () => { await this._owner._wrapApiCall(async () => {
await this._pipe!.send({ message }); await this._pipe!.send({ message });
}, /* isInternal */ true); }, /* isInternal */ true);
} }

View File

@ -47,3 +47,7 @@ export function getPackageManagerExecCommand() {
return 'pnpm exec'; return 'pnpm exec';
return 'npx'; return 'npx';
} }
export function isLikelyNpxGlobal() {
return process.argv.length >= 2 && process.argv[1].includes('_npx');
}

View File

@ -61,10 +61,16 @@ export function setBoxedStackPrefixes(prefixes: string[]) {
boxedStackPrefixes = prefixes; boxedStackPrefixes = prefixes;
} }
const coreDir = path.dirname(require.resolve('../../../package.json'));
export const nodePlatform: Platform = { export const nodePlatform: Platform = {
name: 'node', name: 'node',
boxedStackPrefixes: () => boxedStackPrefixes, boxedStackPrefixes: () => {
if (process.env.PWDEBUGIMPL)
return [];
return [coreDir, ...boxedStackPrefixes];
},
calculateSha1: (text: string) => { calculateSha1: (text: string) => {
const sha1 = crypto.createHash('sha1'); const sha1 = crypto.createHash('sha1');
@ -74,13 +80,15 @@ export const nodePlatform: Platform = {
colors, colors,
coreDir: path.dirname(require.resolve('../../../package.json')), coreDir,
createGuid: () => crypto.randomBytes(16).toString('hex'), createGuid: () => crypto.randomBytes(16).toString('hex'),
defaultMaxListeners: () => EventEmitter.defaultMaxListeners, defaultMaxListeners: () => EventEmitter.defaultMaxListeners,
fs: () => fs, fs: () => fs,
env: process.env,
inspectCustom: util.inspect.custom, inspectCustom: util.inspect.custom,
isDebugMode: () => !!debugMode(), isDebugMode: () => !!debugMode(),
@ -101,6 +109,8 @@ export const nodePlatform: Platform = {
pathSeparator: path.sep, pathSeparator: path.sep,
showInternalStackFrames: () => !!process.env.PWDEBUGIMPL,
async streamFile(path: string, stream: Writable): Promise<void> { async streamFile(path: string, stream: Writable): Promise<void> {
await pipelineAsync(fs.createReadStream(path), stream); await pipelineAsync(fs.createReadStream(path), stream);
}, },

View File

@ -27,5 +27,3 @@ export function isObject(obj: any): obj is NonNullable<object> {
export function isError(obj: any): obj is Error { export function isError(obj: any): obj is Error {
return obj instanceof Error || (obj && Object.getPrototypeOf(obj)?.name === 'Error'); return obj instanceof Error || (obj && Object.getPrototypeOf(obj)?.name === 'Error');
} }
export const isLikelyNpxGlobal = () => process.argv.length >= 2 && process.argv[1].includes('_npx');

View File

@ -37,7 +37,7 @@ export function captureRawStack(): RawStack {
return stack.split('\n'); return stack.split('\n');
} }
export function parseStackFrame(text: string, pathSeparator: string): StackFrame | null { export function parseStackFrame(text: string, pathSeparator: string, showInternalStackFrames: boolean): StackFrame | null {
const match = text && text.match(re); const match = text && text.match(re);
if (!match) if (!match)
return null; return null;
@ -46,7 +46,7 @@ export function parseStackFrame(text: string, pathSeparator: string): StackFrame
let file = match[7]; let file = match[7];
if (!file) if (!file)
return null; return null;
if (!process.env.PWDEBUGIMPL && (file.startsWith('internal') || file.startsWith('node:'))) if (!showInternalStackFrames && (file.startsWith('internal') || file.startsWith('node:')))
return null; return null;
const line = match[8]; const line = match[8];

View File

@ -522,7 +522,7 @@ export function prepareErrorStack(stack: string): {
const stackLines = lines.slice(firstStackLine); const stackLines = lines.slice(firstStackLine);
let location: Location | undefined; let location: Location | undefined;
for (const line of stackLines) { for (const line of stackLines) {
const frame = parseStackFrame(line, path.sep); const frame = parseStackFrame(line, path.sep, !!process.env.PWDEBUGIMPL);
if (!frame || !frame.file) if (!frame || !frame.file)
continue; continue;
if (belongsToNodeModules(frame.file)) if (belongsToNodeModules(frame.file))

View File

@ -55,7 +55,7 @@ export function filterStackFile(file: string) {
export function filteredStackTrace(rawStack: RawStack): StackFrame[] { export function filteredStackTrace(rawStack: RawStack): StackFrame[] {
const frames: StackFrame[] = []; const frames: StackFrame[] = [];
for (const line of rawStack) { for (const line of rawStack) {
const frame = parseStackFrame(line, path.sep); const frame = parseStackFrame(line, path.sep, !!process.env.PWDEBUGIMPL);
if (!frame || !frame.file) if (!frame || !frame.file)
continue; continue;
if (!filterStackFile(frame.file)) if (!filterStackFile(frame.file))