From 1b78442875a4e749fdadf9538de6ea516264b88d Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 22 May 2026 14:11:21 -0400 Subject: [PATCH 1/9] Fix iOS and web CI workflow failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wikipedia-trails: Ubuntu 24.04 renamed libasound2 → libasound2t64; the old package name has no installation candidate and caused the apt-get step to fail. iOS-contacts-trails: job was hitting the 45-minute timeout (run took ~46 min); increase timeout to 60 minutes to give the job room to complete and upload artifacts. --- .github/workflows/ios-contacts-trails.yml | 2 +- .github/workflows/wikipedia-trails.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ios-contacts-trails.yml b/.github/workflows/ios-contacts-trails.yml index bafadab6..24e1ffe4 100644 --- a/.github/workflows/ios-contacts-trails.yml +++ b/.github/workflows/ios-contacts-trails.yml @@ -27,7 +27,7 @@ jobs: # Xcode 16 which includes iOS 18 simulator runtimes. ios-contacts-trails: runs-on: macos-15 - timeout-minutes: 45 + timeout-minutes: 60 env: # Recordings-only — LLM is never invoked. A non-empty sentinel value is # required to satisfy the provider/client wiring. diff --git a/.github/workflows/wikipedia-trails.yml b/.github/workflows/wikipedia-trails.yml index 6fc79632..b04f5711 100644 --- a/.github/workflows/wikipedia-trails.yml +++ b/.github/workflows/wikipedia-trails.yml @@ -52,7 +52,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y \ - libasound2 \ + libasound2t64 \ libatk-bridge2.0-0 \ libatk1.0-0 \ libcups2 \ From 695b5188588d790db8b45bd1861689a9bcbb6098 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 22 May 2026 15:01:14 -0400 Subject: [PATCH 2/9] Increase web timeout to 60min; run 2 iOS contacts trails Wikipedia-trails: Gradle build for :trailblaze-desktop:jar alone takes ~30min on the ubuntu-latest runner, hitting the old 30-minute ceiling before the trail can execute. 60 minutes gives enough headroom for build + daemon startup + trail. iOS-contacts-trails: add test-search-no-results as the second trail run after the existing test-search-by-first-name, reusing the already-running daemon. --- .github/pr_run_ios_contacts_trails.sh | 4 ++++ .github/workflows/wikipedia-trails.yml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/pr_run_ios_contacts_trails.sh b/.github/pr_run_ios_contacts_trails.sh index 37c187ad..8fe04753 100755 --- a/.github/pr_run_ios_contacts_trails.sh +++ b/.github/pr_run_ios_contacts_trails.sh @@ -75,6 +75,10 @@ if [ "$TEST_FAILED" != "true" ]; then ./trailblaze trail -d ios \ trails/ios-contacts/test-search-by-first-name/ios-iphone.trail.yaml \ || TEST_FAILED=true + + ./trailblaze trail -d ios \ + trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml \ + || TEST_FAILED=true else echo "Skipping test execution because setup failed" fi diff --git a/.github/workflows/wikipedia-trails.yml b/.github/workflows/wikipedia-trails.yml index b04f5711..4e4c58db 100644 --- a/.github/workflows/wikipedia-trails.yml +++ b/.github/workflows/wikipedia-trails.yml @@ -22,7 +22,7 @@ jobs: # pre-recorded tool sequences. wikipedia-trails: runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 60 env: # Recordings-only — LLM is never invoked. A non-empty sentinel value is # required to satisfy the provider/client wiring (see android-tests-host-rpc From 674ad121e31efff02f1fe4a3e35d266581fdaf51 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 22 May 2026 18:40:01 -0400 Subject: [PATCH 3/9] Set TRAILBLAZE_CONFIG_DIR for web and iOS trail scripts Without this, the daemon starts without the example packs loaded: - Wikipedia: the 'wikipedia' target can't be resolved, causing the trail to hang until the 1800s internal timeout fires - iOS Contacts: contacts_ios_searchContacts (used by test-search-no-results) is not registered because the contacts pack isn't loaded --- .github/pr_run_ios_contacts_trails.sh | 3 ++- .github/pr_run_wikipedia_trails.sh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/pr_run_ios_contacts_trails.sh b/.github/pr_run_ios_contacts_trails.sh index 8fe04753..88f1c472 100755 --- a/.github/pr_run_ios_contacts_trails.sh +++ b/.github/pr_run_ios_contacts_trails.sh @@ -47,7 +47,8 @@ if [ "$TEST_FAILED" != "true" ]; then # blocks the process, so we background it with `&` and poll /ping until ready. # The `trail` invocation below detects the running daemon and reuses it. echo "Starting Trailblaze daemon (app --foreground --headless)..." - ./trailblaze app --foreground --headless > /tmp/trailblaze.log 2>&1 & + TRAILBLAZE_CONFIG_DIR="$(pwd)/examples/ios-contacts/trails/config" \ + ./trailblaze app --foreground --headless > /tmp/trailblaze.log 2>&1 & TRAILBLAZE_PID=$! echo "Trailblaze daemon started with PID: $TRAILBLAZE_PID" echo "Waiting for Trailblaze daemon to be ready on port 52525 (this may take up to 2 minutes)..." diff --git a/.github/pr_run_wikipedia_trails.sh b/.github/pr_run_wikipedia_trails.sh index 5a772063..542022a2 100755 --- a/.github/pr_run_wikipedia_trails.sh +++ b/.github/pr_run_wikipedia_trails.sh @@ -38,7 +38,8 @@ if [ "$TEST_FAILED" != "true" ]; then # blocks the process, so we background it with `&` and poll /ping until ready. # The `trail` invocation below detects the running daemon and reuses it. echo "Starting Trailblaze daemon (app --foreground --headless)..." - ./trailblaze app --foreground --headless > /tmp/trailblaze.log 2>&1 & + TRAILBLAZE_CONFIG_DIR="$(pwd)/examples/wikipedia/trails/config" \ + ./trailblaze app --foreground --headless > /tmp/trailblaze.log 2>&1 & TRAILBLAZE_PID=$! echo "Trailblaze daemon started with PID: $TRAILBLAZE_PID" echo "Waiting for Trailblaze daemon to be ready on port 52525 (this may take up to 2 minutes)..." From cd19bf9c7175ab65e37632c5b65a41d9e8459f81 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 22 May 2026 20:42:55 -0400 Subject: [PATCH 4/9] Fix TRAILBLAZE_CONFIG_DIR scoping and pre-install Playwright Chromium iOS: TRAILBLAZE_CONFIG_DIR must be exported before the first ./gradlew call. JavaExec subprocesses in CI inherit the Gradle daemon's environment (set at daemon start), not the caller's shell. The inline VAR=value ./trailblaze pattern never reached the JVM because the daemon was already running from the pre-build :trailblaze-desktop:jar step without the variable. Web: Same config-dir fix, plus pre-install Playwright Chromium via bunx playwright@1.59.0 install chromium before starting the daemon. The JVM and npm Playwright libraries share ~/.cache/ms-playwright, so pre-installing avoids the download happening inside DaemonClient's hardcoded 1800s poll window. --- .github/pr_run_ios_contacts_trails.sh | 10 ++++++++-- .github/pr_run_wikipedia_trails.sh | 21 +++++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/.github/pr_run_ios_contacts_trails.sh b/.github/pr_run_ios_contacts_trails.sh index 88f1c472..63f0cf6f 100755 --- a/.github/pr_run_ios_contacts_trails.sh +++ b/.github/pr_run_ios_contacts_trails.sh @@ -23,6 +23,13 @@ echo "Installing TypeScript SDK devDependencies (esbuild)..." (cd sdks/typescript && bun install --frozen-lockfile) \ || { echo "ERROR: bun install failed in sdks/typescript"; TEST_FAILED=true; } +# Export config dir before the first Gradle invocation so the Gradle daemon +# starts with it in its environment. JavaExec subprocesses inherit the daemon's +# environment, not the caller's shell, so the export must precede the daemon's +# first start (triggered by :trailblaze-desktop:jar below). +export TRAILBLAZE_CONFIG_DIR="$(pwd)/examples/ios-contacts/trails/config" +echo "TRAILBLAZE_CONFIG_DIR=$TRAILBLAZE_CONFIG_DIR" + # Pre-compile the Trailblaze desktop module so the daemon starts within the # 110s port-ready window below. if [ "$TEST_FAILED" != "true" ]; then @@ -47,8 +54,7 @@ if [ "$TEST_FAILED" != "true" ]; then # blocks the process, so we background it with `&` and poll /ping until ready. # The `trail` invocation below detects the running daemon and reuses it. echo "Starting Trailblaze daemon (app --foreground --headless)..." - TRAILBLAZE_CONFIG_DIR="$(pwd)/examples/ios-contacts/trails/config" \ - ./trailblaze app --foreground --headless > /tmp/trailblaze.log 2>&1 & + ./trailblaze app --foreground --headless > /tmp/trailblaze.log 2>&1 & TRAILBLAZE_PID=$! echo "Trailblaze daemon started with PID: $TRAILBLAZE_PID" echo "Waiting for Trailblaze daemon to be ready on port 52525 (this may take up to 2 minutes)..." diff --git a/.github/pr_run_wikipedia_trails.sh b/.github/pr_run_wikipedia_trails.sh index 542022a2..4a9a6c5d 100755 --- a/.github/pr_run_wikipedia_trails.sh +++ b/.github/pr_run_wikipedia_trails.sh @@ -22,6 +22,13 @@ echo "Installing TypeScript SDK devDependencies (esbuild)..." (cd sdks/typescript && bun install --frozen-lockfile) \ || { echo "ERROR: bun install failed in sdks/typescript"; TEST_FAILED=true; } +# Export config dir before the first Gradle invocation so the Gradle daemon +# starts with it in its environment. JavaExec subprocesses inherit the daemon's +# environment, not the caller's shell, so the export must precede the daemon's +# first start (triggered by :trailblaze-desktop:jar below). +export TRAILBLAZE_CONFIG_DIR="$(pwd)/examples/wikipedia/trails/config" +echo "TRAILBLAZE_CONFIG_DIR=$TRAILBLAZE_CONFIG_DIR" + # Pre-compile the Trailblaze desktop module so the daemon starts within the # 110s port-ready window below. Without this, `./trailblaze app …` does a cold # Kotlin compile (4+ min on CI) inside the backgrounded process, the wait loop @@ -33,13 +40,23 @@ if [ "$TEST_FAILED" != "true" ]; then ./gradlew :trailblaze-desktop:jar || { echo "ERROR: Failed to build Trailblaze desktop"; TEST_FAILED=true; } fi +# Pre-install Playwright Chromium so the browser is cached before the trail +# runs. The JVM Playwright library (version 1.50.0) and the npm package use the +# same ~/.cache/ms-playwright browser cache, so pre-installing via bunx avoids +# the download happening inside DaemonClient's hardcoded 1800s poll window. +if [ "$TEST_FAILED" != "true" ]; then + echo "Pre-installing Playwright Chromium..." + bunx playwright@1.59.0 install chromium \ + && echo "✓ Playwright Chromium pre-installed" \ + || echo "WARNING: Playwright pre-install failed — download will happen during trail execution" +fi + if [ "$TEST_FAILED" != "true" ]; then # Start the Trailblaze daemon in the background. `app --foreground --headless` # blocks the process, so we background it with `&` and poll /ping until ready. # The `trail` invocation below detects the running daemon and reuses it. echo "Starting Trailblaze daemon (app --foreground --headless)..." - TRAILBLAZE_CONFIG_DIR="$(pwd)/examples/wikipedia/trails/config" \ - ./trailblaze app --foreground --headless > /tmp/trailblaze.log 2>&1 & + ./trailblaze app --foreground --headless > /tmp/trailblaze.log 2>&1 & TRAILBLAZE_PID=$! echo "Trailblaze daemon started with PID: $TRAILBLAZE_PID" echo "Waiting for Trailblaze daemon to be ready on port 52525 (this may take up to 2 minutes)..." From b0de2920cc759e4f3c9c09476d7434498058ffbf Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 22 May 2026 22:12:48 -0400 Subject: [PATCH 5/9] Fix TRAILBLAZE_CONFIG_DIR ignoring pack targets from the env dir's trailblaze.yaml When TRAILBLAZE_CONFIG_DIR is set to an example workspace (e.g. examples/ios-contacts/trails/config), the resolver was using the workspace-root configFile (trails/config/trailblaze.yaml) to resolve pack targets. This caused the repo-root contacts pack (Android tools only) to be loaded instead of the ios-contacts pack (iOS scripted tools including contacts_ios_searchContacts). The fix: when the configDir contains its own trailblaze.yaml, prefer it as the configFile. This makes the daemon load targets from the example workspace's pack definitions rather than the outer workspace root. Also increase DaemonClient.RUN_POLL_TIMEOUT_MS from 30 to 60 minutes so the Wikipedia trail (which takes ~30 min to execute) has enough headroom without hitting the client-side poll timeout. --- .../config/project/TrailblazeWorkspaceConfigResolver.kt | 7 ++++++- .../src/main/java/xyz/block/trailblaze/cli/DaemonClient.kt | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/trailblaze-common/src/jvmAndAndroid/kotlin/xyz/block/trailblaze/config/project/TrailblazeWorkspaceConfigResolver.kt b/trailblaze-common/src/jvmAndAndroid/kotlin/xyz/block/trailblaze/config/project/TrailblazeWorkspaceConfigResolver.kt index b6156944..6997ad32 100644 --- a/trailblaze-common/src/jvmAndAndroid/kotlin/xyz/block/trailblaze/config/project/TrailblazeWorkspaceConfigResolver.kt +++ b/trailblaze-common/src/jvmAndAndroid/kotlin/xyz/block/trailblaze/config/project/TrailblazeWorkspaceConfigResolver.kt @@ -27,9 +27,14 @@ object TrailblazeWorkspaceConfigResolver { if (envOverride != null) { val envDir = File(envOverride) if (envDir.isDirectory) { + // When the configDir has its own trailblaze.yaml, prefer it as the config file so + // pack targets declared there (e.g. ios-contacts/packs/contacts) are loaded instead + // of the workspace-root config's targets (which may point to a different pack of the + // same id with different tools). Falls back to the walk-up configFile when absent. + val envConfigFile = File(envDir, TrailblazeConfigPaths.CONFIG_FILENAME).takeIf { it.isFile } return ResolvedTrailblazeWorkspaceConfig( workspaceRoot = workspaceRoot, - configFile = configFile, + configFile = envConfigFile ?: configFile, configDir = envDir, ) } diff --git a/trailblaze-host/src/main/java/xyz/block/trailblaze/cli/DaemonClient.kt b/trailblaze-host/src/main/java/xyz/block/trailblaze/cli/DaemonClient.kt index 16f492f9..9175c17d 100644 --- a/trailblaze-host/src/main/java/xyz/block/trailblaze/cli/DaemonClient.kt +++ b/trailblaze-host/src/main/java/xyz/block/trailblaze/cli/DaemonClient.kt @@ -490,8 +490,8 @@ class DaemonClient( /** Poll interval when waiting for daemon */ const val POLL_INTERVAL_MS = 500L - /** Overall timeout for polling a run to completion (30 minutes) */ - const val RUN_POLL_TIMEOUT_MS = 30 * 60 * 1000L + /** Overall timeout for polling a run to completion (60 minutes) */ + const val RUN_POLL_TIMEOUT_MS = 60 * 60 * 1000L /** * Max consecutive poll errors before falling back to a /ping health check. From e88b67f161a40d2f5183b38e699cef8199aaf7de Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 22 May 2026 22:56:58 -0400 Subject: [PATCH 6/9] Fix scripted-tool wrapper to expose client.tools Proxy The synthesized wrapper in DaemonScriptedToolBundler created a __client shim with only callTool, leaving __client.tools undefined. Any scripted tool using the client.tools.(args) authoring surface then crashed at runtime with "cannot read property 'X' of undefined". Adds a Proxy on __client.tools that maps property accesses to __client.callTool dispatches, mirroring the SDK's createToolsProxy implementation. Pinned by a new synthesizeWrapper unit test. --- .../scripting/DaemonScriptedToolBundler.kt | 12 ++++++++++++ .../scripting/DaemonScriptedToolBundlerTest.kt | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/trailblaze-host/src/main/java/xyz/block/trailblaze/scripting/DaemonScriptedToolBundler.kt b/trailblaze-host/src/main/java/xyz/block/trailblaze/scripting/DaemonScriptedToolBundler.kt index fdac0317..2da1bd01 100644 --- a/trailblaze-host/src/main/java/xyz/block/trailblaze/scripting/DaemonScriptedToolBundler.kt +++ b/trailblaze-host/src/main/java/xyz/block/trailblaze/scripting/DaemonScriptedToolBundler.kt @@ -535,6 +535,18 @@ class DaemonScriptedToolBundler( appendLine(" return result;") appendLine(" },") appendLine("};") + // Attach a `tools` Proxy so scripted tools that use the `client.tools.(args)` + // authoring surface (the SDK's TrailblazeClient.tools property) work correctly at + // runtime. Without this, `client.tools` is undefined and any `client.tools.X()` + // call throws "cannot read property 'X' of undefined". The Proxy maps any string + // property access to a callable that delegates to `__client.callTool(prop, args)`, + // mirroring the SDK's own `createToolsProxy` implementation. + appendLine("__client.tools = new Proxy({}, {") + appendLine(" get: function(target, prop) {") + appendLine(" if (typeof prop !== \"string\") return undefined;") + appendLine(" return function(args) { return __client.callTool(prop, args); };") + appendLine(" }") + appendLine("});") appendLine() // Normalize user return values into the `{content: [...]}` envelope `QuickJsToolHost` // parses. Authors typically `return "Launched X."` from their handlers; the host diff --git a/trailblaze-host/src/test/java/xyz/block/trailblaze/scripting/DaemonScriptedToolBundlerTest.kt b/trailblaze-host/src/test/java/xyz/block/trailblaze/scripting/DaemonScriptedToolBundlerTest.kt index 77443dd5..68b0c927 100644 --- a/trailblaze-host/src/test/java/xyz/block/trailblaze/scripting/DaemonScriptedToolBundlerTest.kt +++ b/trailblaze-host/src/test/java/xyz/block/trailblaze/scripting/DaemonScriptedToolBundlerTest.kt @@ -588,6 +588,24 @@ class DaemonScriptedToolBundlerTest { ) } + @Test + fun `synthesized wrapper exposes client_tools proxy for client_tools_X authoring surface`() { + // Pins the fix for the runtime failure "cannot read property 'X' of undefined" + // that occurs when a scripted tool uses `client.tools.launchApp(...)` instead of + // `client.callTool("launchApp", ...)`. The wrapper's `__client` shim must expose + // a `tools` Proxy that maps property accesses to `callTool` dispatches. + val src = writeTinyTs("tools-proxy-source.ts", exportName = "doSomething") + val wrapper = bundler.synthesizeWrapper(src, toolName = "doSomething") + assertTrue( + wrapper.contains("__client.tools = new Proxy"), + "expected wrapper to attach client.tools Proxy; got:\n$wrapper", + ) + assertTrue( + wrapper.contains("return function(args) { return __client.callTool(prop, args); }"), + "expected tools Proxy to delegate to __client.callTool; got:\n$wrapper", + ) + } + // --- helpers --- private fun writeTinyTs( From cd5dc550f3441e3b19378c140ce19d0fa139358b Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 22 May 2026 23:48:53 -0400 Subject: [PATCH 7/9] Fix test-search-no-results trail: remove redundant manual search steps and simplify assertion contacts_ios_searchContacts already types the query, so the extra tapOnElementBySelector + inputText steps were doubling the input. The assertVisibleBySelector used a version-specific iOS text ("No Results for ZzZzNoSuchContact") that only appears on iOS 26; iOS 18 (CI runner) shows only "No Results". Simplify to just "No Results" which matches across iOS versions. --- .../test-search-no-results/ios-iphone.trail.yaml | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml b/trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml index 0f9f5924..32babd05 100644 --- a/trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml +++ b/trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml @@ -10,20 +10,10 @@ - contacts_ios_searchContacts: query: ZzZzNoSuchContact openFirstResult: false - - tapOnElementBySelector: - reason: To search for 'ZzZzNoSuchContact', I need to tap the 'Search' field (q410), which is visible at the bottom of the screen. - selector: - textRegex: Search - nodeSelector: - iosMaestro: - hintTextRegex: Search - - inputText: - text: ZzZzNoSuchContact - reasoning: Now that the search field is focused, I will input the search term 'ZzZzNoSuchContact' into the search field to trigger the search and look for 'No Results'. - assertVisibleBySelector: - reason: The screen displays 'No Results for “ZzZzNoSuchContact”', which verifies 'No Results' is visible as required by the objective. + reason: After searching for a nonexistent contact, the Contacts app shows a “No Results” banner. Verify it is visible to confirm the no-results state. selector: - textRegex: No Results for “ZzZzNoSuchContact” + textRegex: No Results nodeSelector: iosMaestro: - accessibilityTextRegex: No Results for “ZzZzNoSuchContact” + accessibilityTextRegex: No Results From 96917727580f47b0c89d4818a6a93cf8fb260a1f Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sat, 23 May 2026 00:24:14 -0400 Subject: [PATCH 8/9] Remove iOS-version-specific assertVisibleBySelector from no-results trail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iOS 18 (CI runner) shows an empty contacts list with no visible "No Results" text when searching returns no matches; the assertion cannot be made reliably across iOS versions. The meaningful coverage here is that contacts_ios_searchContacts returns without error when openFirstResult:false — that behavior is still exercised. --- .../test-search-no-results/ios-iphone.trail.yaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml b/trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml index 32babd05..063e3f37 100644 --- a/trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml +++ b/trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml @@ -10,10 +10,3 @@ - contacts_ios_searchContacts: query: ZzZzNoSuchContact openFirstResult: false - - assertVisibleBySelector: - reason: After searching for a nonexistent contact, the Contacts app shows a “No Results” banner. Verify it is visible to confirm the no-results state. - selector: - textRegex: No Results - nodeSelector: - iosMaestro: - accessibilityTextRegex: No Results From 126f66d17b1c1dc1490712950d2852fe10cf47ce Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sat, 23 May 2026 08:55:47 -0400 Subject: [PATCH 9/9] Restore no-results assertion to test-search-no-results trail Use assertVisibleWithAccessibilityText to verify the iOS 18 'No Results' accessibility text appears after searching for a non-existent contact. This matches what the contacts_ios_searchContacts tool itself uses internally to detect the no-results state. --- .../ios-contacts/test-search-no-results/ios-iphone.trail.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml b/trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml index 063e3f37..d1a1cc17 100644 --- a/trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml +++ b/trails/ios-contacts/test-search-no-results/ios-iphone.trail.yaml @@ -10,3 +10,5 @@ - contacts_ios_searchContacts: query: ZzZzNoSuchContact openFirstResult: false + - assertVisibleWithAccessibilityText: + accessibilityText: "No Results"