Skip to main content

appium-attribute-waits

Why It Exists

Before this skill family, no RN/Appium testing skill existed at all — a grep across ~/.claude/skills and ~/.agents/skills for testid|appium|webdriver|wdio turned up 17 matched files, every one belonging to the Playwright family, Chrome DevTools tooling, or next-frontend-quality-loop, with zero hits in either vercel-react-native-skills or mobile-expo-app. .omc/plans/appium-rn-testing-skills.md (local research artifact, not tracked in this repo) authored four skills — appium-testid-attributes, appium-testid-catalog, this one, and appium-page-objects — to fill that gap, mirroring the playwright-* family's producer/catalog/consumer/ structure split. The plan's own ADR is candid that this skill is "honestly thinner than its Playwright namesake," since swarmdriver's waits are inherited NativeBase methods rather than an authored helper module like Playwright's readiness.ts.

The plan's original draft also had a concrete factual error corrected during review: it described isShown() as a driver.waitUntil() wrapper. Round-1 Architect review traced the real implementation (base.ts:71-96) and found isShown() is a plain try/catch around findEleAndSel() + el.isDisplayed() that swallows any thrown error — a stale element, a dropped driver session, anything — into a bare false, indistinguishable from a genuine "not shown yet." That correction became invariant 2.

Before asserting either an epoch-fencing port or an intentional non-port, Implementation Step 2 required investigating .omc/artifacts/appium-rn-attribute-budget.md rather than assuming Playwright's data-*-based epoch counter pattern simply didn't apply. Both real candidates it evaluated — accessibilityValue.now and nativeID — were rejected: the former is screen-reader-announced and semantically tied to a min/max range (fails the "never widen an a11y prop for test convenience" invariant outright), and the latter has unverified iOS surfacing and collides with its existing accessibilityLabelledBy cross-reference role.

The skill's sharpest invariants, though, came from a real-device incident after initial ship. Commit d640258 records that during a poker-helper next-appium run, the shipped accessibilityState.busy guess turned out to be wrong: WDA exposes no compound accessibilityState.* attribute at all, so polling it silently no-ops instead of erroring. The fix repointed the busy-clearing poll at the native enabled attribute instead. The same run also reproduced a screen's own root container reporting isDisplayed()/visible as false while genuinely on-screen — which hangs waitForIsShown()'s isDisplayed()-based check for the full timeout — and added waitForScreenShown(), an existence-based check, for root-level readiness specifically.

What It Does

appium-attribute-waits documents how to wait correctly on async native UI state in swarmdriver-based Appium/WDIO tests for React Native screens: a spinner clearing, a busy accessibilityState settling, a screen transition finishing, a list finishing a refresh. It covers NativeBase.waitForIsShown()/waitForIsNotShown() semantics, the isShown() trap underneath them, and composing driver.waitUntil()/ nativeUtils.waitForCondition() for anything those inherited methods don't cover.

It does not define how components emit the accessibilityState/accessibilityValue props being waited on — that's the sibling skill appium-testid-attributes — and it does not define page-object class structure — that's the sibling skill appium-page-objects. This skill only covers waiting on native UI state correctly once a page object and its props already exist.

How To Use It

Triggers on: "wait for element visible appium", "flaky Appium test", "waitForIsShown timeout", "isShown() swallowing errors", "driver.waitUntil composition", "wait for accessibilityState.busy to clear", or writing/reviewing an Appium wait against instrumented native UI state.

skills add git@github.com:catesandrew/next-starters.git --skill skills/appium-attribute-waits -g
npm install @next-starters/skill-appium-attribute-waits
/plugin marketplace add catesandrew/next-starters
/plugin install appium-attribute-waits@next-starters

Gotchas & Invariants

  • waitForIsShown()/waitForIsNotShown() (base.ts:39-66) already wrap driver.waitUntil() — never wrap a second driver.waitUntil() around an already-polling inherited method.
  • isShown() (base.ts:71-96) is not a waitUntil wrapper — it's a try/catch that swallows every thrown error into a plain false. Always pair a wait built on it with a specific, selector-including timeoutMsg, never a generic one, or a timeout is indistinguishable from a genuine driver error.
  • Compose driver.waitUntil()/nativeUtils.waitForCondition() directly for anything the two inherited methods don't cover — never a hand-rolled setTimeout/sleep polling loop.
  • Epoch-fencing is intentionally not ported from the Playwright family. accessibilityValue.now and nativeID were both investigated and rejected as counter substitutes (per appium-rn-attribute-budget.md); do not invent a third candidate or repurpose either prop as a test-only counter.
  • The narrowly-scoped fallback for "did an async action provably progress": poll an existing, already-real in-budget state transition — e.g. a control's busy state clearing — via driver.waitUntil()/nativeUtils.waitForCondition(), instead of a dedicated counter prop.
  • v1.1.0, verified against a real device: WDA exposes no compound accessibilityState.* attribute at all, so the busy-clearing poll reads the native enabled attribute instead — safe only when disabled derives from the same in-flight variable as busy.
  • v1.1.0, also verified against a real device: a screen's own root container can report isDisplayed()/visible as false while genuinely on-screen, so root-level readiness checks use the existence-based waitForScreenShown() instead of the inherited, isDisplayed()-based waitForIsShown(). Leaf elements are unaffected and keep using waitForIsShown().
  • Treating an UNVERIFIED page-source attribute mapping from appium-rn-attribute-budget.md as confirmed fact in a real spec, without a first-run validation step, is a red flag — mark it unverified until checked against a real session.
  • appium-page-objects — its action methods call this skill's inherited wait helpers around interactions but never redefine them.
  • appium-testid-attributes — the producer side; emits the accessibilityState/accessibilityValue props this skill's waits poll.
  • appium-testid-catalog — owns locator translation and platform-divergence knowledge that this skill assumes but doesn't itself document.

Sourced from: skills/appium-attribute-waits/metadata.json, skills/appium-attribute-waits/SKILL.md, .omc/plans/appium-rn-testing-skills.md (local research artifact, not tracked in this repo), git log --follow -- skills/appium-attribute-waits (commits 5a4e3d3, d640258)