@educa-corp/sdd-framework 0.9.7 → 0.9.8

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.
Files changed (104) hide show
  1. package/bin/qc-base-map.json +13 -11
  2. package/bin/self-check.js +49 -4
  3. package/bin/trace-schema.json +3226 -3187
  4. package/core/FRAMEWORK_VERSION +1 -1
  5. package/core/commands/qc-analyze.md +2 -2
  6. package/core/commands/qc-automation-assess.md +3 -3
  7. package/core/commands/qc-design-script.md +60 -30
  8. package/core/commands/qc-design-test.md +79 -7
  9. package/core/commands/qc-plan.md +1 -1
  10. package/core/commands/qc-report.md +85 -76
  11. package/core/commands/qc-review-script.md +25 -16
  12. package/core/commands/qc-review-testcase.md +8 -7
  13. package/core/commands/qc-run-manualtest.md +1 -1
  14. package/core/commands/qc-run-script.md +15 -8
  15. package/core/modules/qc-playwright-ts/module.yaml +13 -0
  16. package/core/modules/qc-playwright-ts/stack-profile.yaml +99 -0
  17. package/core/modules/qc-wdio-appium/module.yaml +20 -0
  18. package/core/modules/qc-wdio-appium/stack-profile.yaml +107 -0
  19. package/core/skills/qc/qa-analyst/data-flow.md +1 -1
  20. package/core/skills/qc/qa-automation-assess/matrix.md +6 -3
  21. package/core/skills/qc/{qa-runner → qa-designer}/exploratory/session.md +8 -2
  22. package/core/skills/qc/qa-designer/functional/api.md +1 -1
  23. package/core/skills/qc/qa-designer/functional/job.md +128 -0
  24. package/core/skills/qc/qa-designer/integration/api.md +1 -1
  25. package/core/skills/qc/qa-designer/integration/db.md +1 -1
  26. package/core/skills/qc/qa-designer/integration/{kafka.md → queue.md} +20 -4
  27. package/core/skills/qc/qa-designer/shared/skill-decision-tree.md +17 -0
  28. package/core/skills/qc/qa-designer/shared/tc-metadata-format.md +17 -0
  29. package/core/skills/qc/qa-reviewer/script/_shared/review-rules.md +121 -0
  30. package/core/skills/qc/qa-reviewer/script/api/auth.md +49 -0
  31. package/core/skills/qc/qa-reviewer/script/api/endpoint.md +89 -0
  32. package/core/skills/qc/qa-reviewer/script/api/security.md +46 -0
  33. package/core/skills/qc/qa-reviewer/script/exploratory.md +2 -2
  34. package/core/skills/qc/qa-reviewer/script/mobile/e2e.md +41 -0
  35. package/core/skills/qc/qa-reviewer/script/mobile/functional.md +90 -0
  36. package/core/skills/qc/qa-reviewer/script/mobile/integration.md +41 -0
  37. package/core/skills/qc/qa-reviewer/script/mobile/non-functional.md +43 -0
  38. package/core/skills/qc/qa-reviewer/script/web/e2e.md +46 -0
  39. package/core/skills/qc/qa-reviewer/script/web/functional.md +111 -0
  40. package/core/skills/qc/qa-reviewer/script/web/integration.md +46 -0
  41. package/core/skills/qc/qa-reviewer/script/web/non-functional.md +49 -0
  42. package/core/skills/qc/qa-reviewer/shared/read-doc-gap-inputs.md +1 -1
  43. package/core/skills/qc/qa-reviewer/shared/review-file-template.md +26 -7
  44. package/core/skills/qc/qa-reviewer/test-case/e2e.md +1 -1
  45. package/core/skills/qc/qa-reviewer/test-case/exploratory.md +1 -1
  46. package/core/skills/qc/qa-reviewer/test-case/functional.md +1 -1
  47. package/core/skills/qc/qa-reviewer/test-case/integration.md +1 -1
  48. package/core/skills/qc/qa-reviewer/test-case/non-functional.md +1 -1
  49. package/core/skills/qc/qa-script-designer/_shared/api-conventions.md +94 -0
  50. package/core/skills/qc/qa-script-designer/_shared/file-naming-and-folders.md +109 -0
  51. package/core/skills/qc/qa-script-designer/_shared/mobile-conventions.md +196 -0
  52. package/core/skills/qc/qa-script-designer/_shared/web-conventions.md +257 -0
  53. package/core/skills/qc/qa-script-designer/api/auth.md +43 -0
  54. package/core/skills/qc/qa-script-designer/api/endpoint.md +61 -0
  55. package/core/skills/qc/qa-script-designer/api/security.md +41 -0
  56. package/core/skills/qc/qa-script-designer/mobile/e2e.md +35 -0
  57. package/core/skills/qc/qa-script-designer/mobile/functional/feature.md +32 -0
  58. package/core/skills/qc/qa-script-designer/mobile/functional/screen.md +42 -0
  59. package/core/skills/qc/qa-script-designer/mobile/integration.md +39 -0
  60. package/core/skills/qc/qa-script-designer/mobile/non-functional.md +39 -0
  61. package/core/skills/qc/qa-script-designer/web/e2e.md +36 -0
  62. package/core/skills/qc/qa-script-designer/web/functional/api.md +39 -0
  63. package/core/skills/qc/qa-script-designer/web/functional/gui-feature.md +34 -0
  64. package/core/skills/qc/qa-script-designer/web/functional/gui-screen.md +42 -0
  65. package/core/skills/qc/qa-script-designer/web/integration.md +43 -0
  66. package/core/skills/qc/qa-script-designer/web/non-functional.md +42 -0
  67. package/core/skills/qc/qa-script-runner/mobile/run.md +38 -0
  68. package/core/skills/qc/qa-script-runner/report.md +41 -0
  69. package/core/skills/qc/qa-script-runner/web/run.md +48 -0
  70. package/core/steps/qc-scope.md +43 -0
  71. package/core/steps/report-footer.md +2 -2
  72. package/docs/02-concepts/pipeline-steps/07-dev-selftest.md +1 -1
  73. package/docs/02-concepts/pipeline-steps/08-qc-automation.md +10 -10
  74. package/docs/02-concepts/pipeline-steps/10-feedback-loop.md +1 -1
  75. package/docs/02-concepts/traceability.md +1 -1
  76. package/docs/03-guides/developer.md +1 -1
  77. package/docs/03-guides/tester-qa.md +40 -12
  78. package/docs/04-reference/commands.md +1 -1
  79. package/docs/04-reference/modules.md +2 -1
  80. package/docs/explain/17-qc-design-test.md +2 -2
  81. package/docs/explain/19-qc-run-test.md +4 -4
  82. package/docs/explain/20-qc-report.md +1 -1
  83. package/docs/explain/23-fix-bug.md +2 -2
  84. package/docs/plans/qc-surgery/01-checklist.md +18 -6
  85. package/docs/plans/qc-surgery/PLAN_v2.md +295 -0
  86. package/docs/plans/qc-surgery/exec-S-ap-stack-typescript.md +420 -0
  87. package/docs/plans/qc-surgery/exec-S0-guard-cam-stack-cu.md +400 -0
  88. package/docs/plans/qc-surgery/exec-S1-hai-module-thay-qc-playwright.md +267 -0
  89. package/docs/plans/qc-surgery/exec-S2-qa-runner-thanh-script-designer-runner.md +340 -0
  90. package/docs/plans/qc-surgery/exec-S3-viet-lai-tieu-chi-review-script.md +322 -0
  91. package/docs/plans/qc-surgery/exec-S5-an-theo-don-dau-vet-stack-cu.md +292 -0
  92. package/package.json +1 -1
  93. package/core/modules/qc-playwright/stack-profile.yaml +0 -66
  94. package/core/skills/qc/qa-reviewer/script/e2e.md +0 -95
  95. package/core/skills/qc/qa-reviewer/script/functional.md +0 -109
  96. package/core/skills/qc/qa-reviewer/script/integration.md +0 -99
  97. package/core/skills/qc/qa-reviewer/script/non-functional.md +0 -134
  98. package/core/skills/qc/qa-runner/e2e.md +0 -49
  99. package/core/skills/qc/qa-runner/functional/api.md +0 -35
  100. package/core/skills/qc/qa-runner/functional/gui-feature.md +0 -57
  101. package/core/skills/qc/qa-runner/functional/gui-screen.md +0 -61
  102. package/core/skills/qc/qa-runner/integration.md +0 -47
  103. package/core/skills/qc/qa-runner/non-functional.md +0 -49
  104. package/core/skills/qc/qa-runner/report/report.md +0 -37
@@ -0,0 +1,196 @@
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-09-09
4
+ new_in: QC-Workflow-Proposal (8-phase) — mobile chưa từng có trong skill set cũ
5
+ ---
6
+
7
+ # Mobile Automation Conventions — TypeScript + WebdriverIO + Appium
8
+
9
+ Skill **tự chứa**: quy ước bắt buộc khi sinh automation script mobile bằng WebdriverIO +
10
+ Appium + TypeScript, cho app **Flutter** (dev team đang dùng). Mọi file layer trong
11
+ `qa-script-designer/mobile/*` đọc file này trước.
12
+
13
+ ## 0. Vì sao Flutter cần driver riêng (đọc trước khi viết locator)
14
+
15
+ Flutter tự vẽ UI trên một canvas (Skia), **không** map trực tiếp sang native view tree như
16
+ React Native hay app native thật. Hệ quả: Appium driver chuẩn (`UiAutomator2` Android,
17
+ `XCUITest` iOS) chỉ "thấy" các widget mà Flutter **chủ động expose** qua `Semantics`
18
+ (accessibility tree của OS) — nếu widget không bật `Semantics`/không có `label`, driver chuẩn
19
+ coi như nó không tồn tại, dù mắt người thấy rõ trên màn hình.
20
+
21
+ **Vì vậy dùng `automationName: 'FlutterIntegration'`** (Appium Flutter Integration Driver,
22
+ dựa trên package `integration_test` của Flutter) — driver này đọc **thẳng Flutter widget
23
+ tree** qua `key` (`ValueKey`), không phụ thuộc accessibility tree của OS. Ổn định hơn nhiều
24
+ cho app Flutter, và đây là driver mặc định của mọi skill trong file này.
25
+
26
+ ```typescript
27
+ // wdio.conf.ts (trích)
28
+ export const config: WebdriverIO.Config = {
29
+ capabilities: [{
30
+ platformName: 'Android', // hoặc 'iOS'
31
+ 'appium:automationName': 'FlutterIntegration',
32
+ 'appium:app': process.env.APP_PATH,
33
+ }],
34
+ };
35
+ ```
36
+
37
+ ## 1. Locator Strategy — thứ tự ưu tiên (bắt buộc)
38
+
39
+ | # | Locator | Khi dùng | Ví dụ |
40
+ |---|---|---|---|
41
+ | 1 | **`ValueKey` contract (test-id Flutter)** | Luôn ưu tiên — tương đương `data-testid` bên web | `$('~login_submit_btn')` (FlutterIntegration map `~` → `ValueKey`) hoặc `driver.$('flutter finder', ...)` tuỳ version |
42
+ | 2 | **Semantics label / accessibility-id** | Widget có bật `Semantics(label: ...)` nhưng chưa gán `Key` | `$('~Đăng nhập')` |
43
+ | 3 | **Text hiển thị** (chỉ static, không lặp) | Không có key/label, element tĩnh duy nhất trên màn | `$('android=new UiSelector().text("Đăng nhập")')` |
44
+ | 4 | **XPath theo cấu trúc widget tree** (bất đắc dĩ) | Không còn lựa chọn nào khác | Luôn kèm `// FIXME: cần ValueKey, xem IMPROVE-xxx` |
45
+
46
+ **Không dùng làm mức đầu tiên:** toạ độ tuyệt đối (tap theo x/y — vỡ ngay khi đổi kích thước
47
+ màn hình/device), index không filter trên danh sách động, text đa ngôn ngữ dễ đổi.
48
+
49
+ ### 1.1 Contract `ValueKey` với dev Flutter (bắt buộc xác nhận trước khi viết locator)
50
+
51
+ Tương đương `@trace.testid_attr` bên web — dev phải gán `Key(ValueKey('login_submit_btn'))`
52
+ cho mọi widget tương tác được liệt trong tech-doc §4.5.6. Đọc contract này trước khi viết
53
+ Page Object; thiếu → cảnh báo mềm, fallback Semantics label, và ghi `IMPROVE-xxx` đề nghị dev
54
+ bổ sung `ValueKey`.
55
+
56
+ ### 1.2 Probe widget tree thật trước khi viết locator (bắt buộc nếu §4.5.6 không đủ)
57
+
58
+ Giống bên web phải "Probe DOM thật" khi SPA thiếu `data-testid`, bên mobile **không được đoán
59
+ `ValueKey`/label từ mắt nhìn màn hình** — phải xác nhận bằng công cụ trước khi viết locator:
60
+
61
+ - **Appium Inspector** (GUI chính thức của Appium): kết nối cùng `automationName:
62
+ FlutterIntegration` + capability của app đang test, tap vào widget để xem `ValueKey`/label
63
+ thật nó expose — không phải cấu trúc DOM/View suy đoán.
64
+ - **Flutter DevTools** (`flutter run` + mở DevTools → Widget Inspector): xem trực tiếp widget
65
+ tree đang chạy, kể cả khi widget chưa gán `Key` (giúp biết chính xác cần đề nghị dev thêm
66
+ `Key` ở widget nào, thay vì đoán qua ảnh chụp màn hình).
67
+
68
+ Không có 1 trong 2 công cụ trên khả dụng (vd môi trường không cài được) → fallback: build app
69
+ debug, chạy tay 1 lần, dựa vào Semantics label nếu bật `flutter run --enable-software-rendering
70
+ --dds-port` kèm accessibility bật trên OS — nhưng đây là **fallback chậm hơn**, ưu tiên 2 công
71
+ cụ trên trước.
72
+
73
+ ## 2. Page Object Model — 3 lớp (bắt buộc, giống cấu trúc web)
74
+
75
+ ```typescript
76
+ // pageobjects/login.page.ts
77
+ import { BasePage } from './base.page';
78
+
79
+ class LoginPage extends BasePage {
80
+ // ── Lớp 1: Locators ──
81
+ private get emailInput() { return $('~login_email_input'); }
82
+ private get passwordInput() { return $('~login_password_input'); }
83
+ private get submitBtn() { return $('~login_submit_btn'); }
84
+
85
+ // ── Lớp 2: Actions ──
86
+ async login(email: string, password: string) {
87
+ await (await this.emailInput).setValue(email);
88
+ await (await this.passwordInput).setValue(password);
89
+ await (await this.submitBtn).click();
90
+ }
91
+
92
+ // ── Lớp 3: Assertions ──
93
+ async assertValidationError(message: string) {
94
+ await expect($('~login_error')).toHaveText(message);
95
+ }
96
+ }
97
+ export default new LoginPage();
98
+ ```
99
+
100
+ - `BasePage` gọn: chứa helper chung (chờ app foreground, chụp screenshot).
101
+ - Điều hướng đa màn: mỗi màn 1 Page Object riêng, action trả về hoặc test tự import Page Object
102
+ màn kế tiếp (module singleton pattern WDIO chuẩn — khác Playwright PO là instance theo `page`).
103
+
104
+ ## 3. Wait & Timing (bắt buộc)
105
+
106
+ - **Không** `browser.pause(N)` cố định — dùng `waitForDisplayed()`, `waitForEnabled()`,
107
+ `waitUntil()` của WebdriverIO (auto-retry theo điều kiện).
108
+ - App Flutter cần thời gian dựng frame đầu — dùng `waitForDisplayed({ timeout: 10000 })` cho
109
+ màn hình đầu sau cold start, không phải `pause`.
110
+ - Thao tác vuốt/scroll trên danh sách dài: dùng gesture helper WDIO (`touchAction`/
111
+ `$.scrollIntoView()`), không lặp `pause` giữa mỗi lần vuốt.
112
+
113
+ ## 4. Assertion (bắt buộc)
114
+
115
+ - Dùng matcher `expect(element).toBeDisplayed()/toHaveText()/toBeEnabled()` (WDIO
116
+ `expect-webdriverio`) — auto-retry, thông báo lỗi rõ.
117
+ - Không `assert` trần trụi; mỗi test assert nội dung/state thật, không chỉ "app không crash".
118
+
119
+ ## 5. Test Independence & Data
120
+
121
+ - Không hard-code device/app path/credential → biến môi trường (`process.env.*`) hoặc
122
+ `wdio.conf.ts` theo profile.
123
+ - Reset app state giữa test qua `driver.reset()` hoặc deep-link/API riêng — **không** dựa vào
124
+ thứ tự test để "dọn" state của nhau.
125
+ - Native permission dialog (camera, location, notification) — xử lý qua Appium
126
+ `mobile: acceptAlert` / capability tự-cấp quyền khi cài, không tap toạ độ mù.
127
+
128
+ ### 5.1 `noReset` / `fullReset` — chọn đúng đánh đổi tốc độ vs. cách ly
129
+
130
+ Capability quyết định mức "sạch" của app giữa các phiên chạy — chọn sai làm suite chậm không
131
+ cần thiết **hoặc** flaky do rò rỉ state:
132
+
133
+ | Capability | Hành vi | Khi dùng |
134
+ |---|---|---|
135
+ | `fullReset: true` | Gỡ cài đặt + cài lại app mỗi session | TC cần trạng thái **cài đặt lần đầu** thật (onboarding, permission dialog lần đầu) — chậm nhất, dùng tối thiểu |
136
+ | `noReset: false` (mặc định, không set `fullReset`) | Xoá app data giữa session nhưng **không** gỡ cài đặt | Mặc định hợp lý cho phần lớn TC — cân bằng tốc độ/cách ly |
137
+ | `noReset: true` | Giữ nguyên toàn bộ data/session giữa các lần chạy | Chỉ dùng cho test **đọc** (không side-effect) hoặc khi đã tự quản lý cleanup qua API riêng — nhanh nhất nhưng rủi ro rò rỉ state nếu dùng sai chỗ |
138
+
139
+ Nguyên tắc chọn: TC có side-effect (tạo/sửa/xoá data, đổi setting) → không dùng `noReset: true`
140
+ trừ khi có cleanup API riêng đáng tin; TC chỉ đọc/verify hiển thị → `noReset: true` để tăng tốc
141
+ đáng kể cho suite lớn.
142
+
143
+ ### 5.2 Device Farm cho Compatibility testing
144
+
145
+ Emulator/simulator không thay thế được test trên **thiết bị thật đa dạng OS version/hãng máy**
146
+ — đặc biệt quan trọng cho Android (phân mảnh OS/màn hình lớn hơn iOS nhiều). Khi TC
147
+ compatibility yêu cầu phủ nhiều device/OS thật mà không có đủ máy vật lý, dùng device farm
148
+ (BrowserStack App Automate, Sauce Labs, hoặc Firebase Test Lab) — cấu hình `capabilities` trỏ
149
+ tới farm qua remote WebDriver URL thay vì local Appium server, code test không đổi. Ghi rõ
150
+ trong TC non-functional compatibility: farm nào, danh sách device/OS mục tiêu, vì đây là chi
151
+ phí (thường tính phí theo phút chạy) cần PM/lead duyệt trước khi đưa vào CI thường xuyên.
152
+
153
+ ## 6. Naming & Structure
154
+
155
+ Vị trí file + quy tắc slug — xem **`_shared/file-naming-and-folders.md`** (nguồn chuẩn duy
156
+ nhất). Quy ước còn lại riêng cho code:
157
+
158
+ | Gì | Convention |
159
+ |---|---|
160
+ | Test title | `it('TC_xxx — <mô tả ngắn>', async () => {...})` — tag Priority nhúng trong title (xem §6.1, WDIO/Mocha không có `tag` option như Playwright) |
161
+ | Comment trace | `// @trace.verifies={UC-ID}-SC{N}` ngay trên mỗi `it(...)` |
162
+
163
+ ### 6.1 Mapping `Priority` (TC) → tag (script) — bắt buộc, suy tự động
164
+
165
+ WebdriverIO chạy trên Mocha — không có cơ chế `tag` như Playwright Test, nên tag nhúng ngay
166
+ trong title và lọc bằng `--mochaOpts.grep`. Đọc field `Priority` (`P0`/`P1`/`P2`) từ
167
+ `TC_<FEATURE>.md` — **không tự đặt theo cảm tính**:
168
+
169
+ | Priority (TC) | Title convention | Lý do |
170
+ |:---:|---|---|
171
+ | `P0` | `it('TC_xxx @smoke @regression — <mô tả>', ...)` | Tính năng lõi — vào cả smoke lẫn regression |
172
+ | `P1`/`P2` | `it('TC_xxx @regression — <mô tả>', ...)` | Chỉ vào regression đầy đủ |
173
+
174
+ **Chạy riêng smoke suite** (xem `/qc-smoke-test`):
175
+ `npx wdio run wdio.conf.ts --mochaOpts.grep "@smoke"`.
176
+ Review script đối chiếu đúng mapping này — P0 thiếu `@smoke` trong title là lỗi 🟠.
177
+
178
+ ## 7. Common Pitfalls — lỗi thường gặp cần tránh (checklist self-review)
179
+
180
+ | # | Lỗi | Tại sao sai | Sửa |
181
+ |---|---|---|---|
182
+ | 1 | `browser.pause(5000)` sau mọi action | Chậm, flaky theo tốc độ máy/device thật vs emulator | `waitForDisplayed()`/`waitUntil()` |
183
+ | 2 | Locator theo toạ độ (`tap({x: 100, y: 200})`) | Vỡ ngay khi đổi kích thước màn hình/device khác | `ValueKey`/Semantics label |
184
+ | 3 | Dùng driver `UiAutomator2`/`XCUITest` thuần cho app Flutter không bật Semantics | Driver "mù" phần lớn UI, locator not-found sai lệch thành "bug sản phẩm" | `automationName: FlutterIntegration` + `ValueKey` |
185
+ | 4 | Không reset app state giữa test | Test sau phụ thuộc state test trước — flaky khi chạy lẻ | `driver.reset()` hoặc API/deep-link reset riêng |
186
+ | 5 | Bỏ qua xử lý permission dialog native | Test treo chờ dialog vô thời hạn hoặc tap sai nút | Capability tự-cấp quyền, hoặc `mobile: acceptAlert` |
187
+ | 6 | Test giả định luôn chạy trên 1 kích thước màn hình | Vỡ trên device thật khác resolution/aspect ratio | Locator theo key, tránh toạ độ; test responsive riêng nếu cần |
188
+ | 7 | Không phân biệt cold start vs app đã mở sẵn | Timeout sai (cold start chậm hơn nhiều) | Timeout riêng cho bước launch đầu tiên |
189
+ | 8 | Gộp test Android + iOS chung 1 file không parametrize | Trùng lặp code, khó bảo trì khi 1 platform đổi hành vi | Cấu hình `capabilities` theo platform, share Page Object logic, tách config |
190
+
191
+ ## 8. Compile & Lint (bắt buộc trước khi báo Human approve)
192
+
193
+ ```bash
194
+ npx tsc --noEmit # type-check toàn bộ
195
+ npx wdio run wdio.conf.ts --spec ... --dryRun # nếu runner hỗ trợ; nếu không, review danh sách it() thủ công = số TC Automatable=Y
196
+ ```
@@ -0,0 +1,257 @@
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-09-09
4
+ source: upstream/qc-base-new/Automation-Standards.md §4 §6 §7 §8 §9 §10 §11 (Approved)
5
+ ---
6
+
7
+ # Web Automation Conventions — TypeScript + Playwright Test
8
+
9
+ Skill **tự chứa**: quy ước bắt buộc khi sinh automation script web bằng `@playwright/test` +
10
+ TypeScript. Mọi file layer trong `qa-script-designer/web/*` đọc file này trước — không lặp
11
+ lại các quy tắc ở đây, chỉ có phần logic sinh-script riêng của layer.
12
+
13
+ ## 1. Locator Strategy — thứ tự ưu tiên (bắt buộc)
14
+
15
+ Đây là nguồn lỗi automation phổ biến nhất: locator giòn (fragile) làm script fail không phải
16
+ vì bug sản phẩm mà vì UI đổi CSS/text/thứ tự DOM. Luôn theo đúng thứ tự ưu tiên sau, **dừng ở
17
+ mức đầu tiên khả dụng** — không nhảy xuống mức thấp hơn "cho nhanh":
18
+
19
+ | # | Locator | Khi dùng | Ví dụ |
20
+ |---|---|---|---|
21
+ | 1 | **Test-ID contract** | Luôn ưu tiên nếu tech-doc có `@trace.testid_attr` + bảng §4.5.6 | `page.getByTestId('login-submit-btn')` |
22
+ | 2 | **Role + Accessible Name** | Không có test-id nhưng element có role/aria chuẩn (button/link/textbox/checkbox…) | `page.getByRole('button', { name: 'Đăng nhập' })` |
23
+ | 3 | **Label** (cho form field) | Input có `<label for>` hoặc `aria-label` | `page.getByLabel('Email')` |
24
+ | 4 | **Text** (chỉ cho element tĩnh, không lặp lại nhiều lần trên trang) | Heading, thông báo, tiêu đề duy nhất | `page.getByText('Đăng nhập thành công')` |
25
+ | 5 | **CSS/XPath theo cấu trúc ổn định** (BEM class, không phải class utility sinh tự động) | Bất khả kháng — không có gì ở trên | `page.locator('.login-form__submit')` |
26
+
27
+ **Không bao giờ dùng làm mức đầu tiên** (chỉ là fallback bất đắc dĩ, luôn kèm ghi chú
28
+ `// FIXME: cần test-id, xem IMPROVE-xxx`):
29
+ - Class do bundler/CSS-in-JS sinh tự động (`css-1a2b3c`, `sc-hGRVom`, các hash động) — đổi mỗi
30
+ lần build, **không ổn định giữa các lần deploy**.
31
+ - XPath tuyệt đối/theo vị trí (`/html/body/div[3]/div[2]/button`) — vỡ ngay khi DOM chèn thêm 1
32
+ node.
33
+ - Index không có điều kiện đi kèm (`.first()`, `nth(2)`) khi danh sách có thể thay đổi thứ tự.
34
+ - Text tiếng Việt cho element có thể đổi copy/đa ngôn ngữ.
35
+
36
+ **Locator cho danh sách/bảng động:** dùng `locator().filter({ hasText })` hoặc lọc theo
37
+ test-id của row thay vì đếm index cố định; nếu cần verify thứ tự thật (sort/paging), so sánh
38
+ mảng giá trị thay vì giả định vị trí.
39
+
40
+ ### 1.1 Cấu hình test-id attribute (bắt buộc trước khi dùng `getByTestId`)
41
+
42
+ Đọc `@trace.testid_attr` từ header tech-doc gộp:
43
+
44
+ | Đọc được | Làm gì |
45
+ |---|---|
46
+ | `data-testid` (mặc định Playwright) | Dùng thẳng `getByTestId()`. |
47
+ | Khác `data-testid` (vd `data-test`, `data-qa`) | **Bắt buộc** cấu hình trước: `playwright.config.ts` → `use: { testIdAttribute: 'data-test' }`. Bỏ bước này → `getByTestId()` luôn tìm `data-testid` mặc định → **locator trượt 100%**, trông giống bug sản phẩm nhưng thực ra là script-bug. |
48
+ | Không có field | Cảnh báo mềm, fallback Role/Text, và ghi `IMPROVE-xxx` đề nghị dev bổ sung field. |
49
+
50
+ ## 2. Page Object Model — 3 lớp (bắt buộc)
51
+
52
+ ```typescript
53
+ // pages/login.page.ts
54
+ import { type Page, type Locator, expect } from '@playwright/test';
55
+ import { BasePage } from './base.page';
56
+
57
+ export class LoginPage extends BasePage {
58
+ // ── Lớp 1: Locators — chỉ khai báo, KHÔNG hành động ──
59
+ private readonly emailInput: Locator;
60
+ private readonly passwordInput: Locator;
61
+ private readonly submitBtn: Locator;
62
+
63
+ constructor(page: Page) {
64
+ super(page);
65
+ this.emailInput = page.getByTestId('login-email-input');
66
+ this.passwordInput = page.getByTestId('login-password-input');
67
+ this.submitBtn = page.getByTestId('login-submit-btn');
68
+ }
69
+
70
+ // ── Lớp 2: Actions — verb_noun(), trả về Page kế tiếp nếu điều hướng ──
71
+ async login(email: string, password: string): Promise<DashboardPage> {
72
+ await this.emailInput.fill(email);
73
+ await this.passwordInput.fill(password);
74
+ await this.submitBtn.click();
75
+ return new DashboardPage(this.page);
76
+ }
77
+
78
+ // ── Lớp 3: Assertions — expect(), không bare assert ──
79
+ async assertValidationError(message: string): Promise<void> {
80
+ await expect(this.page.getByTestId('login-error')).toHaveText(message);
81
+ }
82
+ }
83
+ ```
84
+
85
+ - Kế thừa `BasePage` gọn (chứa `page`, helper chung như `screenshot()`), **không** framework
86
+ report riêng (Allure…) — reporter là của Playwright Test.
87
+ - Điều hướng giữa màn: action trả về Page Object màn kế tiếp (`return new NextPage(this.page)`)
88
+ — test chuỗi được `const dashboard = await loginPage.login(...)`.
89
+ - Selector khai báo tập trung ở constructor/đầu class — không rải trong action method.
90
+ - Test **không bao giờ** gọi `page.click()`/`page.fill()` trực tiếp — luôn qua method của PO.
91
+
92
+ ## 3. Report Readability — `test.step()` (bắt buộc)
93
+
94
+ Playwright Test có `test.step()` built-in — report/trace viewer hiển thị từng step rõ ràng,
95
+ thu gọn được, giúp đọc lại 1 test dài dễ hơn nhiều so với đọc thẳng code:
96
+
97
+ ```typescript
98
+ test('TC_LOGIN_001 — đăng nhập thành công', async ({ page }) => {
99
+ const loginPage = new LoginPage(page);
100
+
101
+ await test.step('Nhập thông tin đăng nhập hợp lệ', async () => {
102
+ await loginPage.login('user@test.com', 'Password123');
103
+ });
104
+
105
+ await test.step('Xác nhận vào được Dashboard', async () => {
106
+ await expect(page).toHaveURL(/\/dashboard/);
107
+ });
108
+ });
109
+ ```
110
+
111
+ Bọc **mỗi chặng có ý nghĩa nghiệp vụ** (không phải mỗi dòng code) bằng `test.step()` — đặc
112
+ biệt quan trọng cho test đa bước (feature đa-màn, E2E journey) nơi report cần chỉ đúng bước
113
+ nào fail thay vì chỉ báo cả `test()` fail chung chung.
114
+
115
+ ## 4. Wait & Timing (bắt buộc)
116
+
117
+ - **Không bao giờ** `page.waitForTimeout(N)` cố định để "chờ cho chắc" — dùng auto-wait của
118
+ `expect()`/action locator (Playwright tự retry tới khi actionable hoặc timeout).
119
+ - Sau navigation/submit cần chờ mạng ổn định: `page.waitForLoadState('networkidle')` — nhưng
120
+ **không** gọi `waitForTimeout` ngay sau đó (thừa, che giấu race condition thật).
121
+ networkidle không tương thích tốt hoặc app poll liên tục.
122
+ - Verify network call thật: `page.waitForResponse(url => ...)` — không mock trong integration
123
+ test (mock = unit test, không phải test tích hợp).
124
+
125
+ ## 5. Assertion (bắt buộc)
126
+
127
+ - Dùng `expect(locator).toHaveText/toBeVisible/toHaveValue(...)` — auto-retry, thông báo lỗi rõ.
128
+ - **Không** `assert someCondition` trần trụi không thông điệp; **không** `assert response` mơ hồ.
129
+ - Mỗi test **assert nội dung thật**, không chỉ assert URL sau điều hướng.
130
+ - Negative case: assert đúng thông báo lỗi cụ thể, không chỉ "có lỗi xuất hiện".
131
+ - **`expect.soft()`** khi cần assert nhiều điều độc lập trong 1 test mà không muốn dừng ngay ở
132
+ assert đầu tiên fail (vd: kiểm tra đồng thời 5 field hiển thị đúng trên 1 form) — Playwright
133
+ gom hết soft-assertion fail rồi báo 1 lần cuối test, đọc report đỡ mất công chạy lại từng cái
134
+ một. **Không lạm dụng**: assertion mang tính điều kiện tiên quyết cho bước sau (vd "đã login
135
+ chưa") vẫn phải dùng `expect()` thường để dừng ngay, tránh test tiếp tục chạy trên state sai.
136
+
137
+ ## 6. Test Independence & Data
138
+
139
+ - Không hard-code URL/credential/timeout → `process.env.*` hoặc file `config/env.ts`.
140
+ - Mỗi test độc lập: dữ liệu từ `TEST_DATA_PLAN.md` (factory/fixture), cleanup sau khi tạo.
141
+ - Không global state chia sẻ giữa test; gom test theo (role, account) nếu auth tốn chi phí,
142
+ qua Playwright `test.describe.serial` có chủ đích — không phải mặc định.
143
+
144
+ ### 6.1 Auth reuse — `storageState` (bắt buộc khi feature cần login)
145
+
146
+ Login qua UI ở **mỗi** test (điền form, submit, chờ redirect) là nguồn chậm + flaky lớn nhất
147
+ của một suite Playwright — mỗi lần login là một chuỗi network call thật. Best practice chuẩn
148
+ của Playwright: **login một lần trong global setup, lưu lại session, mọi test tái sử dụng**:
149
+
150
+ ```typescript
151
+ // auth.setup.ts — chạy 1 lần trước cả suite (khai trong playwright.config.ts: projects: [{ name: 'setup', testMatch: /auth\.setup\.ts/ }])
152
+ import { test as setup } from '@playwright/test';
153
+
154
+ setup('authenticate as teacher', async ({ page }) => {
155
+ await page.goto('/login');
156
+ await page.getByTestId('login-email-input').fill(process.env.TEACHER_EMAIL!);
157
+ await page.getByTestId('login-password-input').fill(process.env.TEACHER_PASSWORD!);
158
+ await page.getByTestId('login-submit-btn').click();
159
+ await page.waitForURL(/\/dashboard/);
160
+ await page.context().storageState({ path: 'playwright/.auth/teacher.json' });
161
+ });
162
+ ```
163
+
164
+ ```typescript
165
+ // playwright.config.ts (trích) — project phụ thuộc setup, dùng lại storageState
166
+ projects: [
167
+ { name: 'setup', testMatch: /auth\.setup\.ts/ },
168
+ { name: 'teacher-tests', use: { storageState: 'playwright/.auth/teacher.json' }, dependencies: ['setup'] },
169
+ ],
170
+ ```
171
+
172
+ - Mỗi role cần 1 file storageState riêng (`teacher.json`, `admin.json`…) — khớp `Account` trong
173
+ `TEST_DATA_PLAN.md`.
174
+ - TC **về chính hành vi login** (validate lỗi sai mật khẩu, khoá tài khoản…) **không** dùng
175
+ storageState — đó là TC test luôn chính flow login qua UI, không phải TC cần login xong mới
176
+ test cái khác.
177
+ - File storageState chứa session thật → thêm vào `.gitignore`, không commit.
178
+
179
+ ## 7. Retry & Flaky Quarantine (bắt buộc cấu hình)
180
+
181
+ Test automation **luôn** có một tỷ lệ flaky nền do timing/network/môi trường CI — coi đó là
182
+ bình thường và có quy trình xử lý có hệ thống, thay vì coi mọi lần fail là product-gap hoặc
183
+ mọi lần pass-sau-retry là "không sao":
184
+
185
+ ```typescript
186
+ // playwright.config.ts (trích)
187
+ export default defineConfig({
188
+ retries: process.env.CI ? 2 : 0, // CI retry tối đa 2 lần; local KHÔNG retry (fail phải hiện ngay để debug)
189
+ reporter: [['html'], ['list']],
190
+ });
191
+ ```
192
+
193
+ - **Retry chỉ để hấp thụ nhiễu môi trường CI, không phải để che giấu bug.** Test pass sau
194
+ retry vẫn hiện trong report là "flaky" (Playwright HTML Report tự đánh dấu `flaky` khác với
195
+ `passed`) — **không coi là pass sạch**. Đây là tín hiệu `qc-run-script` phải đọc để đưa vào
196
+ nhóm cần điều tra riêng (xem gate phân loại Fail của `/qc-run-script`), không tự động lờ đi.
197
+ - **Quarantine**: một test flaky lặp lại ≥3 lần chạy gần nhất → tách khỏi suite chính
198
+ (`test.skip` tạm + tag `@flaky`, hoặc project riêng `flaky-tests` không chặn CI), mở
199
+ `IMPROVE-xxx`/ghi chú kỹ thuật để điều tra root cause (thường là thiếu `storageState`, thiếu
200
+ cách ly data, hoặc race condition thật trong sản phẩm) — không để một test flaky nằm mãi
201
+ trong suite chính làm nhiễu tín hiệu của tất cả lần chạy sau.
202
+
203
+ ## 8. Naming & Structure
204
+
205
+ Vị trí file + quy tắc slug (`<feature>` từ `TC_<FEATURE>.md`, kiểm tra tồn tại trước khi tạo
206
+ mới khi nhiều UC cùng chạm 1 màn) — xem **`_shared/file-naming-and-folders.md`** (nguồn chuẩn
207
+ duy nhất, không lặp lại bảng path ở đây nữa). Quy ước còn lại riêng cho code:
208
+
209
+ | Gì | Convention |
210
+ |---|---|
211
+ | Test title | `test('TC_xxx — <mô tả ngắn>', async ({ page }) => {...})` — TC_ID **trong title**, không chỉ comment |
212
+ | Tag | Suy **tự động** từ `Priority` của TC gốc — xem §8.1, không tự chọn tuỳ ý |
213
+ | Comment trace | `// @trace.verifies={UC-ID}-SC{N}` ngay trên mỗi `test(...)` |
214
+
215
+ ### 8.1 Mapping `Priority` (TC) → tag (script) — bắt buộc, suy tự động
216
+
217
+ `qc-design-script` đọc field `Priority` (`P0`/`P1`/`P2`) đã có sẵn trong `TC_<FEATURE>.md` —
218
+ **không tự đặt tag theo cảm tính**:
219
+
220
+ | Priority (TC) | Tag gắn vào `test()` | Lý do |
221
+ |:---:|---|---|
222
+ | `P0` | `{ tag: ['@smoke', '@regression'] }` | P0 = tính năng lõi/luồng chính — vừa chạy trong smoke suite (nhanh, mỗi build) vừa nằm trong regression đầy đủ |
223
+ | `P1` | `{ tag: ['@regression'] }` | Quan trọng nhưng không phải lõi — không cần chặn mỗi build, chạy ở regression đầy đủ |
224
+ | `P2` | `{ tag: ['@regression'] }` | Biên/phụ — cùng regression, không vào smoke |
225
+
226
+ ```typescript
227
+ test('TC_LOGIN_001 — đăng nhập thành công', { tag: ['@smoke', '@regression'] }, async ({ page }) => {
228
+ // ...
229
+ });
230
+ ```
231
+
232
+ **Chạy riêng smoke suite** (xem `/qc-smoke-test`): `npx playwright test --grep @smoke`.
233
+ Review script (`/qc-review`, `qa-reviewer/script/web/*`) đối chiếu đúng mapping này — tag lệch
234
+ Priority (vd P0 nhưng thiếu `@smoke`) là lỗi 🟠 cần sửa, vì nó khiến smoke suite bỏ sót tính
235
+ năng lõi.
236
+
237
+ ## 9. Common Pitfalls — lỗi thường gặp cần tránh (checklist self-review)
238
+
239
+ | # | Lỗi | Tại sao sai | Sửa |
240
+ |---|---|---|---|
241
+ | 1 | `await page.waitForTimeout(3000)` để "chờ load" | Chậm, flaky (mạng chậm hơn 3s vẫn fail; nhanh hơn thì lãng phí) | `expect(locator).toBeVisible()` / `waitForLoadState` |
242
+ | 2 | Locator theo class hash (`'.css-1x2y3z'`) | Vỡ mỗi lần build lại CSS-in-JS | Test-id contract, hoặc Role/Label |
243
+ | 3 | `test.skip()` không lý do | Không biết TC bị bỏ vì gì, dễ quên xử lý | `test.skip(true, 'GAP-03 — chưa có API reset state')` |
244
+ | 4 | Assertion chỉ ở bước cuối cùng của journey dài | Bug ở giữa flow bị che bởi state cuối đúng "tình cờ" | Assert sau mỗi bước có side-effect quan trọng |
245
+ | 5 | Test phụ thuộc thứ tự chạy (`test B` cần `test A` chạy trước) | Playwright chạy song song mặc định — sinh flaky ngẫu nhiên | Fixture độc lập cho mỗi test, không biến global |
246
+ | 6 | Hard-code data đã tồn tại sẵn trên môi trường (`userId: 42`) | Vỡ khi seed data đổi, không chạy song song được | Tạo qua API/fixture trong `beforeEach`, cleanup `afterEach` |
247
+ | 7 | Bắt exception rồi bỏ qua (`try { ... } catch {}`) để test "luôn xanh" | Che giấu lỗi thật, fake-pass | Không catch; để test fail đúng bản chất |
248
+ | 8 | Dùng `.first()`/`.nth(N)` không kèm filter | Thứ tự phần tử có thể đổi (server-side sort, feature flag) | `.filter({ hasText })`, hoặc locator theo test-id của item |
249
+ | 9 | Test gọi trực tiếp `page.locator(...)` thay vì qua Page Object | Trùng lặp selector nhiều nơi, khó bảo trì khi UI đổi | Mọi interaction qua method PO |
250
+ | 10 | So sánh screenshot toàn trang cho mọi test (visual regression tràn lan) | Flaky theo font/OS/timing render, chi phí review cao | Chỉ dùng visual assertion cho component cụ thể cần, có mask vùng động |
251
+
252
+ ## 10. Compile & Lint (bắt buộc trước khi báo Human approve)
253
+
254
+ ```bash
255
+ npx tsc --noEmit # type-check toàn bộ, không build ra file
256
+ npx playwright test --list # liệt kê test sẽ chạy — đếm phải khớp số TC Automatable=Y
257
+ ```
@@ -0,0 +1,43 @@
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-09-17
4
+ source: upstream/qc-base-new/API-Testing-Standards.md §5.5 §9.1 · AGT-010 (Approved)
5
+ ---
6
+
7
+ # Sinh script — API xác thực & phân quyền
8
+
9
+ **Đọc `_shared/api-conventions.md` rồi `endpoint.md` trước.** Đây là phần thêm.
10
+
11
+ ## Khi nào trigger
12
+ - TC kiểm đăng nhập, vòng đời token, hoặc **quyền truy cập theo vai** lên endpoint.
13
+
14
+ ## Phase 1 — Clarify
15
+ Liệt kê **mọi endpoint được bảo vệ** trong phạm vi, và **mọi vai** có trong hợp đồng. Bảng
16
+ vai × endpoint là đầu vào của Phase 2.
17
+
18
+ ## Phase 2 — Generate
19
+
20
+ **Fixture token theo vai** *(§5.5)* — `fixtures/api.fixture.ts` cấp một context cho mỗi vai.
21
+ Không đăng nhập lại trong từng test: chậm, và dễ chạm rate-limit.
22
+
23
+ **Mỗi endpoint được bảo vệ sinh đủ ba ca** *(§9.1)*:
24
+
25
+ | Ca | Mong đợi |
26
+ |---|---|
27
+ | Không token | `401` |
28
+ | Token sai / hết hạn | `401` |
29
+ | Token đúng nhưng **sai vai** | `403` |
30
+
31
+ Không gộp `401` với `403` — hai lỗi khác nhau: *chưa biết anh là ai* vs *biết rồi nhưng anh
32
+ không được phép*. Gộp là mất khả năng phân biệt lỗi cấu hình xác thực với lỗi phân quyền.
33
+
34
+ **Assert phải là "bị chặn", không phải "không lỗi":** assert đúng mã, **và** assert body không
35
+ rò dữ liệu của tài nguyên bị cấm.
36
+
37
+ ## Phase 3 — Trace tag + cột `Script file`
38
+ Như `endpoint.md` Phase 3 và Phase 4, `@trace.test_type=functional`.
39
+
40
+ ## Self-review
41
+ - Mỗi endpoint được bảo vệ có đủ 3 ca?
42
+ - Fixture phân biệt **theo vai**, không dùng một token cho mọi test?
43
+ - Không token thật nào nằm trong code?
@@ -0,0 +1,61 @@
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-09-17
4
+ source: upstream/qc-base-new/API-Testing-Standards.md §4 §5 §6 §8 · AGT-010 (Approved)
5
+ ---
6
+
7
+ # Sinh script — API endpoint *(CRUD, mã trạng thái, cấu trúc response)*
8
+
9
+ **Đọc `_shared/api-conventions.md` trước.** File này chỉ có phần sinh-script riêng của tầng
10
+ endpoint. Lane này dùng khi `active_platform = system`.
11
+
12
+ ## Khi nào trigger
13
+ - Chuyển TC `Automatable: Y` của một endpoint *(từ `AUTOMATION_ASSESSMENT.md`)* thành spec,
14
+ sau khi `TC_<FEATURE>.Test.md` đã qua `/qc-review-testcase`.
15
+
16
+ ## Khi KHÔNG trigger
17
+ - Xác thực / phân quyền → `auth.md` · biên đầu vào & injection → `security.md`
18
+ - API gọi phụ trợ **bên trong** dự án web *(setup/teardown)* → `web/functional/api.md`
19
+
20
+ ## Phase 1 — Clarify
21
+
22
+ Đọc `TC_<FEATURE>.Test.md` **chỉ các TC `Automatable: Y`** + `TEST_DATA_PLAN.md`. API Object cho
23
+ resource này đã có chưa → tạo mới nếu chưa. Đọc hợp đồng endpoint từ tech-doc: path · method ·
24
+ payload · mã trạng thái · cấu trúc response.
25
+
26
+ ## Phase 2 — Generate
27
+
28
+ **Phủ đúng số TC `Automatable: Y`** — đếm trong `AUTOMATION_ASSESSMENT.md` = số `test(...)` phải
29
+ sinh, **không hơn không kém**. TC `Y` mà gặp rào cản kỹ thuật lúc viết → `test.fixme('TC-API-xxx — {lý do}')`
30
+ + ghi `IMPROVE-xxx`, **không âm thầm bỏ qua**.
31
+
32
+ Gom bằng `test.describe` theo **resource → method**. Mỗi TC một `test()`, tiêu đề mang `TC-API-ID`.
33
+
34
+ Sinh đủ ba lớp:
35
+
36
+ | Lớp | File |
37
+ |---|---|
38
+ | API Object | `api-automation/api/<resource>.api.ts` — 1 method = 1 endpoint action |
39
+ | Test data | `api-automation/data/<resource>.data.ts` |
40
+ | Spec | `api-automation/tests/{TICKET-ID}/<feature>-<scenario>.spec.ts` |
41
+
42
+ ## Phase 3 — Trace tag bắt buộc
43
+
44
+ ```typescript
45
+ // @trace.verifies={UC-ID}-SC{N}
46
+ // @trace.source=specs/{domain}/{prd-slug}/bdd/system/{UC-ID}-{slug}.feature
47
+ // @trace.test_type=functional
48
+ ```
49
+
50
+ ## Phase 4 — Điền cột `Script file`
51
+
52
+ Sau khi sinh, **điền đường dẫn thật** vào cột `Script file` của từng TC `Y` trong
53
+ `AUTOMATION_ASSESSMENT.md`. Đây là chỉ mục ngược duy nhất TC → file code; `/qc-run-script` đọc
54
+ đúng cột này để biết chạy file nào.
55
+
56
+ ## Self-review trước khi báo xong
57
+
58
+ - Số `test()` = số TC `Automatable: Y`?
59
+ - Mọi request đi qua API Object, **không** `request.*` thô trong spec?
60
+ - Mọi test assert **mã trạng thái** + ít nhất một assert về nội dung?
61
+ - `npx tsc --noEmit` sạch và `--list` collect đúng số?
@@ -0,0 +1,41 @@
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-09-17
4
+ source: upstream/qc-base-new/API-Testing-Standards.md §8 §9.2 · AGT-010 (Approved)
5
+ ---
6
+
7
+ # Sinh script — API biên đầu vào & injection
8
+
9
+ **Đọc `_shared/api-conventions.md` rồi `endpoint.md` trước.** Đây là phần thêm.
10
+
11
+ ## Khi nào trigger
12
+ - TC kiểm **biên của đầu vào** hoặc các mẫu injection lên endpoint.
13
+
14
+ ## Phase 1 — Clarify
15
+ Với mỗi trường đầu vào, lấy từ hợp đồng: kiểu · bắt buộc/không · giới hạn min–max · tập giá trị
16
+ hợp lệ. Thiếu ràng buộc trong hợp đồng → ghi `IMPROVE-xxx`, **không tự bịa ngưỡng**.
17
+
18
+ ## Phase 2 — Generate
19
+
20
+ **Ba kỹ thuật phải thấy được trong code** *(§8)*:
21
+
22
+ | Kỹ thuật | Sinh gì |
23
+ |---|---|
24
+ | **Phân lớp tương đương** | mỗi lớp ít nhất một ca — không chỉ ca hợp lệ |
25
+ | **Giá trị biên** | với trường có giới hạn: `min-1` · `min` · `max` · `max+1` |
26
+ | **Phủ HTTP method** | gọi method không hỗ trợ → mong đợi `405` |
27
+
28
+ **Payload injection** *(§9.2)* — gom vào `data/<resource>.data.ts` thành **một bộ dùng lại**,
29
+ không rải trong spec. Phủ đủ các họ: SQL · NoSQL · command · path traversal · XSS lưu trữ.
30
+
31
+ **Mong đợi phải cụ thể:** `400`/`422` + thông điệp lỗi có cấu trúc, hoặc dữ liệu được làm sạch.
32
+ **Không** assert kiểu *"không sập"* — một endpoint trả `200` kèm dữ liệu đã bị nhiễm vẫn "không sập".
33
+
34
+ Thêm một assert: thông điệp lỗi **không rò** stack trace, tên bảng, hay phiên bản thư viện.
35
+
36
+ ## Phase 3 — Trace tag + cột `Script file`
37
+ Như `endpoint.md`, `@trace.test_type=non-functional`.
38
+
39
+ ## Ranh giới
40
+ Đây **không** phải kiểm thâm nhập. Sinh script theo TC đã duyệt; **không** tự mở rộng sang dò
41
+ quét lỗ hổng, và **không** kết luận gì về mức an toàn của hệ thống.
@@ -0,0 +1,35 @@
1
+ ---
2
+ version: 1.0
3
+ updated: 2026-09-09
4
+ ---
5
+
6
+ # Gen Script — Mobile E2E Journey (Flutter)
7
+
8
+ **Đọc `_shared/mobile-conventions.md` trước.**
9
+
10
+ ## Khi nào trigger
11
+ - Convert TC E2E journey (output `qa-designer/e2e/journey.md`, platform `app`), TC Automatable=Y.
12
+
13
+ ## Khi KHÔNG trigger
14
+ - Test 1 màn/field → `functional/*` · 1 điểm tích hợp → `integration.md`
15
+
16
+ ## Quy ước riêng
17
+ - Chuỗi Page Object xuyên các màn; verify V1…Vn sau mỗi chặng.
18
+ - Journey dài trên thiết bị thật/emulator chậm hơn web đáng kể — timeout cho mỗi bước chờ nên
19
+ rộng hơn mặc định (cold start, animation transition Flutter).
20
+ - Tiền điều kiện (tài khoản, data) qua fixture riêng (`beforeEach`), reset app state
21
+ (`driver.reset()`) trước mỗi journey — không dựa vào journey trước "dọn" giúp.
22
+
23
+ ## Phase 1 — Clarify
24
+ Các màn/PO + hệ thống verify ngoài (nếu có); tài khoản/data cần dựng.
25
+
26
+ ## Phase 2 — Generate
27
+ Mỗi journey → 1 test; verify đủ V1…Vn. Journey phụ thuộc gap → `it.skip('...GAP-xx')`.
28
+
29
+ ## Phase 3 — Self-Verify
30
+ ```bash
31
+ npx tsc --noEmit
32
+ ```
33
+
34
+ ## Output
35
+ `mobile-automation/test/specs/{TICKET-ID}/e2e/<feature>-<scenario>.spec.ts` + Page Object tái dùng.