Skip to content

Commit d31be12

Browse files
committed
chore: devtools experiment
1 parent bfd1ec6 commit d31be12

22 files changed

Lines changed: 1285 additions & 17 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
name: playwright-api
3+
description: Explains how to add playwright API methods.
4+
---
5+
6+
# API
7+
8+
## Adding and modifying APIs
9+
- Before performing the implementation, go over the steps to understand and plan the work ahead. It is important to follow the steps in order, as some of them are prerequisites for others.
10+
- Define (or update) API in `docs/api/class-xxx.md`. For the new methods, params and options use the version from package.json (without -next).
11+
- Watch will kick in and re-generate types for the API
12+
- Implement the new API in `packages/playwright/src/client/xxx.ts`
13+
- Define (or update) channel for the API in `packages/protocol/src/protocol.yml` as needed
14+
- Watch will kick in and re-generate types for protocol channels
15+
- Implement dispatcher handler in `packages/playwright/src/server/dispatchers/xxxDispatcher.ts` as needed
16+
- Handler should just route the call into the corresponding method in `packages/playwright-core/src/server/xxx.ts`
17+
- Place new tests in `tests/page/xxx.spec.ts` or create new test file if needed
18+
19+
# Build
20+
- Assume watch is running and everything is up to date.
21+
22+
# Test
23+
- If your tests are only using page, prefer to place them in `tests/page/xxx.spec.ts` and use page fixture. If you need to use browser context, place them in `tests/library/xxx.spec.ts`.
24+
- Run npm test as `npm run ctest <file>`
25+
26+
# Lint
27+
- In the end lint via `npm run flint`.

.claude/skills/playwright-mcp-dev/SKILL.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ description: Explains how to add and debug playwright MCP tools and CLI commands
3030
in `packages/playwright/src/skill/references/`
3131
- Place new tests in `tests/mcp/cli-<category>.spec.ts`
3232

33+
## Adding CLI options or Config options
34+
When you need to add something to config.
35+
36+
- `packages/playwright/src/mcp/program.ts`
37+
- add CLI option and doc
38+
- `packages/playwright/src/mcp/config.d.ts`
39+
- add and document the option
40+
- `packages/playwright/src/mcp/config.ts`
41+
- modify FullConfig if needed
42+
- and CLIOptions if needed
43+
- add it to configFromEnv
44+
3345
## Building
3446
- Assume watch is running at all times, run lint to see type errors
3547

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,14 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
585585
return;
586586
throw new Error(`File access denied: ${filePath} is outside allowed roots. Allowed roots: ${this._allowedDirectories.length ? this._allowedDirectories.join(', ') : 'none'}`);
587587
}
588+
589+
async _devtoolsStart(options: { size?: { width: number, height: number }, port?: number, host?: string } = {}): Promise<{ url: string }> {
590+
return await this._channel.devtoolsStart(options);
591+
}
592+
593+
async _devtoolsStop(): Promise<void> {
594+
await this._channel.devtoolsStop();
595+
}
588596
}
589597

590598
async function prepareStorageState(platform: Platform, storageState: string | SetStorageState): Promise<NonNullable<channels.BrowserNewContextParams['storageState']>> {

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,19 @@ scheme.BrowserContextClockSetSystemTimeParams = tObject({
11571157
timeString: tOptional(tString),
11581158
});
11591159
scheme.BrowserContextClockSetSystemTimeResult = tOptional(tObject({}));
1160+
scheme.BrowserContextDevtoolsStartParams = tObject({
1161+
size: tOptional(tObject({
1162+
width: tInt,
1163+
height: tInt,
1164+
})),
1165+
port: tOptional(tInt),
1166+
host: tOptional(tString),
1167+
});
1168+
scheme.BrowserContextDevtoolsStartResult = tObject({
1169+
url: tString,
1170+
});
1171+
scheme.BrowserContextDevtoolsStopParams = tOptional(tObject({}));
1172+
scheme.BrowserContextDevtoolsStopResult = tOptional(tObject({}));
11601173
scheme.PageInitializer = tObject({
11611174
mainFrame: tChannel(['Frame']),
11621175
viewportSize: tOptional(tObject({

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { Page, PageBinding } from './page';
3535
import { RecorderApp } from './recorder/recorderApp';
3636
import { Selectors } from './selectors';
3737
import { Tracing } from './trace/recorder/tracing';
38+
import { DevToolsController } from './devtoolsController';
3839
import * as rawStorageSource from '../generated/storageScriptSource';
3940

4041
import type { Artifact } from './artifact';
@@ -64,6 +65,8 @@ const BrowserContextEvent = {
6465
RequestContinued: 'requestcontinued',
6566
BeforeClose: 'beforeclose',
6667
RecorderEvent: 'recorderevent',
68+
PageClosed: 'pageclosed',
69+
InternalFrameNavigatedToNewDocument: 'internalframenavigatedtonewdocument',
6770
} as const;
6871

6972
export type BrowserContextEventMap = {
@@ -80,6 +83,8 @@ export type BrowserContextEventMap = {
8083
[BrowserContextEvent.RequestContinued]: [request: network.Request];
8184
[BrowserContextEvent.BeforeClose]: [];
8285
[BrowserContextEvent.RecorderEvent]: [event: { event: 'actionAdded' | 'actionUpdated' | 'signalAdded', data: any, page: Page, code: string }];
86+
[BrowserContextEvent.PageClosed]: [page: Page];
87+
[BrowserContextEvent.InternalFrameNavigatedToNewDocument]: [frame: frames.Frame, page: Page];
8388
};
8489

8590
export abstract class BrowserContext<EM extends EventMap = EventMap> extends SdkObject<BrowserContextEventMap | EM> {
@@ -114,6 +119,7 @@ export abstract class BrowserContext<EM extends EventMap = EventMap> extends Sdk
114119
private _playwrightBindingExposed?: Promise<void>;
115120
readonly dialogManager: DialogManager;
116121
private _consoleApiExposed = false;
122+
private _devtools: DevToolsController | undefined;
117123

118124
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
119125
super(browser, 'browser-context');
@@ -509,6 +515,22 @@ export abstract class BrowserContext<EM extends EventMap = EventMap> extends Sdk
509515
await this.doUpdateRequestInterception();
510516
}
511517

518+
async devtoolsStart(options: { size?: types.Size, port?: number, host?: string } = {}): Promise<string> {
519+
if (this._devtools)
520+
throw new Error('DevTools is already running');
521+
const size = validateVideoSize(options.size, undefined);
522+
this._devtools = new DevToolsController(this);
523+
const url = await this._devtools.start({ width: size.width, height: size.height, quality: 90, port: options.port, host: options.host });
524+
return url;
525+
}
526+
527+
async devtoolsStop(): Promise<void> {
528+
if (!this._devtools)
529+
throw new Error('DevTools is not running');
530+
await this._devtools.stop();
531+
this._devtools = undefined;
532+
}
533+
512534
isClosingOrClosed() {
513535
return this._closedStatus !== 'open';
514536
}
@@ -532,6 +554,9 @@ export abstract class BrowserContext<EM extends EventMap = EventMap> extends Sdk
532554
this.emit(BrowserContext.Events.BeforeClose);
533555
this._closedStatus = 'closing';
534556

557+
await this._devtools?.stop();
558+
this._devtools = undefined;
559+
535560
for (const harRecorder of this._harRecorders.values())
536561
await harRecorder.flush();
537562
await this.tracing.flush();

0 commit comments

Comments
 (0)