appium-testid-catalog
Why It Exists
This skill exists to fix a mistake the plan itself made. .omc/plans/appium-rn-testing-skills.md
(local research artifact, not tracked in this repo) records that the original
draft of this whole appium-* family had the testID convention backwards,
proposing ~-prefixed selectors for the catalog. Round-1 Architect review
traced that against swarmdriver's real buildSelector() (utils.ts:124-139)
and found a ~ prefix resolves to WDIO's accessibility id strategy, which
buildSelector returns untranslated — silently dropping the Android
<appPackage>:id/ resource-id prefix the bare-id path would otherwise add.
That defect is what the plan calls "a live instance" of a deeper structural
problem, not a one-off typo: RN/Appium's dominant organizing axis is platform
(Android vs iOS), not the producer/consumer axis the sibling playwright-*
family mirrors, and a straight 1:1 content mirror across four skills would
have sharded that platform knowledge into duplication or platform-blind
half-truths.
The plan's chosen fix, "Option A′," is why this specific skill carries more
than a typed-object pattern: it promotes appium-testid-catalog to the single
authoritative home for locator translation, owning the full
nativeUtils.buildSelector() contract and the Android/iOS divergence table,
rather than letting appium-testid-attributes or appium-page-objects
re-derive platform-specific behavior inline. The plan names this risk
explicitly and assigns a dedicated cross-reference verification pass (its
Implementation Step 7) to catch platform knowledge leaking into the wrong
skill — because nothing mechanically stops an author from re-deriving it
inline instead of citing back here.
Two further hard-won facts came out of the same review cycle and now live in
this skill's invariant 9: nativeUtils.findEles() computes a translated
selector and then queries with the raw, untranslated one (utils.ts:293 vs
:299), discarding its own translation; and buildSelector reads the bare
appPackage capability key on Android while a nearby reference in the same
module uses the W3C-namespaced appium:appPackage, so a strict W3C session
can silently produce id=undefined:id/<testID> locators. Both are documented
here as workarounds for real dependency defects, not conventions to canonize.
What It Does
appium-testid-catalog defines a shared registry helper that builds a nested,
section-keyed catalog object of bare testID strings for a feature area,
validates uniqueness at dev-time (throwing on a duplicate leaf and naming
every conflicting path), and a WDIO-side mirror module that re-exports the
identical object so RN components and WDIO specs read one in-memory object
and can never drift on an id string.
It is also the authoritative home for the nativeUtils.buildSelector()
locator-translation contract and the full Android/iOS platform-divergence
table — knowledge no sibling skill duplicates. appium-testid-attributes
consumes catalog ids in components but does not define the registry, mirror,
or translation mechanics; appium-attribute-waits and appium-page-objects
pass catalog ids through buildSelector at lookup time but likewise treat
this skill as the source of truth for what that translation actually does.
How To Use It
Triggers on: "testID catalog", "typed test ids for RN", "testID registry", "duplicate testID", "migrate hardcoded testID strings", "WDIO mirror module", "buildSelector for Android vs iOS", "platform-divergent locator", or standing up the catalog layer for a new feature area.
skills add git@github.com:catesandrew/next-starters.git --skill skills/appium-testid-catalog -g
npm install @next-starters/skill-appium-testid-catalog
/plugin marketplace add catesandrew/next-starters
/plugin install appium-testid-catalog@next-starters
Gotchas & Invariants
- Catalogs store bare testIDs only — never
~-prefixed, neverid=-prefixed, never platform-qualified. A~-prefixed value hits WDIO'saccessibility idstrategy and returns untranslated frombuildSelector, losing the Android<appPackage>:id/prefix; the reference registry helper rejects any leaf beginning with~or containing astrategy=prefix. - One typed catalog module per feature area, nested by view/section (
list,detail,create,edit,dialogs), built via the sharedcreateTestIdRegistryhelper — never a hand-rolled plain object literal, which validates nothing. nativeUtils.buildSelector()is the only locator-translation authority. Never hand-build`id=${pkg}:id/${id}`at a call site and never branch ondriver.isAndroidin a spec — that duplicates the prefixing rule this skill already owns.- Never import swarmdriver's
find-strategyinternals.defineStrategyandfindStrategyare real exports but are not part of@caps/core's public surface (absent from the native barrel and the package'sexportsmap); import onlynativeUtils.buildSelector. - Dynamic per-row ids use a
`prefix-${id}`convention and must be looked up withfindEleonce per individually-built id — neverfindElesagainst a shared pattern.findElescomputes the translated selector and then queries with the raw, untranslated one (utils.ts:293vs:299), silently discarding the translation. - Dev-time uniqueness validation must throw and name both conflicting
catalog paths (e.g.
list.pageanddetail.page), not just the duplicated value — a message naming only the value forces a grep; naming both paths is the fix instruction. - The WDIO-side mirror module re-exports the exact same catalog object — zero string duplication. A renamed or removed id breaks the mirror's import at compile time instead of leaving a stale string alive in test code.
buildSelectorreads the bareappPackagecapability key on Android (utils.ts:132) while a nearby reference in the same module uses the W3C-namespacedappium:appPackage(utils.ts:168). Under a strict W3C session carrying only the namespaced key, every Android locator silently becomesid=undefined:id/<testID>. Assertdriver.capabilities.appPackageis a non-empty string at session start and fail loudly rather than treatingundefined:id/as expected.
Related Skills
- appium-testid-attributes — the
producer side; binds this skill's catalog ids onto actual RN components via
testIDand typed accessibility props. - appium-attribute-waits — consumes
bare catalog ids through
buildSelector()at lookup time but does not redefine the translation contract this skill owns. - appium-page-objects — locator getters pull bare testIDs from this skill's typed registry rather than hand-writing or hard-coding id strings.
Sourced from: skills/appium-testid-catalog/metadata.json, skills/appium-testid-catalog/SKILL.md, .omc/plans/appium-rn-testing-skills.md (local research artifact, not tracked in this repo), git log --follow -- skills/appium-testid-catalog (commit 5a4e3d3)