Skip to content

Commit e368e50

Browse files
authored
feat: context.debugger api (microsoft#39667)
1 parent 98872a4 commit e368e50

20 files changed

Lines changed: 620 additions & 42 deletions

File tree

docs/src/api/class-browsercontext.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ This event is not emitted.
7474

7575
Playwright has ability to mock clock and passage of time.
7676

77+
## property: BrowserContext.debugger
78+
* since: v1.59
79+
* langs: js
80+
- type: <[Debugger]>
81+
82+
Debugger allows to pause and resume the execution.
83+
7784
## event: BrowserContext.close
7885
* since: v1.8
7986
- argument: <[BrowserContext]>

docs/src/api/class-debugger.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# class: Debugger
2+
* since: v1.59
3+
* langs: js
4+
5+
API for controlling the Playwright debugger. The debugger allows pausing script execution and inspecting the page.
6+
Obtain the debugger instance via [`property: BrowserContext.debugger`].
7+
8+
See also [`method: Page.pause`] for a simple way to pause script execution.
9+
10+
## event: Debugger.pausedStateChanged
11+
* since: v1.59
12+
13+
Emitted when the debugger pauses or resumes.
14+
15+
## method: Debugger.pausedDetails
16+
* since: v1.59
17+
- returns: <[Array]<[Object]>>
18+
- `location` <[Object]>
19+
- `file` <[string]>
20+
- `line` ?<[int]>
21+
- `column` ?<[int]>
22+
- `title` <[string]>
23+
24+
Returns details about the currently paused calls. Returns an empty array if the debugger is not paused.
25+
26+
## async method: Debugger.resume
27+
* since: v1.59
28+
29+
Resumes script execution if the debugger is paused.
30+
31+
## async method: Debugger.setPauseAt
32+
* since: v1.59
33+
34+
Configures the debugger to pause at the next action or at a specific source location.
35+
Call without arguments to reset the pausing behavior.
36+
37+
### option: Debugger.setPauseAt.next
38+
* since: v1.59
39+
- `next` <[boolean]>
40+
41+
When `true`, the debugger will pause before the next action.
42+
43+
### option: Debugger.setPauseAt.location
44+
* since: v1.59
45+
- `location` <[Object]>
46+
- `file` <[string]>
47+
- `line` ?<[int]>
48+
- `column` ?<[int]>
49+
50+
When specified, the debugger will pause when the action originates from the given source location.

packages/playwright-client/types/types.d.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9695,6 +9695,11 @@ export interface BrowserContext {
96959695
*/
96969696
clock: Clock;
96979697

9698+
/**
9699+
* Debugger allows to pause and resume the execution.
9700+
*/
9701+
debugger: Debugger;
9702+
96989703
/**
96999704
* API testing helper associated with this context. Requests made with this API will use context cookies.
97009705
*/
@@ -19404,6 +19409,89 @@ export interface Coverage {
1940419409
}>>;
1940519410
}
1940619411

19412+
/**
19413+
* API for controlling the Playwright debugger. The debugger allows pausing script execution and inspecting the page.
19414+
* Obtain the debugger instance via
19415+
* [browserContext.debugger](https://playwright.dev/docs/api/class-browsercontext#browser-context-debugger).
19416+
*
19417+
* See also [page.pause()](https://playwright.dev/docs/api/class-page#page-pause) for a simple way to pause script
19418+
* execution.
19419+
*/
19420+
export interface Debugger {
19421+
/**
19422+
* Emitted when the debugger pauses or resumes.
19423+
*/
19424+
on(event: 'pausedstatechanged', listener: () => any): this;
19425+
19426+
/**
19427+
* Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event.
19428+
*/
19429+
once(event: 'pausedstatechanged', listener: () => any): this;
19430+
19431+
/**
19432+
* Emitted when the debugger pauses or resumes.
19433+
*/
19434+
addListener(event: 'pausedstatechanged', listener: () => any): this;
19435+
19436+
/**
19437+
* Removes an event listener added by `on` or `addListener`.
19438+
*/
19439+
removeListener(event: 'pausedstatechanged', listener: () => any): this;
19440+
19441+
/**
19442+
* Removes an event listener added by `on` or `addListener`.
19443+
*/
19444+
off(event: 'pausedstatechanged', listener: () => any): this;
19445+
19446+
/**
19447+
* Emitted when the debugger pauses or resumes.
19448+
*/
19449+
prependListener(event: 'pausedstatechanged', listener: () => any): this;
19450+
19451+
/**
19452+
* Returns details about the currently paused calls. Returns an empty array if the debugger is not paused.
19453+
*/
19454+
pausedDetails(): Array<{
19455+
location: {
19456+
file: string;
19457+
19458+
line?: number;
19459+
19460+
column?: number;
19461+
};
19462+
19463+
title: string;
19464+
}>;
19465+
19466+
/**
19467+
* Resumes script execution if the debugger is paused.
19468+
*/
19469+
resume(): Promise<void>;
19470+
19471+
/**
19472+
* Configures the debugger to pause at the next action or at a specific source location. Call without arguments to
19473+
* reset the pausing behavior.
19474+
* @param options
19475+
*/
19476+
setPauseAt(options?: {
19477+
/**
19478+
* When specified, the debugger will pause when the action originates from the given source location.
19479+
*/
19480+
location?: {
19481+
file: string;
19482+
19483+
line?: number;
19484+
19485+
column?: number;
19486+
};
19487+
19488+
/**
19489+
* When `true`, the debugger will pause before the next action.
19490+
*/
19491+
next?: boolean;
19492+
}): Promise<void>;
19493+
}
19494+
1940719495
/**
1940819496
* [Dialog](https://playwright.dev/docs/api/class-dialog) objects are dispatched by page via the
1940919497
* [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) event.

packages/playwright-core/src/client/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export { BrowserType } from './browserType';
2222
export { Clock } from './clock';
2323
export { ConsoleMessage } from './consoleMessage';
2424
export { Coverage } from './coverage';
25+
export { Debugger } from './debugger';
2526
export { Dialog } from './dialog';
2627
export type { Disposable } from './disposable';
2728
export { Download } from './download';

packages/playwright-core/src/client/browserContext.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { ChannelOwner } from './channelOwner';
2222
import { evaluationScript } from './clientHelper';
2323
import { Clock } from './clock';
2424
import { ConsoleMessage } from './consoleMessage';
25+
import { Debugger } from './debugger';
2526
import { Dialog } from './dialog';
2627
import { DisposableObject, DisposableStub } from './disposable';
2728
import { TargetClosedError, parseError } from './errors';
@@ -69,6 +70,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
6970
private _closedPromise: Promise<void>;
7071
readonly _options: channels.BrowserNewContextParams;
7172

73+
readonly debugger: Debugger;
7274
readonly request: APIRequestContext;
7375
readonly tracing: Tracing;
7476
readonly clock: Clock;
@@ -93,6 +95,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
9395
super(parent, type, guid, initializer);
9496
this._options = initializer.options;
9597
this._timeoutSettings = new TimeoutSettings(this._platform);
98+
this.debugger = Debugger.from(initializer.debugger);
9699
this.tracing = Tracing.from(initializer.tracing);
97100
this.request = APIRequestContext.from(initializer.requestContext);
98101
this.request._timeoutSettings = this._timeoutSettings;

packages/playwright-core/src/client/connection.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { BrowserType } from './browserType';
2323
import { CDPSession } from './cdpSession';
2424
import { ChannelOwner } from './channelOwner';
2525
import { createInstrumentation } from './clientInstrumentation';
26+
import { Debugger } from './debugger';
2627
import { Dialog } from './dialog';
2728
import { DisposableObject } from './disposable';
2829
import { Electron, ElectronApplication } from './electron';
@@ -266,6 +267,9 @@ export class Connection extends EventEmitter {
266267
case 'CDPSession':
267268
result = new CDPSession(parent, type, guid, initializer);
268269
break;
270+
case 'Debugger':
271+
result = new Debugger(parent, type, guid, initializer);
272+
break;
269273
case 'Dialog':
270274
result = new Dialog(parent, type, guid, initializer);
271275
break;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { ChannelOwner } from './channelOwner';
18+
import { Events } from './events';
19+
20+
import type * as api from '../../types/types';
21+
import type * as channels from '@protocol/channels';
22+
23+
type PausedDetail = { location: { file: string, line?: number, column?: number }, title: string };
24+
25+
export class Debugger extends ChannelOwner<channels.DebuggerChannel> implements api.Debugger {
26+
private _pausedDetails: PausedDetail[] = [];
27+
28+
static from(channel: channels.DebuggerChannel): Debugger {
29+
return (channel as any)._object;
30+
}
31+
32+
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.DebuggerInitializer) {
33+
super(parent, type, guid, initializer);
34+
this._channel.on('pausedStateChanged', ({ pausedDetails }) => {
35+
this._pausedDetails = pausedDetails;
36+
this.emit(Events.Debugger.PausedStateChanged);
37+
});
38+
}
39+
40+
async setPauseAt(options: { next?: boolean, location?: { file: string, line?: number, column?: number } } = {}) {
41+
await this._channel.setPauseAt(options);
42+
}
43+
44+
async resume(): Promise<void> {
45+
await this._channel.resume();
46+
}
47+
48+
pausedDetails(): PausedDetail[] {
49+
return this._pausedDetails;
50+
}
51+
}

packages/playwright-core/src/client/events.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ export const Events = {
3434
Disconnected: 'disconnected'
3535
},
3636

37+
Debugger: {
38+
PausedStateChanged: 'pausedstatechanged'
39+
},
40+
3741
BrowserContext: {
3842
Console: 'console',
3943
Close: 'close',

packages/playwright-core/src/protocol/validator.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,16 +844,19 @@ scheme.BrowserContextWaitForEventInfoParams = tType('EventTargetWaitForEventInfo
844844
scheme.PageWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
845845
scheme.WorkerWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
846846
scheme.WebSocketWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
847+
scheme.DebuggerWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
847848
scheme.ElectronApplicationWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
848849
scheme.AndroidDeviceWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
849850
scheme.EventTargetWaitForEventInfoResult = tOptional(tObject({}));
850851
scheme.BrowserContextWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
851852
scheme.PageWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
852853
scheme.WorkerWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
853854
scheme.WebSocketWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
855+
scheme.DebuggerWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
854856
scheme.ElectronApplicationWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
855857
scheme.AndroidDeviceWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
856858
scheme.BrowserContextInitializer = tObject({
859+
debugger: tChannel(['Debugger']),
857860
requestContext: tChannel(['APIRequestContext']),
858861
tracing: tChannel(['Tracing']),
859862
options: tObject({
@@ -2494,6 +2497,28 @@ scheme.BindingCallResolveParams = tObject({
24942497
result: tType('SerializedArgument'),
24952498
});
24962499
scheme.BindingCallResolveResult = tOptional(tObject({}));
2500+
scheme.DebuggerInitializer = tOptional(tObject({}));
2501+
scheme.DebuggerPausedStateChangedEvent = tObject({
2502+
pausedDetails: tArray(tObject({
2503+
location: tObject({
2504+
file: tString,
2505+
line: tOptional(tInt),
2506+
column: tOptional(tInt),
2507+
}),
2508+
title: tString,
2509+
})),
2510+
});
2511+
scheme.DebuggerSetPauseAtParams = tObject({
2512+
next: tOptional(tBoolean),
2513+
location: tOptional(tObject({
2514+
file: tString,
2515+
line: tOptional(tInt),
2516+
column: tOptional(tInt),
2517+
})),
2518+
});
2519+
scheme.DebuggerSetPauseAtResult = tOptional(tObject({}));
2520+
scheme.DebuggerResumeParams = tOptional(tObject({}));
2521+
scheme.DebuggerResumeResult = tOptional(tObject({}));
24972522
scheme.DialogInitializer = tObject({
24982523
page: tOptional(tChannel(['Page'])),
24992524
type: tString,

0 commit comments

Comments
 (0)