@roopesh.yadava/qa-pack 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,200 @@
1
+ # Test Design Guide — Requirement Analysis, Coverage Dimensions, Scenario Framework
2
+
3
+ Loaded once by the manual-testing skill at Phase 1 (Requirement Analysis). Reference sections
4
+ by number afterward (e.g. "per §2 trigger table") — never re-quote this file in chat or in
5
+ saved reports.
6
+
7
+ Purpose: manual testing should verify a feature is functionally correct, usable, reliable,
8
+ secure, compatible, and aligned with business expectations — not just that the happy path
9
+ works. This guide operationalizes that without inflating every card into the same fixed
10
+ checklist: coverage is scoped by what the card actually touches (§2) and by risk (§4), so a
11
+ copy-change card and a payment card get proportionally different depth.
12
+
13
+ ---
14
+
15
+ ## §1 — Requirement Gap Checklist
16
+
17
+ Run once per card, right after Phase 1 fetch, before the test plan is drafted. For each row
18
+ whose signal is present, fold the question into the **one** consolidated gap-question message
19
+ (same message as any Phase 0 gaps — never a second round of questions). Skip a row entirely
20
+ if the AC already answers it — do not manufacture questions on a precise, complete AC.
21
+
22
+ | Gap type | Signal to look for | Question to ask if missing |
23
+ |---|---|---|
24
+ | No AC at all | Card has no AC/COS section | "No Acceptance Criteria found — what should be tested?" (most severe — if this fires, skip the rest of this table and just ask this) |
25
+ | Ambiguous requirement | Vague terms in AC ("appropriate", "as needed", "similar to", "etc.") | Quote the exact phrase, ask for the precise rule |
26
+ | Missing validation rule | A field/form is named with no stated format or limit | "What's the valid format/limit for {field}?" |
27
+ | Missing negative/error case | AC describes only the happy path, no failure behavior | "What should happen on {plausible failure mode}?" |
28
+ | Missing permission rule | Roles/access mentioned but AC doesn't say who can do what | "Which roles can perform this action?" |
29
+ | Missing boundary condition | A numeric/length limit is implied ("a short code", "a few items") but not exact | "What's the exact min/max for {field}?" |
30
+ | Missing state/workflow rule | A status/workflow field exists but transitions aren't listed | "What are the valid status transitions?" |
31
+ | Missing data relationship | AC needs a specific paired entity (parent+child, account+card) without naming which | Already covered by the skill's Test Data & Entity Selection Rules — ask once or mark `🔒 BLOCKED`, don't duplicate the question here |
32
+
33
+ ---
34
+
35
+ ## §2 — Feature Signal → Coverage Dimension Trigger Table
36
+
37
+ Scan the card title + AC (already read in Phase 1 — no extra fetching, no grepping `src/`)
38
+ for these signals. A matched signal turns its dimension(s) ON for test-plan generation in
39
+ Phase 3a. Untriggered dimensions are not drafted — this is what keeps test count proportional
40
+ to the card instead of forcing all 19 dimensions onto every run.
41
+
42
+ | Signal in title/AC | Dimensions turned ON |
43
+ |---|---|
44
+ | login, auth, OTP, session, token, password, logout | Authentication, Security-oriented, State/Workflow (session) |
45
+ | role, permission, admin, restricted, access level | Authorization / Permissions |
46
+ | payment, price, amount, invoice, refund, subscription, billing, calculation | Boundary/Equivalence, Business Rules, Database, Security-oriented |
47
+ | delete, remove, deactivate, cancel, archive | Negative, State/Workflow, Database (soft vs hard delete) |
48
+ | upload, download, attachment, file, import, export | File Upload/Download |
49
+ | search, filter, sort, paginate, list, table, grid | Search/Filter/Sort/Pagination |
50
+ | API, endpoint, integration, webhook, third-party, external service, sync | API, Integration |
51
+ | status, state, workflow, transition, approve, submit, draft, review | State/Workflow |
52
+ | any form or input field named in the AC | Validation, Boundary/Equivalence |
53
+ | notification, email, SMS, push, alert, toast, reminder | Notifications |
54
+ | multi-step flow, wizard, redirect, navigation between screens | End-to-End |
55
+ | *(always on, every card)* | Functional Positive, Functional Negative, UI/UX States |
56
+
57
+ No specific signal matched → draft Functional (positive + one negative) + UI/UX States + one
58
+ Boundary check only. Do not force File Upload tests on a card with no file field, API tests on
59
+ a pure front-end copy change, etc.
60
+
61
+ ---
62
+
63
+ ## §3 — Coverage Dimension Catalog
64
+
65
+ What each dimension actually means when drafting a test idea:
66
+
67
+ - **Functional Positive** — valid input/action produces the documented expected result.
68
+ - **Functional Negative** — invalid input, missing mandatory field, unauthorized action, wrong
69
+ state transition, or a dependency failure (API/network) is handled without a crash and with
70
+ a correct, user-facing message.
71
+ - **Boundary/Equivalence** — see §6 templates. Values at/around a stated limit, and one
72
+ representative value per valid/invalid partition — not exhaustive enumeration.
73
+ - **UI/UX States** — see §8. Initial, loading, empty, success, error, disabled states of the
74
+ components the card actually touches.
75
+ - **Validation** — mandatory/optional fields, character/numeric limits, special characters,
76
+ whitespace, duplicate values, invalid format, field interdependencies.
77
+ - **Business Rules** — role-based/status-based/date-based behavior, calculations, eligibility
78
+ and workflow-transition rules stated in the AC.
79
+ - **Authentication** — valid/invalid credentials, empty credentials, OTP flow, session
80
+ expiry, logout, token expiration.
81
+ - **Authorization / Permissions** — each distinct role named in the AC gets one test for
82
+ allowed and one for denied behavior; direct-URL/API access with an unauthorized session
83
+ should not succeed just because the UI hid the button.
84
+ - **API** (only when the card's flow visibly depends on a request) — correct status code for
85
+ the scenario (200/201/204 success; 400 invalid; 401 unauthenticated; 403 unauthorized; 404
86
+ not found; 409 conflict/duplicate; 500 server failure), response shape sanity, not full
87
+ contract testing.
88
+ - **Database** (only when the UI exposes persisted state) — does the value shown after a
89
+ reload match what was submitted; does a delete actually remove/soft-remove the record;
90
+ spot-check only, not a DB audit.
91
+ - **Integration** — cross-system handoff visible from the UI (payment gateway redirect,
92
+ auth-provider callback, file-storage upload) — verify the round trip completes and failures
93
+ are surfaced, not the integration's internals.
94
+ - **End-to-End** — the full journey named in the card (e.g. Login → create entity → submit →
95
+ see it reflected downstream), not just the single screen in the AC.
96
+ - **File Upload/Download** — supported/unsupported type, size limit, empty/corrupted file,
97
+ duplicate filename, correct downloaded filename/contents.
98
+ - **Search/Filter/Sort/Pagination** — exact match, no-match, empty query, filter
99
+ combination, clear filters, sort direction, first/last page, empty result page.
100
+ - **Notifications** — correct message, correct recipient/trigger, not duplicated, and (if
101
+ observable within the run) sent after both success and retry-after-failure.
102
+ - **State/Workflow** — every transition named in the AC, one attempt at an invalid transition,
103
+ and what the UI shows on reopening/refreshing after a transition.
104
+ - **Security-oriented** (only for high-risk signals in §4) — direct URL access while logged
105
+ out or as the wrong role, sensitive data not exposed in the URL or in error text, password
106
+ masked, session actually ends on logout.
107
+ - **Accessibility spot-check** — one keyboard-only pass through the card's critical path
108
+ (tab order reaches every control, focus is visible, no dead-end). This is a lightweight
109
+ spot-check, not the full WCAG audit — hand off to the `accessibility-testing` skill if the
110
+ card needs that level of coverage.
111
+ - **Compatibility** — only when the card explicitly calls out a browser/viewport; otherwise
112
+ out of scope for a single-card manual run (covered separately, not by default here).
113
+
114
+ ---
115
+
116
+ ## §4 — Risk-Based Depth
117
+
118
+ | Risk tier | Signals (from §2, or explicit in AC) | Depth |
119
+ |---|---|---|
120
+ | **High** | payment, auth/authz, delete, financial calculation, subscription/billing, critical third-party integration, sensitive data | Each triggered dimension gets 2–3 tests; also add Security-oriented and Database checks even if not explicitly triggered by §2 |
121
+ | **Medium** | ordinary business feature, none of the High signals | Each triggered dimension gets 1–2 tests |
122
+ | **Low** | copy/label/minor UI-only change | Functional (positive + one negative) plus one regression spot-check — nothing else |
123
+
124
+ This tier decides **how many tests to draft per dimension**. It's separate from the
125
+ `risk-score` toolkit call, which orders the **execution sequence** of whatever was drafted, by
126
+ git churn + known-bug density. Run both — they answer different questions.
127
+
128
+ ---
129
+
130
+ ## §5 — Test Scenario Identification Framework
131
+
132
+ While turning triggered dimensions into numbered T-01, T-02... ideas, walk these 10 questions
133
+ once — they map straight onto §3's dimensions and stop scenario drafting from stalling out at
134
+ "just the AC's happy path":
135
+
136
+ 1. What can the user do? → Functional Positive
137
+ 2. What can the user enter? → Validation, Boundary/Equivalence
138
+ 3. What can go wrong? → Functional Negative
139
+ 4. What are the limits? → Boundary
140
+ 5. Who can perform the action? → Authorization/Permissions
141
+ 6. What happens to the data? → Database/API
142
+ 7. What happens after the action? → End-to-End, Notifications
143
+ 8. What happens when the system fails? → Functional Negative (dependency failure)
144
+ 9. What existing functionality could break? → flag for qa-agent's post-phase regression
145
+ suggestion (Covered Flows overlap) — don't draft duplicate regression tests here
146
+ 10. What unusual behavior could a real user perform? → at most one exploratory idea, tagged
147
+ clearly (`[exploratory]`) so it doesn't inflate the planned-test count
148
+
149
+ ---
150
+
151
+ ## §6 — Boundary & Equivalence Quick Templates
152
+
153
+ If the AC (or the answer to a §1 gap question) states a numeric/length range `[MIN, MAX]`:
154
+
155
+ - Full set (High-risk fields — payments, quotas, security limits): `MIN-1` (invalid), `MIN`
156
+ (valid), `MIN+1` (valid), `MAX-1` (valid), `MAX` (valid), `MAX+1` (invalid) — 6 tests.
157
+ - Collapsed set (Medium/Low-risk, wide range): `MIN` (valid), one mid-range value (valid),
158
+ `MAX` (valid), one just-out-of-range value (invalid) — 4 tests.
159
+
160
+ Equivalence partitioning (no exact boundary stated, just a described valid category): one
161
+ representative value below the valid range, one within it, one above it — 3 tests, not an
162
+ enumeration of every possible value.
163
+
164
+ ---
165
+
166
+ ## §7 — Defect Record Fields (Phase 4 payload)
167
+
168
+ Every bug filed from this skill should carry: Title, Environment, Preconditions, Steps to
169
+ Reproduce, Expected Result, Actual Result, Severity, Priority, Screenshot, Test data used, and
170
+ Build/version (the card ID + run date stands in when the product has no separate build
171
+ number). Severity/Priority derivation stays as defined in the skill's Phase 4 table — this
172
+ section only adds the two fields (Preconditions, Test data used) that weren't previously
173
+ captured.
174
+
175
+ ---
176
+
177
+ ## §8 — State Validation Checklist
178
+
179
+ For each key screen/component the card's test plan actually reaches, note which of these
180
+ states were observed and their result — skip states that are genuinely unreachable within the
181
+ test rather than forcing them:
182
+
183
+ Initial · Loading · Empty · Success · Error · Disabled
184
+
185
+ ---
186
+
187
+ ## §9 — Exit Criteria Quick Check
188
+
189
+ Before handing off to Phase 5 (test charter), confirm:
190
+
191
+ - Every planned test executed — `🔒 BLOCKED` is an acceptable outcome, a silently skipped test
192
+ is not.
193
+ - No open Critical/Blocker-severity bug among those just filed goes unmentioned — flag it
194
+ prominently in the final summary. This pack does not gate the pipeline on it (no release
195
+ authority here), it only makes sure the flag isn't buried.
196
+ - If the product context's `Known Bugs` table (already loaded in Phase 1) lists a bug on this
197
+ same flow with a status other than Closed/Done, one retest of that specific bug was included
198
+ in the plan.
199
+ - The regression suggestion in qa-agent Step 5 runs after this skill returns — nothing to do
200
+ here, just don't skip past it.
@@ -0,0 +1,221 @@
1
+ # Mobile BDD Templates & Reference
2
+
3
+ > Load this file at the start of Phase 1 (Gherkin generation) and Phase 3 (POM), alongside
4
+ > `LOCATOR_PATTERNS.md` and `MOBILE_MCP_REFERENCE.md`.
5
+ > Contains the Gherkin format, step definition template, POM class template, platform-
6
+ > conditional step pattern, app-lifecycle steps, and permission-dialog handling.
7
+
8
+ ---
9
+
10
+ ## Gherkin Format Example
11
+
12
+ ```gherkin
13
+ Feature: <feature title from card>
14
+
15
+ Background: (optional — only if 2+ scenarios share the same Given)
16
+ Given the app is launched fresh
17
+
18
+ Rule: <business rule — taken verbatim from AC/COS>
19
+
20
+ @android @ios
21
+ Example: <Persona> as <role> <scenario description>
22
+ Given <persona> is on the <screen name> screen
23
+ When <persona> <action described as intent, not mechanics>
24
+ Then <persona> should <observable outcome>
25
+
26
+ @android
27
+ Example: <Android-only variation — e.g. hardware back button behavior>
28
+ Given ...
29
+ When <persona> presses the hardware back button
30
+ Then ...
31
+
32
+ @ios
33
+ Example: <iOS-only variation — e.g. swipe-back gesture>
34
+ Given ...
35
+ When <persona> swipes back from the left edge
36
+ Then ...
37
+
38
+ Scenario Outline: <persona> <flow description> with multiple <data type>
39
+ Given <persona> is on the <screen name> screen
40
+ When <persona> enters credentials "<username>" and "<password>"
41
+ Then <persona> should see "<expected_message>"
42
+
43
+ Examples:
44
+ | persona | username | password | expected_message |
45
+ | John | john@test.com | Admin@1234 | Verification passed |
46
+ | Maria | maria@test.com | Branch@1234 | Verification passed |
47
+ ```
48
+
49
+ **Platform tags:** tag every `Example`/`Scenario` `@android`, `@ios`, or both, when the
50
+ underlying interaction differs (see `LOCATOR_PATTERNS.md` §7 hardware BACK). Untagged
51
+ scenarios run on whichever `--project` is active per the config's platform matrix.
52
+
53
+ ---
54
+
55
+ ## Step Definition Template
56
+
57
+ ```javascript
58
+ const { Given, When, Then, Before, After } = require('@cucumber/cucumber');
59
+ const { faker } = require('@faker-js/faker');
60
+ const LoginScreen = require('../../Pages/Auth/login-screen.cjs');
61
+
62
+ let loginScreen;
63
+
64
+ // ── Unconditional Before — resolve platform + fresh app state ────────────────
65
+ Before(async function () {
66
+ this.platform = this.config.platform; // 'android' | 'ios' — set once, reused by every
67
+ // platform-conditional step in this run
68
+ });
69
+
70
+ // ── Steps ──────────────────────────────────────────────────────────────────
71
+
72
+ Given('{word} is on the {string} screen', async function (persona, screenName) {
73
+ loginScreen = new LoginScreen(this.screen);
74
+ await loginScreen.navigateTo(screenName);
75
+ });
76
+
77
+ When('{word} enters credentials {string} and {string}', async function (persona, username, password) {
78
+ await loginScreen.fillCredentials(username, password);
79
+ });
80
+
81
+ Then('{word} should see {string}', async function (persona, expectedMessage) {
82
+ await loginScreen.verifyMessage(expectedMessage);
83
+ });
84
+
85
+ // ── Platform-conditional step (see LOCATOR_PATTERNS.md §7) ───────────────────
86
+ When('{word} goes back', async function (persona) {
87
+ if (this.platform === 'android') {
88
+ await this.screen.pressButton('BACK');
89
+ } else {
90
+ await loginScreen.tapBackButton(); // POM method — POM decides the iOS-specific locator
91
+ }
92
+ });
93
+
94
+ // ── App lifecycle steps — reusable across every feature ──────────────────────
95
+ Given('the app is launched fresh', async function () {
96
+ await this.device.launchApp({ newInstance: true });
97
+ });
98
+
99
+ When('{word} backgrounds the app', async function (persona) {
100
+ await this.screen.pressButton('HOME');
101
+ });
102
+
103
+ When('{word} resumes the app', async function (persona) {
104
+ await this.device.launchApp(); // foregrounds an existing instance, does not cold-start
105
+ });
106
+
107
+ // ── Permission dialog handling — call after any step that may trigger one ────
108
+ When('the app requests permission', async function () {
109
+ const elements = await this.device.listElementsOnScreen();
110
+ const allowButton = elements.find(el => /allow|while using the app/i.test(el.text));
111
+ if (allowButton) {
112
+ await this.screen.getByText(/allow|while using the app/i).tap();
113
+ }
114
+ // No dialog present → no-op, don't fail the scenario over an OS dialog that didn't fire.
115
+ });
116
+ ```
117
+
118
+ ---
119
+
120
+ ## POM Class Template
121
+
122
+ ```javascript
123
+ const { expect } = require('@mobilewright/test');
124
+
125
+ class LoginScreen {
126
+ /**
127
+ * @param {import('@mobilewright/test').Screen} screen
128
+ */
129
+ constructor(screen) {
130
+ this.screen = screen;
131
+
132
+ // ── Locators (constructor ONLY — never inside methods) ───────────────
133
+ // All locators confirmed via mobile_list_elements_on_screen on the live device
134
+ // Priority: resource-id/accessibility-id → getByRole/getByLabel → getByText → getByType → coordinates
135
+ this.usernameInput = screen.getByTestId('login-username');
136
+ this.passwordInput = screen.getByTestId('login-password');
137
+ this.submitButton = screen.getByTestId('login-submit');
138
+ this.errorMessage = screen.getByTestId('login-error');
139
+ this.backButton = screen.getByTestId('nav-back-button'); // iOS-specific usage — see step def
140
+ }
141
+
142
+ // ── Navigation ───────────────────────────────────────────────────────
143
+
144
+ async navigateTo(screenName) {
145
+ // Screen reached via app navigation, not a URL — describe the path taken,
146
+ // not just the destination, since there's no direct deep link by default.
147
+ await this.screen.getByText(screenName).tap();
148
+ }
149
+
150
+ // ── Actions ──────────────────────────────────────────────────────────
151
+
152
+ async fillCredentials(username, password) {
153
+ await this.usernameInput.fill(username);
154
+ await this.passwordInput.fill(password);
155
+ await this.submitButton.tap();
156
+ }
157
+
158
+ async tapBackButton() {
159
+ await this.backButton.tap();
160
+ }
161
+
162
+ // ── Assertions ───────────────────────────────────────────────────────
163
+
164
+ async verifyMessage(expectedText) {
165
+ await expect(this.screen.getByText(expectedText)).toBeVisible();
166
+ }
167
+ }
168
+
169
+ module.exports = LoginScreen;
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Faker.js Patterns (same library, same discipline as the web `automation` skill)
175
+
176
+ ```javascript
177
+ const { faker } = require('@faker-js/faker');
178
+
179
+ faker.string.alphanumeric(15) // random name
180
+ `test+${faker.string.alphanumeric(8)}@yourdomain.com` // email
181
+ faker.number.int({ min: 1, max: 100 })
182
+ faker.date.future().toISOString().split('T')[0] // 'YYYY-MM-DD'
183
+ ```
184
+
185
+ Generate inside the method — never in the constructor or at module scope.
186
+
187
+ ---
188
+
189
+ ## App-Lifecycle Step Catalog (reuse these before writing a new lifecycle step)
190
+
191
+ | Step | Underlying call |
192
+ |---|---|
193
+ | `the app is launched fresh` | `mobile_launch_app` / `device.launchApp({ newInstance: true })` — cold start |
194
+ | `{word} backgrounds the app` | `screen.pressButton('HOME')` |
195
+ | `{word} resumes the app` | `device.launchApp()` (no `newInstance`) — foregrounds, doesn't cold-start (see `LOCATOR_PATTERNS.md` §10) |
196
+ | `the app is force-closed` | `mobile_terminate_app` |
197
+ | `the app requests permission` | dump screen, tap Allow/While-using if a system dialog is present, no-op otherwise |
198
+ | `{word} rotates the device to {string}` | `mobile_set_orientation` + re-dump per `LOCATOR_PATTERNS.md` §9 |
199
+ | `{word} opens the deep link {string}` | `mobile_open_url` |
200
+
201
+ ---
202
+
203
+ ## Quick Reference
204
+
205
+ | Content | Location |
206
+ |---------|----------|
207
+ | Business rule | `Rule:` in `.feature` |
208
+ | Platform-specific variation | `@android` / `@ios` tag on the `Example`/`Scenario` |
209
+ | Persona/role variation | `Example:` block |
210
+ | Same flow, different data | `Scenario Outline` + `Examples:` |
211
+ | Locators discovered | `mobile_list_elements_on_screen` on the live device |
212
+ | Locators written | POM constructor only |
213
+ | Actions | POM async methods |
214
+ | Assertions | POM `verify*` methods |
215
+ | Generated test data | `faker.js` — inside methods |
216
+ | Direct `screen.getByX()` calls in step files | ❌ Never — always go through POM methods |
217
+ | Hardcoded coordinates | ❌ Last resort only, see `LOCATOR_PATTERNS.md` |
218
+ | Hardcoded waits | ❌ Never — use `expect(...).toBeVisible()` |
219
+ | `import`/`export` | ❌ Use `require`/`module.exports` (CommonJS, matches the web skill's convention unless the repo's own discovered convention says otherwise) |
220
+ | Gate 1 skipped | ❌ Never — always confirm Gherkin before writing code |
221
+ | Gate 2 skipped | ❌ Never — always confirm step defs before writing POM |
@@ -0,0 +1,205 @@
1
+ # Mobile Locator Patterns — Hard-Won Lessons
2
+
3
+ > Load this file before Phase 2 (Step Definitions) and Phase 3 (POM), alongside
4
+ > `BDD_TEMPLATES.md` and `MOBILE_MCP_REFERENCE.md`. Apply every rule below before writing
5
+ > any locator.
6
+
7
+ ## Locator Priority (strictly enforced)
8
+
9
+ | Priority | Method | Use when |
10
+ |----------|--------|----------|
11
+ | 1 ✅ | `screen.getByTestId('...')` | Always prefer — maps to `resource-id` (Android) / `accessibility-id` (iOS). Ask for one to be added if missing rather than falling further down this table |
12
+ | 2 ✅ | `screen.getByRole(...)` | When no resource-id/accessibility-id exists |
13
+ | 3 ⚠️ | `screen.getByLabel('...')` | Accessibility label present, no role match |
14
+ | 4 ⚠️ | `screen.getByText('...')` | Visible text only — fragile across locales, use a regex where wording may vary |
15
+ | 5 ⚠️ | `screen.getByType('...')` | Element type only — broad, matches multiple elements easily |
16
+ | 6 ❌ | `mobile_click_on_screen_at_coordinates(x, y)` | Last resort only — breaks on any resolution/orientation/layout change. Comment why no better locator existed |
17
+
18
+ ---
19
+
20
+ ### 1. `resource-id` and `accessibility-id` are not the same field
21
+
22
+ Android exposes `resource-id`; iOS exposes `accessibility-id` (sometimes surfaced as `name`
23
+ depending on the inspection tool). `mobile_list_elements_on_screen` returns whichever field
24
+ the platform actually has — checking only one will silently miss the other platform's
25
+ elements in a cross-platform project.
26
+
27
+ ```javascript
28
+ // ❌ WRONG — only checks the Android field name, misses every iOS element
29
+ const hasTestId = element['resource-id'];
30
+
31
+ // ✅ CORRECT — check both, platform-agnostic
32
+ const hasTestId = element['resource-id'] || element['accessibility-id'] || element['name'];
33
+ ```
34
+
35
+ `screen.getByTestId(...)` in MobileWright abstracts this for you at the framework level —
36
+ but when reasoning from a raw `mobile_list_elements_on_screen` dump during authoring or
37
+ self-heal, check both fields yourself before concluding "no testid on this element."
38
+
39
+ ---
40
+
41
+ ### 2. WebView / hybrid screens don't expose native resource-ids for their content
42
+
43
+ A screen embedding a WebView (in-app browser, hybrid checkout, help center) reports the
44
+ WebView container as one native element — the HTML content inside it is invisible to
45
+ `mobile_list_elements_on_screen`.
46
+
47
+ ```javascript
48
+ // ❌ WRONG — assumes the hybrid content has a resource-id, times out
49
+ this.helpArticleLink = screen.getByTestId('help-article-3');
50
+
51
+ // ✅ CORRECT — flag it, don't retry the native locator harder
52
+ // "This screen is a WebView — native resource-id inspection won't find its content.
53
+ // Falls back to getByText() on visible text, or needs a web-style inspection strategy."
54
+ this.helpArticleLink = screen.getByText('Refund policy');
55
+ ```
56
+
57
+ ---
58
+
59
+ ### 3. System permission dialogs interrupt the flow unpredictably
60
+
61
+ A location/camera/notification/contacts permission prompt is OS-level chrome, not app UI —
62
+ it can appear right after a navigation step and block every subsequent tap until dismissed.
63
+
64
+ ```javascript
65
+ // ❌ WRONG — assumes the next screen loaded; every following action times out
66
+ await screen.getByTestId('enable-location').tap();
67
+ await screen.getByTestId('map-view').waitFor(); // times out — a permission dialog is covering it
68
+
69
+ // ✅ CORRECT — check for a system dialog before assuming the locator is wrong
70
+ const elements = await mobile_list_elements_on_screen();
71
+ if (elements.some(el => /allow|deny|while using the app/i.test(el.text))) {
72
+ await screen.getByText(/allow|while using the app/i).tap();
73
+ }
74
+ await screen.getByTestId('map-view').waitFor();
75
+ ```
76
+
77
+ If a test hits "element not found" right after a step that plausibly triggers a permission
78
+ prompt, dump the screen and check for dialog text before concluding the locator is broken.
79
+
80
+ ---
81
+
82
+ ### 4. Coordinate taps are resolution/DPI-relative — never hardcode across a device matrix
83
+
84
+ A coordinate captured on one simulator breaks on a different device profile, and definitely
85
+ breaks across a `projects` matrix spanning multiple device sizes.
86
+
87
+ ```javascript
88
+ // ❌ WRONG — works on iPhone 15 Pro sim, wrong on every other device in the matrix
89
+ await mobile_click_on_screen_at_coordinates(187, 640);
90
+
91
+ // ✅ CORRECT — resolve locator by identifier, use coordinates only as documented last resort
92
+ await screen.getByTestId('agree-checkbox').tap();
93
+ ```
94
+
95
+ ---
96
+
97
+ ### 5. The soft keyboard shifts layout — re-dump before any coordinate fallback
98
+
99
+ Focusing a text field can push screen content upward to make room for the keyboard. Any
100
+ coordinates recorded before that focus are now wrong.
101
+
102
+ ```javascript
103
+ // ❌ WRONG — coordinate recorded before the keyboard appeared
104
+ await screen.getByLabel('Email').fill('user@example.com');
105
+ await mobile_click_on_screen_at_coordinates(200, 500); // may now hit a different element
106
+
107
+ // ✅ CORRECT — re-dump after any focus change if a coordinate fallback is unavoidable
108
+ await screen.getByLabel('Email').fill('user@example.com');
109
+ const elements = await mobile_list_elements_on_screen(); // re-dump post-keyboard
110
+ ```
111
+
112
+ ---
113
+
114
+ ### 6. OTP autofill can race a manual `type` action
115
+
116
+ Both platforms often auto-fill an SMS OTP into the field without any typed input. A step that
117
+ types digit-by-digit can collide with the autofill and leave the field in an unexpected state.
118
+
119
+ ```javascript
120
+ // ❌ WRONG — assumes typing succeeded, doesn't verify against a possible autofill race
121
+ await screen.getByTestId('otp-input').fill(otpCode);
122
+ await screen.getByTestId('verify-button').tap();
123
+
124
+ // ✅ CORRECT — verify the field's actual value before proceeding
125
+ await screen.getByTestId('otp-input').fill(otpCode);
126
+ await expect(screen.getByTestId('otp-input')).toHaveText(otpCode);
127
+ await screen.getByTestId('verify-button').tap();
128
+ ```
129
+
130
+ ---
131
+
132
+ ### 7. Hardware BACK is Android-only
133
+
134
+ `screen.pressButton('BACK')` has no iOS equivalent — iOS uses an edge swipe gesture or an
135
+ explicit on-screen back element instead.
136
+
137
+ ```javascript
138
+ // ❌ WRONG — a shared step across both platforms with no branch
139
+ When('{word} goes back', async function () {
140
+ await this.screen.pressButton('BACK'); // does nothing meaningful on iOS
141
+ });
142
+
143
+ // ✅ CORRECT — platform-conditional step, resolved from the World/config
144
+ When('{word} goes back', async function () {
145
+ if (this.platform === 'android') {
146
+ await this.screen.pressButton('BACK');
147
+ } else {
148
+ await this.screen.getByTestId('nav-back-button').tap(); // or a swipe gesture
149
+ }
150
+ });
151
+ ```
152
+
153
+ ---
154
+
155
+ ### 8. Dynamic/virtualized lists — an element not in the dump may just be off-screen
156
+
157
+ Long lists (`RecyclerView`, `UICollectionView`, virtualized FlatLists) only render items near
158
+ the viewport. `mobile_list_elements_on_screen` returning no match for a list item doesn't
159
+ always mean the locator is wrong.
160
+
161
+ ```javascript
162
+ // ❌ WRONG — concludes the locator is broken after one failed dump
163
+ // "getByTestId('item-42') not found — locator must be wrong"
164
+
165
+ // ✅ CORRECT — swipe toward the expected item first, then re-check
166
+ await screen.swipe('up', { distance: 400 });
167
+ const elements = await mobile_list_elements_on_screen(); // re-dump after scroll
168
+ ```
169
+
170
+ ---
171
+
172
+ ### 9. Orientation changes re-lay the whole screen
173
+
174
+ A `resource-id`/`accessibility-id` usually survives rotation; its on-screen coordinates never
175
+ do. Any scenario that rotates mid-flow must re-dump before falling back to a coordinate
176
+ locator afterward.
177
+
178
+ ```javascript
179
+ // ❌ WRONG — coordinate captured in portrait, used again after rotating to landscape
180
+ await mobile_set_orientation('landscape');
181
+ await mobile_click_on_screen_at_coordinates(300, 120); // stale — layout has changed
182
+
183
+ // ✅ CORRECT
184
+ await mobile_set_orientation('landscape');
185
+ const elements = await mobile_list_elements_on_screen(); // re-dump post-rotation
186
+ ```
187
+
188
+ ---
189
+
190
+ ### 10. Backgrounding vs terminating are different device states
191
+
192
+ `mobile_terminate_app` forces a cold start on next launch. Pressing `HOME`
193
+ (`screen.pressButton('HOME')`) leaves the app alive in the background. A scenario testing
194
+ "resume where you left off" needs HOME + relaunch, not terminate + launch — using the wrong
195
+ one tests the wrong behavior entirely.
196
+
197
+ ```javascript
198
+ // ❌ WRONG — tests cold-start behavior when the scenario is about background resume
199
+ await mobile_terminate_app(bundleId);
200
+ await mobile_launch_app(bundleId);
201
+
202
+ // ✅ CORRECT — genuine background/resume
203
+ await screen.pressButton('HOME');
204
+ await mobile_launch_app(bundleId); // brings the backgrounded app back to foreground
205
+ ```