diff --git a/packages/serve-sim/src/__tests__/ax-normalization.test.ts b/packages/serve-sim/src/__tests__/ax-normalization.test.ts new file mode 100644 index 00000000..eaa60def --- /dev/null +++ b/packages/serve-sim/src/__tests__/ax-normalization.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, test } from "bun:test"; + +import { normalizeAxTree, type RawAxeNode } from "../ax"; + +function node(overrides: Partial = {}): RawAxeNode { + return { + AXUniqueId: null, + AXLabel: null, + AXValue: null, + enabled: true, + frame: { x: 0, y: 0, width: 0, height: 0 }, + role_description: "group", + type: "Group", + children: [], + ...overrides, + }; +} + +describe("normalizeAxTree", () => { + test("filters oversized anonymous groups without dropping valid controls", () => { + const tree = normalizeAxTree([ + node({ + AXLabel: "FoodwayApp", + frame: { x: 0, y: 0, width: 402, height: 874 }, + role_description: "application", + type: "Application", + children: [ + node({ + frame: { x: -119, y: 0, width: 600, height: 874 }, + }), + node({ + AXLabel: "Řádek panelů", + frame: { x: 0, y: 791, width: 402, height: 83 }, + }), + node({ + AXLabel: "Profil", + AXUniqueId: "person.crop.circle.fill", + frame: { x: 25, y: 795, width: 74, height: 54 }, + role_description: "tab", + type: "RadioButton", + AXValue: "0", + }), + ], + }), + ]); + + expect(tree.elements.map((element) => element.path)).toEqual(["0.1", "0.2"]); + expect(tree.elements.find((element) => element.path === "0.1")?.label).toBe("Řádek panelů"); + expect(tree.elements.find((element) => element.path === "0.2")?.label).toBe("Profil"); + }); +}); diff --git a/packages/serve-sim/src/ax.ts b/packages/serve-sim/src/ax.ts index 99125239..1e6733aa 100644 --- a/packages/serve-sim/src/ax.ts +++ b/packages/serve-sim/src/ax.ts @@ -9,7 +9,7 @@ const POLL_INTERVAL_MS = 500; const MAX_POLL_INTERVAL_MS = 2000; const UNAVAILABLE_RETRY_INTERVAL_MS = 15_000; -interface RawAxeNode { +export interface RawAxeNode { AXUniqueId: string | null; AXLabel: string | null; AXValue: string | null; @@ -38,7 +38,22 @@ function sameRect(a: AxRect, b: AxRect) { ); } -function normalizeAxTree(roots: RawAxeNode[]): AxSnapshot { +function isOversizedAnonymousGroup(node: RawAxeNode, screen: AxRect) { + const frame = node.frame; + const label = node.AXLabel?.trim() ?? ""; + const value = node.AXValue?.trim() ?? ""; + const role = node.role_description.trim().toLowerCase(); + + return ( + role === "group" && + !label && + !value && + frame.width >= screen.width * 1.25 && + frame.height >= screen.height * 0.95 + ); +} + +export function normalizeAxTree(roots: RawAxeNode[]): AxSnapshot { const screen = chooseScreenFrame(roots); const elements: AxElement[] = []; @@ -47,8 +62,9 @@ function normalizeAxTree(roots: RawAxeNode[]): AxSnapshot { const frame = node.frame; const isScreenSized = sameRect(frame, screen); + const isIgnoredOverlayGroup = isOversizedAnonymousGroup(node, screen); - if (!isScreenSized) { + if (!isScreenSized && !isIgnoredOverlayGroup) { elements.push({ id: node.AXUniqueId ?? path, path,