项目文件夹

文件
Alex Rudenko 3fcca026cb chore: support structured content (#744)
The idea is to turn formatters into instances and support both text and
JSON formatting. The structured content is output if the experimental
structured content flag is passed. For now, only the snapshots are
returned in a structured way.

Refs https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/689
2026-01-12 13:53:12 +00:00

296 行
7.2 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import assert from 'node:assert';
import {describe, it} from 'node:test';
import type {ElementHandle} from 'puppeteer-core';
import {SnapshotFormatter} from '../../src/formatters/SnapshotFormatter.js';
import type {TextSnapshot, TextSnapshotNode} from '../../src/McpContext.js';
describe('snapshotFormatter', () => {
it('formats a snapshot with value properties', () => {
const node: TextSnapshotNode = {
id: '1_1',
role: 'textbox',
name: 'textbox',
value: 'value',
children: [
{
id: '1_2',
role: 'statictext',
name: 'text',
children: [],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
},
],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
};
const formatter = new SnapshotFormatter({root: node} as TextSnapshot);
const formatted = formatter.toString();
assert.strictEqual(
formatted,
`uid=1_1 textbox "textbox" value="value"
uid=1_2 statictext "text"
`,
);
});
it('formats a snapshot with boolean properties', () => {
const node: TextSnapshotNode = {
id: '1_1',
role: 'button',
name: 'button',
disabled: true,
children: [
{
id: '1_2',
role: 'statictext',
name: 'text',
children: [],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
},
],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
};
const formatter = new SnapshotFormatter({root: node} as TextSnapshot);
const formatted = formatter.toString();
assert.strictEqual(
formatted,
`uid=1_1 button "button" disableable disabled
uid=1_2 statictext "text"
`,
);
});
it('formats a snapshot with checked properties', () => {
const node: TextSnapshotNode = {
id: '1_1',
role: 'checkbox',
name: 'checkbox',
checked: true,
children: [
{
id: '1_2',
role: 'statictext',
name: 'text',
children: [],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
},
],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
};
const formatter = new SnapshotFormatter({root: node} as TextSnapshot);
const formatted = formatter.toString();
assert.strictEqual(
formatted,
`uid=1_1 checkbox "checkbox" checked
uid=1_2 statictext "text"
`,
);
});
it('formats a snapshot with multiple different type attributes', () => {
const node: TextSnapshotNode = {
id: '1_1',
role: 'root',
name: 'root',
children: [
{
id: '1_2',
role: 'button',
name: 'button',
focused: true,
disabled: true,
children: [],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
},
{
id: '1_3',
role: 'textbox',
name: 'textbox',
value: 'value',
children: [],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
},
],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
};
const formatter = new SnapshotFormatter({root: node} as TextSnapshot);
const formatted = formatter.toString();
assert.strictEqual(
formatted,
`uid=1_1 root "root"
uid=1_2 button "button" disableable disabled focusable focused
uid=1_3 textbox "textbox" value="value"
`,
);
});
it('formats with DevTools data not included into a snapshot', t => {
const node: TextSnapshotNode = {
id: '1_1',
role: 'checkbox',
name: 'checkbox',
checked: true,
children: [
{
id: '1_2',
role: 'statictext',
name: 'text',
children: [],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
},
],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
};
const formatter = new SnapshotFormatter({
snapshotId: '1',
root: node,
idToNode: new Map(),
hasSelectedElement: true,
verbose: false,
});
const formatted = formatter.toString();
t.assert.snapshot?.(formatted);
});
it('does not include a note if the snapshot is already verbose', t => {
const node: TextSnapshotNode = {
id: '1_1',
role: 'checkbox',
name: 'checkbox',
checked: true,
children: [
{
id: '1_2',
role: 'statictext',
name: 'text',
children: [],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
},
],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
};
const formatter = new SnapshotFormatter({
snapshotId: '1',
root: node,
idToNode: new Map(),
hasSelectedElement: true,
verbose: true,
});
const formatted = formatter.toString();
t.assert.snapshot?.(formatted);
});
it('formats with DevTools data included into a snapshot', t => {
const node: TextSnapshotNode = {
id: '1_1',
role: 'checkbox',
name: 'checkbox',
checked: true,
children: [
{
id: '1_2',
role: 'statictext',
name: 'text',
children: [],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
},
],
elementHandle: async (): Promise<ElementHandle<Element> | null> => {
return null;
},
};
const formatter = new SnapshotFormatter({
snapshotId: '1',
root: node,
idToNode: new Map(),
hasSelectedElement: true,
selectedElementUid: '1_1',
verbose: false,
});
const formatted = formatter.toString();
t.assert.snapshot?.(formatted);
});
it('toJSON returns expected structure', () => {
const node: TextSnapshotNode = {
id: '1_1',
role: 'root',
name: 'root',
children: [
{
id: '1_2',
role: 'button',
name: 'button',
disabled: true,
children: [],
elementHandle: async () => null,
},
],
elementHandle: async () => null,
};
const formatter = new SnapshotFormatter({root: node} as TextSnapshot);
const json = formatter.toJSON();
assert.deepStrictEqual(json, {
id: '1_1',
role: 'root',
name: 'root',
children: [
{
id: '1_2',
role: 'button',
name: 'button',
disableable: true,
disabled: true,
},
],
});
});
});