Skip to content

Commit 3136174

Browse files
committed
test(a11y): capture android qa hierarchy evidence
1 parent da54214 commit 3136174

4 files changed

Lines changed: 134 additions & 2 deletions

File tree

docs/release/accessibility-qa.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,23 @@ npm run release:qa:capture -- \
5151
--matrix accessibility \
5252
--row ios-voiceover-line-charts \
5353
--platform ios \
54-
--output docs/release/artifacts/ios-voiceover-line-charts.png
54+
--output docs/release/artifacts/ios-voiceover-line-charts.png \
55+
--ios-log-output docs/release/artifacts/ios-voiceover-line-charts.log
5556
```
5657

57-
Use `--device <simulator-udid-or-adb-serial>` for a specific target and `--no-launch` when the screen-reader state is already positioned on the target page. Record the artifact with `npm run release:qa:record -- --matrix accessibility --row <row-id> --status pass --evidence <artifact> --reviewed-by <name> --device "<device/os>" --build-surface "<build>" --notes "<screen-reader checks passed>"` only after the required VoiceOver or TalkBack checks pass.
58+
Android TalkBack rows can also capture a UIAutomator hierarchy snapshot:
59+
60+
```sh
61+
npm run release:qa:capture -- \
62+
--matrix accessibility \
63+
--row android-talkback-line-charts \
64+
--platform android \
65+
--output docs/release/artifacts/android-talkback-line-charts.png \
66+
--android-log-output docs/release/artifacts/android-talkback-line-charts.log \
67+
--android-ui-output docs/release/artifacts/android-talkback-line-charts.xml
68+
```
69+
70+
Use `--device <simulator-udid-or-adb-serial>` for a specific target and `--no-launch` when the screen-reader state is already positioned on the target page. The iOS log and Android UI hierarchy artifacts are supporting evidence; they do not replace the required VoiceOver or TalkBack manual review. Record the artifacts with `npm run release:qa:record -- --matrix accessibility --row <row-id> --status pass --evidence <artifact> --evidence <log-or-ui-artifact> --reviewed-by <name> --device "<device/os>" --build-surface "<build>" --notes "<screen-reader checks passed>"` only after the required VoiceOver or TalkBack checks pass.
5871

5972
Representative stories on the Line, Bar, Combined, Financial, Pie & Donut, Progress, and Heatmaps pages include a collapsed `Data details` panel. Use those panels during VoiceOver and TalkBack review to verify the table-fallback checks without making the public preview visually dense by default.
6073

scripts/capture-native-qa-options.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const captureNativeQaUsage = `Usage:
88
Options:
99
--android-log-output <path> Also clear/capture Android logcat to this repo-relative path.
1010
--android-log-lines <number> Number of trailing logcat lines to capture. Defaults to 400.
11+
--android-ui-output <path> Also capture Android UIAutomator hierarchy XML.
1112
--device <id> iOS simulator UDID or Android adb serial. Defaults to booted/default device.
1213
--dry-run Print launch and screenshot commands without executing them.
1314
--ios-log-output <path> Also capture iOS simulator logs to this repo-relative path.
@@ -47,6 +48,8 @@ export const parseCaptureNativeQaArgs = (argv) => {
4748
options.androidLogLines = Number(readValue());
4849
} else if (arg === "--android-log-output") {
4950
options.androidLogOutput = readValue();
51+
} else if (arg === "--android-ui-output") {
52+
options.androidUiOutput = readValue();
5053
} else if (arg === "--device") {
5154
options.device = readValue();
5255
} else if (arg === "--dry-run") {

scripts/capture-native-qa-screenshot.mjs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const shouldWaitAfterCommand = ({ args, command }) =>
4343
const validateCaptureOptions = ({
4444
androidLogLines,
4545
androidLogOutput,
46+
androidUiOutput,
4647
iosLogLast,
4748
iosLogOutput,
4849
iosLogPredicate,
@@ -56,6 +57,10 @@ const validateCaptureOptions = ({
5657
throw new Error("--android-log-output can only be used with Android");
5758
}
5859

60+
if (androidUiOutput && platform !== "android") {
61+
throw new Error("--android-ui-output can only be used with Android");
62+
}
63+
5964
if (iosLogOutput && platform !== "ios") {
6065
throw new Error("--ios-log-output can only be used with iOS");
6166
}
@@ -170,11 +175,13 @@ const buildAndroidCommands = ({
170175
androidLogLines,
171176
androidLogOutputPath,
172177
androidPackage,
178+
androidUiOutputPath,
173179
device,
174180
launch,
175181
launchUrl
176182
}) => {
177183
const commands = [];
184+
const uiDumpPath = "/sdcard/chartkit-native-qa-window.xml";
178185

179186
if (androidLogOutputPath) {
180187
commands.push({
@@ -196,6 +203,26 @@ const buildAndroidCommands = ({
196203
writesStdoutToFile: true
197204
});
198205

206+
if (androidUiOutputPath) {
207+
commands.push({
208+
args: [
209+
...(device ? ["-s", device] : []),
210+
"shell",
211+
"uiautomator",
212+
"dump",
213+
uiDumpPath
214+
],
215+
command: "adb"
216+
});
217+
commands.push({
218+
args: [...(device ? ["-s", device] : []), "exec-out", "cat", uiDumpPath],
219+
command: "adb",
220+
encoding: "utf8",
221+
outputPath: androidUiOutputPath,
222+
writesStdoutToFile: true
223+
});
224+
}
225+
199226
if (androidLogOutputPath) {
200227
commands.push({
201228
args: [
@@ -240,6 +267,7 @@ export const createNativeQaScreenshotPlan = async ({
240267
androidLogLines = 400,
241268
androidLogOutput,
242269
androidPackage = defaultAndroidPackage,
270+
androidUiOutput,
243271
device,
244272
iosLogLast = "2m",
245273
iosLogOutput,
@@ -255,6 +283,7 @@ export const createNativeQaScreenshotPlan = async ({
255283
validateCaptureOptions({
256284
androidLogLines,
257285
androidLogOutput,
286+
androidUiOutput,
258287
iosLogLast,
259288
iosLogOutput,
260289
iosLogPredicate,
@@ -267,13 +296,17 @@ export const createNativeQaScreenshotPlan = async ({
267296
const absoluteAndroidLogOutputPath = androidLogOutput
268297
? path.resolve(repoRoot, androidLogOutput)
269298
: undefined;
299+
const absoluteAndroidUiOutputPath = androidUiOutput
300+
? path.resolve(repoRoot, androidUiOutput)
301+
: undefined;
270302
const absoluteIosLogOutputPath = iosLogOutput
271303
? path.resolve(repoRoot, iosLogOutput)
272304
: undefined;
273305
const commandOptions = {
274306
androidLogLines,
275307
androidLogOutputPath: absoluteAndroidLogOutputPath,
276308
androidPackage,
309+
androidUiOutputPath: absoluteAndroidUiOutputPath,
277310
device,
278311
iosLogLast,
279312
iosLogOutputPath: absoluteIosLogOutputPath,
@@ -289,9 +322,11 @@ export const createNativeQaScreenshotPlan = async ({
289322

290323
return {
291324
absoluteAndroidLogOutputPath,
325+
absoluteAndroidUiOutputPath,
292326
absoluteIosLogOutputPath,
293327
absoluteOutputPath,
294328
androidLogOutput,
329+
androidUiOutput,
295330
commands,
296331
iosLogOutput,
297332
launchUrl: row.launchUrl,
@@ -301,6 +336,7 @@ export const createNativeQaScreenshotPlan = async ({
301336
"--status partial",
302337
`--evidence ${outputPath}`,
303338
androidLogOutput ? `--evidence ${androidLogOutput}` : "",
339+
androidUiOutput ? `--evidence ${androidUiOutput}` : "",
304340
iosLogOutput ? `--evidence ${iosLogOutput}` : ""
305341
]
306342
.filter(Boolean)
@@ -327,6 +363,11 @@ export const captureNativeQaScreenshot = async ({
327363
recursive: true
328364
});
329365
}
366+
if (plan.absoluteAndroidUiOutputPath) {
367+
await mkdir(path.dirname(plan.absoluteAndroidUiOutputPath), {
368+
recursive: true
369+
});
370+
}
330371
if (plan.absoluteIosLogOutputPath) {
331372
await mkdir(path.dirname(plan.absoluteIosLogOutputPath), {
332373
recursive: true

scripts/capture-native-qa-screenshot.test.mjs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,68 @@ describe("native QA screenshot capture", () => {
241241
expect(logcat).toBe("logcat lines");
242242
});
243243

244+
it("can capture Android UI hierarchy evidence with the screenshot", async () => {
245+
const tempRepo = await createTempRepo();
246+
const calls = [];
247+
const result = await captureNativeQaScreenshot({
248+
androidUiOutput: "docs/release/artifacts/android-line-ui.xml",
249+
matrixName: "runtime",
250+
output: "docs/release/artifacts/android-line.png",
251+
platform: "android",
252+
repoRoot: tempRepo,
253+
rowId: "android-line-charts",
254+
runner: (command) => {
255+
calls.push(command);
256+
257+
return command.encoding === "buffer"
258+
? Buffer.from("png-bytes")
259+
: "<hierarchy />";
260+
},
261+
waitMs: 0
262+
});
263+
const uiHierarchy = await readFile(
264+
join(tempRepo, "docs/release/artifacts/android-line-ui.xml"),
265+
"utf8"
266+
);
267+
268+
expect(result.recordCommand).toBe(
269+
"npm run release:qa:record -- --matrix runtime --row android-line-charts --status partial --evidence docs/release/artifacts/android-line.png --evidence docs/release/artifacts/android-line-ui.xml"
270+
);
271+
expect(calls.map((call) => call.args)).toEqual([
272+
[
273+
"shell",
274+
"am",
275+
"start",
276+
"-W",
277+
"-a",
278+
"android.intent.action.VIEW",
279+
"-d",
280+
"'chartkitshowcase://showcase?view=charts&page=line-area'",
281+
"io.chartkit.showcase"
282+
],
283+
["exec-out", "screencap", "-p"],
284+
["shell", "uiautomator", "dump", "/sdcard/chartkit-native-qa-window.xml"],
285+
["exec-out", "cat", "/sdcard/chartkit-native-qa-window.xml"]
286+
]);
287+
expect(uiHierarchy).toBe("<hierarchy />");
288+
});
289+
290+
it("can combine Android screenshot, UI hierarchy, and logcat evidence", async () => {
291+
const plan = await createNativeQaScreenshotPlan({
292+
androidLogOutput: "docs/release/artifacts/android-line.log",
293+
androidUiOutput: "docs/release/artifacts/android-line-ui.xml",
294+
matrixName: "runtime",
295+
output: "docs/release/artifacts/android-line.png",
296+
platform: "android",
297+
repoRoot,
298+
rowId: "android-line-charts"
299+
});
300+
301+
expect(plan.recordCommand).toBe(
302+
"npm run release:qa:record -- --matrix runtime --row android-line-charts --status partial --evidence docs/release/artifacts/android-line.png --evidence docs/release/artifacts/android-line.log --evidence docs/release/artifacts/android-line-ui.xml"
303+
);
304+
});
305+
244306
it("can skip launching and capture the current Android screen", async () => {
245307
const plan = await createNativeQaScreenshotPlan({
246308
launch: false,
@@ -295,4 +357,17 @@ describe("native QA screenshot capture", () => {
295357
})
296358
).rejects.toThrow("--ios-log-output can only be used with iOS");
297359
});
360+
361+
it("rejects Android UI output for iOS captures", async () => {
362+
await expect(
363+
captureNativeQaScreenshot({
364+
androidUiOutput: "docs/release/artifacts/ios-line.xml",
365+
dryRun: true,
366+
matrixName: "runtime",
367+
platform: "ios",
368+
repoRoot,
369+
rowId: "ios-line-charts"
370+
})
371+
).rejects.toThrow("--android-ui-output can only be used with Android");
372+
});
298373
});

0 commit comments

Comments
 (0)