---
name: execute-flow
description: Executing Flow — 4 gates độc lập để thực thi Playwright từ testcase có sẵn. Gate 1 load TC file và sinh work plan. Gate 2 sync scripts (hash-based). Gate 3 chạy playwright và thu thập evidence. Gate 4 sinh report và hỏi log bug lên Jira. Không cần đi qua workflow.md Gate 1-2.
allowed-tools: Read, Write, Edit, Glob, Grep, Bash, mcp__playwright__browser_navigate, mcp__playwright__browser_snapshot, mcp__playwright__browser_generate_locator, mcp__playwright__browser_close
---

# Executing Flow

Flow độc lập để tester thực thi test từ TC file có sẵn, không cần chạy lại phân tích (workflow.md Gate 1-2).

## Entry Points

| Nguồn | Trigger | Cách AI xử lý |
|-------|---------|---------------|
| Ticket | `ak execute PROJ-33` | Tìm TC file trong `.aiflow/context/current.json` |
| File | `ak execute ./testcases/AD10.md` | Đọc trực tiếp file path |
| Manual | `ak execute` (không argument) | Hỏi tester: "TC file của bạn ở đâu?" |

---

## Cấu trúc thư mục đầu ra

**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.

```
AK-Docs/03.Testing/
├── 04.Evidence/{repo}/{featureDir}/run-{N}/    # ⚠️ gitignored — xem Gate 1 item 6
│   ├── results.json                            # Playwright JSON output thô
│   └── {TC_ID}-{kebab-scenario}/
│       ├── step-NN-{desc}.png                  # 1 ảnh / step có mô tả UI
│       └── trace.zip
├── 05.Scripts/                                 # Gate 2 — Playwright project, chạy được tại chỗ
│   ├── package.json / tsconfig.json / node_modules/ (gitignored)
│   ├── Shared/
│   │   ├── fixtures/test.ts
│   │   ├── BasePage.ts
│   │   ├── evidence-helper.ts
│   │   └── evidence-helper.config.ts
│   └── {repo}/
│       ├── playwright.config.ts
│       ├── pages/{ScreenID}Page.ts
│       └── {featureDir}/{ScreenID}.spec.ts
├── 02.Reports/{repo}/{featureDir}/run-{N}/
│   ├── {TC_ID}-{kebab-scenario}/result.md
│   └── testreport.md                           # Gate 4
└── 06.Bugs/{repo}/{featureDir}/run-{N}/
    └── BUG-{NNN}-{slug}.md                     # Gate 3
```

`featureDir` = `{ScreenID}_{Screen-Name-kebab-case}` (vd `AD10_create-product`).

> **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.

---

## ⛩️ GATE 1 — Context & Work Plan

### Pre-flight checks (auto, không hỏi tester)

Kiểm tra trước khi làm gì:

0. **MCP Playwright** có được cấu hình không?
   - Kiểm tra **`~/.claude/settings.json`** (global user-level, áp dụng cho mọi project): có key `playwright` trong `mcpServers` không?
   - Nếu thiếu → dừng, hỏi tester:
     > "⚠️ MCP Playwright chưa được cấu hình. Đây là yêu cầu bắt buộc để chạy browser automation.
     > Sẽ cài vào **global settings** (`~/.claude/settings.json`) — chỉ cần làm một lần, dùng được cho mọi repo.
     > → Tự động cài đặt? [Y/n]"
   - Nếu tester chọn **Y** (hoặc Enter):
     - Đọc `~/.claude/settings.json`
     - Thêm key `playwright` vào `mcpServers` (giữ nguyên các key khác):
       ```json
       "playwright": {
         "command": "npx",
         "args": ["-y", "@playwright/mcp@latest", "--isolated", "--browser", "chromium", "--headless"]
       }
       ```
     - Ghi lại file `~/.claude/settings.json`
     - Thông báo:
       > "✅ Đã cài MCP Playwright vào global settings. **Vui lòng restart Claude Code** để load MCP server, sau đó chạy lại lệnh `ak execute`."
     - Dừng. Không proceed cho đến khi tester restart và chạy lại.
   - Nếu tester chọn **n** → dừng, hướng dẫn:
     > "Để cài thủ công: mở `~/.claude/settings.json`, thêm `playwright` vào `mcpServers`, rồi restart Claude Code."

1. TC file có parse được không? (file tồn tại, có Section 3 — Danh sách Test Cases)
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)
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?"

Items 1–3 chạy ngay khi entry. Items 4–8 chạy sau khi repo được xác định từ Bước 1 (parse TC file):

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.
5. **Browser dùng để chạy test — cảnh báo nếu khác Chromium (hỏi tester nếu cần).** Xác định browser hiệu lực cho lần chạy này:
   - Nếu tester đã set env var `PW_BROWSER` cho phiên làm việc này → dùng giá trị đó.
   - Ngược lại, nếu `playwright.config.ts` đã scaffold từ trước → đọc `projects[0].name` trong file đó để biết browser đang cấu hình sẵn.
   - Ngược lại (project mới, chưa scaffold, chưa set `PW_BROWSER`) → mặc định `chromium`.
   - Nếu browser hiệu lực **≠ `chromium`** → dừng, hỏi tester:
     > "⚠️ Test sẽ chạy trên **{browser}**, không phải Chromium. WebKit/Firefox có thể render font khác Chromium (đặc biệt chữ CJK/tiếng Nhật-Hàn-Trung trên Linux — dễ mất chữ/hiện ô vuông trong evidence screenshot, xem 'Chất lượng ảnh' bên dưới). Bạn có chắc muốn tiếp tục với {browser} không? [y/N]
     > (Chọn n hoặc bỏ qua để đổi lại Chromium: unset `PW_BROWSER`, hoặc set `PW_BROWSER=chromium` rồi chạy lại; nếu `playwright.config.ts` đã tồn tại với browser khác thì tester tự sửa `projects[0].name` lại thành `chromium`.)"
   - Tester xác nhận **y** → proceed với browser đó cho **toàn bộ run này** (bao gồm cả RETEST loop sau này của cùng run — không hỏi lại mỗi lần chạy `npx playwright test`). Tester không xác nhận / chọn **n** → dừng, không chạy test cho đến khi browser được đổi lại.
   - Nếu browser hiệu lực = `chromium` → bỏ qua, không cần hỏi gì (đây là default, không có rủi ro font đã biết).
6. `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 {browser}` (browser xác định ở item 5, mặc định `chromium`; một lần, cache browser ở `~/.cache/ms-playwright`, không nằm trong repo).
7. `.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ũ):
   - `03.Testing/04.Evidence/` — **bắt buộc**, evidence là binary (screenshot/video/trace.zip), không commit dù nằm trong AK-Docs
   - `03.Testing/05.Scripts/**/node_modules/`, `03.Testing/05.Scripts/**/test-results/`, `03.Testing/05.Scripts/**/playwright-report/`, `03.Testing/05.Scripts/**/blob-report/`
8. **`Shared/evidence-helper.ts` re-sync — chạy độc lập với item 4, kể cả khi dự án đã scaffold từ trước** (item 4 chỉ scaffold khi CHƯA có gì; item này lo trường hợp ngược lại, đã có `Shared/` từ một phiên bản ai-flow-kit cũ hơn):
   - Nếu `AK-Docs/03.Testing/05.Scripts/Shared/evidence-helper.ts` đã tồn tại → **luôn ghi đè lại** bằng nội dung mới nhất của `custom/skills/execute-flow/templates/evidence-helper.ts` (file này generic, không chứa tuỳ biến riêng của app nào, nên ghi đè an toàn — đây là cách duy nhất để dự án đã scaffold từ trước nhận được cải tiến evidence-capture mới: chỉ cần update ai-flow-kit rồi chạy `ak execute` lại, KHÔNG cần scaffold lại từ đầu).
   - Nếu `AK-Docs/03.Testing/05.Scripts/Shared/evidence-helper.config.ts` **chưa tồn tại** (dự án scaffold trước khi file này ra đời) → tạo mới từ `templates/evidence-helper.config.ts` (mảng rỗng mặc định). Nếu đã tồn tại → **không đụng vào**, giữ nguyên tuỳ biến của tester.
   - Nếu cả `Shared/evidence-helper.ts` lẫn `evidence-helper.config.ts` đều chưa tồn tại (repo hoàn toàn mới) → bỏ qua item này, item 4/Scaffold bên dưới sẽ tạo cả hai.

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.

### Scaffold Playwright Project (chỉ chạy lần đầu, khi chưa có `AK-Docs/03.Testing/05.Scripts/`)

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):

```
AK-Docs/03.Testing/05.Scripts/
├── package.json              # 1 lần duy nhất, dùng chung mọi repo
├── tsconfig.json
├── node_modules/              # gitignored
├── Shared/
│   ├── fixtures/test.ts       # test fixture chung
│   ├── BasePage.ts
│   ├── evidence-helper.ts        # xem templates/evidence-helper.ts — generic, an toàn re-sync
│   └── evidence-helper.config.ts # override riêng cho app này — KHÔNG BAO GIỜ bị ghi đè lại
└── {repo}/
    ├── playwright.config.ts   # baseURL riêng cho repo này
    ├── pages/{ScreenID}Page.ts
    └── {featureDir}/{ScreenID}.spec.ts
```

- `package.json`: devDependencies `@playwright/test`, `typescript`, `@types/node` (bắt buộc — `tsconfig.json` khai báo `"types": ["node"]` và `evidence-helper.ts` import module `path` của Node, thiếu package này khiến `tsc --noEmit` báo lỗi dù `npx playwright test` vẫn chạy được nhờ esbuild transform không type-check nghiêm). Tạo bằng `npm init -y` rồi `npm install -D @playwright/test typescript @types/node`.
- 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 đó.
- Copy `custom/skills/execute-flow/templates/evidence-helper.ts` → `AK-Docs/03.Testing/05.Scripts/Shared/evidence-helper.ts` — **file này generic/không chứa kiến thức riêng của app nào, an toàn ghi đè lại mỗi khi ai-flow-kit update** (khác quy tắc "không tạo lại nếu đã tồn tại" ở các file khác trong danh sách này — luôn đồng bộ file này theo template mới nhất).
- Copy `custom/skills/execute-flow/templates/evidence-helper.config.ts` → `AK-Docs/03.Testing/05.Scripts/Shared/evidence-helper.config.ts` (không tạo lại nếu đã tồn tại) — nơi khai báo loading selector riêng của app này (vd overlay class của 1 UI kit cụ thể); file này KHÔNG BAO GIỜ bị ghi đè lại nên tuỳ biến ở đây luôn sống sót qua mọi lần update.
- Copy `custom/harness/playwright/tests/e2e/fixtures/test.ts` → `AK-Docs/03.Testing/05.Scripts/Shared/fixtures/test.ts` (không tạo lại nếu đã tồn tại) — test fixture chung, tái dùng từ harness scaffold đã có sẵn (`ak scaffold playwright`).
- Copy `custom/harness/playwright/tests/e2e/pages/BasePage.ts` → `AK-Docs/03.Testing/05.Scripts/Shared/BasePage.ts` (không tạo lại nếu đã tồn tại) — Page Object mới sinh (`{ScreenID}Page.ts`) extends class này.
- `featureDir` = `{ScreenID}_{Screen-Name-kebab-case}` (vd `AD10_create-product`) — naming riêng cho execute-test flow, khác với `F-{3-digit}_{Pascal-Case}` mà `docs/common/Testing-Structure.md` dùng cho BA-Specs/Coding (xem note trong Testing-Structure.md's File Naming Convention).

> **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/` là chạy được cho toàn bộ project.

### Bước 1: Parse TC file

Đọc TC file, extract từ Section 1 — Thông tin chung:
- `Screen_ID`: từ "Mã màn hình - Tên màn hình" (vd `AD10_Create Product` → ScreenID = `AD10`)
- `Screen_Name`: phần sau `_` (vd `Create Product`)
- `Repo`: từ dòng "Repo" (vd `repo-fe`)

Nếu thiếu `Repo` field → hỏi tester:
> "TC file của màn hình `{ScreenID}` chưa có thông tin Repo. Repo này nằm trong repository nào?
> (vd: repo-fe, repo-be)"

Chờ tester trả lời rồi mới tiếp tục.

Nếu nhiều màn hình khác repo → hỏi từng màn hình một.

### Bước 1b: Đọc Source Code (nếu workspace có source repos)

Chạy ngay sau khi xác định được `Repo`, trước khi extract TCs.

Nếu workspace mở dạng parent folder chứa cả `ak docs` lẫn source code repos:

1. **Nếu GitNexus MCP có sẵn** (`.mcp.json` có entry `gitnexus`):
   - `gitnexus: query("tên màn hình {ScreenID}")` → tìm component, page, form liên quan
   - `gitnexus: context("ComponentName")` → xem toàn bộ component/template
2. **Nếu không có GitNexus**: đọc trực tiếp trong `{repo}/`:
   - Component / Page / View của màn hình — tìm `data-testid`, `id`, `name` của các element
   - Form / Input structure — hiểu hierarchy của DOM
   - Route / path mapping — xác nhận URL path của màn hình
3. Lưu lại thông tin để Gate 2 (Script Sync) sử dụng:
   - Danh sách `data-testid` / `id` của các element tương tác
   - Cấu trúc form (fieldset, section, tab nếu có)
   - URL path chính xác của màn hình
4. **Quét pattern loading/spinner riêng của màn hình này** — xem "Đề xuất bổ sung Loading Selector" ngay bên dưới.

> Mục đích: Giúp `script-sync` (Gate 2) viết Playwright scripts với selectors chính xác hơn, giảm phụ thuộc vào browser snapshot khi app chưa chạy.

#### Đề xuất bổ sung Loading Selector (`evidence-helper.config.ts`)

**Vì sao cần bước này:** `Shared/evidence-helper.ts` (generic, dùng chung mọi dự án) chỉ nhận diện loading qua chuẩn ARIA + vài quy ước phổ biến (`[aria-busy="true"]`, `.spinner`, `.loading`, `.skeleton`, `role="progressbar"`) cộng các animation/DOM-mutation check framework-agnostic — nhưng KHÔNG biết trước overlay riêng của từng UI kit (Element Plus's `.el-loading-mask`, MUI's `.MuiBackdrop-root`, Ant Design's `.ant-spin`, Bootstrap's `.spinner-border`, hay 1 component `<AppSpinner>` tự viết trong app). Nếu màn hình đang test dùng loại overlay này mà chưa khai báo trong `Shared/evidence-helper.config.ts`, evidence có thể bị chụp ngay khi overlay còn hiển thị (sai thời điểm, ảnh mờ/che nội dung) mà AI/Playwright không hề báo lỗi gì — chỉ tester nhìn ảnh mới phát hiện. Dev có thể tự thêm dòng này khi cần, nhưng **Tester chạy execute-flow không có lý do để biết selector nào cần thêm** — nên AI phải chủ động tìm và đề xuất, không im lặng bỏ qua.

**Khi nào chạy:** mỗi lần Bước 1b đọc source code cho 1 màn hình (`{ScreenID}`), quét trong đúng component/page vừa đọc — không quét toàn bộ app, không chạy lại cho màn hình đã quét trong cùng Gate 1.

**Cách quét (theo source code đã đọc ở bước 1-2 phía trên):**
- Tìm directive/component loading gắn với UI kit đang dùng: Vue + Element Plus/Element UI (`v-loading` directive → `.el-loading-mask`), Vuetify (`v-overlay`/`v-progress-circular` → `.v-overlay__scrim`), React + MUI (`<CircularProgress>`/`<Backdrop>` → `.MuiCircularProgress-root`/`.MuiBackdrop-root`), React/Vue + Ant Design (`<Spin>`/`a-spin` → `.ant-spin`), Bootstrap (`.spinner-border`/`.spinner-grow`), Chakra UI (`<Spinner>` → class có prefix `chakra-spinner`).
- Tìm component tự viết trong app có tên chứa `Loading`/`Spinner`/`Overlay`/`Backdrop` (case-insensitive) — đọc class/CSS của nó.
- Kiểm tra class/CSS tìm được có **class-based** (không phải `aria-busy`/`role="progressbar"` — 2 cái này generic layer đã bắt được, không cần đề xuất).
- Bỏ qua candidate nếu đã khớp 1 trong các selector generic mặc định (`[data-loading="true"]`, `.spinner`, `.loading`, `.skeleton`) HOẶC đã có sẵn trong `Shared/evidence-helper.config.ts` (đọc file này trước khi quét để dedupe).

**Nếu tìm được candidate mới** → dừng lại, hỏi tester (1 câu, giải thích bằng ngôn ngữ non-technical — tester không cần biết ARIA/directive là gì, chỉ cần hiểu hậu quả):

> 🔍 **Kiểm tra chất lượng evidence — màn hình `{ScreenID}` có 1 hiệu ứng loading riêng**
>
> Màn hình này hiển thị loading bằng class `{selector}` (tìm thấy ở `{file}:{line}`) — cách chụp ảnh mặc định của công cụ **không nhận diện được** loại loading này, nên có rủi ro ảnh evidence bị chụp ngay lúc màn hình còn đang loading (mờ/che nội dung/chưa load xong), dù test PASS/FAIL vẫn đúng.
>
> → Thêm class `{selector}` vào danh sách "chờ loading xong" của dự án này để ảnh evidence chuẩn hơn? [Y/n]

- Nếu tester `Y` (hoặc Enter) → thêm `'{selector}'` vào mảng `PROJECT_LOADING_SELECTORS` trong `Shared/evidence-helper.config.ts` (giữ nguyên các dòng đã có, comment ngắn ghi rõ tìm thấy ở màn hình nào/ngày nào để sau này dễ trace), báo lại "✅ Đã thêm."
- Nếu tester `n` → bỏ qua, không sửa file, tiếp tục Bước 2 bình thường. Không hỏi lại candidate này trong cùng Gate 1, nhưng KHÔNG note "đã từ chối" vào đâu cả — lần chạy `ak execute` sau cho màn hình khác vẫn được tự do đề xuất lại nếu gặp cùng selector (tránh giả định tester luôn từ chối mãi mãi khi tình huống/quyết định có thể đổi).
- Nếu không tìm được candidate nào (app đã dùng chuẩn ARIA, hoặc màn hình không có loading riêng gì đặc biệt) → không hỏi gì cả, im lặng tiếp tục — không làm phiền tester với câu hỏi rỗng.

### Bước 2: Extract TCs từ Section 3

Đọc tất cả rows trong các bảng Section 3.x, extract:
- TC_ID, Test Case Name, Severity

Phân loại sơ bộ:
- **Manual TC**: Steps đề cập "kiểm tra DB", "kiểm tra file", "Quan sát giao diện tổng quan", "Kiểm tra layout/màu sắc"
- **Automation TC**: còn lại

### Bước 3: Scan scripts hiện tại

Tìm `AK-Docs/03.Testing/05.Scripts/{repo}/{featureDir}/{ScreenID}.spec.ts`:
- Nếu tồn tại: đọc `@tc-id` comments → xác định TCs đã có script
- Tính hash cho từng TC → so với `@tc-hash` trong file → xác định `updated` vs `skipped`
- Nếu không tồn tại: tất cả automation TCs đều là `new`

### Bước 4: Hiển thị Work Plan

```
📋 WORK PLAN — {ticket hoặc tên TC file}

Requirement Overview:
- Màn hình: {ScreenID}_{Screen Name} ({repo})
- Tổng TC: {N} | Automation: {N} | Manual/Untest: {N}

Script Sync Plan:
| Screen | TC_ID    | Script hiện tại           | Trạng thái    |
|--------|----------|---------------------------|---------------|
| AD10   | AD10_001 | -                         | 🆕 Gen mới    |
| AD10   | AD10_002 | AD10.spec.ts (hash match) | ⏭️ Skip       |
| AD10   | AD10_005 | AD10.spec.ts (hash diff)  | 🔄 Update     |
| AD10   | AD10_023 | -                         | ⚠️ Manual TC  |

→ Cần gen mới: {N} | Update: {N} | Skip: {N} | Manual (Untest): {N}
```

```
⏸️ GATE 1: WORK PLAN READY

Screens: {N} | Total TC: {N} ({N} automation / {N} manual)
Scripts: {N} gen mới | {N} update | {N} skip
Repo mapping: {ScreenID} → {repo}

→ Type APPROVED to proceed to Gate 2 (Script Sync)
→ Type REPO: {ScreenID}={repo} để cập nhật repo mapping
```

Dừng. Chờ APPROVED.

---

## ⛩️ GATE 2 — Script Sync

Chỉ chạy sau Gate 1 APPROVED.

### Invoke script-sync skill

Gọi `script-sync` skill với context:
- `tcFile`: path TC file
- `repo`: từ Gate 1
- `screenId`: always lowercase ScreenID (vd `ad10`, not `AD10`) — directory path uses lowercase
- `featureDir`: `{ScreenID}_{Screen-Name-kebab-case}` (vd `AD10_create-product`)
- `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`)"
- `baseUrl`: từ `BASE_URL` env var
- `scriptsRoot`: `AK-Docs/03.Testing/05.Scripts/{repo}/` (script + Page Object nằm ở đây)

Nhận về: danh sách kết quả per TC.

### Hiển thị kết quả

⏸️ GATE 2: SCRIPT SYNC COMPLETE

Gen mới: {N} | Updated: {N} | Skipped: {N}
Manual (Untest): {N} TCs
Blocked: {N} TCs — xem lý do bên trên

→ Review: [AK-Docs/03.Testing/05.Scripts/{repo}/{featureDir}/{ScreenID}.spec.ts](AK-Docs/03.Testing/05.Scripts/{repo}/{featureDir}/{ScreenID}.spec.ts)
→ Type APPROVED to run tests
→ Type SKIP: {TC_ID} để đánh dấu Pending và tiếp tục
→ Or provide feedback to fix scripts

Dừng. Chờ APPROVED.

Khi tester gõ `SKIP: AD10_019`: đánh dấu TC đó là Pending, loại khỏi danh sách sẽ chạy.

---

## ⛩️ GATE 3 — Execute & Evidence

Chỉ chạy sau Gate 2 APPROVED.

### Bước 1: Xác định run number

Scan `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/` để tìm run folder hiện có (nguồn sự thật cho run number):
- Nếu không có folder nào → `run-1`
- Nếu có `run-1`, `run-2` → `run-3`

Tạo 2 folder song song cho run này (evidence vs report/bug, xem "Cấu trúc thư mục đầu ra"):
- `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/` — evidence nhị phân (screenshots, video, `trace.zip`)
- `AK-Docs/03.Testing/02.Reports/{repo}/{featureDir}/run-{N}/` — `result.md` + `testreport.md`

### Bước 2: Chạy Playwright

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:

```bash
cd AK-Docs/03.Testing/05.Scripts
EVIDENCE_DIR="$(pwd)/../04.Evidence/{repo}/{featureDir}/run-{N}" \
BASE_URL={baseUrl} \
npx playwright test --config={repo}/playwright.config.ts {repo}/{featureDir}/{ScreenID}.spec.ts
```

Nếu item 5 (pre-flight) xác định browser hiệu lực ≠ `chromium` (tester đã xác nhận), thêm `PW_BROWSER={browser}` vào cùng dòng env var ở trên — dùng lại đúng giá trị đã xác nhận cho mọi lệnh `npx playwright test` của run này, kể cả RETEST loop bên dưới.

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.

### Bước 3: Parse kết quả

Đọ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`).

Với mỗi test:
- Extract TC_ID từ test title (format: `AD10_001 - {name}` hoặc `[AD10_001] {name}`)
- Map: TC_ID → `{status: 'passed'|'failed', duration, attachments, errors}`

**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.

### Bước 4: Tổ chức Evidence + Report

Với mỗi TC:

**Evidence (ở `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/{TC_ID}-{kebab-scenario}/`)** — `evidence-helper.ts`'s `captureStepEvidence()`/`openAndCapture()` ghi trực tiếp vào đúng folder này (tự tính từ `testInfo.title`, không phụ thuộc tên thư mục auto-gen của Playwright — xem `tcFolderName()` trong template), không cần copy tay:
- 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)
- Trace file → `trace.zip`

Với `kebab-scenario` = Test Case Name lowercase, dấu cách thành `-`, bỏ ký tự đặc biệt.

**Riêng với TC failed/retry**, artifact tự động của Playwright (`screenshot: 'only-on-failure'`, `video: 'retain-on-failure'`, `trace: 'retain-on-failure'` trong `playwright.config.ts`) vẫn ghi vào thư mục `testInfo.outputDir` auto-gen riêng của Playwright — thư mục này KHÔNG đi qua `evidence-helper.ts` nên vẫn dùng tên tự sinh (sanitize + hash, không đoán trước được, có thể lệch hẳn với `{TC_ID}-{kebab-scenario}` tuỳ độ dài/số dấu tiếng Việt trong tiêu đề). Sau khi chạy xong, với mỗi TC failed: tìm các thư mục auto-gen đó trong evidence dir của run này (không khớp pattern `{TC_ID}-*`), lấy attempt cuối cùng (`-retryN` cao nhất nếu có retry), copy `test-failed-*.png` → `{TC_ID}-{kebab-scenario}/failure-screenshot.png`, `trace.zip` → `.../trace.zip`, `video.webm` → `.../video.webm`, rồi xoá các thư mục auto-gen đó — đảm bảo mọi evidence của 1 TC (kể cả PASS lẫn FAIL) nằm cùng 1 folder theo đúng convention, khớp với đường dẫn mà bug report / result.md FAIL template bên dưới tham chiếu tới.

**Report (`AK-Docs/03.Testing/02.Reports/{repo}/{featureDir}/run-{N}/{TC_ID}-{kebab-scenario}/result.md`)** — file text, không chứa binary:

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).

Khi PASS:
```markdown
# Result: {TC_ID} — {Test Case Name}

**Status:** ✅ PASS
**Duration:** {X}s
**Run:** run-{N} | {YYYY-MM-DD HH:MM}

## Evidence
→ `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/{TC_ID}-{kebab-scenario}/`

| Step | Screenshot |
|------|-----------|
| {step 1} | step-01-{desc}.png |
| {step 2} | step-02-{desc}.png |
```

Khi FAIL:
```markdown
# Result: {TC_ID} — {Test Case Name}

**Status:** ❌ FAIL
**Duration:** {X}s
**Run:** run-{N} | {YYYY-MM-DD HH:MM}

## Failure Details
**Error:** {error message từ Playwright}

## Evidence
→ `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/{TC_ID}-{kebab-scenario}/`

| File | Mô tả |
|------|-------|
| failure-screenshot.png | Trạng thái lúc fail |
| trace.zip | Playwright trace |

## Bug Reference
→ Xem: `AK-Docs/03.Testing/06.Bugs/{repo}/{featureDir}/run-{N}/BUG-{NNN}-{slug}.md`
```

Với Manual TCs (Untest): không tạo evidence folder / result.md.

### Bước 5: Auto-draft Bug Reports

Với mỗi TC failed, tạo `AK-Docs/03.Testing/06.Bugs/{repo}/{featureDir}/run-{N}/BUG-{NNN}-{slug}.md`:

```markdown
# BUG-{NNN} — {Test Case Name}

**TC_ID:** {TC_ID}
**Severity:** {severity từ TC file}
**Status:** 🔴 Open
**Found at:** Gate 3 Execution | {YYYY-MM-DD}
**Environment:** {baseUrl} | Chrome | Windows

---

## Steps to Reproduce

{Steps từ TC file, numbered}

## Expected Result

{Expected Result từ TC file}

## Actual Result

{Actual behavior từ Playwright error}

## Evidence

- Screenshot: `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/{TC_ID}-{scenario}/failure-screenshot.png`
- Playwright trace: `AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/{TC_ID}-{scenario}/trace.zip`

## Technical Notes

```
{Playwright error message}
```

---

## Resolution

- [ ] Dev fix
- [ ] Retest: `RETEST: {TC_ID}`
```

BUG số: đếm file trong `AK-Docs/03.Testing/06.Bugs/{repo}/{featureDir}/run-{N}/` folder, +1. Format: `001`, `002`,...

### DOM Verification Rules (bắt buộc — tránh false pass/fail)

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:

- ❌ **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.
- ❌ **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(...)`).
- ❌ **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ế.
- ✅ 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.
- ✅ Khi TC yêu cầu "field Y bị disable/readonly" → assertion phải là `toBeDisabled()`/`toHaveAttribute('readonly', ...)`, không phải `toBeVisible()`.
- ✅ 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.
- ✅ 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.

### Evidence Quality Rules (bắt buộc)

Giải quyết vấn đề "chụp evidence sai thời điểm / mờ / thiếu step / không mở được modal":

1. **Thời điểm chụp — technology-agnostic, không đoán riêng theo framework:** 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ờ tuần tự 5 lớp, từ rẻ/cụ thể nhất đến đắt/tổng quát nhất, để đa số trang dừng lại sớm mà vẫn có lớp chót bắt được mọi trường hợp còn sót:
   1. `networkidle` + không còn loading indicator theo selector fast-path (`[aria-busy="true"]` — chuẩn ARIA, tín hiệu đáng tin nhất giữa mọi framework — `[data-loading]`, `.spinner`, `.loading`, role `progressbar`, cộng selector riêng của app khai báo trong `Shared/evidence-helper.config.ts`)
   2. Không còn CSS animation/transition nào đang chạy (Web Animations API — không cần biết tên class, hoạt động như nhau trên mọi framework)
   3. DOM ngừng mutate trong một khoảng lặng ngắn (MutationObserver — bắt các trường hợp nội dung loading bị thay thế mà không có animation)
   4. Fonts đã load xong (`document.fonts.ready`)
   5. **Lớp chót, thật sự agnostic — OPT-IN, mặc định TẮT:** so sánh 2 frame screenshot liên tiếp cho đến khi giống hệt pixel-by-pixel — bắt mọi kiểu loading mà 4 lớp trên bỏ sót (canvas/WebGL loader, GIF động, sprite chạy bằng `background-position`, hoặc trang server-render thuần như Java/JSP/PHP không hề có DOM/ARIA hook nào để theo dõi). Đây là lớp đắt nhất (poll thêm screenshot, có thể tốn vài giây mỗi lần gọi) — bật mặc định từng khiến TC có nhiều `captureStepEvidence()` trong 1 test (vd loop qua nhiều cột để test sort) vượt quá `timeout` 30s mặc định của Playwright dù app không hề có lỗi. Chỉ bật khi biết chắc màn hình có loader mà 4 lớp trên không bắt được: truyền `{ enableVisualStability: true }` vào `captureStepEvidence(...)` / `waitForUiSettled(page, timeout, { enableVisualStability: true })`, và tăng `timeout` của spec đó trong `playwright.config.ts` hoặc `test.setTimeout(...)` để bù chi phí thêm.

   Nếu app đang test có 1 spinner/overlay riêng (vd class của 1 UI kit cụ thể) không bị 5 lớp trên bắt được → thêm selector đó vào `Shared/evidence-helper.config.ts` (file này không bao giờ bị ghi đè lại khi re-sync), **không sửa trực tiếp `evidence-helper.ts`** — sửa trực tiếp sẽ mất tuỳ biến ở lần update ai-flow-kit tiếp theo. Không chụp ngay sau khi trigger action.
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. Browser mặc định là `chromium` (validated cho evidence quality) — WebKit/Firefox thiếu font fallback tương tự trên Linux là nguyên nhân phổ biến của lỗi mất chữ này dù mọi config khác đều đúng; xem cơ chế chọn browser (`PW_BROWSER`) và cảnh báo tương ứng ở pre-flight check item 5.
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.
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í.
5. **Dropdown/popper đang mở (Element UI/Ant Design/MUI Select, Tooltip, Popover, ...) → LUÔN `fullPage: false`, không bao giờ `fullPage: true` (mặc định).** Các UI kit này teleport popper ra `document.body` và định vị bằng `position: absolute`/`fixed` tính theo viewport **lúc mở** — nếu trigger nằm ở cuối 1 trang/bảng dài (vd control phân trang dưới bảng đơn hàng), viewport đang cuộn xuống khi popper mở. Chụp `fullPage: true` khiến Chromium giãn viewport lên full chiều cao tài liệu để chụp 1 lần duy nhất, nhưng vị trí `top/left` (tính bằng px tuyệt đối) của popper đã "đóng băng" theo toạ độ lúc mở — kết quả: ảnh xuất hiện 1 dải header/nội dung bị lặp lại giữa trang, còn popper thật thì lệch hẳn khỏi vị trí mong đợi hoặc không nằm đúng chỗ được highlight. Đây không phải bug của `evidence-helper.ts` hay của app — là cách Chromium composite ảnh `fullPage` với phần tử `absolute`/`fixed`. Vì trigger + popper đã được Playwright tự cuộn vào viewport khi click mở (actionability check), `fullPage: false` (viewport-only, mặc định của `page.screenshot()` khi không truyền `fullPage`) chụp đúng vị trí thật không cần code thêm gì. Áp dụng cho MỌI bước `captureStepEvidence(...)` chụp trong lúc dropdown/popover còn đang mở — kể cả khi không truyền `highlightSelector` vào chính popper đó (dropdown vẫn hiện trên màn hình, ảnh vẫn bị lỗi nếu không set `fullPage: false`). Case thực tế phát hiện: `TC-MFG113-020` (dropdown "Số dòng/trang" của ec-core, control nằm cuối bảng dài 57 trang) — ảnh evidence xuất hiện 1 thanh header lặp lại giữa trang do spec dùng `fullPage: true` mặc định khi chụp lúc dropdown đang mở.
6. **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...) → truyền `highlightSelector` (một `Locator`) vào `captureStepEvidence(...)` — helper tự gọi `highlightElement(locator)` (khoanh viền đỏ 3px + không che nội dung) ngay trước `page.screenshot()` và `removeHighlight(locator)` ngay sau đó để không ảnh hưởng bước tiếp theo. Không tự gọi `highlightElement`/`removeHighlight` rời rạc trong test — luôn đi qua `captureStepEvidence`.
7. **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.
8. Dùng helper `captureStepEvidence(page, testInfo, stepIndex, stepDesc, { highlightSelector, scrollSelector })` (xem `templates/evidence-helper.ts` — `testInfo` là param thứ 2 của Playwright test callback: `async ({ page }, testInfo) => {...}`) 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.

### Bước 6: Hiển thị Gate 3

```
⏸️ GATE 3: EXECUTION COMPLETE

Results (run-{N}):
- Passed:  {N} / {total automation}
- Failed:  {N} / {total automation}
- Untest:  {N} (manual TCs)
- Pending: {N} (blocked/skipped)

Bugs drafted: {N} ({N} Critical, {N} High, {N} Medium, {N} Low)
Unresolved critical: {N}

→ Evidence: AK-Docs/03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}/
→ Type APPROVED when all critical/high bugs resolved
→ Type RETEST: {TC_ID} to re-execute
→ Type PENDING: {TC_ID} để đánh dấu TC không chạy được
```

Dừng. Chờ APPROVED.

### RETEST loop

Khi tester gõ `RETEST: AD10_005, AD10_008`:

1. **Re-run Dev Artifacts Check**: invoke `pr-impact-analysis` skill nếu PR đã khai báo.
   - Nếu phát hiện commit mới → đề xuất TC bổ sung, tester approve delta trước
2. **Chạy selective test:**
   ```bash
   cd AK-Docs/03.Testing/05.Scripts
   EVIDENCE_DIR="$(pwd)/../04.Evidence/{repo}/{featureDir}/run-{N+1}" \
   BASE_URL={baseUrl} \
   npx playwright test --config={repo}/playwright.config.ts --grep "AD10_005|AD10_008"
   ```
3. Xác định run number mới (run-{N+1})
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ũ)
5. Cập nhật bug status:
   - TC pass sau retest → ghi `RESOLVED` vào bug file: `**Status:** ✅ Resolved`
   - TC vẫn fail → ghi `**Retest {N}:** Still OPEN` vào bug file

Khi tester gõ `PENDING: AD10_025`: cập nhật TC row với `⏳ Pending`.

---

## ⛩️ GATE 4 — Report & Bug Logging

Chỉ chạy sau Gate 3 APPROVED.

### Bước 1: Tổng hợp kết quả

Đọc tất cả `result.md` trong `AK-Docs/03.Testing/02.Reports/{repo}/{featureDir}/run-{N}/`:
- Count passed/failed/untest/pending per run
- Identify last run number (run-{max})

Đọc `AK-Docs/03.Testing/06.Bugs/{repo}/{featureDir}/run-{N}/BUG-*.md`: phân loại severity + status (Open/Resolved).

### Bước 2: Sinh testreport.md

Tạo `AK-Docs/03.Testing/02.Reports/{repo}/{featureDir}/run-{N}/testreport.md` theo template chuẩn:

```markdown
# SUMMARY TEST REPORT

## Project Information
| Property | Value | Property | Value |
|:---|:---|:---|:---|
| Project Name | {tên dự án từ TC file hoặc hỏi tester} | Author | AI |
| Project Code | {ScreenID} | Reviewer | {tên tester} |
| Created At | {ngày chạy R1} | Report Date | {hôm nay} |
| Last updated at | {hôm nay} | | |

## Test Execution Summary
| No | Module code | Written By | Created Date | R1 Passed | R1 Failed | R1 UnTest | R1 Pending | R2 Passed | R2 Failed | R2 UnTest | R2 Pending | Blocked | Total test cases | Tổng passed | % hoàn thành | Created Date | Updated Date | Sprint | Creator | Reviewer / Approver |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | {ScreenID} | AI | {date} | {r1p} | {r1f} | {r1u} | {r1pe} | {r2p} | {r2f} | {r2u} | {r2pe} | {blocked} | {total} | {tổng passed} | {%} | {date} | {date} | {sprint nếu có} | AI | {tester} |

## Bug Summary
| Bug ID | Severity | Title | Status | Jira Ticket |
|--------|----------|-------|--------|-------------|
{rows từ bugs/}

## Sign-off
- **Recommendation:** {Go nếu 0 Critical+High Open / No-go nếu còn / Conditional go nếu chỉ Medium còn open}
- **Conditions:** {liệt kê nếu Conditional}
```

**Exit criteria:**
- 0 Critical bugs Open → Go (kể cả còn Medium/Low)
- 0 High bugs Open → Go (nếu không có Critical)
- Còn Critical hoặc High Open → No-go
- Chỉ còn Medium/Low Open → Conditional go

### Bước 3: Bug logging flow

Hỏi từng bug theo thứ tự severity (Critical → High → Medium → Low):

```
🐛 BUG-{NNN} [{Severity}] — {Title}
   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}/
   Steps: {steps tóm tắt}
   Expected: {expected}
   Actual: {actual}

→ Log lên Jira? [Y/n]  (hoặc gõ SKIP-BUG để bỏ qua)
```

Nếu tester gõ `Y`:
- Gọi `ak bug-log` (nếu adapter có) hoặc hướng dẫn tester copy nội dung
- Ghi ticket ID vào bug file: `**Jira Ticket:** {ticket-id}`
- Ghi ticket ID vào cột Ticket ID của TC trong TC file

Nếu `n` hoặc `SKIP-BUG`: ghi `Not logged` trong testreport.md Bug Summary.

### Bước 4: Hiển thị Gate 4

⏸️ GATE 4: REPORT READY

Overall: {Pass / Fail / Conditional}
Passed: {N}/{total} | % hoàn thành: {N}%
Bugs logged to Jira: {N}/{total bugs}
Recommendation: {Go / No-go / Conditional go}

→ 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)
→ Type APPROVED to sign off
→ Type NO-GO: {reason} to reject release

Dừng. Chờ APPROVED hoặc NO-GO.

---

## Commands Reference

```
# Entry points
ak execute PROJ-33                    # từ ticket
ak execute ./testcases/AD10.md        # từ file
ak execute                            # manual

# Gate 1
REPO: AD10=repo-fe, API01=repo-be     # cập nhật repo mapping

# Gate 2
SKIP: {TC_ID}                         # đánh dấu Pending, bỏ qua script

# Gate 3
RETEST: {TC_ID}                       # re-execute 1 TC
RETEST: {TC_ID1}, {TC_ID2}            # re-execute nhiều TCs
PENDING: {TC_ID}                      # đánh dấu không chạy được

# Gate approval
APPROVED                              # chuyển gate tiếp theo
NO-GO: {reason}                       # reject (chỉ Gate 4)
SKIP-BUG: BUG-{NNN}                   # bỏ qua không log bug này
```

---

## Mandatory Rules

- ✅ Dừng ở mỗi gate, chờ APPROVED trước khi proceed
- ✅ `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/`)
- ✅ Hỏi từng bug một — không auto-log toàn bộ
- ✅ 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
- ✅ 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
- ✅ 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
- ✅ 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
- ❌ Không lấy expected result từ PR/code — chỉ từ TC file
- ❌ Không skip gate kể cả khi "chỉ có 1 TC"
