(
page: McpPage,
options: {
verbose?: boolean;
devtoolsData?: DevToolsData;
extraHandles?: ElementHandle[];
} = {},
)
| 45 | } |
| 46 | |
| 47 | static async create( |
| 48 | page: McpPage, |
| 49 | options: { |
| 50 | verbose?: boolean; |
| 51 | devtoolsData?: DevToolsData; |
| 52 | extraHandles?: ElementHandle[]; |
| 53 | } = {}, |
| 54 | ): Promise<TextSnapshot> { |
| 55 | const verbose = options.verbose ?? false; |
| 56 | const rootNode = await page.pptrPage.accessibility.snapshot({ |
| 57 | includeIframes: true, |
| 58 | interestingOnly: !verbose, |
| 59 | }); |
| 60 | if (!rootNode) { |
| 61 | throw new Error('Failed to create accessibility snapshot'); |
| 62 | } |
| 63 | |
| 64 | const {uniqueBackendNodeIdToMcpId} = page; |
| 65 | const snapshotId = TextSnapshot.nextSnapshotId++; |
| 66 | // Iterate through the whole accessibility node tree and assign node ids that |
| 67 | // will be used for the tree serialization and mapping ids back to nodes. |
| 68 | let idCounter = 0; |
| 69 | const idToNode = new Map<string, TextSnapshotNode>(); |
| 70 | const seenUniqueIds = new Set<string>(); |
| 71 | const seenBackendNodeIds = new Set<number>(); |
| 72 | |
| 73 | const assignIds = (node: SerializedAXNode): TextSnapshotNode => { |
| 74 | let id = ''; |
| 75 | // @ts-expect-error untyped backendNodeId. |
| 76 | const backendNodeId: number = node.backendNodeId; |
| 77 | // @ts-expect-error untyped loaderId. |
| 78 | const uniqueBackendId = `${node.loaderId}_${backendNodeId}`; |
| 79 | const existingMcpId = uniqueBackendNodeIdToMcpId.get(uniqueBackendId); |
| 80 | if (existingMcpId !== undefined) { |
| 81 | // Re-use MCP exposed ID if the uniqueId is the same. |
| 82 | id = existingMcpId; |
| 83 | } else { |
| 84 | // Only generate a new ID if we have not seen the node before. |
| 85 | id = `${snapshotId}_${idCounter++}`; |
| 86 | uniqueBackendNodeIdToMcpId.set(uniqueBackendId, id); |
| 87 | } |
| 88 | seenUniqueIds.add(uniqueBackendId); |
| 89 | seenBackendNodeIds.add(backendNodeId); |
| 90 | |
| 91 | const nodeWithId: TextSnapshotNode = { |
| 92 | ...node, |
| 93 | id, |
| 94 | children: node.children |
| 95 | ? node.children.map(child => assignIds(child)) |
| 96 | : [], |
| 97 | }; |
| 98 | |
| 99 | // The AXNode for an option doesn't contain its `value`. |
| 100 | // Therefore, set text content of the option as value. |
| 101 | if (node.role === 'option') { |
| 102 | const optionText = node.name; |
| 103 | if (optionText) { |
| 104 | nodeWithId.value = optionText.toString(); |
no test coverage detected