Skip to content

Commit 225694d

Browse files
authored
Merge branch 'main' into fix/screenshot-mime-type-config
2 parents be931d0 + 6cacdfb commit 225694d

File tree

10 files changed

+203
-40
lines changed

10 files changed

+203
-40
lines changed

package-lock.json

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

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@
5252
"@types/yargs": "^17.0.33",
5353
"@typescript-eslint/eslint-plugin": "^8.43.0",
5454
"@typescript-eslint/parser": "^8.43.0",
55-
"chrome-devtools-frontend": "1.0.1544076",
56-
"core-js": "3.46.0",
55+
"chrome-devtools-frontend": "1.0.1547147",
56+
"core-js": "3.47.0",
5757
"debug": "4.4.3",
5858
"eslint": "^9.35.0",
5959
"eslint-import-resolver-typescript": "^4.4.4",
6060
"eslint-plugin-import": "^2.32.0",
6161
"globals": "^16.4.0",
6262
"prettier": "^3.6.2",
63-
"puppeteer": "24.30.0",
63+
"puppeteer": "24.31.0",
6464
"rollup": "4.53.2",
6565
"rollup-plugin-cleanup": "^3.2.1",
6666
"rollup-plugin-license": "^3.6.0",

src/McpContext.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ export interface TextSnapshot {
4242
idToNode: Map<string, TextSnapshotNode>;
4343
snapshotId: string;
4444
selectedElementUid?: string;
45+
// It might happen that there is a selected element, but it is not part of the
46+
// snapshot. This flag indicates if there is any selected element.
47+
hasSelectedElement: boolean;
48+
verbose: boolean;
4549
}
4650

4751
interface McpContextOptions {
@@ -535,9 +539,12 @@ export class McpContext implements Context {
535539
root: rootNodeWithId,
536540
snapshotId: String(snapshotId),
537541
idToNode,
542+
hasSelectedElement: false,
543+
verbose,
538544
};
539545
const data = devtoolsData ?? (await this.getDevToolsData());
540546
if (data?.cdpBackendNodeId) {
547+
this.#textSnapshot.hasSelectedElement = true;
541548
this.#textSnapshot.selectedElementUid = this.resolveCdpElementId(
542549
data?.cdpBackendNodeId,
543550
);

src/formatters/snapshotFormatter.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,20 @@ export function formatSnapshotNode(
1010
snapshot?: TextSnapshot,
1111
depth = 0,
1212
): string {
13-
let result = '';
13+
const chunks: string[] = [];
14+
15+
if (depth === 0) {
16+
// Top-level content of the snapshot.
17+
if (
18+
snapshot?.verbose &&
19+
snapshot?.hasSelectedElement &&
20+
!snapshot.selectedElementUid
21+
) {
22+
chunks.push(`Note: there is a selected element in the DevTools Elements panel but it is not included into the current a11y tree snapshot.
23+
Get a verbose snapshot to include all elements if you are interested in the selected element.\n\n`);
24+
}
25+
}
26+
1427
const attributes = getAttributes(root);
1528
const line =
1629
' '.repeat(depth * 2) +
@@ -19,13 +32,13 @@ export function formatSnapshotNode(
1932
? ' [selected in the DevTools Elements panel]'
2033
: '') +
2134
'\n';
22-
result += line;
35+
chunks.push(line);
2336

2437
for (const child of root.children) {
25-
result += formatSnapshotNode(child, snapshot, depth + 1);
38+
chunks.push(formatSnapshotNode(child, snapshot, depth + 1));
2639
}
2740

28-
return result;
41+
return chunks.join('');
2942
}
3043

3144
function getAttributes(serializedAXNodeRoot: TextSnapshotNode): string[] {

src/tools/screenshot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export const screenshot = defineTool({
6868
const screenshot = await pageOrHandle.screenshot({
6969
type: format,
7070
fullPage: request.params.fullPage,
71-
quality: request.params.quality,
71+
quality,
7272
optimizeForSpeed: true, // Bonus: optimize encoding for speed
7373
});
7474

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
exports[`snapshotFormatter > does not include a note if the snapshot is already verbose 1`] = `
2+
Note: there is a selected element in the DevTools Elements panel but it is not included into the current a11y tree snapshot.
3+
Get a verbose snapshot to include all elements if you are interested in the selected element.
4+
5+
uid=1_1 checkbox "checkbox" checked
6+
uid=1_2 statictext "text"
7+
8+
`;
9+
10+
exports[`snapshotFormatter > formats with DevTools data included into a snapshot 1`] = `
11+
uid=1_1 checkbox "checkbox" checked [selected in the DevTools Elements panel]
12+
uid=1_2 statictext "text"
13+
14+
`;
15+
16+
exports[`snapshotFormatter > formats with DevTools data not included into a snapshot 1`] = `
17+
uid=1_1 checkbox "checkbox" checked
18+
uid=1_2 statictext "text"
19+
20+
`;

tests/formatters/snapshotFormatter.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,104 @@ describe('snapshotFormatter', () => {
148148
`,
149149
);
150150
});
151+
152+
it('formats with DevTools data not included into a snapshot', t => {
153+
const node: TextSnapshotNode = {
154+
id: '1_1',
155+
role: 'checkbox',
156+
name: 'checkbox',
157+
checked: true,
158+
children: [
159+
{
160+
id: '1_2',
161+
role: 'statictext',
162+
name: 'text',
163+
children: [],
164+
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
165+
return null;
166+
},
167+
},
168+
],
169+
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
170+
return null;
171+
},
172+
};
173+
174+
const formatted = formatSnapshotNode(node, {
175+
snapshotId: '1',
176+
root: node,
177+
idToNode: new Map(),
178+
hasSelectedElement: true,
179+
verbose: false,
180+
});
181+
182+
t.assert.snapshot?.(formatted);
183+
});
184+
185+
it('does not include a note if the snapshot is already verbose', t => {
186+
const node: TextSnapshotNode = {
187+
id: '1_1',
188+
role: 'checkbox',
189+
name: 'checkbox',
190+
checked: true,
191+
children: [
192+
{
193+
id: '1_2',
194+
role: 'statictext',
195+
name: 'text',
196+
children: [],
197+
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
198+
return null;
199+
},
200+
},
201+
],
202+
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
203+
return null;
204+
},
205+
};
206+
207+
const formatted = formatSnapshotNode(node, {
208+
snapshotId: '1',
209+
root: node,
210+
idToNode: new Map(),
211+
hasSelectedElement: true,
212+
verbose: true,
213+
});
214+
215+
t.assert.snapshot?.(formatted);
216+
});
217+
218+
it('formats with DevTools data included into a snapshot', t => {
219+
const node: TextSnapshotNode = {
220+
id: '1_1',
221+
role: 'checkbox',
222+
name: 'checkbox',
223+
checked: true,
224+
children: [
225+
{
226+
id: '1_2',
227+
role: 'statictext',
228+
name: 'text',
229+
children: [],
230+
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
231+
return null;
232+
},
233+
},
234+
],
235+
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
236+
return null;
237+
},
238+
};
239+
240+
const formatted = formatSnapshotNode(node, {
241+
snapshotId: '1',
242+
root: node,
243+
idToNode: new Map(),
244+
hasSelectedElement: true,
245+
selectedElementUid: '1_1',
246+
verbose: false,
247+
});
248+
249+
t.assert.snapshot?.(formatted);
250+
});
151251
});

tests/tools/performance.test.js.snapshot

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ Information on performance traces may contain main thread activity represented a
109109

110110
Each call frame is presented in the following format:
111111

112-
'id;eventKey;name;duration;selfTime;urlIndex;childRange;[S]'
112+
'id;eventKey;name;duration;selfTime;urlIndex;childRange;[line];[column];[S]'
113113

114114
Key definitions:
115115

@@ -120,15 +120,17 @@ Key definitions:
120120
* selfTime: The time spent directly within the call frame, excluding its children's execution.
121121
* urlIndex: Index referencing the "All URLs" list. Empty if no specific script URL is associated.
122122
* childRange: Specifies the direct children of this node using their IDs. If empty ('' or 'S' at the end), the node has no children. If a single number (e.g., '4'), the node has one child with that ID. If in the format 'firstId-lastId' (e.g., '4-5'), it indicates a consecutive range of child IDs from 'firstId' to 'lastId', inclusive.
123+
* line: An optional field for a call frame's line number. This is where the function is defined.
124+
* column: An optional field for a call frame's column number. This is where the function is defined.
123125
* S: _Optional_. The letter 'S' terminates the line if that call frame was selected by the user.
124126

125127
Example Call Tree:
126128

127-
1;r-123;main;500;100;;
128-
2;r-124;update;200;50;;3
129-
3;p-49575-15428179-2834-374;animate;150;20;0;4-5;S
130-
4;p-49575-15428179-3505-1162;calculatePosition;80;80;;
131-
5;p-49575-15428179-5391-2767;applyStyles;50;50;;
129+
1;r-123;main;500;100;0;1;;
130+
2;r-124;update;200;50;;3;0;1;
131+
3;p-49575-15428179-2834-374;animate;150;20;0;4-5;0;1;S
132+
4;p-49575-15428179-3505-1162;calculatePosition;80;80;0;1;;
133+
5;p-49575-15428179-5391-2767;applyStyles;50;50;0;1;;
132134

133135

134136
Network requests are formatted like this:

tests/tools/screenshot.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,25 @@ describe('screenshot', () => {
3030
);
3131
});
3232
});
33+
it('ignores quality', async () => {
34+
await withBrowser(async (response, context) => {
35+
const fixture = screenshots.basic;
36+
const page = context.getSelectedPage();
37+
await page.setContent(fixture.html);
38+
await screenshot.handler(
39+
{params: {format: 'png', quality: 0}},
40+
response,
41+
context,
42+
);
43+
44+
assert.equal(response.images.length, 1);
45+
assert.equal(response.images[0].mimeType, 'image/png');
46+
assert.equal(
47+
response.responseLines.at(0),
48+
"Took a screenshot of the current page's viewport.",
49+
);
50+
});
51+
});
3352
it('with jpeg', async () => {
3453
await withBrowser(async (response, context) => {
3554
await screenshot.handler({params: {format: 'jpeg'}}, response, context);

0 commit comments

Comments
 (0)