@relipa/ai-flow-kit 0.2.1 → 0.2.2-beta.0

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.
@@ -18,6 +18,40 @@ Flow độc lập để tester thực thi test từ TC file có sẵn, không c
18
18
 
19
19
  ---
20
20
 
21
+ ## Cấu trúc thư mục đầu ra
22
+
23
+ **Không còn dùng `ak-test/` working dir.** Toàn bộ output — script, evidence, result, report, bug — nằm trong `AK-Docs/03.Testing/`, theo cấu trúc chuẩn ở `docs/common/Testing-Structure.md`. `04.Evidence/` chứa screenshot/video/`trace.zip`/`results.json` thô, nhưng **phải nằm trong `.gitignore`** — đây vẫn là binary lớn, không commit lên Git kể cả khi nằm trong AK-Docs.
24
+
25
+ ```
26
+ AK-Docs/03.Testing/
27
+ ├── 04.Evidence/{repo}/{featureDir}/run-{N}/ # ⚠️ gitignored — xem Gate 1 item 6
28
+ │ ├── results.json # Playwright JSON output thô
29
+ │ └── {TC_ID}-{kebab-scenario}/
30
+ │ ├── step-NN-{desc}.png # 1 ảnh / step có mô tả UI
31
+ │ └── trace.zip
32
+ ├── 05.Scripts/ # Gate 2 — Playwright project, chạy được tại chỗ
33
+ │ ├── package.json / tsconfig.json / node_modules/ (gitignored)
34
+ │ ├── Shared/
35
+ │ │ ├── fixtures/test.ts
36
+ │ │ ├── BasePage.ts
37
+ │ │ └── evidence-helper.ts
38
+ │ └── {repo}/
39
+ │ ├── playwright.config.ts
40
+ │ ├── pages/{ScreenID}Page.ts
41
+ │ └── {featureDir}/{ScreenID}.spec.ts
42
+ ├── 02.Reports/{repo}/{featureDir}/run-{N}/
43
+ │ ├── {TC_ID}-{kebab-scenario}/result.md
44
+ │ └── testreport.md # Gate 4
45
+ └── 06.Bugs/{repo}/{featureDir}/run-{N}/
46
+ └── BUG-{NNN}-{slug}.md # Gate 3
47
+ ```
48
+
49
+ `featureDir` = `{ScreenID}_{Screen-Name-kebab-case}` (vd `AD10_create-product`).
50
+
51
+ > **Lịch sử:** bản trước dùng `ak-test/` working dir ngoài AK-Docs cho evidence để tránh phình git history. Tester quyết định bỏ `ak-test/` hoàn toàn — evidence chuyển vào `04.Evidence/` (đúng theo mapping gốc của `docs/common/Testing-Structure.md`), và xử lý vấn đề binary lớn bằng `.gitignore` thay vì tách working dir riêng.
52
+
53
+ ---
54
+
21
55
  ## ⛩️ GATE 1 — Context & Work Plan
22
56
 
23
57
  ### Pre-flight checks (auto, không hỏi tester)
@@ -47,14 +81,44 @@ Kiểm tra trước khi làm gì:
47
81
  > "Để cài thủ công: mở `~/.claude/settings.json`, thêm `playwright` vào `mcpServers`, rồi restart Claude Code."
48
82
 
49
83
  1. TC file có parse được không? (file tồn tại, có Section 3 — Danh sách Test Cases)
50
- 2. `ak-test/` folder có tồn tại không? Nếu không hướng dẫn tester tạo: `mkdir ak-test`
84
+ 2. `AK-Docs/03.Testing/` có tồn tại không? (thư mục chuẩn theo `docs/common/Testing-Structure.md` nếu thiếu subfolder `04.Evidence/`, `05.Scripts/`, `02.Reports/`, `06.Bugs/` thì tự tạo)
51
85
  3. `BASE_URL` env var có set không? Nếu không → hỏi: "BASE_URL chưa set. App đang chạy ở URL nào?"
52
86
 
53
- Items 1–3 chạy ngay khi entry. Item 4 chạy sau khi repo được xác định từ Bước 1 (parse TC file):
87
+ Items 1–3 chạy ngay khi entry. Items 4–6 chạy sau khi repo được xác định từ Bước 1 (parse TC file):
88
+
89
+ 4. `AK-Docs/03.Testing/05.Scripts/{repo}/playwright.config.ts` có tồn tại không? Nếu không → **tự scaffold** (không cần hỏi tester), xem "Scaffold Playwright Project" bên dưới.
90
+ 5. `AK-Docs/03.Testing/05.Scripts/package.json` + `node_modules/` có tồn tại không? Nếu chưa `npm install` → chạy `npm install` trong `AK-Docs/03.Testing/05.Scripts/`, sau đó `npx playwright install chromium` (một lần, cache browser ở `~/.cache/ms-playwright`, không nằm trong repo).
91
+ 6. `.gitignore` (ở root của AK-Docs, hoặc root repo chứa AK-Docs) có ignore các dòng sau chưa? Nếu chưa → tự thêm (append, không xoá nội dung cũ):
92
+ - `03.Testing/04.Evidence/` — **bắt buộc**, evidence là binary (screenshot/video/trace.zip), không commit dù nằm trong AK-Docs
93
+ - `03.Testing/05.Scripts/**/node_modules/`, `03.Testing/05.Scripts/**/test-results/`, `03.Testing/05.Scripts/**/playwright-report/`, `03.Testing/05.Scripts/**/blob-report/`
94
+
95
+ Nếu bất kỳ check nào fail và không tự fix được → dừng, hướng dẫn fix. Không proceed.
96
+
97
+ ### Scaffold Playwright Project (chỉ chạy lần đầu, khi chưa có `AK-Docs/03.Testing/05.Scripts/`)
98
+
99
+ AK-Docs vốn là docs-only repo (không có `package.json`). Vì tester yêu cầu script phải nằm trong AK-Docs để dễ trace cùng report/bug, ta scaffold một Playwright project bên trong `03.Testing/05.Scripts/` — **một package.json/node_modules chung cho tất cả repo**, mỗi repo có `playwright.config.ts` riêng (baseURL khác nhau):
100
+
101
+ ```
102
+ AK-Docs/03.Testing/05.Scripts/
103
+ ├── package.json # 1 lần duy nhất, dùng chung mọi repo
104
+ ├── tsconfig.json
105
+ ├── node_modules/ # gitignored
106
+ ├── Shared/
107
+ │ ├── fixtures/test.ts # test fixture chung
108
+ │ ├── BasePage.ts
109
+ │ └── evidence-helper.ts # xem templates/evidence-helper.ts
110
+ └── {repo}/
111
+ ├── playwright.config.ts # baseURL riêng cho repo này
112
+ ├── pages/{ScreenID}Page.ts
113
+ └── {featureDir}/{ScreenID}.spec.ts
114
+ ```
54
115
 
55
- 4. `playwright.config.ts` tồn tại trong `ak-test/{repo}/` không? Nếu không dừng, hướng dẫn tester: "Chưa playwright.config.ts cho repo {repo}. Tạo file từ template tại `ak-test/{repo}/playwright.config.ts` (xem skill execute-flow templates)."
116
+ - `package.json`: devDependencies `@playwright/test`, `typescript`. Tạo bằng `npm init -y` rồi `npm install -D @playwright/test typescript`.
117
+ - Copy `custom/skills/execute-flow/templates/playwright.config.ts` → `AK-Docs/03.Testing/05.Scripts/{repo}/playwright.config.ts`, điền `baseURL` = BASE_URL của repo đó.
118
+ - Copy `custom/skills/execute-flow/templates/evidence-helper.ts` → `AK-Docs/03.Testing/05.Scripts/Shared/evidence-helper.ts` (không tạo lại nếu đã tồn tại).
119
+ - `featureDir` = `{ScreenID}_{Screen-Name-kebab-case}` (vd `AD10_create-product`), khớp naming convention của `docs/common/Testing-Structure.md`.
56
120
 
57
- Nếu bất kỳ check nào fail dừng, hướng dẫn fix. Không proceed.
121
+ > **Vì sao không dùng node_modules riêng cho mỗi repo:** tránh cài `@playwright/test` + Chromium nhiều lần, chỉ cần 1 lần install ở `05.Scripts/` chạy được cho toàn bộ project.
58
122
 
59
123
  ### Bước 1: Parse TC file
60
124
 
@@ -102,7 +166,7 @@ Phân loại sơ bộ:
102
166
 
103
167
  ### Bước 3: Scan scripts hiện tại
104
168
 
105
- Tìm `ak-test/{repo}/scripts/{screenId}/{ScreenID}.spec.ts`:
169
+ Tìm `AK-Docs/03.Testing/05.Scripts/{repo}/{featureDir}/{ScreenID}.spec.ts`:
106
170
  - Nếu tồn tại: đọc `@tc-id` comments → xác định TCs đã có script
107
171
  - Tính hash cho từng TC → so với `@tc-hash` trong file → xác định `updated` vs `skipped`
108
172
  - Nếu không tồn tại: tất cả automation TCs đều là `new`
@@ -152,8 +216,10 @@ Gọi `script-sync` skill với context:
152
216
  - `tcFile`: path TC file
153
217
  - `repo`: từ Gate 1
154
218
  - `screenId`: always lowercase ScreenID (vd `ad10`, not `AD10`) — directory path uses lowercase
219
+ - `featureDir`: `{ScreenID}_{Screen-Name-kebab-case}` (vd `AD10_create-product`)
155
220
  - `screenUrl`: hỏi tester nếu chưa biết: "URL path của màn hình {ScreenID} là gì? (vd `/admin/products/create`)"
156
221
  - `baseUrl`: từ `BASE_URL` env var
222
+ - `scriptsRoot`: `AK-Docs/03.Testing/05.Scripts/{repo}/` (script + Page Object nằm ở đây)
157
223
 
158
224
  Nhận về: danh sách kết quả per TC.
159
225
 
@@ -165,7 +231,7 @@ Gen mới: {N} | Updated: {N} | Skipped: {N}
165
231
  Manual (Untest): {N} TCs
166
232
  Blocked: {N} TCs — xem lý do bên trên
167
233
 
168
- → Review: [ak-test/{repo}/scripts/{screenId}/{ScreenID}.spec.ts](ak-test/{repo}/scripts/{screenId}/{ScreenID}.spec.ts)
234
+ → Review: [AK-Docs/03.Testing/05.Scripts/{repo}/{featureDir}/{ScreenID}.spec.ts](AK-Docs/03.Testing/05.Scripts/{repo}/{featureDir}/{ScreenID}.spec.ts)
169
235
  → Type APPROVED to run tests
170
236
  → Type SKIP: {TC_ID} để đánh dấu Pending và tiếp tục
171
237
  → Or provide feedback to fix scripts
@@ -182,44 +248,50 @@ Chỉ chạy sau Gate 2 APPROVED.
182
248
 
183
249
  ### Bước 1: Xác định run number
184
250
 
185
- Scan `ak-test/{repo}/results/{screenId}/` để tìm run folder hiện có:
251
+ Scan `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/` để tìm run folder hiện có (nguồn sự thật cho run number):
186
252
  - Nếu không có folder nào → `run-1`
187
253
  - Nếu có `run-1`, `run-2` → `run-3`
188
254
 
189
- Tạo folder: `ak-test/{repo}/results/{screenId}/run-{N}/`
255
+ Tạo 2 folder song song cho run này (evidence vs report/bug, xem "Cấu trúc thư mục đầu ra"):
256
+ - `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/` — evidence nhị phân (screenshots, video, `trace.zip`)
257
+ - `AK-Docs/03.Testing/02.Reports/{repo}/{featureDir}/run-{N}/` — `result.md` + `testreport.md`
190
258
 
191
259
  ### Bước 2: Chạy Playwright
192
260
 
261
+ Script nằm trong `AK-Docs/03.Testing/05.Scripts/{repo}/`, evidence nhị phân (screenshot/video/trace) ghi trực tiếp vào `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/` — dùng env var `EVIDENCE_DIR` để `playwright.config.ts` set `outputDir` động (xem template). Vì `04.Evidence/` và `05.Scripts/` đều là con trực tiếp của `03.Testing/`, chỉ cần đi lên 1 cấp:
262
+
193
263
  ```bash
194
- cd ak-test/{repo}
195
- BASE_URL={baseUrl} npx playwright test scripts/{screenId}/{ScreenID}.spec.ts
264
+ cd AK-Docs/03.Testing/05.Scripts
265
+ EVIDENCE_DIR="$(pwd)/../04.Evidence/{repo}/{featureDir}/run-{N}" \
266
+ BASE_URL={baseUrl} \
267
+ npx playwright test --config={repo}/playwright.config.ts {repo}/{featureDir}/{ScreenID}.spec.ts
196
268
  ```
197
269
 
198
- Note: Reporters are already configured in `playwright.config.ts` (json → `test-results/results.json`, html, list). The CLI `--reporter` flag would override these settings, so it is omitted here.
270
+ Note: Reporters đã được config trong `playwright.config.ts` (json → `{EVIDENCE_DIR}/results.json`, html, list) CLI `--reporter` flag sẽ override nên không dùng đây.
199
271
 
200
272
  ### Bước 3: Parse kết quả
201
273
 
202
- Đọc `ak-test/{repo}/test-results/results.json` (Playwright JSON output).
274
+ Đọc `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/results.json` (Playwright JSON output, ghi trực tiếp vào evidence dir nhờ `EVIDENCE_DIR`).
203
275
 
204
276
  Với mỗi test:
205
277
  - Extract TC_ID từ test title (format: `AD10_001 - {name}` hoặc `[AD10_001] {name}`)
206
278
  - Map: TC_ID → `{status: 'passed'|'failed', duration, attachments, errors}`
207
279
 
208
- ### Bước 4: Tổ chức Evidence
280
+ **Cross-check trước khi ghi PASS/FAIL** (xem "DOM Verification Rules" bên dưới) — không tin tuyệt đối vào exit code của Playwright, phải verify lại assertion có thực sự map đúng Expected Result trong TC file.
281
+
282
+ ### Bước 4: Tổ chức Evidence + Report
209
283
 
210
284
  Với mỗi TC:
211
285
 
212
- **Tạo folder:** `ak-test/{repo}/results/{screenId}/run-{N}/{TC_ID}-{kebab-scenario}/`
286
+ **Evidence (ở `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/{TC_ID}-{kebab-scenario}/`)** — Playwright ghi trực tiếp vào đây nhờ `EVIDENCE_DIR`, không cần copy tay:
287
+ - Screenshots per-step → `step-NN-{desc}.png` (xem "Evidence Quality Rules" — mỗi step trong TC Steps phải có 1 screenshot riêng, không chỉ chụp cuối cùng)
288
+ - Trace file → `trace.zip`
213
289
 
214
290
  Với `kebab-scenario` = Test Case Name lowercase, dấu cách thành `-`, bỏ ký tự đặc biệt.
215
291
 
216
- Ngôn ngữ output: auto-detect theo ngôn ngữ của ticket/testcase input — xem `custom/rules/output-language.md` và `custom/skills/test-skills/rules/qa-writing-standards.md` (input tiếng Việt output tiếng Việt; ngược lại mặc định tiếng Anh; tiêu đề cột bảng vẫn giữ tiếng Anh).
217
-
218
- **Copy files từ Playwright output:**
219
- - Screenshots → `step-NN-{desc}.png`
220
- - Trace file → `trace.zip`
292
+ **Report (`AK-Docs/03.Testing/02.Reports/{repo}/{featureDir}/run-{N}/{TC_ID}-{kebab-scenario}/result.md`)** file text, không chứa binary:
221
293
 
222
- **Tạo `result.md`:**
294
+ Ngôn ngữ output: auto-detect theo ngôn ngữ của ticket/testcase input — xem `custom/rules/output-language.md` và `custom/skills/test-skills/rules/qa-writing-standards.md` (input tiếng Việt → output tiếng Việt; ngược lại mặc định tiếng Anh; tiêu đề cột bảng vẫn giữ tiếng Anh).
223
295
 
224
296
  Khi PASS:
225
297
  ```markdown
@@ -230,6 +302,8 @@ Khi PASS:
230
302
  **Run:** run-{N} | {YYYY-MM-DD HH:MM}
231
303
 
232
304
  ## Evidence
305
+ → `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/{TC_ID}-{kebab-scenario}/`
306
+
233
307
  | Step | Screenshot |
234
308
  |------|-----------|
235
309
  | {step 1} | step-01-{desc}.png |
@@ -248,20 +322,22 @@ Khi FAIL:
248
322
  **Error:** {error message từ Playwright}
249
323
 
250
324
  ## Evidence
325
+ → `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/{TC_ID}-{kebab-scenario}/`
326
+
251
327
  | File | Mô tả |
252
328
  |------|-------|
253
329
  | failure-screenshot.png | Trạng thái lúc fail |
254
330
  | trace.zip | Playwright trace |
255
331
 
256
332
  ## Bug Reference
257
- → Xem: bugs/BUG-{NNN}-{slug}.md
333
+ → Xem: `AK-Docs/03.Testing/06.Bugs/{repo}/{featureDir}/run-{N}/BUG-{NNN}-{slug}.md`
258
334
  ```
259
335
 
260
- Với Manual TCs (Untest): không tạo evidence folder.
336
+ Với Manual TCs (Untest): không tạo evidence folder / result.md.
261
337
 
262
338
  ### Bước 5: Auto-draft Bug Reports
263
339
 
264
- Với mỗi TC failed, tạo `ak-test/{repo}/results/{screenId}/run-{N}/bugs/BUG-{NNN}-{slug}.md`:
340
+ Với mỗi TC failed, tạo `AK-Docs/03.Testing/06.Bugs/{repo}/{featureDir}/run-{N}/BUG-{NNN}-{slug}.md`:
265
341
 
266
342
  ```markdown
267
343
  # BUG-{NNN} — {Test Case Name}
@@ -288,8 +364,8 @@ Với mỗi TC failed, tạo `ak-test/{repo}/results/{screenId}/run-{N}/bugs/BUG
288
364
 
289
365
  ## Evidence
290
366
 
291
- - Screenshot: `{TC_ID}-{scenario}/failure-screenshot.png`
292
- - Playwright trace: `{TC_ID}-{scenario}/trace.zip`
367
+ - Screenshot: `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/{TC_ID}-{scenario}/failure-screenshot.png`
368
+ - Playwright trace: `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/{TC_ID}-{scenario}/trace.zip`
293
369
 
294
370
  ## Technical Notes
295
371
 
@@ -305,7 +381,31 @@ Với mỗi TC failed, tạo `ak-test/{repo}/results/{screenId}/run-{N}/bugs/BUG
305
381
  - [ ] Retest: `RETEST: {TC_ID}`
306
382
  ```
307
383
 
308
- BUG số: đếm file trong `bugs/` folder, +1. Format: `001`, `002`,...
384
+ BUG số: đếm file trong `AK-Docs/03.Testing/06.Bugs/{repo}/{featureDir}/run-{N}/` folder, +1. Format: `001`, `002`,...
385
+
386
+ ### DOM Verification Rules (bắt buộc — tránh false pass/fail)
387
+
388
+ Script-sync (Gate 2) chịu trách nhiệm sinh assertion đúng, nhưng Gate 3 vẫn phải verify lại trước khi ghi kết quả — vì nguyên nhân phổ biến khiến "TC fail bị báo thành pass" hoặc ngược lại là assertion quá lỏng hoặc chọn nhầm phần tử, không phải do Playwright chạy sai:
389
+
390
+ - ❌ **Không** coi "test không throw exception" = pass. Phải có ít nhất 1 `expect(...)` khẳng định đúng **nội dung** Expected Result của TC (text, giá trị, trạng thái field, số lượng item...), không chỉ `toBeVisible()` chung.
391
+ - ❌ **Không** dùng locator quá rộng (vd `page.locator('.modal')` khi có nhiều `.modal` trên trang, hoặc `page.locator('button').first()` khi có nhiều button cùng loại) — nếu locator match nhiều hơn 1 element, đây chính là nguyên nhân hay gặp khiến assertion pass "nhầm" vào element khác không phải element cần kiểm tra. Dùng `data-testid`/`id` cụ thể hoặc scope locator trong đúng container (`page.locator('#order-detail').getByText(...)`).
392
+ - ❌ **Không** dùng `try/catch` nuốt lỗi rồi coi là pass nếu catch block không re-throw hoặc không có assertion thay thế.
393
+ - ✅ Khi TC yêu cầu "hiển thị message lỗi X" → assertion phải check đúng text X (`toHaveText`/`toContainText`), không chỉ check element lỗi tồn tại.
394
+ - ✅ Khi TC yêu cầu "field Y bị disable/readonly" → assertion phải là `toBeDisabled()`/`toHaveAttribute('readonly', ...)`, không phải `toBeVisible()`.
395
+ - ✅ Sau khi Playwright báo `passed`, nếu kịch bản TC là **negative/validation case** (kỳ vọng lỗi/chặn hành động) mà spec không có assertion kiểm tra đúng behavior chặn đó → đánh dấu **NEEDS_REVIEW** trong result.md thay vì tin theo status "passed" của Playwright, và note lại trong Gate 3 summary để tester tự kiểm tra bằng mắt qua evidence.
396
+ - ✅ Nếu Playwright báo `failed` do lỗi hạ tầng (timeout do element chưa load kịp, network flake, không phải do app sai) → thử lại tối đa 1 lần (`--retries=1` đã có trong config) trước khi kết luận FAIL và draft bug — tránh báo fail giả do timing.
397
+
398
+ ### Evidence Quality Rules (bắt buộc)
399
+
400
+ Giải quyết vấn đề "chụp evidence sai thời điểm / mờ / thiếu step / không mở được modal":
401
+
402
+ 1. **Thời điểm chụp:** Nếu TC không có yêu cầu cụ thể về thời điểm chụp, luôn gọi `waitForUiSettled(page)` (từ `Shared/evidence-helper.ts`) trước khi screenshot — hàm này chờ `networkidle` **và** không còn spinner/loading indicator (`[data-loading]`, `.spinner`, `.loading`, role `progressbar`...) **và** fonts đã load xong (`document.fonts.ready`). Không chụp ngay sau khi trigger action.
403
+ 2. **Chất lượng ảnh:** Dùng `page.screenshot({ fullPage: true, animations: 'disabled', scale: 'css' })` qua helper — không dùng screenshot mặc định của reporter. Config `deviceScaleFactor: 2` trong `playwright.config.ts` để ảnh sắc nét (đặc biệt chữ Kanji/Katakana). Nếu app hiển thị tiếng Nhật, set `locale: 'ja-JP'` trong config để đúng font rendering, và đảm bảo container/CI có font Nhật (Noto Sans JP) cài sẵn — nếu không có, ảnh sẽ hiện ô vuông/mờ chữ dù code đúng.
404
+ 3. **Popup/Modal cần click để mở:** Nếu TC Step mô tả hành động mở popup/modal (vd "Click vào item A để mở modal B1") → script **phải thực hiện click đó** rồi `waitForUiSettled` trước khi chụp bước đó — không chụp màn hình danh sách rồi bỏ qua bước mở modal.
405
+ 4. **Nội dung có scroll:** Nếu vùng cần chụp (modal, table, page) cao hơn viewport → dùng `scrollIntoViewIfNeeded()` trên phần tử cần confirm trước khi chụp, hoặc chụp `fullPage: true` cho toàn trang nếu modal không giới hạn scroll riêng. Không chụp full page nếu modal có scroll riêng nội bộ — trong trường hợp đó chụp riêng viewport của modal sau khi cuộn tới đúng vị trí.
406
+ 5. **Highlight vùng cần confirm:** Trước khi chụp step có 1 item/element cụ thể cần tester chú ý (item vừa click, message lỗi, field vừa thay đổi...) → gọi `highlightElement(page, selector)` (khoanh viền đỏ 3px + không che nội dung) ngay trước `page.screenshot()`, rồi gọi `removeHighlight(page)` ngay sau đó để không ảnh hưởng bước tiếp theo.
407
+ 6. **1 screenshot cho mỗi Step có mô tả UI trong TC:** Nếu TC Steps mô tả nhiều bước điều hướng (vd: "1. Trên màn hình danh sách, click item A để mở A1 → 2. Trên A1, click button B để mở modal B1") → chụp **evidence riêng cho từng bước có thay đổi màn hình/state đáng chú ý**, không chỉ chụp kết quả cuối. Với ví dụ trên: `step-01-{...}.png` (màn hình danh sách, khoanh đỏ item A trước khi click) + `step-02-{...}.png` (màn hình A1, khoanh đỏ button B trước khi click) + `step-03-{...}.png` (modal B1 mở ra, khoanh đỏ nội dung cần confirm). File `step-NN-{desc}.png` đặt tên theo đúng thứ tự Step trong TC file.
408
+ 7. Dùng helper `captureStepEvidence(page, evidenceDir, stepIndex, stepDesc, { highlightSelector, scrollSelector })` (xem `templates/evidence-helper.ts`) trong mọi spec mới sinh ra ở Gate 2 — không tự viết lại logic wait/highlight/scroll/screenshot rời rạc trong từng test.
309
409
 
310
410
  ### Bước 6: Hiển thị Gate 3
311
411
 
@@ -321,7 +421,7 @@ Results (run-{N}):
321
421
  Bugs drafted: {N} ({N} Critical, {N} High, {N} Medium, {N} Low)
322
422
  Unresolved critical: {N}
323
423
 
324
- → Evidence: ak-test/{repo}/results/{screenId}/run-{N}/
424
+ → Evidence: AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/
325
425
  → Type APPROVED when all critical/high bugs resolved
326
426
  → Type RETEST: {TC_ID} to re-execute
327
427
  → Type PENDING: {TC_ID} để đánh dấu TC không chạy được
@@ -337,11 +437,13 @@ Khi tester gõ `RETEST: AD10_005, AD10_008`:
337
437
  - Nếu phát hiện commit mới → đề xuất TC bổ sung, tester approve delta trước
338
438
  2. **Chạy selective test:**
339
439
  ```bash
340
- cd ak-test/{repo}
341
- BASE_URL={baseUrl} npx playwright test --grep "AD10_005|AD10_008"
440
+ cd AK-Docs/03.Testing/05.Scripts
441
+ EVIDENCE_DIR="$(pwd)/../04.Evidence/{repo}/{featureDir}/run-{N+1}" \
442
+ BASE_URL={baseUrl} \
443
+ npx playwright test --config={repo}/playwright.config.ts --grep "AD10_005|AD10_008"
342
444
  ```
343
445
  3. Xác định run number mới (run-{N+1})
344
- 4. Tổ chức evidence vào `run-{N+1}/` (không xóa run cũ)
446
+ 4. Tổ chức evidence vào `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N+1}/` + report vào `AK-Docs/03.Testing/02.Reports/{repo}/{featureDir}/run-{N+1}/` (không xóa run cũ)
345
447
  5. Cập nhật bug status:
346
448
  - TC pass sau retest → ghi `RESOLVED` vào bug file: `**Status:** ✅ Resolved`
347
449
  - TC vẫn fail → ghi `**Retest {N}:** Still OPEN` vào bug file
@@ -356,15 +458,15 @@ Chỉ chạy sau Gate 3 APPROVED.
356
458
 
357
459
  ### Bước 1: Tổng hợp kết quả
358
460
 
359
- Đọc tất cả `result.md` trong `run-{N}/`:
461
+ Đọc tất cả `result.md` trong `AK-Docs/03.Testing/02.Reports/{repo}/{featureDir}/run-{N}/`:
360
462
  - Count passed/failed/untest/pending per run
361
463
  - Identify last run number (run-{max})
362
464
 
363
- Đọc `bugs/BUG-*.md` trong run folder: phân loại severity + status (Open/Resolved).
465
+ Đọc `AK-Docs/03.Testing/06.Bugs/{repo}/{featureDir}/run-{N}/BUG-*.md`: phân loại severity + status (Open/Resolved).
364
466
 
365
467
  ### Bước 2: Sinh testreport.md
366
468
 
367
- Tạo `ak-test/{repo}/results/{screenId}/run-{N}/testreport.md` theo template chuẩn:
469
+ Tạo `AK-Docs/03.Testing/02.Reports/{repo}/{featureDir}/run-{N}/testreport.md` theo template chuẩn:
368
470
 
369
471
  ```markdown
370
472
  # SUMMARY TEST REPORT
@@ -404,7 +506,7 @@ Hỏi từng bug theo thứ tự severity (Critical → High → Medium → Low)
404
506
 
405
507
  ```
406
508
  🐛 BUG-{NNN} [{Severity}] — {Title}
407
- TC: {TC_ID} | Evidence: run-{N}/bugs/BUG-{NNN}...
509
+ TC: {TC_ID} | Bug: AK-Docs/03.Testing/06.Bugs/{repo}/{featureDir}/run-{N}/BUG-{NNN}... | Evidence: AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/{TC_ID}-{scenario}/
408
510
  Steps: {steps tóm tắt}
409
511
  Expected: {expected}
410
512
  Actual: {actual}
@@ -428,7 +530,7 @@ Passed: {N}/{total} | % hoàn thành: {N}%
428
530
  Bugs logged to Jira: {N}/{total bugs}
429
531
  Recommendation: {Go / No-go / Conditional go}
430
532
 
431
- → Review: [ak-test/{repo}/results/{screenId}/run-{N}/testreport.md](ak-test/{repo}/results/{screenId}/run-{N}/testreport.md)
533
+ → Review: [AK-Docs/03.Testing/02.Reports/{repo}/{featureDir}/run-{N}/testreport.md](AK-Docs/03.Testing/02.Reports/{repo}/{featureDir}/run-{N}/testreport.md)
432
534
  → Type APPROVED to sign off
433
535
  → Type NO-GO: {reason} to reject release
434
536
 
@@ -466,7 +568,11 @@ SKIP-BUG: BUG-{NNN} # bỏ qua không log bug này
466
568
  ## Mandatory Rules
467
569
 
468
570
  - ✅ Dừng ở mỗi gate, chờ APPROVED trước khi proceed
469
- - ✅ `run-{N}` không bao giờ bị xóa hay overwrite — luôn tạo folder mới
571
+ - ✅ `run-{N}` không bao giờ bị xóa hay overwrite — luôn tạo folder mới (cả ở `04.Evidence/`, `02.Reports/` và `06.Bugs/`)
470
572
  - ✅ Hỏi từng bug một — không auto-log toàn bộ
573
+ - ✅ Evidence nhị phân (screenshot/video/`trace.zip`) luôn ở `AK-Docs/03.Testing/04.Evidence/` — **phải nằm trong `.gitignore`**, không commit lên Git
574
+ - ✅ Script/result/report/bug (text) luôn ở `AK-Docs/03.Testing/{05.Scripts,02.Reports,06.Bugs}/` — không dùng `ak-test/` nữa
575
+ - ✅ Trước khi ghi PASS/FAIL vào result.md, verify lại assertion theo "DOM Verification Rules" — không tin tuyệt đối exit code Playwright
576
+ - ✅ Mọi spec mới sinh dùng `captureStepEvidence()` từ `Shared/evidence-helper.ts` cho evidence — không viết lại logic wait/highlight/scroll rời rạc
471
577
  - ❌ Không lấy expected result từ PR/code — chỉ từ TC file
472
578
  - ❌ Không skip gate kể cả khi "chỉ có 1 TC"
@@ -0,0 +1,145 @@
1
+ import type { Page, Locator, TestInfo } from '@playwright/test'
2
+
3
+ /**
4
+ * Shared evidence helpers for generated Playwright specs (execute-flow / script-sync skills).
5
+ * Copied once to AK-Docs/03.Testing/05.Scripts/Shared/evidence-helper.ts — do not regenerate
6
+ * if it already exists; hand edits (e.g. project-specific loading selectors) must survive re-sync.
7
+ */
8
+
9
+ /** CSS selectors treated as "still loading" — extend this list per-project if needed. */
10
+ const LOADING_SELECTORS = [
11
+ '[data-loading="true"]',
12
+ '.spinner',
13
+ '.loading',
14
+ '.skeleton',
15
+ '[role="progressbar"]',
16
+ ]
17
+
18
+ /**
19
+ * Waits until the page is visually stable before a screenshot is taken:
20
+ * network idle, no known loading indicator visible, and web fonts finished loading
21
+ * (prevents blurry / tofu-box Kanji text in evidence screenshots).
22
+ */
23
+ export async function waitForUiSettled(page: Page, timeoutMs = 15_000): Promise<void> {
24
+ await page.waitForLoadState('networkidle', { timeout: timeoutMs }).catch(() => {})
25
+
26
+ await page.waitForFunction(
27
+ (selectors) =>
28
+ selectors.every((sel) => {
29
+ const el = document.querySelector(sel)
30
+ if (!el) return true
31
+ const style = window.getComputedStyle(el)
32
+ return style.display === 'none' || style.visibility === 'hidden'
33
+ }),
34
+ LOADING_SELECTORS,
35
+ { timeout: timeoutMs },
36
+ ).catch(() => {})
37
+
38
+ await page.evaluate(() => (document as any).fonts?.ready).catch(() => {})
39
+ }
40
+
41
+ /** Draws a 3px solid red outline around `target` without shifting layout (outline, not border). */
42
+ export async function highlightElement(target: Locator): Promise<void> {
43
+ await target.evaluate((el: HTMLElement) => {
44
+ el.setAttribute('data-evidence-highlight', '1')
45
+ el.style.outline = '3px solid #ff0000'
46
+ el.style.outlineOffset = '2px'
47
+ })
48
+ }
49
+
50
+ /** Removes the highlight applied by `highlightElement` so it doesn't leak into the next step's screenshot. */
51
+ export async function removeHighlight(target: Locator): Promise<void> {
52
+ await target
53
+ .evaluate((el: HTMLElement) => {
54
+ el.style.outline = ''
55
+ el.style.outlineOffset = ''
56
+ el.removeAttribute('data-evidence-highlight')
57
+ })
58
+ .catch(() => {})
59
+ }
60
+
61
+ export interface CaptureStepOptions {
62
+ /** Element to draw a red rectangle around before the screenshot (the item/content the tester must confirm). */
63
+ highlightSelector?: Locator
64
+ /** Element to scroll into view before the screenshot (for content inside a scrollable modal/table). */
65
+ scrollSelector?: Locator
66
+ /** Screenshot the full page instead of the current viewport. Default: true. */
67
+ fullPage?: boolean
68
+ }
69
+
70
+ /**
71
+ * Captures one evidence screenshot for one TC Step, following the naming convention
72
+ * `step-NN-{desc}.png` inside the run's evidence dir (Playwright's configured outputDir,
73
+ * i.e. `EVIDENCE_DIR` — see playwright.config.ts template).
74
+ *
75
+ * Call this once per TC Step that changes UI state or navigates to a new screen/modal —
76
+ * not just once at the end of the test.
77
+ */
78
+ export async function captureStepEvidence(
79
+ page: Page,
80
+ testInfo: TestInfo,
81
+ stepIndex: number,
82
+ stepDesc: string,
83
+ opts: CaptureStepOptions = {},
84
+ ): Promise<void> {
85
+ await waitForUiSettled(page)
86
+
87
+ if (opts.scrollSelector) {
88
+ await opts.scrollSelector.scrollIntoViewIfNeeded()
89
+ await waitForUiSettled(page)
90
+ }
91
+
92
+ if (opts.highlightSelector) {
93
+ await highlightElement(opts.highlightSelector)
94
+ }
95
+
96
+ const slug = stepDesc
97
+ .toLowerCase()
98
+ .normalize('NFD')
99
+ .replace(/[̀-ͯ]/g, '')
100
+ .replace(/[^a-z0-9]+/g, '-')
101
+ .replace(/(^-|-$)/g, '')
102
+ const fileName = `step-${String(stepIndex).padStart(2, '0')}-${slug}.png`
103
+
104
+ await page.screenshot({
105
+ path: testInfo.outputPath(fileName),
106
+ fullPage: opts.fullPage ?? true,
107
+ animations: 'disabled',
108
+ scale: 'css',
109
+ })
110
+
111
+ if (opts.highlightSelector) {
112
+ await removeHighlight(opts.highlightSelector)
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Convenience wrapper for steps that click a trigger to open a popup/modal, then capture
118
+ * the resulting state. Ensures the click happens BEFORE the screenshot — a common source
119
+ * of "modal never shown in evidence" bugs.
120
+ */
121
+ export async function openAndCapture(
122
+ page: Page,
123
+ testInfo: TestInfo,
124
+ trigger: Locator,
125
+ resultLocator: Locator,
126
+ stepIndex: number,
127
+ stepDesc: string,
128
+ opts: CaptureStepOptions = {},
129
+ ): Promise<void> {
130
+ await highlightElement(trigger)
131
+ await page.screenshot({
132
+ path: testInfo.outputPath(`step-${String(stepIndex).padStart(2, '0')}-before-click.png`),
133
+ fullPage: opts.fullPage ?? true,
134
+ animations: 'disabled',
135
+ })
136
+ await removeHighlight(trigger)
137
+
138
+ await trigger.click()
139
+ await resultLocator.waitFor({ state: 'visible' })
140
+
141
+ await captureStepEvidence(page, testInfo, stepIndex, stepDesc, {
142
+ ...opts,
143
+ highlightSelector: opts.highlightSelector ?? resultLocator,
144
+ })
145
+ }
@@ -3,24 +3,44 @@ import path from 'path'
3
3
 
4
4
  const LOGGED_OUT = { storageState: undefined }
5
5
 
6
+ // Script lives in AK-Docs/03.Testing/05.Scripts/{repo}/ but binary evidence (screenshots,
7
+ // video, trace.zip, results.json) must land in AK-Docs/03.Testing/04.Evidence/ instead —
8
+ // that folder is gitignored (binaries, never committed) while this script folder is not.
9
+ // See execute-flow SKILL.md "Cấu trúc thư mục đầu ra". The AI sets EVIDENCE_DIR per run
10
+ // (03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}) when invoking `npx playwright test`.
11
+ const evidenceDir = process.env.EVIDENCE_DIR
12
+ ? path.resolve(process.env.EVIDENCE_DIR)
13
+ : path.resolve(__dirname, 'test-results')
14
+
6
15
  export default defineConfig({
7
- testDir: './scripts',
8
- outputDir: './test-results',
16
+ testDir: '.',
17
+ outputDir: evidenceDir,
9
18
  fullyParallel: false,
10
19
  forbidOnly: !!process.env.CI,
11
- retries: process.env.CI ? 1 : 0,
20
+ retries: 1, // absorb transient timing flakes before reporting FAIL — see "DOM Verification Rules"
12
21
  workers: 1,
13
22
  reporter: [
14
- ['json', { outputFile: 'test-results/results.json' }],
15
- ['html', { outputFolder: 'playwright-report', open: 'never' }],
23
+ ['json', { outputFile: path.join(evidenceDir, 'results.json') }],
24
+ ['html', { outputFolder: path.join(evidenceDir, 'html-report'), open: 'never' }],
16
25
  ['list'],
17
26
  ],
18
27
  use: {
19
28
  baseURL: process.env.BASE_URL || 'http://localhost:3000',
20
- trace: 'on-first-retry',
21
- screenshot: 'on',
22
- video: 'on-first-retry',
29
+ trace: 'retain-on-failure',
30
+ // Manual per-step screenshots come from captureStepEvidence() in evidence-helper.ts —
31
+ // this stays 'only-on-failure' so it doesn't duplicate/clash with those step-NN-*.png files.
32
+ screenshot: 'only-on-failure',
33
+ video: 'retain-on-failure',
23
34
  headless: !process.env.HEADED,
35
+ // Higher device scale factor = sharper text in evidence, especially Kanji/Katakana.
36
+ deviceScaleFactor: 2,
37
+ // Set to the app's actual UI locale so date/number formatting and font fallback match
38
+ // what the browser renders in production — mismatched locale is a common cause of
39
+ // "screenshot color/rendering doesn't match what I see in my own browser" reports.
40
+ locale: process.env.UI_LOCALE || 'ja-JP',
41
+ viewport: { width: 1440, height: 900 },
42
+ // Stops CSS/JS animations from being mid-transition when a screenshot fires.
43
+ reducedMotion: 'reduce',
24
44
  },
25
45
  projects: [
26
46
  // Uncomment nếu cần auth setup:
@@ -22,8 +22,7 @@ Before reading the ticket in depth, verify the bridge document from UC Spec —
22
22
  3. Check `AK-Docs/02.BA-Specs/00.Requirements/[functionId]/System-Requirement_v*.md`:
23
23
  - **Missing** → `⚠️ CẢNH BÁO: Không tìm thấy System Requirement cho [functionId]. Khuyến nghị chạy "ak use" → "📐 Create System Requirement" trước ticket kế tiếp — Gate 1 vẫn tiếp tục ngay bây giờ dựa trên UC Spec/ticket.`
24
24
  - **Exists, but its `UC-Spec-Version` header doesn't match the UC Spec found in step 2** → `⚠️ CẢNH BÁO: System Requirement đang trace theo UC Spec v[X], UC Spec hiện tại là v[Y] — nội dung có thể lỗi thời.` Treat its items as Assumption rather than Fact in Step 1.75.
25
- - **Exists, matches, but its `Status` header is not `✅ Approved`** `⚠️ CẢNH BÁO: System Requirement cho [functionId] chưa Approved (Status: [giá trị hiện tại])nội dung thể còn thay đổi.`
26
- - **Exists, matches, and Approved** → continue silently. Keep this file's path — Step 1 must read it as mandatory input.
25
+ - **Exists and matches** continue silently. Keep this file's path Step 1 must read it as mandatory input. (Its presence in the repo already means DEV approved and pushed it approval isn't tracked with a separate header.)
27
26
 
28
27
  Any warning raised above must also be written into this ticket's requirement doc (Section 1, Facts/Assumptions/Gaps in Step 4) — not just printed and forgotten. If DEV wants the gap closed properly, they can run `ak use` → "📐 Create System Requirement" for `[functionId]` at any point (before or after this ticket) — it's a separate 2-gate task type, not an inline sub-skill here.
29
28