@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.
- package/README.md +15 -1
- package/claude/skills/SKILLS_CONTEXT.md +72 -5
- package/claude/skills/automation/SKILL.md +53 -6
- package/claude/skills/bug-reporting/SKILL.md +38 -0
- package/claude/skills/manual-testing/SKILL.md +159 -21
- package/claude/skills/manual-testing/TEST_DESIGN_GUIDE.md +200 -0
- package/claude/skills/mobile-automation/BDD_TEMPLATES.md +221 -0
- package/claude/skills/mobile-automation/LOCATOR_PATTERNS.md +205 -0
- package/claude/skills/mobile-automation/MOBILE_MCP_REFERENCE.md +234 -0
- package/claude/skills/mobile-automation/SKILL.md +593 -0
- package/claude/skills/qa-agent/SKILL.md +30 -0
- package/claude/skills/qa-agent/product_context/README.md +16 -0
- package/claude/skills/qa-agent/toolkit/qa-toolkit.cjs +758 -0
- package/claude/skills/qa-insights/SKILL.md +55 -0
- package/claude/skills/roam-testing/SKILL.md +187 -0
- package/package.json +1 -1
|
@@ -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
|
+
```
|