From 2987c7d160d9804df797819f15ed46ec1ae15ce7 Mon Sep 17 00:00:00 2001 From: "farhan.labib" Date: Tue, 12 May 2026 13:05:15 +0600 Subject: [PATCH 1/2] feat(test): attach accessibility tree on test failure via saveTreeOnFailure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Screenshots show what the screen looks like when a test fails, but not why a locator didn't match. The accessibility tree carries that context — element labels, types, visibility flags, and bounds — which is exactly what you need to debug a failing getBy* call. Add saveTreeOnFailure: boolean to MobilewrightConfig. When enabled, the screen fixture calls viewTree() after a failure and attaches the result as view-tree-on-failure (JSON) to the HTML report, next to the existing screenshot. Opt-in, defaults to false, no impact on existing projects. --- packages/mobilewright/src/config.ts | 2 ++ packages/test/src/fixtures.ts | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/mobilewright/src/config.ts b/packages/mobilewright/src/config.ts index 7c00ced..2640d65 100644 --- a/packages/mobilewright/src/config.ts +++ b/packages/mobilewright/src/config.ts @@ -69,6 +69,8 @@ export interface MobilewrightConfig { installApps?: string | string[]; /** Automatically launch the app after connecting. Default: true. */ autoAppLaunch?: boolean; + /** Attach the accessibility tree as JSON to the test report on failure. Default: false. */ + saveTreeOnFailure?: boolean; /** mobilecli server URL (use for remote servers). */ url?: string; /** Path to mobilecli binary (if not on PATH). */ diff --git a/packages/test/src/fixtures.ts b/packages/test/src/fixtures.ts index 56ded3e..bd4af2c 100644 --- a/packages/test/src/fixtures.ts +++ b/packages/test/src/fixtures.ts @@ -33,6 +33,7 @@ type MobilewrightTestFixtures = { bundleId: string | undefined; platform: 'ios' | 'android' | undefined; deviceName: RegExp | undefined; + saveTreeOnFailure: boolean; device: Device; }; @@ -53,6 +54,11 @@ export const test = base.extend({ platform: [undefined, { option: true }], deviceName: [undefined, { option: true }], + saveTreeOnFailure: [async ({}, use, testInfo) => { + const config = await loadConfig(process.cwd(), testInfo.config.configFile); + await use(config.saveTreeOnFailure ?? false); + }, { option: true }], + device: async ({ platform, deviceName, bundleId }, use, testInfo) => { const config = await loadConfig(process.cwd(), testInfo.config.configFile); const merged = { @@ -104,7 +110,7 @@ export const test = base.extend({ } }, - screen: async ({ device, video }, use, testInfo) => { + screen: async ({ device, video, saveTreeOnFailure }, use, testInfo) => { const videoMode = typeof video === 'object' ? video.mode : video; const shouldRecord = videoMode === 'on' || videoMode === 'retain-on-failure'; const videoPath = shouldRecord @@ -145,6 +151,17 @@ export const test = base.extend({ } catch { // device may be disconnected } + if (saveTreeOnFailure) { + try { + const tree = await device.screen.viewTree(); + await testInfo.attach('view-tree-on-failure', { + body: Buffer.from(JSON.stringify(tree, null, 2)), + contentType: 'application/json', + }); + } catch { + // device may be disconnected + } + } } }, }); From bfd9269484916806d94cb82cbba47af1fc9c1d27 Mon Sep 17 00:00:00 2001 From: "farhan.labib" Date: Tue, 12 May 2026 17:49:39 +0600 Subject: [PATCH 2/2] rename saveTreeOnFailure to viewTree: 'on-failure' | 'off' Align with Playwright's naming convention for fixture options (like `video` and `trace`). The new config key is `viewTree` with values `'on-failure'` (attach on test failure) or `'off'` (default). Passing any other value throws at test start. Co-Authored-By: Claude Sonnet 4.6 --- packages/mobilewright/src/config.ts | 4 ++-- packages/test/src/fixtures.ts | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/mobilewright/src/config.ts b/packages/mobilewright/src/config.ts index 2640d65..69f65b5 100644 --- a/packages/mobilewright/src/config.ts +++ b/packages/mobilewright/src/config.ts @@ -69,8 +69,8 @@ export interface MobilewrightConfig { installApps?: string | string[]; /** Automatically launch the app after connecting. Default: true. */ autoAppLaunch?: boolean; - /** Attach the accessibility tree as JSON to the test report on failure. Default: false. */ - saveTreeOnFailure?: boolean; + /** Attach the accessibility tree as JSON to the test report. 'on-failure' attaches on test failure, 'off' disables. Default: 'off'. */ + viewTree?: 'on-failure' | 'off'; /** mobilecli server URL (use for remote servers). */ url?: string; /** Path to mobilecli binary (if not on PATH). */ diff --git a/packages/test/src/fixtures.ts b/packages/test/src/fixtures.ts index bd4af2c..f0aab5e 100644 --- a/packages/test/src/fixtures.ts +++ b/packages/test/src/fixtures.ts @@ -33,7 +33,7 @@ type MobilewrightTestFixtures = { bundleId: string | undefined; platform: 'ios' | 'android' | undefined; deviceName: RegExp | undefined; - saveTreeOnFailure: boolean; + viewTree: 'on-failure' | 'off'; device: Device; }; @@ -54,9 +54,13 @@ export const test = base.extend({ platform: [undefined, { option: true }], deviceName: [undefined, { option: true }], - saveTreeOnFailure: [async ({}, use, testInfo) => { + viewTree: [async ({}, use, testInfo) => { const config = await loadConfig(process.cwd(), testInfo.config.configFile); - await use(config.saveTreeOnFailure ?? false); + const value = config.viewTree ?? 'off'; + if (value !== 'on-failure' && value !== 'off') { + throw new Error(`Invalid viewTree value: "${value}". Must be "on-failure" or "off".`); + } + await use(value); }, { option: true }], device: async ({ platform, deviceName, bundleId }, use, testInfo) => { @@ -110,7 +114,7 @@ export const test = base.extend({ } }, - screen: async ({ device, video, saveTreeOnFailure }, use, testInfo) => { + screen: async ({ device, video, viewTree }, use, testInfo) => { const videoMode = typeof video === 'object' ? video.mode : video; const shouldRecord = videoMode === 'on' || videoMode === 'retain-on-failure'; const videoPath = shouldRecord @@ -151,7 +155,7 @@ export const test = base.extend({ } catch { // device may be disconnected } - if (saveTreeOnFailure) { + if (viewTree === 'on-failure') { try { const tree = await device.screen.viewTree(); await testInfo.attach('view-tree-on-failure', {