MCPcopy
hub / github.com/garrytan/gstack / handleSnapshot

Function handleSnapshot

browse/src/snapshot.ts:138–634  ·  view source on GitHub ↗
(
  args: string[],
  session: TabSession,
  securityOpts?: { splitForScoped?: boolean },
)

Source from the content-addressed store, hash-verified

136 * Take an accessibility snapshot and build the ref map.
137 */
138export async function handleSnapshot(
139 args: string[],
140 session: TabSession,
141 securityOpts?: { splitForScoped?: boolean },
142): Promise<string> {
143 const opts = parseSnapshotArgs(args);
144 const page = session.getPage();
145 // Frame-aware target for accessibility tree
146 const target = session.getActiveFrameOrPage();
147 const inFrame = session.getFrame() !== null;
148
149 // Get accessibility tree via ariaSnapshot
150 let rootLocator: Locator;
151 if (opts.selector) {
152 rootLocator = target.locator(opts.selector);
153 const count = await rootLocator.count();
154 if (count === 0) throw new Error(`Selector not found: ${opts.selector}`);
155 } else {
156 rootLocator = target.locator('body');
157 }
158
159 const ariaText = await rootLocator.ariaSnapshot();
160 if (!ariaText || ariaText.trim().length === 0) {
161 session.setRefMap(new Map());
162 return '(no accessible elements found)';
163 }
164
165 // Parse the ariaSnapshot output
166 const lines = ariaText.split('\n');
167 const refMap = new Map<string, RefEntry>();
168 const output: string[] = [];
169 let refCounter = 1;
170
171 // Track role+name occurrences for nth() disambiguation
172 const roleNameCounts = new Map<string, number>();
173 const roleNameSeen = new Map<string, number>();
174
175 // First pass: count role+name pairs for disambiguation
176 for (const line of lines) {
177 const node = parseLine(line);
178 if (!node) continue;
179 const key = `${node.role}:${node.name || ''}`;
180 roleNameCounts.set(key, (roleNameCounts.get(key) || 0) + 1);
181 }
182
183 // Second pass: assign refs and build locators
184 for (const line of lines) {
185 const node = parseLine(line);
186 if (!node) continue;
187
188 const depth = Math.floor(node.indent / 2);
189 const isInteractive = INTERACTIVE_ROLES.has(node.role);
190
191 // Depth filter
192 if (opts.depth !== undefined && depth > opts.depth) continue;
193
194 // Interactive filter: skip non-interactive but still count for locator indices
195 if (opts.interactive && !isInteractive) {

Callers 3

batch.test.tsFile · 0.90
handleMetaCommandFunction · 0.90

Calls 15

isPathWithinFunction · 0.90
guardScreenshotPathFunction · 0.90
stripLoneSurrogatesFunction · 0.90
parseSnapshotArgsFunction · 0.85
parseLineFunction · 0.85
setMethod · 0.80
getPageMethod · 0.45
getActiveFrameOrPageMethod · 0.45
getFrameMethod · 0.45
setRefMapMethod · 0.45
getMethod · 0.45
pushMethod · 0.45

Tested by

no test coverage detected