Skip to content

Commit b097022

Browse files
authored
Merge branch 'main' into move-resolveCdpElementId
2 parents 975b286 + 86ffd58 commit b097022

34 files changed

Lines changed: 3396 additions & 2289 deletions

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,11 +497,10 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
497497
- **Emulation** (2 tools)
498498
- [`emulate`](docs/tool-reference.md#emulate)
499499
- [`resize_page`](docs/tool-reference.md#resize_page)
500-
- **Performance** (4 tools)
500+
- **Performance** (3 tools)
501501
- [`performance_analyze_insight`](docs/tool-reference.md#performance_analyze_insight)
502502
- [`performance_start_trace`](docs/tool-reference.md#performance_start_trace)
503503
- [`performance_stop_trace`](docs/tool-reference.md#performance_stop_trace)
504-
- [`take_memory_snapshot`](docs/tool-reference.md#take_memory_snapshot)
505504
- **Network** (2 tools)
506505
- [`get_network_request`](docs/tool-reference.md#get_network_request)
507506
- [`list_network_requests`](docs/tool-reference.md#list_network_requests)
@@ -512,6 +511,8 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
512511
- [`list_console_messages`](docs/tool-reference.md#list_console_messages)
513512
- [`take_screenshot`](docs/tool-reference.md#take_screenshot)
514513
- [`take_snapshot`](docs/tool-reference.md#take_snapshot)
514+
- **Memory** (1 tools)
515+
- [`take_memory_snapshot`](docs/tool-reference.md#take_memory_snapshot)
515516

516517
<!-- END AUTO GENERATED TOOLS -->
517518

docs/tool-reference.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@
2222
- **[Emulation](#emulation)** (2 tools)
2323
- [`emulate`](#emulate)
2424
- [`resize_page`](#resize_page)
25-
- **[Performance](#performance)** (4 tools)
25+
- **[Performance](#performance)** (3 tools)
2626
- [`performance_analyze_insight`](#performance_analyze_insight)
2727
- [`performance_start_trace`](#performance_start_trace)
2828
- [`performance_stop_trace`](#performance_stop_trace)
29-
- [`take_memory_snapshot`](#take_memory_snapshot)
3029
- **[Network](#network)** (2 tools)
3130
- [`get_network_request`](#get_network_request)
3231
- [`list_network_requests`](#list_network_requests)
@@ -37,6 +36,8 @@
3736
- [`list_console_messages`](#list_console_messages)
3837
- [`take_screenshot`](#take_screenshot)
3938
- [`take_snapshot`](#take_snapshot)
39+
- **[Memory](#memory)** (1 tools)
40+
- [`take_memory_snapshot`](#take_memory_snapshot)
4041

4142
## Input automation
4243

@@ -276,16 +277,6 @@
276277

277278
---
278279

279-
### `take_memory_snapshot`
280-
281-
**Description:** Capture a heap snapshot of the currently selected page. Use to analyze the memory distribution of JavaScript objects and debug memory leaks.
282-
283-
**Parameters:**
284-
285-
- **filePath** (string) **(required)**: A path to a .heapsnapshot file to save the heapsnapshot to.
286-
287-
---
288-
289280
## Network
290281

291282
### `get_network_request`
@@ -398,3 +389,15 @@ in the DevTools Elements panel (if any).
398389
- **verbose** (boolean) _(optional)_: Whether to include all possible information available in the full a11y tree. Default is false.
399390

400391
---
392+
393+
## Memory
394+
395+
### `take_memory_snapshot`
396+
397+
**Description:** Capture a heap snapshot of the currently selected page. Use to analyze the memory distribution of JavaScript objects and debug memory leaks.
398+
399+
**Parameters:**
400+
401+
- **filePath** (string) **(required)**: A path to a .heapsnapshot file to save the heapsnapshot to.
402+
403+
---

package-lock.json

Lines changed: 118 additions & 118 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@
7171
"globals": "^17.0.0",
7272
"lighthouse": "13.1.0",
7373
"prettier": "^3.6.2",
74-
"puppeteer": "24.41.0",
75-
"rollup": "4.60.1",
74+
"puppeteer": "24.42.0",
75+
"rollup": "4.60.2",
7676
"rollup-plugin-cleanup": "^3.2.1",
7777
"rollup-plugin-license": "^3.6.0",
7878
"semver": "^7.7.4",

scripts/eval_scenarios/emulation_viewport_test.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
import assert from 'node:assert';
88

9-
import {KnownDevices} from 'puppeteer';
10-
119
import type {TestScenario} from '../eval_gemini.ts';
1210

1311
export const scenario: TestScenario = {
@@ -16,16 +14,6 @@ export const scenario: TestScenario = {
1614
expectations: calls => {
1715
assert.strictEqual(calls.length, 1);
1816
assert.strictEqual(calls[0].name, 'emulate');
19-
assert.deepStrictEqual(
20-
{
21-
...(calls[0].args.viewport as object),
22-
// models might not send defaults.
23-
isLandscape: KnownDevices['iPhone 14'].viewport.isLandscape ?? false,
24-
},
25-
{
26-
...KnownDevices['iPhone 14'].viewport,
27-
height: 844, // Puppeteer is wrong about the expected height.
28-
},
29-
);
17+
assert.deepStrictEqual(calls[0].args.viewport, '390x844x3,mobile,touch');
3018
},
3119
};

scripts/test.mjs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const nodeArgs = [
7777
...files,
7878
];
7979

80-
function installChrome(version) {
80+
function _installChrome(version) {
8181
try {
8282
return execSync(
8383
`npx puppeteer browsers install chrome@${version} --format "{{path}}"`,
@@ -112,9 +112,6 @@ async function runTests(attempt) {
112112
});
113113
}
114114

115-
const chromePath = installChrome('146.0.7680.31');
116-
process.env.CHROME_M146_EXECUTABLE_PATH = chromePath;
117-
118115
const maxAttempts = shouldRetry ? 3 : 1;
119116
let exitCode = 1;
120117

src/HeapSnapshotManager.ts

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,25 @@ import fsSync from 'node:fs';
88
import path from 'node:path';
99

1010
import {DevTools} from './third_party/index.js';
11+
import {
12+
createIdGenerator,
13+
stableIdSymbol,
14+
type WithSymbolId,
15+
} from './utils/id.js';
16+
17+
export type AggregatedInfoWithUid =
18+
WithSymbolId<DevTools.HeapSnapshotModel.HeapSnapshotModel.AggregatedInfo>;
1119

1220
export class HeapSnapshotManager {
1321
#snapshots = new Map<
1422
string,
1523
{
1624
snapshot: DevTools.HeapSnapshotModel.HeapSnapshotProxy.HeapSnapshotProxy;
1725
worker: DevTools.HeapSnapshotModel.HeapSnapshotProxy.HeapSnapshotWorkerProxy;
26+
// TODO: use a multimap
27+
uidToClassKey: Map<number, string>;
28+
classKeyToUid: Map<string, number>;
29+
idGenerator: () => number;
1830
}
1931
>();
2032

@@ -28,20 +40,35 @@ export class HeapSnapshotManager {
2840
}
2941

3042
const {snapshot, worker} = await this.#loadSnapshot(absolutePath);
31-
this.#snapshots.set(absolutePath, {snapshot, worker});
43+
this.#snapshots.set(absolutePath, {
44+
snapshot,
45+
worker,
46+
uidToClassKey: new Map<number, string>(),
47+
classKeyToUid: new Map<string, number>(),
48+
idGenerator: createIdGenerator(),
49+
});
3250

3351
return snapshot;
3452
}
3553

3654
async getAggregates(
3755
filePath: string,
38-
): Promise<
39-
Record<string, DevTools.HeapSnapshotModel.HeapSnapshotModel.AggregatedInfo>
40-
> {
56+
): Promise<Record<string, AggregatedInfoWithUid>> {
4157
const snapshot = await this.getSnapshot(filePath);
4258
const filter =
4359
new DevTools.HeapSnapshotModel.HeapSnapshotModel.NodeFilter();
44-
return await snapshot.aggregatesWithFilter(filter);
60+
const aggregates: Record<string, AggregatedInfoWithUid> =
61+
await snapshot.aggregatesWithFilter(filter);
62+
63+
for (const key of Object.keys(aggregates)) {
64+
const uid = await this.getOrCreateUidForClassKey(filePath, key);
65+
const aggregate = aggregates[key];
66+
if (aggregate) {
67+
aggregate[stableIdSymbol] = uid;
68+
}
69+
}
70+
71+
return aggregates;
4572
}
4673

4774
async getStats(
@@ -58,6 +85,29 @@ export class HeapSnapshotManager {
5885
return snapshot.staticData;
5986
}
6087

88+
async getOrCreateUidForClassKey(
89+
filePath: string,
90+
classKey: string,
91+
): Promise<number> {
92+
const cached = this.#getCachedSnapshot(filePath);
93+
let uid = cached.classKeyToUid.get(classKey);
94+
if (!uid) {
95+
uid = cached.idGenerator();
96+
cached.classKeyToUid.set(classKey, uid);
97+
cached.uidToClassKey.set(uid, classKey);
98+
}
99+
return uid;
100+
}
101+
102+
#getCachedSnapshot(filePath: string) {
103+
const absolutePath = path.resolve(filePath);
104+
const cached = this.#snapshots.get(absolutePath);
105+
if (!cached) {
106+
throw new Error(`Snapshot not loaded for ${filePath}`);
107+
}
108+
return cached;
109+
}
110+
61111
async #loadSnapshot(absolutePath: string): Promise<{
62112
snapshot: DevTools.HeapSnapshotModel.HeapSnapshotProxy.HeapSnapshotProxy;
63113
worker: DevTools.HeapSnapshotModel.HeapSnapshotProxy.HeapSnapshotWorkerProxy;

src/McpContext.ts

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import path from 'node:path';
1010
import type {TargetUniverse} from './DevtoolsUtils.js';
1111
import {UniverseManager} from './DevtoolsUtils.js';
1212
import {HeapSnapshotManager} from './HeapSnapshotManager.js';
13+
import type {AggregatedInfoWithUid} from './HeapSnapshotManager.js';
1314
import {McpPage} from './McpPage.js';
1415
import {
1516
NetworkCollector,
@@ -28,6 +29,7 @@ import type {
2829
SerializedAXNode,
2930
Viewport,
3031
Target,
32+
Extension,
3133
} from './third_party/index.js';
3234
import type {DevTools} from './third_party/index.js';
3335
import {Locator} from './third_party/index.js';
@@ -47,10 +49,6 @@ import type {
4749
TextSnapshotNode,
4850
ExtensionServiceWorker,
4951
} from './types.js';
50-
import {
51-
ExtensionRegistry,
52-
type InstalledExtension,
53-
} from './utils/ExtensionRegistry.js';
5452
import {ensureExtension, saveTemporaryFile} from './utils/files.js';
5553
import {getNetworkMultiplierFromString} from './WaitForHelper.js';
5654

@@ -83,7 +81,6 @@ export class McpContext implements Context {
8381
#networkCollector: NetworkCollector;
8482
#consoleCollector: ConsoleCollector;
8583
#devtoolsUniverseManager: UniverseManager;
86-
#extensionRegistry = new ExtensionRegistry();
8784

8885
#isRunningTrace = false;
8986
#screenRecorderData: {recorder: ScreenRecorder; filePath: string} | null =
@@ -854,45 +851,35 @@ export class McpContext implements Context {
854851

855852
async installExtension(extensionPath: string): Promise<string> {
856853
const id = await this.browser.installExtension(extensionPath);
857-
await this.#extensionRegistry.registerExtension(id, extensionPath);
858854
return id;
859855
}
860856

861857
async uninstallExtension(id: string): Promise<void> {
862858
await this.browser.uninstallExtension(id);
863-
this.#extensionRegistry.remove(id);
864859
}
865860

866861
async triggerExtensionAction(id: string): Promise<void> {
867-
const page = this.getSelectedPptrPage();
868-
// @ts-expect-error internal puppeteer api is needed since we don't have a way to get
869-
// a tab id at the moment
870-
const theTarget = page._tabId;
871-
const session = await this.browser.target().createCDPSession();
872-
873-
try {
874-
await session.send('Extensions.triggerAction', {
875-
id,
876-
targetId: theTarget,
877-
});
878-
} finally {
879-
await session.detach();
862+
const extensions = await this.browser.extensions();
863+
const extension = extensions.get(id);
864+
if (!extension) {
865+
throw new Error(`Extension with ID ${id} not found.`);
880866
}
867+
const page = this.getSelectedPptrPage();
868+
await extension.triggerAction(page);
881869
}
882870

883-
listExtensions(): InstalledExtension[] {
884-
return this.#extensionRegistry.list();
871+
listExtensions(): Promise<Map<string, Extension>> {
872+
return this.browser.extensions();
885873
}
886874

887-
getExtension(id: string): InstalledExtension | undefined {
888-
return this.#extensionRegistry.getById(id);
875+
async getExtension(id: string): Promise<Extension | undefined> {
876+
const pptrExtensions = await this.browser.extensions();
877+
return pptrExtensions.get(id);
889878
}
890879

891880
async getHeapSnapshotAggregates(
892881
filePath: string,
893-
): Promise<
894-
Record<string, DevTools.HeapSnapshotModel.HeapSnapshotModel.AggregatedInfo>
895-
> {
882+
): Promise<Record<string, AggregatedInfoWithUid>> {
896883
return await this.#heapSnapshotManager.getAggregates(filePath);
897884
}
898885

src/McpResponse.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
ResourceType,
2424
TextContent,
2525
JSONSchema7Definition,
26+
Extension,
2627
} from './third_party/index.js';
2728
import type {ToolGroup, ToolDefinition} from './tools/inPage.js';
2829
import {handleDialog} from './tools/pages.js';
@@ -35,7 +36,6 @@ import type {
3536
} from './tools/ToolDefinition.js';
3637
import type {InsightName, TraceResult} from './trace-processing/parse.js';
3738
import {getInsightOutput, getTraceSummary} from './trace-processing/parse.js';
38-
import type {InstalledExtension} from './utils/ExtensionRegistry.js';
3939
import {paginate} from './utils/pagination.js';
4040
import type {PaginationOptions} from './utils/types.js';
4141

@@ -524,9 +524,9 @@ export class McpResponse implements Response {
524524
}
525525
}
526526

527-
let extensions: InstalledExtension[] | undefined;
527+
let extensions: Map<string, Extension> | undefined;
528528
if (this.#listExtensions) {
529-
extensions = context.listExtensions();
529+
extensions = await context.listExtensions();
530530
}
531531

532532
let inPageTools: ToolGroup<ToolDefinition> | undefined;
@@ -662,7 +662,7 @@ export class McpResponse implements Response {
662662
networkRequests?: NetworkFormatter[];
663663
traceSummary?: TraceResult;
664664
traceInsight?: TraceInsightData;
665-
extensions?: InstalledExtension[];
665+
extensions?: Map<string, Extension>;
666666
lighthouseResult?: LighthouseData;
667667
inPageTools?: ToolGroup<ToolDefinition>;
668668
webmcpTools?: WebMCPTool[];
@@ -944,14 +944,15 @@ Call ${handleDialog.name} to handle it before continuing.`);
944944
}
945945

946946
if (data.extensions) {
947-
structuredContent.extensions = data.extensions;
947+
const extensionArray = Array.from(data.extensions.values());
948+
structuredContent.extensions = extensionArray;
948949
response.push('## Extensions');
949-
if (data.extensions.length === 0) {
950+
if (extensionArray.length === 0) {
950951
response.push('No extensions installed.');
951952
} else {
952-
const extensionsMessage = data.extensions
953+
const extensionsMessage = extensionArray
953954
.map(extension => {
954-
return `id=${extension.id} "${extension.name}" v${extension.version} ${extension.isEnabled ? 'Enabled' : 'Disabled'}`;
955+
return `id=${extension.id} "${extension.name}" v${extension.version} ${extension.enabled ? 'Enabled' : 'Disabled'}`;
955956
})
956957
.join('\n');
957958
response.push(extensionsMessage);

0 commit comments

Comments
 (0)