@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.
- package/bin/qc-base-map.json +13 -11
- package/bin/self-check.js +49 -4
- package/bin/trace-schema.json +3226 -3187
- package/core/FRAMEWORK_VERSION +1 -1
- package/core/commands/qc-analyze.md +2 -2
- package/core/commands/qc-automation-assess.md +3 -3
- package/core/commands/qc-design-script.md +60 -30
- package/core/commands/qc-design-test.md +79 -7
- package/core/commands/qc-plan.md +1 -1
- package/core/commands/qc-report.md +85 -76
- package/core/commands/qc-review-script.md +25 -16
- package/core/commands/qc-review-testcase.md +8 -7
- package/core/commands/qc-run-manualtest.md +1 -1
- package/core/commands/qc-run-script.md +15 -8
- package/core/modules/qc-playwright-ts/module.yaml +13 -0
- package/core/modules/qc-playwright-ts/stack-profile.yaml +99 -0
- package/core/modules/qc-wdio-appium/module.yaml +20 -0
- package/core/modules/qc-wdio-appium/stack-profile.yaml +107 -0
- package/core/skills/qc/qa-analyst/data-flow.md +1 -1
- package/core/skills/qc/qa-automation-assess/matrix.md +6 -3
- package/core/skills/qc/{qa-runner → qa-designer}/exploratory/session.md +8 -2
- package/core/skills/qc/qa-designer/functional/api.md +1 -1
- package/core/skills/qc/qa-designer/functional/job.md +128 -0
- package/core/skills/qc/qa-designer/integration/api.md +1 -1
- package/core/skills/qc/qa-designer/integration/db.md +1 -1
- package/core/skills/qc/qa-designer/integration/{kafka.md → queue.md} +20 -4
- package/core/skills/qc/qa-designer/shared/skill-decision-tree.md +17 -0
- package/core/skills/qc/qa-designer/shared/tc-metadata-format.md +17 -0
- package/core/skills/qc/qa-reviewer/script/_shared/review-rules.md +121 -0
- package/core/skills/qc/qa-reviewer/script/api/auth.md +49 -0
- package/core/skills/qc/qa-reviewer/script/api/endpoint.md +89 -0
- package/core/skills/qc/qa-reviewer/script/api/security.md +46 -0
- package/core/skills/qc/qa-reviewer/script/exploratory.md +2 -2
- package/core/skills/qc/qa-reviewer/script/mobile/e2e.md +41 -0
- package/core/skills/qc/qa-reviewer/script/mobile/functional.md +90 -0
- package/core/skills/qc/qa-reviewer/script/mobile/integration.md +41 -0
- package/core/skills/qc/qa-reviewer/script/mobile/non-functional.md +43 -0
- package/core/skills/qc/qa-reviewer/script/web/e2e.md +46 -0
- package/core/skills/qc/qa-reviewer/script/web/functional.md +111 -0
- package/core/skills/qc/qa-reviewer/script/web/integration.md +46 -0
- package/core/skills/qc/qa-reviewer/script/web/non-functional.md +49 -0
- package/core/skills/qc/qa-reviewer/shared/read-doc-gap-inputs.md +1 -1
- package/core/skills/qc/qa-reviewer/shared/review-file-template.md +26 -7
- package/core/skills/qc/qa-reviewer/test-case/e2e.md +1 -1
- package/core/skills/qc/qa-reviewer/test-case/exploratory.md +1 -1
- package/core/skills/qc/qa-reviewer/test-case/functional.md +1 -1
- package/core/skills/qc/qa-reviewer/test-case/integration.md +1 -1
- package/core/skills/qc/qa-reviewer/test-case/non-functional.md +1 -1
- package/core/skills/qc/qa-script-designer/_shared/api-conventions.md +94 -0
- package/core/skills/qc/qa-script-designer/_shared/file-naming-and-folders.md +109 -0
- package/core/skills/qc/qa-script-designer/_shared/mobile-conventions.md +196 -0
- package/core/skills/qc/qa-script-designer/_shared/web-conventions.md +257 -0
- package/core/skills/qc/qa-script-designer/api/auth.md +43 -0
- package/core/skills/qc/qa-script-designer/api/endpoint.md +61 -0
- package/core/skills/qc/qa-script-designer/api/security.md +41 -0
- package/core/skills/qc/qa-script-designer/mobile/e2e.md +35 -0
- package/core/skills/qc/qa-script-designer/mobile/functional/feature.md +32 -0
- package/core/skills/qc/qa-script-designer/mobile/functional/screen.md +42 -0
- package/core/skills/qc/qa-script-designer/mobile/integration.md +39 -0
- package/core/skills/qc/qa-script-designer/mobile/non-functional.md +39 -0
- package/core/skills/qc/qa-script-designer/web/e2e.md +36 -0
- package/core/skills/qc/qa-script-designer/web/functional/api.md +39 -0
- package/core/skills/qc/qa-script-designer/web/functional/gui-feature.md +34 -0
- package/core/skills/qc/qa-script-designer/web/functional/gui-screen.md +42 -0
- package/core/skills/qc/qa-script-designer/web/integration.md +43 -0
- package/core/skills/qc/qa-script-designer/web/non-functional.md +42 -0
- package/core/skills/qc/qa-script-runner/mobile/run.md +38 -0
- package/core/skills/qc/qa-script-runner/report.md +41 -0
- package/core/skills/qc/qa-script-runner/web/run.md +48 -0
- package/core/steps/qc-scope.md +43 -0
- package/core/steps/report-footer.md +2 -2
- package/docs/02-concepts/pipeline-steps/07-dev-selftest.md +1 -1
- package/docs/02-concepts/pipeline-steps/08-qc-automation.md +10 -10
- package/docs/02-concepts/pipeline-steps/10-feedback-loop.md +1 -1
- package/docs/02-concepts/traceability.md +1 -1
- package/docs/03-guides/developer.md +1 -1
- package/docs/03-guides/tester-qa.md +40 -12
- package/docs/04-reference/commands.md +1 -1
- package/docs/04-reference/modules.md +2 -1
- package/docs/explain/17-qc-design-test.md +2 -2
- package/docs/explain/19-qc-run-test.md +4 -4
- package/docs/explain/20-qc-report.md +1 -1
- package/docs/explain/23-fix-bug.md +2 -2
- package/docs/plans/qc-surgery/01-checklist.md +18 -6
- package/docs/plans/qc-surgery/PLAN_v2.md +295 -0
- package/docs/plans/qc-surgery/exec-S-ap-stack-typescript.md +420 -0
- package/docs/plans/qc-surgery/exec-S0-guard-cam-stack-cu.md +400 -0
- package/docs/plans/qc-surgery/exec-S1-hai-module-thay-qc-playwright.md +267 -0
- package/docs/plans/qc-surgery/exec-S2-qa-runner-thanh-script-designer-runner.md +340 -0
- package/docs/plans/qc-surgery/exec-S3-viet-lai-tieu-chi-review-script.md +322 -0
- package/docs/plans/qc-surgery/exec-S5-an-theo-don-dau-vet-stack-cu.md +292 -0
- package/package.json +1 -1
- package/core/modules/qc-playwright/stack-profile.yaml +0 -66
- package/core/skills/qc/qa-reviewer/script/e2e.md +0 -95
- package/core/skills/qc/qa-reviewer/script/functional.md +0 -109
- package/core/skills/qc/qa-reviewer/script/integration.md +0 -99
- package/core/skills/qc/qa-reviewer/script/non-functional.md +0 -134
- package/core/skills/qc/qa-runner/e2e.md +0 -49
- package/core/skills/qc/qa-runner/functional/api.md +0 -35
- package/core/skills/qc/qa-runner/functional/gui-feature.md +0 -57
- package/core/skills/qc/qa-runner/functional/gui-screen.md +0 -61
- package/core/skills/qc/qa-runner/integration.md +0 -47
- package/core/skills/qc/qa-runner/non-functional.md +0 -49
- 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.
|