chore(zones): prepare to remove wrapApiCall, introduce zones (#10427)
This commit is contained in:
parent
19f739dec8
commit
b302152789
|
|
@ -147,7 +147,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
if (routeHandler.handle(route, request)) {
|
if (routeHandler.handle(route, request)) {
|
||||||
this._routes.splice(this._routes.indexOf(routeHandler), 1);
|
this._routes.splice(this._routes.indexOf(routeHandler), 1);
|
||||||
if (!this._routes.length)
|
if (!this._routes.length)
|
||||||
this._wrapApiCall(channel => this._disableInterception(channel), undefined, true).catch(() => {});
|
this._wrapApiCall(channel => this._disableInterception(channel), true).catch(() => {});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
browser._logger = logger;
|
browser._logger = logger;
|
||||||
browser._setBrowserType(this);
|
browser._setBrowserType(this);
|
||||||
return browser;
|
return browser;
|
||||||
}, logger);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async launchServer(options: LaunchServerOptions = {}): Promise<api.BrowserServer> {
|
async launchServer(options: LaunchServerOptions = {}): Promise<api.BrowserServer> {
|
||||||
|
|
@ -113,7 +113,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
context._setBrowserType(this);
|
context._setBrowserType(this);
|
||||||
await this._onDidCreateContext?.(context);
|
await this._onDidCreateContext?.(context);
|
||||||
return context;
|
return context;
|
||||||
}, logger);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(options: api.ConnectOptions & { wsEndpoint?: string }): Promise<api.Browser>;
|
connect(options: api.ConnectOptions & { wsEndpoint?: string }): Promise<api.Browser>;
|
||||||
|
|
@ -190,7 +190,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
closePipe();
|
closePipe();
|
||||||
throw new Error(`Timeout ${params.timeout}ms exceeded`);
|
throw new Error(`Timeout ${params.timeout}ms exceeded`);
|
||||||
}
|
}
|
||||||
}, logger);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
connectOverCDP(options: api.ConnectOverCDPOptions & { wsEndpoint?: string }): Promise<api.Browser>;
|
connectOverCDP(options: api.ConnectOverCDPOptions & { wsEndpoint?: string }): Promise<api.Browser>;
|
||||||
|
|
@ -222,6 +222,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
browser._logger = logger;
|
browser._logger = logger;
|
||||||
browser._setBrowserType(this);
|
browser._setBrowserType(this);
|
||||||
return browser;
|
return browser;
|
||||||
}, logger);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,9 @@ import { EventEmitter } from 'events';
|
||||||
import * as channels from '../protocol/channels';
|
import * as channels from '../protocol/channels';
|
||||||
import { createScheme, ValidationError, Validator } from '../protocol/validator';
|
import { createScheme, ValidationError, Validator } from '../protocol/validator';
|
||||||
import { debugLogger } from '../utils/debugLogger';
|
import { debugLogger } from '../utils/debugLogger';
|
||||||
import { captureStackTrace, ParsedStackTrace } from '../utils/stackTrace';
|
import { captureRawStack, captureStackTrace, ParsedStackTrace } from '../utils/stackTrace';
|
||||||
import { isUnderTest } from '../utils/utils';
|
import { isUnderTest } from '../utils/utils';
|
||||||
|
import { zones } from '../utils/zones';
|
||||||
import { ClientInstrumentation } from './clientInstrumentation';
|
import { ClientInstrumentation } from './clientInstrumentation';
|
||||||
import type { Connection } from './connection';
|
import type { Connection } from './connection';
|
||||||
import type { Logger } from './types';
|
import type { Logger } from './types';
|
||||||
|
|
@ -51,7 +52,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
||||||
this._logger = this._parent._logger;
|
this._logger = this._parent._logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._channel = this._createChannel(new EventEmitter(), null);
|
this._channel = this._createChannel(new EventEmitter());
|
||||||
this._initializer = initializer;
|
this._initializer = initializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,20 +75,22 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createChannel(base: Object, stackTrace: ParsedStackTrace | null, csi?: ClientInstrumentation, callCookie?: any): T {
|
private _createChannel(base: Object): T {
|
||||||
const channel = new Proxy(base, {
|
const channel = new Proxy(base, {
|
||||||
get: (obj: any, prop) => {
|
get: (obj: any, prop) => {
|
||||||
if (prop === 'debugScopeState')
|
if (prop === 'debugScopeState')
|
||||||
return (params: any) => this._connection.sendMessageToServer(this, prop, params, stackTrace);
|
return (params: any) => this._connection.sendMessageToServer(this, prop, params, null);
|
||||||
if (typeof prop === 'string') {
|
if (typeof prop === 'string') {
|
||||||
const validator = scheme[paramsName(this._type, prop)];
|
const validator = scheme[paramsName(this._type, prop)];
|
||||||
if (validator) {
|
if (validator) {
|
||||||
return (params: any) => {
|
return (params: any) => {
|
||||||
if (callCookie && csi) {
|
return this._wrapApiCall((channel, apiZone) => {
|
||||||
csi.onApiCallBegin(renderCallWithParams(stackTrace!.apiName!, params), stackTrace, callCookie);
|
const { stackTrace, csi, callCookie } = apiZone.reported ? { csi: undefined, callCookie: undefined, stackTrace: null } : apiZone;
|
||||||
csi = undefined;
|
apiZone.reported = true;
|
||||||
}
|
if (csi && stackTrace && stackTrace.apiName)
|
||||||
return this._connection.sendMessageToServer(this, prop, validator(params, ''), stackTrace);
|
csi.onApiCallBegin(renderCallWithParams(stackTrace.apiName, params), stackTrace, callCookie);
|
||||||
|
return this._connection.sendMessageToServer(this, prop, validator(params, ''), stackTrace);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,22 +101,27 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _wrapApiCall<R, C extends channels.Channel = T>(func: (channel: C, stackTrace: ParsedStackTrace) => Promise<R>, logger?: Logger, isInternal?: boolean): Promise<R> {
|
async _wrapApiCall<R, C extends channels.Channel = T>(func: (channel: C, apiZone: ApiZone) => Promise<R>, isInternal = false): Promise<R> {
|
||||||
logger = logger || this._logger;
|
const logger = this._logger;
|
||||||
const stackTrace = captureStackTrace();
|
const stack = captureRawStack();
|
||||||
const { apiName, frameTexts } = stackTrace;
|
const apiZone = zones.zoneData<ApiZone>('apiZone', stack);
|
||||||
|
if (apiZone)
|
||||||
|
return func(this._channel as any, apiZone);
|
||||||
|
|
||||||
// Do not report nested async calls to _wrapApiCall.
|
const stackTrace = captureStackTrace(stack);
|
||||||
isInternal = isInternal || stackTrace.allFrames.filter(f => f.function?.includes('_wrapApiCall')).length > 1;
|
|
||||||
if (isInternal)
|
if (isInternal)
|
||||||
delete stackTrace.apiName;
|
delete stackTrace.apiName;
|
||||||
const csi = isInternal ? undefined : this._instrumentation;
|
const csi = isInternal ? undefined : this._instrumentation;
|
||||||
const callCookie: any = {};
|
const callCookie: any = {};
|
||||||
|
|
||||||
|
const { apiName, frameTexts } = stackTrace;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logApiCall(logger, `=> ${apiName} started`, isInternal);
|
logApiCall(logger, `=> ${apiName} started`, isInternal);
|
||||||
const channel = this._createChannel({}, stackTrace, csi, callCookie);
|
const apiZone = { stackTrace, isInternal, reported: false, csi, callCookie };
|
||||||
const result = await func(channel as any, stackTrace);
|
const result = await zones.run<ApiZone, R>('apiZone', apiZone, async () => {
|
||||||
|
return await func(this._channel as any, apiZone);
|
||||||
|
});
|
||||||
csi?.onApiCallEnd(callCookie);
|
csi?.onApiCallEnd(callCookie);
|
||||||
logApiCall(logger, `<= ${apiName} succeeded`, isInternal);
|
logApiCall(logger, `<= ${apiName} succeeded`, isInternal);
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -173,3 +181,11 @@ const tChannel = (name: string): Validator => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const scheme = createScheme(tChannel);
|
const scheme = createScheme(tChannel);
|
||||||
|
|
||||||
|
type ApiZone = {
|
||||||
|
stackTrace: ParsedStackTrace;
|
||||||
|
isInternal: boolean;
|
||||||
|
reported: boolean;
|
||||||
|
csi: ClientInstrumentation | undefined;
|
||||||
|
callCookie: any;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ export class Connection extends EventEmitter {
|
||||||
private _waitingForObject = new Map<string, any>();
|
private _waitingForObject = new Map<string, any>();
|
||||||
onmessage = (message: object): void => {};
|
onmessage = (message: object): void => {};
|
||||||
private _lastId = 0;
|
private _lastId = 0;
|
||||||
private _callbacks = new Map<number, { resolve: (a: any) => void, reject: (a: Error) => void, stackTrace: ParsedStackTrace }>();
|
private _callbacks = new Map<number, { resolve: (a: any) => void, reject: (a: Error) => void, stackTrace: ParsedStackTrace | null }>();
|
||||||
private _rootObject: Root;
|
private _rootObject: Root;
|
||||||
private _closedErrorMessage: string | undefined;
|
private _closedErrorMessage: string | undefined;
|
||||||
private _isRemote = false;
|
private _isRemote = false;
|
||||||
|
|
@ -81,20 +81,19 @@ export class Connection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingProtocolCalls(): ParsedStackTrace[] {
|
pendingProtocolCalls(): ParsedStackTrace[] {
|
||||||
return Array.from(this._callbacks.values()).map(callback => callback.stackTrace);
|
return Array.from(this._callbacks.values()).map(callback => callback.stackTrace).filter(Boolean) as ParsedStackTrace[];
|
||||||
}
|
}
|
||||||
|
|
||||||
getObjectWithKnownName(guid: string): any {
|
getObjectWithKnownName(guid: string): any {
|
||||||
return this._objects.get(guid)!;
|
return this._objects.get(guid)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendMessageToServer(object: ChannelOwner, method: string, params: any, maybeStackTrace: ParsedStackTrace | null): Promise<any> {
|
async sendMessageToServer(object: ChannelOwner, method: string, params: any, stackTrace: ParsedStackTrace | null): Promise<any> {
|
||||||
if (this._closedErrorMessage)
|
if (this._closedErrorMessage)
|
||||||
throw new Error(this._closedErrorMessage);
|
throw new Error(this._closedErrorMessage);
|
||||||
|
|
||||||
|
const { apiName, frames } = stackTrace || { apiName: '', frames: [] };
|
||||||
const guid = object._guid;
|
const guid = object._guid;
|
||||||
const stackTrace: ParsedStackTrace = maybeStackTrace || { frameTexts: [], frames: [], apiName: '', allFrames: [] };
|
|
||||||
const { frames, apiName } = stackTrace;
|
|
||||||
const id = ++this._lastId;
|
const id = ++this._lastId;
|
||||||
const converted = { id, guid, method, params };
|
const converted = { id, guid, method, params };
|
||||||
// Do not include metadata in debug logs to avoid noise.
|
// Do not include metadata in debug logs to avoid noise.
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap
|
||||||
async _internalResponse(): Promise<Response | null> {
|
async _internalResponse(): Promise<Response | null> {
|
||||||
return this._wrapApiCall(async (channel: channels.RequestChannel) => {
|
return this._wrapApiCall(async (channel: channels.RequestChannel) => {
|
||||||
return Response.fromNullable((await channel.response()).response);
|
return Response.fromNullable((await channel.response()).response);
|
||||||
}, undefined, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
frame(): Frame {
|
frame(): Frame {
|
||||||
|
|
@ -309,7 +309,7 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
|
||||||
headers: options.headers ? headersObjectToArray(options.headers) : undefined,
|
headers: options.headers ? headersObjectToArray(options.headers) : undefined,
|
||||||
postData: postDataBuffer ? postDataBuffer.toString('base64') : undefined,
|
postData: postDataBuffer ? postDataBuffer.toString('base64') : undefined,
|
||||||
}));
|
}));
|
||||||
}, undefined, isInternal);
|
}, isInternal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||||
if (routeHandler.handle(route, request)) {
|
if (routeHandler.handle(route, request)) {
|
||||||
this._routes.splice(this._routes.indexOf(routeHandler), 1);
|
this._routes.splice(this._routes.indexOf(routeHandler), 1);
|
||||||
if (!this._routes.length)
|
if (!this._routes.length)
|
||||||
this._wrapApiCall(channel => this._disableInterception(channel), undefined, true).catch(() => {});
|
this._wrapApiCall(channel => this._disableInterception(channel), true).catch(() => {});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ export function rewriteErrorMessage<E extends Error>(e: E, newMessage: string):
|
||||||
const CORE_DIR = path.resolve(__dirname, '..', '..');
|
const CORE_DIR = path.resolve(__dirname, '..', '..');
|
||||||
const CLIENT_LIB = path.join(CORE_DIR, 'lib', 'client');
|
const CLIENT_LIB = path.join(CORE_DIR, 'lib', 'client');
|
||||||
const CLIENT_SRC = path.join(CORE_DIR, 'src', 'client');
|
const CLIENT_SRC = path.join(CORE_DIR, 'src', 'client');
|
||||||
|
const UTIL_LIB = path.join(CORE_DIR, 'lib', 'util');
|
||||||
|
const UTIL_SRC = path.join(CORE_DIR, 'src', 'util');
|
||||||
const TEST_DIR_SRC = path.resolve(CORE_DIR, '..', 'playwright-test');
|
const TEST_DIR_SRC = path.resolve(CORE_DIR, '..', 'playwright-test');
|
||||||
const TEST_DIR_LIB = path.resolve(CORE_DIR, '..', '@playwright', 'test');
|
const TEST_DIR_LIB = path.resolve(CORE_DIR, '..', '@playwright', 'test');
|
||||||
const WS_LIB = path.relative(process.cwd(), path.dirname(require.resolve('ws')));
|
const WS_LIB = path.relative(process.cwd(), path.dirname(require.resolve('ws')));
|
||||||
|
|
@ -44,12 +46,17 @@ export type ParsedStackTrace = {
|
||||||
apiName: string | undefined;
|
apiName: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function captureStackTrace(): ParsedStackTrace {
|
export function captureRawStack(): string {
|
||||||
const stackTraceLimit = Error.stackTraceLimit;
|
const stackTraceLimit = Error.stackTraceLimit;
|
||||||
Error.stackTraceLimit = 30;
|
Error.stackTraceLimit = 30;
|
||||||
const error = new Error();
|
const error = new Error();
|
||||||
const stack = error.stack!;
|
const stack = error.stack!;
|
||||||
Error.stackTraceLimit = stackTraceLimit;
|
Error.stackTraceLimit = stackTraceLimit;
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function captureStackTrace(rawStack?: string): ParsedStackTrace {
|
||||||
|
const stack = rawStack || captureRawStack();
|
||||||
|
|
||||||
const isTesting = isUnderTest();
|
const isTesting = isUnderTest();
|
||||||
type ParsedFrame = {
|
type ParsedFrame = {
|
||||||
|
|
@ -80,7 +87,7 @@ export function captureStackTrace(): ParsedStackTrace {
|
||||||
fileName = path.resolve(process.cwd(), frame.file);
|
fileName = path.resolve(process.cwd(), frame.file);
|
||||||
if (isTesting && fileName.includes(path.join('playwright', 'tests', 'config', 'coverage.js')))
|
if (isTesting && fileName.includes(path.join('playwright', 'tests', 'config', 'coverage.js')))
|
||||||
return null;
|
return null;
|
||||||
const inClient = fileName.startsWith(CLIENT_LIB) || fileName.startsWith(CLIENT_SRC);
|
const inClient = fileName.startsWith(CLIENT_LIB) || fileName.startsWith(CLIENT_SRC) || fileName.startsWith(UTIL_LIB) || fileName.startsWith(UTIL_SRC);
|
||||||
const parsed: ParsedFrame = {
|
const parsed: ParsedFrame = {
|
||||||
frame: {
|
frame: {
|
||||||
file: fileName,
|
file: fileName,
|
||||||
|
|
|
||||||
71
packages/playwright-core/src/utils/zones.ts
Normal file
71
packages/playwright-core/src/utils/zones.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* 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 { captureRawStack } from './stackTrace';
|
||||||
|
|
||||||
|
class ZoneManager {
|
||||||
|
lastZoneId = 0;
|
||||||
|
readonly _zones = new Map<number, Zone>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
async run<T, R>(type: string, data: T, func: () => Promise<R>): Promise<R> {
|
||||||
|
const zone = new Zone(this, ++this.lastZoneId, type, data);
|
||||||
|
this._zones.set(zone.id, zone);
|
||||||
|
return zone.run(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
zoneData<T>(type: string, rawStack?: string): T | null {
|
||||||
|
const stack = rawStack || captureRawStack();
|
||||||
|
|
||||||
|
for (const line of stack.split('\n')) {
|
||||||
|
const index = line.indexOf('__PWZONE__[');
|
||||||
|
if (index !== -1) {
|
||||||
|
const zoneId = + line.substring(index + '__PWZONE__['.length, line.indexOf(']', index));
|
||||||
|
const zone = this._zones.get(zoneId);
|
||||||
|
if (zone && zone.type === type)
|
||||||
|
return zone.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Zone {
|
||||||
|
private _manager: ZoneManager;
|
||||||
|
readonly id: number;
|
||||||
|
readonly type: string;
|
||||||
|
readonly data: any = {};
|
||||||
|
|
||||||
|
constructor(manager: ZoneManager, id: number, type: string, data: any) {
|
||||||
|
this._manager = manager;
|
||||||
|
this.id = id;
|
||||||
|
this.type = type;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async run<R>(func: () => Promise<R>): Promise<R> {
|
||||||
|
Object.defineProperty(func, 'name', { value: `__PWZONE__[${this.id}]` });
|
||||||
|
try {
|
||||||
|
return await func();
|
||||||
|
} finally {
|
||||||
|
this._manager._zones.delete(this.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const zones = new ZoneManager();
|
||||||
|
|
@ -26,8 +26,8 @@ it('should log', async ({ browserType }) => {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
expect(log.length > 0).toBeTruthy();
|
expect(log.length > 0).toBeTruthy();
|
||||||
expect(log.filter(item => item.severity === 'info').length > 0).toBeTruthy();
|
expect(log.filter(item => item.severity === 'info').length > 0).toBeTruthy();
|
||||||
expect(log.filter(item => item.message.includes('browserType.launch started')).length > 0).toBeTruthy();
|
expect(log.filter(item => item.message.includes('browser.newContext started')).length > 0).toBeTruthy();
|
||||||
expect(log.filter(item => item.message.includes('browserType.launch succeeded')).length > 0).toBeTruthy();
|
expect(log.filter(item => item.message.includes('browser.newContext succeeded')).length > 0).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should log context-level', async ({ browserType }) => {
|
it('should log context-level', async ({ browserType }) => {
|
||||||
|
|
|
||||||
|
|
@ -234,12 +234,12 @@ test('should call logger from launchOptions config', async ({ runInlineTest }, t
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should support config logger', async ({browser}) => {
|
test('should support config logger', async ({browser, context}) => {
|
||||||
expect(browser.version()).toBeTruthy();
|
expect(browser.version()).toBeTruthy();
|
||||||
expect(log.length > 0).toBeTruthy();
|
expect(log.length > 0).toBeTruthy();
|
||||||
expect(log.filter(item => item.severity === 'info').length > 0).toBeTruthy();
|
expect(log.filter(item => item.severity === 'info').length > 0).toBeTruthy();
|
||||||
expect(log.filter(item => item.message.includes('browserType.launch started')).length > 0).toBeTruthy();
|
expect(log.filter(item => item.message.includes('browser.newContext started')).length > 0).toBeTruthy();
|
||||||
expect(log.filter(item => item.message.includes('browserType.launch succeeded')).length > 0).toBeTruthy();
|
expect(log.filter(item => item.message.includes('browser.newContext succeeded')).length > 0).toBeTruthy();
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
}, { workers: 1 });
|
}, { workers: 1 });
|
||||||
|
|
|
||||||
|
|
@ -107,17 +107,17 @@ it('should handle errors', async ({ playwright, browser }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
error = await playwright.selectors.register('$', createDummySelector).catch(e => e);
|
error = await playwright.selectors.register('$', createDummySelector).catch(e => e);
|
||||||
expect(error.message).toBe('Selector engine name may only contain [a-zA-Z0-9_] characters');
|
expect(error.message).toBe('selectors.register: Selector engine name may only contain [a-zA-Z0-9_] characters');
|
||||||
|
|
||||||
// Selector names are case-sensitive.
|
// Selector names are case-sensitive.
|
||||||
await playwright.selectors.register('dummy', createDummySelector);
|
await playwright.selectors.register('dummy', createDummySelector);
|
||||||
await playwright.selectors.register('duMMy', createDummySelector);
|
await playwright.selectors.register('duMMy', createDummySelector);
|
||||||
|
|
||||||
error = await playwright.selectors.register('dummy', createDummySelector).catch(e => e);
|
error = await playwright.selectors.register('dummy', createDummySelector).catch(e => e);
|
||||||
expect(error.message).toBe('"dummy" selector engine has been already registered');
|
expect(error.message).toBe('selectors.register: "dummy" selector engine has been already registered');
|
||||||
|
|
||||||
error = await playwright.selectors.register('css', createDummySelector).catch(e => e);
|
error = await playwright.selectors.register('css', createDummySelector).catch(e => e);
|
||||||
expect(error.message).toBe('"css" is a predefined selector engine');
|
expect(error.message).toBe('selectors.register: "css" is a predefined selector engine');
|
||||||
await page.close();
|
await page.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,7 @@ test('should open simple trace viewer', async ({ showTraceViewer }) => {
|
||||||
/page.gotohttp:\/\/localhost:\d+\/frames\/frame.html/,
|
/page.gotohttp:\/\/localhost:\d+\/frames\/frame.html/,
|
||||||
/route.continue/,
|
/route.continue/,
|
||||||
/page.setViewportSize/,
|
/page.setViewportSize/,
|
||||||
|
/browserContext.close/,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue