Skip to content

Commit 478f2db

Browse files
committed
test: simplify android selector integration flow
1 parent ce0e46c commit 478f2db

1 file changed

Lines changed: 16 additions & 117 deletions

File tree

test/integration/android.test.ts

Lines changed: 16 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,24 @@ test('android settings commands', () => {
3434
detail: 'expected snapshot to include a nodes array',
3535
});
3636

37-
const nodes = Array.isArray(snapshot.json?.data?.nodes) ? (snapshot.json.data.nodes as SnapshotNode[]) : [];
38-
const clickTarget = selectSettingsClickSelector(nodes);
37+
const settingsSectionSelector = [
38+
'label=Apps',
39+
'label="Apps & notifications"',
40+
'label="Network & internet"',
41+
'label="Connected devices"',
42+
'label=Display',
43+
'label=Battery',
44+
'label=Notifications',
45+
].join(' || ');
46+
const clickArgs = ['click', settingsSectionSelector, '--json', ...session];
47+
const openSection = integration.runStep('open settings section', clickArgs);
3948
integration.assertResult(
40-
Boolean(clickTarget),
41-
'select click target',
42-
snapshotArgs,
43-
snapshot,
44-
{ detail: 'expected at least one bounded, labeled node in snapshot output' },
49+
openSection.json?.success,
50+
'open settings section success',
51+
clickArgs,
52+
openSection,
53+
{ detail: 'expected selector-based click to return success=true' },
4554
);
46-
if (!clickTarget) return;
47-
48-
const clickArgs = ['click', clickTarget.selector, '--json', ...session];
49-
integration.runStep(`click ${clickTarget.label}`, clickArgs);
5055

5156
const snapshotAppsArgs = ['snapshot', '-i', '--json', ...session];
5257
const snapshotApps = integration.runStep('snapshot apps', snapshotAppsArgs);
@@ -75,109 +80,3 @@ test('android settings commands', () => {
7580
const backArgs = ['back', '--json', ...session];
7681
integration.runStep('back', backArgs);
7782
});
78-
79-
type SnapshotNode = {
80-
type?: string;
81-
label?: string;
82-
rect?: { width?: number; height?: number };
83-
};
84-
85-
type SelectorTarget = {
86-
label: string;
87-
selector: string;
88-
};
89-
90-
function selectSettingsClickSelector(nodes: SnapshotNode[]): SelectorTarget | null {
91-
const clickableNodes = nodes.filter((node) => {
92-
const label = typeof node.label === 'string' ? node.label.trim() : '';
93-
const width = node.rect?.width;
94-
const height = node.rect?.height;
95-
return (
96-
label.length > 0 &&
97-
label !== '(no-label)' &&
98-
typeof width === 'number' &&
99-
width > 0 &&
100-
typeof height === 'number' &&
101-
height > 0
102-
);
103-
});
104-
if (clickableNodes.length === 0) return null;
105-
106-
const labelCounts = new Map<string, number>();
107-
const roleLabelCounts = new Map<string, number>();
108-
for (const node of clickableNodes) {
109-
const labelKey = normalizeSelectorText(node.label);
110-
if (!labelKey) continue;
111-
labelCounts.set(labelKey, (labelCounts.get(labelKey) ?? 0) + 1);
112-
const roleKey = normalizeSelectorText(node.type) ?? '';
113-
const roleLabelKey = `${roleKey}::${labelKey}`;
114-
roleLabelCounts.set(roleLabelKey, (roleLabelCounts.get(roleLabelKey) ?? 0) + 1);
115-
}
116-
117-
const preferredLabels = [
118-
'Apps',
119-
'Apps & notifications',
120-
'Network & internet',
121-
'Connected devices',
122-
'Display',
123-
'Battery',
124-
'Notifications',
125-
];
126-
const preferredTargets = clickableNodes.filter((node) => {
127-
const label = normalizeSelectorText(node.label);
128-
if (!label) return false;
129-
return preferredLabels.some((candidate) => label.includes(candidate.toLowerCase()));
130-
});
131-
const candidates = preferredTargets.length > 0 ? preferredTargets : clickableNodes;
132-
const selected = candidates.find((node) => hasUniqueRoleAndLabel(node, labelCounts, roleLabelCounts))
133-
?? candidates.find((node) => hasUniqueLabel(node, labelCounts))
134-
?? candidates[0];
135-
if (!selected) return null;
136-
137-
const selector = toSelectorExpression(selected, labelCounts, roleLabelCounts);
138-
if (!selector) return null;
139-
const label = String(selected.label ?? '').trim();
140-
return { label: label.length > 0 ? label : selector, selector };
141-
}
142-
143-
function hasUniqueLabel(node: SnapshotNode, labelCounts: Map<string, number>): boolean {
144-
const labelKey = normalizeSelectorText(node.label);
145-
if (!labelKey) return false;
146-
return (labelCounts.get(labelKey) ?? 0) === 1;
147-
}
148-
149-
function hasUniqueRoleAndLabel(
150-
node: SnapshotNode,
151-
labelCounts: Map<string, number>,
152-
roleLabelCounts: Map<string, number>,
153-
): boolean {
154-
const labelKey = normalizeSelectorText(node.label);
155-
if (!labelKey) return false;
156-
const roleKey = normalizeSelectorText(node.type) ?? '';
157-
const roleLabelKey = `${roleKey}::${labelKey}`;
158-
if (roleKey.length === 0) return hasUniqueLabel(node, labelCounts);
159-
return (roleLabelCounts.get(roleLabelKey) ?? 0) === 1;
160-
}
161-
162-
function toSelectorExpression(
163-
node: SnapshotNode,
164-
labelCounts: Map<string, number>,
165-
roleLabelCounts: Map<string, number>,
166-
): string | null {
167-
const label = normalizeSelectorText(node.label);
168-
if (!label) return null;
169-
const role = normalizeSelectorText(node.type);
170-
if (role) {
171-
const roleLabelKey = `${role}::${label}`;
172-
if ((roleLabelCounts.get(roleLabelKey) ?? 0) === 1 || (labelCounts.get(label) ?? 0) > 1) {
173-
return `role=${JSON.stringify(role)} label=${JSON.stringify(label)}`;
174-
}
175-
}
176-
return `label=${JSON.stringify(label)}`;
177-
}
178-
179-
function normalizeSelectorText(value: string | undefined): string | null {
180-
if (!value) return null;
181-
const normalized = value.trim().toLowerCase().replace(/\s+/g, ' ');
182-
return normalized.length > 0 ? normalized : null;
183-
}

0 commit comments

Comments
 (0)