Skip to content

Commit c51ea0a

Browse files
authored
feat(rpc): remove PageAttribution from the protocol, attribute on the client side (#2957)
This also changes timeout error format to "page.click: Timeout 5000ms exceeded", so that all errors can be similarly prefixed with api name. We can now have different api names in different clients, and our protocol is more reasonable.
1 parent 7f61715 commit c51ea0a

25 files changed

+429
-321
lines changed

src/dom.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,11 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
287287
};
288288
}
289289

290-
async _retryPointerAction(progress: Progress, action: (point: types.Point) => Promise<void>, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
290+
async _retryPointerAction(progress: Progress, actionName: string, action: (point: types.Point) => Promise<void>, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
291291
let first = true;
292292
while (progress.isRunning()) {
293-
progress.logger.info(`${first ? 'attempting' : 'retrying'} ${progress.apiName} action`);
294-
const result = await this._performPointerAction(progress, action, options);
293+
progress.logger.info(`${first ? 'attempting' : 'retrying'} ${actionName} action`);
294+
const result = await this._performPointerAction(progress, actionName, action, options);
295295
first = false;
296296
if (result === 'error:notvisible') {
297297
if (options.force)
@@ -316,7 +316,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
316316
return 'done';
317317
}
318318

319-
async _performPointerAction(progress: Progress, action: (point: types.Point) => Promise<void>, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notvisible' | 'error:notconnected' | 'error:notinviewport' | 'error:nothittarget' | 'done'> {
319+
async _performPointerAction(progress: Progress, actionName: string, action: (point: types.Point) => Promise<void>, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notvisible' | 'error:notconnected' | 'error:notinviewport' | 'error:nothittarget' | 'done'> {
320320
const { force = false, position } = options;
321321
if ((options as any).__testHookBeforeStable)
322322
await (options as any).__testHookBeforeStable();
@@ -357,9 +357,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
357357
let restoreModifiers: types.KeyboardModifier[] | undefined;
358358
if (options && options.modifiers)
359359
restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
360-
progress.logger.info(` performing ${progress.apiName} action`);
360+
progress.logger.info(` performing ${actionName} action`);
361361
await action(point);
362-
progress.logger.info(` ${progress.apiName} action done`);
362+
progress.logger.info(` ${actionName} action done`);
363363
progress.logger.info(' waiting for scheduled navigations to finish');
364364
if ((options as any).__testHookAfterPointerAction)
365365
await (options as any).__testHookAfterPointerAction();
@@ -379,7 +379,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
379379
}
380380

381381
_hover(progress: Progress, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> {
382-
return this._retryPointerAction(progress, point => this._page.mouse.move(point.x, point.y), options);
382+
return this._retryPointerAction(progress, 'hover', point => this._page.mouse.move(point.x, point.y), options);
383383
}
384384

385385
click(options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
@@ -390,7 +390,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
390390
}
391391

392392
_click(progress: Progress, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
393-
return this._retryPointerAction(progress, point => this._page.mouse.click(point.x, point.y, options), options);
393+
return this._retryPointerAction(progress, 'click', point => this._page.mouse.click(point.x, point.y, options), options);
394394
}
395395

396396
dblclick(options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
@@ -401,7 +401,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
401401
}
402402

403403
_dblclick(progress: Progress, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
404-
return this._retryPointerAction(progress, point => this._page.mouse.dblclick(point.x, point.y, options), options);
404+
return this._retryPointerAction(progress, 'dblclick', point => this._page.mouse.dblclick(point.x, point.y, options), options);
405405
}
406406

407407
async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise<string[]> {

src/logger.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ export class Logger {
5858
return this._innerLog('error', message, args);
5959
}
6060

61-
createScope(scopeName: string, record?: boolean): Logger {
62-
this._loggerSink.log(this._name, 'info', `=> ${scopeName} started`, [], this._hints);
61+
createScope(scopeName: string | undefined, record?: boolean): Logger {
62+
if (scopeName)
63+
this._loggerSink.log(this._name, 'info', `=> ${scopeName} started`, [], this._hints);
6364
return new Logger(this._loggerSink, this._name, this._hints, scopeName, record);
6465
}
6566

6667
endScope(status: string) {
67-
this._loggerSink.log(this._name, 'info', `<= ${this._scopeName} ${status}`, [], this._hints);
68+
if (this._scopeName)
69+
this._loggerSink.log(this._name, 'info', `<= ${this._scopeName} ${status}`, [], this._hints);
6870
}
6971

7072
private _innerLog(severity: LoggerSeverity, message: string | Error, ...args: any[]) {

src/progress.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { assert } from './helper';
2020
import { rewriteErrorMessage } from './utils/stackTrace';
2121

2222
export interface Progress {
23-
readonly apiName: string;
2423
readonly aborted: Promise<void>;
2524
readonly logger: Logger;
2625
timeUntilDeadline(): number;
@@ -34,6 +33,11 @@ export async function runAbortableTask<T>(task: (progress: Progress) => Promise<
3433
return controller.run(task);
3534
}
3635

36+
let useApiName = true;
37+
export function setUseApiName(value: boolean) {
38+
useApiName = value;
39+
}
40+
3741
export class ProgressController {
3842
// Promise and callback that forcefully abort the progress.
3943
// This promise always rejects.
@@ -70,10 +74,9 @@ export class ProgressController {
7074
assert(this._state === 'before');
7175
this._state = 'running';
7276

73-
const loggerScope = this._logger.createScope(this._apiName, true);
77+
const loggerScope = this._logger.createScope(useApiName ? this._apiName : undefined, true);
7478

7579
const progress: Progress = {
76-
apiName: this._apiName,
7780
aborted: this._abortedPromise,
7881
logger: loggerScope,
7982
timeUntilDeadline: () => this._deadline ? this._deadline - monotonicTime() : 2147483647, // 2^31-1 safe setTimeout in Node.
@@ -90,7 +93,7 @@ export class ProgressController {
9093
},
9194
};
9295

93-
const timeoutError = new TimeoutError(`Timeout ${this._timeout}ms exceeded during ${this._apiName}.`);
96+
const timeoutError = new TimeoutError(`Timeout ${this._timeout}ms exceeded.`);
9497
const timer = setTimeout(() => this._forceAbort(timeoutError), progress.timeUntilDeadline());
9598
try {
9699
const promise = task(progress);
@@ -101,7 +104,11 @@ export class ProgressController {
101104
return result;
102105
} catch (e) {
103106
this._aborted();
104-
rewriteErrorMessage(e, e.message + formatLogRecording(loggerScope.recording(), this._apiName) + kLoggingNote);
107+
rewriteErrorMessage(e,
108+
(useApiName ? `${this._apiName}: ` : '') +
109+
e.message +
110+
formatLogRecording(loggerScope.recording()) +
111+
kLoggingNote);
105112
clearTimeout(timer);
106113
this._state = 'aborted';
107114
loggerScope.endScope(`failed`);
@@ -124,14 +131,14 @@ async function runCleanup(cleanup: () => any) {
124131

125132
const kLoggingNote = `\nNote: use DEBUG=pw:api environment variable and rerun to capture Playwright logs.`;
126133

127-
function formatLogRecording(log: string[], name: string): string {
134+
function formatLogRecording(log: string[]): string {
128135
if (!log.length)
129136
return '';
130-
name = ` ${name} logs `;
137+
const header = ` logs `;
131138
const headerLength = 60;
132-
const leftLength = (headerLength - name.length) / 2;
133-
const rightLength = headerLength - name.length - leftLength;
134-
return `\n${'='.repeat(leftLength)}${name}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`;
139+
const leftLength = (headerLength - header.length) / 2;
140+
const rightLength = headerLength - header.length - leftLength;
141+
return `\n${'='.repeat(leftLength)}${header}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`;
135142
}
136143

137144
function monotonicTime(): number {

src/rpc/channels.ts

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -173,43 +173,41 @@ export type PageInitializer = {
173173
isClosed: boolean
174174
};
175175

176-
export type PageAttribution = { isPage?: boolean };
177-
178176
export interface FrameChannel extends Channel {
179177
on(event: 'loadstate', callback: (params: { add?: types.LifecycleEvent, remove?: types.LifecycleEvent }) => void): this;
180178

181-
evalOnSelector(params: { selector: string; expression: string, isFunction: boolean, arg: any} & PageAttribution): Promise<{ value: any }>;
182-
evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: any} & PageAttribution): Promise<{ value: any }>;
183-
addScriptTag(params: { url?: string, content?: string, type?: string } & PageAttribution): Promise<{ element: ElementHandleChannel }>;
184-
addStyleTag(params: { url?: string, content?: string } & PageAttribution): Promise<{ element: ElementHandleChannel }>;
185-
check(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise<void>;
186-
click(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & PageAttribution): Promise<void>;
179+
evalOnSelector(params: { selector: string; expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>;
180+
evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>;
181+
addScriptTag(params: { url?: string, content?: string, type?: string }): Promise<{ element: ElementHandleChannel }>;
182+
addStyleTag(params: { url?: string, content?: string }): Promise<{ element: ElementHandleChannel }>;
183+
check(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
184+
click(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions): Promise<void>;
187185
content(): Promise<{ value: string }>;
188-
dblclick(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions & PageAttribution): Promise<void>;
189-
dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions & PageAttribution): Promise<void>;
190-
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any} & PageAttribution): Promise<{ value: any }>;
191-
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any} & PageAttribution): Promise<{ handle: JSHandleChannel }>;
192-
fill(params: { selector: string, value: string } & types.NavigatingActionWaitOptions & PageAttribution): Promise<void>;
193-
focus(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<void>;
186+
dblclick(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions): Promise<void>;
187+
dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions): Promise<void>;
188+
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>;
189+
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<{ handle: JSHandleChannel }>;
190+
fill(params: { selector: string, value: string } & types.NavigatingActionWaitOptions): Promise<void>;
191+
focus(params: { selector: string } & types.TimeoutOptions): Promise<void>;
194192
frameElement(): Promise<{ element: ElementHandleChannel }>;
195-
getAttribute(params: { selector: string, name: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string | null }>;
196-
goto(params: { url: string } & types.GotoOptions & PageAttribution): Promise<{ response: ResponseChannel | null }>;
197-
hover(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.TimeoutOptions & PageAttribution): Promise<void>;
198-
innerHTML(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string }>;
199-
innerText(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string }>;
200-
press(params: { selector: string, key: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise<void>;
201-
querySelector(params: { selector: string} & PageAttribution): Promise<{ element: ElementHandleChannel | null }>;
202-
querySelectorAll(params: { selector: string} & PageAttribution): Promise<{ elements: ElementHandleChannel[] }>;
203-
selectOption(params: { selector: string, elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise<{ values: string[] }>;
204-
setContent(params: { html: string } & types.NavigateOptions & PageAttribution): Promise<void>;
205-
setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: Binary }[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise<void>;
206-
textContent(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string | null }>;
193+
getAttribute(params: { selector: string, name: string } & types.TimeoutOptions): Promise<{ value: string | null }>;
194+
goto(params: { url: string } & types.GotoOptions): Promise<{ response: ResponseChannel | null }>;
195+
hover(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.TimeoutOptions): Promise<void>;
196+
innerHTML(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string }>;
197+
innerText(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string }>;
198+
press(params: { selector: string, key: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
199+
querySelector(params: { selector: string}): Promise<{ element: ElementHandleChannel | null }>;
200+
querySelectorAll(params: { selector: string}): Promise<{ elements: ElementHandleChannel[] }>;
201+
selectOption(params: { selector: string, elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions): Promise<{ values: string[] }>;
202+
setContent(params: { html: string } & types.NavigateOptions): Promise<void>;
203+
setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: Binary }[] } & types.NavigatingActionWaitOptions): Promise<void>;
204+
textContent(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string | null }>;
207205
title(): Promise<{ value: string }>;
208-
type(params: { selector: string, text: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise<void>;
209-
uncheck(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise<void>;
210-
waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions & PageAttribution): Promise<{ handle: JSHandleChannel }>;
211-
waitForNavigation(params: types.WaitForNavigationOptions & PageAttribution): Promise<{ response: ResponseChannel | null }>;
212-
waitForSelector(params: { selector: string } & types.WaitForElementOptions & PageAttribution): Promise<{ element: ElementHandleChannel | null }>;
206+
type(params: { selector: string, text: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
207+
uncheck(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
208+
waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions): Promise<{ handle: JSHandleChannel }>;
209+
waitForNavigation(params: types.WaitForNavigationOptions): Promise<{ response: ResponseChannel | null }>;
210+
waitForSelector(params: { selector: string } & types.WaitForElementOptions): Promise<{ element: ElementHandleChannel | null }>;
213211
}
214212
export type FrameInitializer = {
215213
url: string,

src/rpc/client/browserType.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,30 +43,39 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni
4343
async launch(options: types.LaunchOptions & { logger?: LoggerSink } = {}): Promise<Browser> {
4444
const logger = options.logger;
4545
options = { ...options, logger: undefined };
46-
const browser = Browser.from((await this._channel.launch(options)).browser);
47-
browser._logger = logger;
48-
return browser;
46+
return this._wrapApiCall('browserType.launch', async () => {
47+
const browser = Browser.from((await this._channel.launch(options)).browser);
48+
browser._logger = logger;
49+
return browser;
50+
}, logger);
4951
}
5052

5153
async launchServer(options: types.LaunchServerOptions & { logger?: LoggerSink } = {}): Promise<BrowserServer> {
54+
const logger = options.logger;
5255
options = { ...options, logger: undefined };
53-
return BrowserServer.from((await this._channel.launchServer(options)).server);
56+
return this._wrapApiCall('browserType.launchServer', async () => {
57+
return BrowserServer.from((await this._channel.launchServer(options)).server);
58+
}, logger);
5459
}
5560

5661
async launchPersistentContext(userDataDir: string, options: types.LaunchOptions & types.BrowserContextOptions & { logger?: LoggerSink } = {}): Promise<BrowserContext> {
5762
const logger = options.logger;
5863
options = { ...options, logger: undefined };
59-
const result = await this._channel.launchPersistentContext({ userDataDir, ...options });
60-
const context = BrowserContext.from(result.context);
61-
context._logger = logger;
62-
return context;
64+
return this._wrapApiCall('browserType.launchPersistentContext', async () => {
65+
const result = await this._channel.launchPersistentContext({ userDataDir, ...options });
66+
const context = BrowserContext.from(result.context);
67+
context._logger = logger;
68+
return context;
69+
}, logger);
6370
}
6471

6572
async connect(options: types.ConnectOptions & { logger?: LoggerSink }): Promise<Browser> {
6673
const logger = options.logger;
6774
options = { ...options, logger: undefined };
68-
const browser = Browser.from((await this._channel.connect(options)).browser);
69-
browser._logger = logger;
70-
return browser;
75+
return this._wrapApiCall('browserType.connect', async () => {
76+
const browser = Browser.from((await this._channel.connect(options)).browser);
77+
browser._logger = logger;
78+
return browser;
79+
}, logger);
7180
}
7281
}

0 commit comments

Comments
 (0)