---
version: 1.0
updated: 2026-06-11
ported_from: ai-automation-qc-base
---

# /qc-run-test — QC Script Generation & Run (ghi qc_status)

> Stage 5 của QC automation pipeline native (qc-analyze → qc-plan → qc-design-test → qc-review → qc-run-test → qc-report). Port từ qa-runner của team QC. Sinh & chạy Python pytest-playwright từ `.Test.md` đã review, rồi ghi `qc_status` **chính thức** vào trace TSV.

## Gate
{{include:steps/gate.md}}

*Lưu ý: Với lệnh này, target ở Bước 1 là một UC-ID. **Phân giải `active_platform`** (QC pass khoá 1 platform): nếu `$ARGUMENTS` nêu platform → dùng; else glob `{paths.qc_dir}/{UC-ID}/*/` — đúng 1 folder platform → dùng nó, nhiều folder → hỏi. `active_platform` này CHÍNH LÀ platform của sổ trace sẽ ghi `qc_status` (`{UC-ID}-{active_platform}.tsv`) — nên `@trace.verifies={UC-ID}-SC{N}` resolve không nhập nhằng. Đọc `.Test.md` đã review từ `{paths.qc_dir}/{UC-ID}/{active_platform}/test-cases/`. Lệnh này dùng stack module **qc-playwright** (`.agent/modules/qc-playwright/stack-profile.yaml`) — Python + pytest-playwright + Page Object — độc lập với module implementation của dev.*

## Context
{{include:steps/context-loader.md}}

---

## Role & stack (theo module qc-playwright)

Bạn là **QC Runner** — stage 5. Chuyển `.Test.md` đã review thành Python
pytest-playwright script + Page Object, chạy chúng, và report kết quả theo từng scenario.

Quy tắc stack (BẮT BUỘC — từ `modules/qc-playwright/stack-profile.yaml`):
- Markdown-first: không bao giờ sinh Python khi chưa có `.Test.md` đã review.
- Page Object extends `BasePage` gọn, 3 lớp: locator `_x()`, action `verb_noun()`, assertion `assert_x()` dùng `expect()`.
- **Locator từ test-id contract (không scan runtime).** Đọc bảng *Test Selectors* §4.5.6 (block platform) của tech-doc gộp tại `{paths.tech_docs_dir}/{domain}/{prd-slug}/tech-docs/{TICKET-ID}-tech-design.md` (nếu có — bảng gộp mọi UC của platform, **lọc theo cột "Serves SC" khớp SC của UC này**) và dựng mỗi Page Object locator từ test-id ổn định của nó — web `get_by_test_id("...")`, RN `testID`, Flutter `Key`/`Semantics`, native `accessibilityIdentifier`. **Ưu tiên map; fallback** về role/label/text/CSS (scan chậm hơn) chỉ cho một element có action mà **không** có test-id trong §4.5.6 — và ghi chú để gap được thêm vào tech-design.
- pytest-playwright fixture; mỗi test độc lập; gom theo (role, account) để auth không bao giờ xen kẽ.
- Không hard-code URL/cred/timeout (dùng `Env.*` / `CONFIG[...]`); không `time.sleep()`; không Allure.
- Phủ **100%** TC trong file — mỗi TC kết thúc Pass/Fail/Skip (không còn Draft).
- Phân loại mỗi FAIL: script-bug (fix selector/logic) vs product-gap (giữ FAIL + evidence, không bao giờ fake-pass).

## Skills — chọn layer, nạp MỘT file (`{paths.qc_skills_dir}/qa-runner/`)

`functional/{gui-screen,gui-feature,api}.md`, `integration.md`, `e2e.md`,
`non-functional.md`, `exploratory/session.md`.

## Trace tag (bắt buộc)

Mỗi pytest test được sinh ra mang scenario framework mà nó verify, lấy từ
Trace matrix của `.Test.md`:

```python
# @trace.verifies={UC-ID}-SC{N}
def test_TC_<FEATURE>_001_...(...): ...
```

## Write Trace State — qc_status (kết quả QC CHÍNH THỨC)

Sau khi chạy, cập nhật **sổ của platform đang test** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{active_platform}.tsv` (`{active_platform}` đã phân giải ở Bước 1 — chính là platform của QC pass này; nếu `domain`/`prd_slug` không phân giải được từ spec target, định vị TSV bằng cách glob `{paths.trace_dir}/**/{UC-ID}-{active_platform}.tsv` — nó được tạo trước đó bởi `/generate-bdd`) — cho mỗi scenario row (khớp
`sc_id` qua tag `@trace.verifies={UC-ID}-SC{N}` của test). *(Umbrella + `spec_source`: `trace_dir` là `{spec_source}/.trace` — ghi update `qc_status` vào **spec repo** và commit/push spec submodule, giống `feedback/`.)*

| Cột | Giá trị |
|--------|-------|
| `qc_status` | `pass` nếu mọi QC test của SC này pass · `fail` nếu có cái fail · `skip` nếu tất cả skip/xfail · `not_run` nếu không QC test nào phủ nó |
| `qc_run_at` | hôm nay `YYYY-MM-DD` |
| `qc_owner` | **SC đang chờ ai** (view "pending" của PM/PO): `dev` nếu FAIL = product-gap (defect thật → dev fix) · `po` nếu `skip`/`not_run` vì một **`DOC_GAPS` 🔴 Blocker đang open** chặn test (PO phải làm rõ PRD/BDD) · `—` nếu `pass`, hoặc FAIL = script-bug (QC tự fix — tạm thời) |
| `qc_blocked_by` | artifact liên kết: `GAP-{id}` khi bị chặn bởi spec gap (set ở đây) · `BUG-{id}` khi `/report-bug` đã được file cho product-gap (backfill bởi `/report-bug`) · `—` ngược lại |

Set `qc_owner`/`qc_blocked_by` cùng với `qc_status`. Khi `pass`, **clear** cả hai về `—`.
Với FAIL product-gap, set `qc_owner=dev` ngay; `BUG-{id}` được backfill vào `qc_blocked_by`
khi QC chạy `/report-bug` mà `/qc-report` nhắc.

Giữ nguyên mọi cột khác — **không bao giờ** đụng `dev_selftest`/`dev_selftest_at`
(do `/dev-run-test` sở hữu). `qc_status` (QC chính thức) và `dev_selftest` (dev smoke) là
hai tín hiệu riêng; cả hai trực giao với `status` (OK/GAP/DRIFT/UNTRACKED = coverage).

## Refresh Panel Mirror
{{include:steps/trace-mirror.md}}

## Report

{{include:steps/report-footer.md}}

```
/qc-run-test Report — {UC-ID} ({qc-playwright})
QC: ✅ {pass} pass | ❌ {fail} fail | ⏭️ {skip} skip   (TCs: {total})
Trace: {paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{active_platform}.tsv updated (qc_status, qc_run_at)
Next: /qc-report {UC-ID}   ← sinh report + evidence
      /qc-review {UC-ID}   ← review script đã sinh trước khi merge
📊 Living Docs: chạy /validate-traces (hoặc /sync) để push qc_status lên dashboard spec-module.
```
