fix: keep nth indices stable across snapshot filters

This commit is contained in:
morluto 2026-03-12 19:43:04 +08:00
parent 5152aca65e
commit 24c3e20aa0
2 changed files with 44 additions and 4 deletions

View File

@ -146,6 +146,11 @@ export async function handleSnapshot(
roleNameCounts.set(key, (roleNameCounts.get(key) || 0) + 1);
}
function markRoleNameSeen(node: ParsedNode) {
const key = `${node.role}:${node.name || ''}`;
roleNameSeen.set(key, (roleNameSeen.get(key) || 0) + 1);
}
// Second pass: assign refs and build locators
for (const line of lines) {
const node = parseLine(line);
@ -155,18 +160,24 @@ export async function handleSnapshot(
const isInteractive = INTERACTIVE_ROLES.has(node.role);
// Depth filter
if (opts.depth !== undefined && depth > opts.depth) continue;
if (opts.depth !== undefined && depth > opts.depth) {
// Skipped nodes still occupy nth() slots for later same-name matches.
markRoleNameSeen(node);
continue;
}
// Interactive filter: skip non-interactive but still count for locator indices
if (opts.interactive && !isInteractive) {
// Still track for nth() counts
const key = `${node.role}:${node.name || ''}`;
roleNameSeen.set(key, (roleNameSeen.get(key) || 0) + 1);
markRoleNameSeen(node);
continue;
}
// Compact filter: skip elements with no name and no inline content that aren't interactive
if (opts.compact && !isInteractive && !node.name && !node.children) continue;
if (opts.compact && !isInteractive && !node.name && !node.children) {
markRoleNameSeen(node);
continue;
}
const indent = ' '.repeat(depth);

View File

@ -299,4 +299,33 @@ describe('Ref invalidation', () => {
await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
expect(bm.getRefCount()).toBe(0);
});
test('depth filter still freezes the correct nth same-name element', async () => {
const page = bm.getPage();
await page.setContent(`<!doctype html><body>
<ul><li><button id="nested" onclick="window.clicked='nested'">Save</button></li></ul>
<button id="outer" onclick="window.clicked='outer'">Save</button>
</body>`);
const snap = await handleMetaCommand('snapshot', ['-i', '-d', '1'], bm, shutdown);
const ref = extractRef(snap, (line) => line.includes('[button]') && line.includes('"Save"'));
await handleWriteCommand('click', [ref], bm);
const clicked = await handleReadCommand('js', ['window.clicked'], bm);
expect(clicked).toBe('outer');
});
test('compact filter still freezes the correct nth unnamed element', async () => {
const page = bm.getPage();
await page.setContent(`<!doctype html><body>
<p id="empty"></p>
<p id="filled">Hello</p>
</body>`);
const snap = await handleMetaCommand('snapshot', ['-c'], bm, shutdown);
const ref = extractRef(snap, (line) => line.includes('[paragraph]') && line.includes('Hello'));
const html = await handleReadCommand('html', [ref], bm);
expect(html).toBe('Hello');
});
});