Skip to content

Commit 164727c

Browse files
cameroncookecodex
andcommitted
fix(ui-automation): Preserve AXe tab roles
Bundle AXe 1.7.1 so SwiftUI tab bar accessibility nodes are exposed through the packaged AXe artifact. Also classify AXe radio button nodes with role_description "tab" as runtime tabs so wait-for-ui and tap selectors can target them by role. Fixes GH-439 Co-Authored-By: OpenAI Codex <codex@openai.com>
1 parent a01a8c1 commit 164727c

3 files changed

Lines changed: 29 additions & 1 deletion

File tree

.axe-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.7.0
1+
1.7.1

src/mcp/tools/ui-automation/__tests__/runtime-snapshot.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,31 @@ describe('runtime snapshot normalization', () => {
142142
expect(snapshot.payload.elements[0]?.actions).not.toContain('tap');
143143
});
144144

145+
it('classifies tab radio buttons from AXe as tabs', () => {
146+
const snapshot = createRuntimeSnapshotRecord({
147+
simulatorId,
148+
uiHierarchy: [
149+
createNode({
150+
type: 'RadioButton',
151+
role: 'AXRadioButton',
152+
role_description: 'tab',
153+
AXLabel: 'Reports',
154+
AXValue: '0',
155+
}),
156+
],
157+
nowMs: 1_000,
158+
});
159+
160+
expect(snapshot.payload.elements[0]).toEqual(
161+
expect.objectContaining({
162+
role: 'tab',
163+
label: 'Reports',
164+
value: '0',
165+
actions: expect.arrayContaining(['tap', 'longPress', 'touch']),
166+
}),
167+
);
168+
});
169+
145170
it('derives deterministic screen hashes from normalized UI content', () => {
146171
const uiHierarchy = [createNode({ AXLabel: 'Continue' }), createNode({ AXLabel: 'Cancel' })];
147172

src/mcp/tools/ui-automation/shared/runtime-snapshot.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ function deriveRole(
108108
node: AccessibilityNode,
109109
identifier: string | undefined,
110110
): RuntimeElementRoleV1 | undefined {
111+
const roleDescription = normalizeText(node.role_description)?.toLowerCase();
112+
if (roleDescription === 'tab') return 'tab';
113+
111114
const roleText = [node.role, node.type, node.subrole, node.role_description]
112115
.map((value) => normalizeText(value)?.toLowerCase())
113116
.filter((value): value is string => value !== undefined)

0 commit comments

Comments
 (0)