---
name: automation-testing
description: Sinh Playwright TypeScript E2E test từ test case draft của tester. Dùng Playwright MCP để xem DOM THẬT của app và lấy selector chính xác (không đoán). Dùng khi tester muốn tạo script test tự động từ mô tả test case bằng ngôn ngữ tự nhiên.
allowed-tools: Read, Write, Edit, Glob, Grep, Bash, mcp__playwright__browser_navigate, mcp__playwright__browser_snapshot, mcp__playwright__browser_generate_locator, mcp__playwright__browser_click, mcp__playwright__browser_type, mcp__playwright__browser_fill_form, mcp__playwright__browser_select_option, mcp__playwright__browser_press_key, mcp__playwright__browser_wait_for, mcp__playwright__browser_take_screenshot, mcp__playwright__browser_console_messages, mcp__playwright__browser_navigate_back, mcp__playwright__browser_close
---

# Skill: automation-testing

Sinh **Playwright TypeScript** test scripts từ test case draft của tester.
Điểm khác biệt cốt lõi so với "sinh code thuần": skill **mở app thật bằng Playwright MCP**,
chụp cây accessibility/DOM và sinh selector từ element CÓ THẬT — nên script chạy được ngay,
tester không cần biết TypeScript và hạn chế tối đa việc nhờ dev sửa selector.

## Cách gọi

```
/automation-testing baseUrl: <url> [autoRun: true|false] [headed: true|false] [ticketId: <id>]
---
<test case draft của tester — text tự do, mỗi dòng một case>
```

Ví dụ:
```
/automation-testing baseUrl: http://localhost:3000 autoRun: false
---
1. Vào trang login, nhập đúng email/password → vào được dashboard
2. Nhập sai password → hiện thông báo lỗi
3. Để trống email → hiện validation lỗi
```

## Tham số (parse từ phần đầu lời gọi)

Đọc các tham số từ text người dùng truyền vào (dạng `key: value`). Nếu không thấy, dùng default.

| Param      | Default | Ý nghĩa |
|------------|---------|---------|
| `baseUrl`  | —       | URL web app đang test. **Bắt buộc.** Nếu thiếu → hỏi tester rồi mới tiếp tục. |
| `autoRun`  | `false` | Tự chạy `npx playwright test` sau khi sinh script. |
| `headed`   | `true`  | Hiện cửa sổ browser khi `autoRun=true`. |
| `ticketId` | suy ra  | Thư mục con cho spec (`tests/e2e/specs/<ticketId>/`). Nếu thiếu → suy từ tên feature (vd `login`). |

Phần text sau dấu `---` là **test case draft**.

---

## Phase 1 — Collect (Thu thập input)

Đọc tất cả nguồn có sẵn, theo thứ tự ưu tiên:

1. `plan/<ticketId>/requirement.md` (nếu có) — dùng `Read`.
2. `plan/<ticketId>/plan.md` (nếu có).
3. **Test case draft** — phần text sau `---` trong lời gọi.
4. `custom/rules/test-patterns.md` — các pattern chuẩn (login, CRUD, form, modal...). **Luôn đọc file này** để áp dụng quy ước chung.

Kiểm tra điều kiện tiên quyết:
- Thiếu `baseUrl` → hỏi tester, dừng lại chờ.
- Không có bất kỳ test case nào (cả draft lẫn requirement) → hỏi:
  > "Bạn muốn test feature gì? Hãy mô tả các test case hoặc paste test case draft vào đây."

---

## Phase 2 — Analyze (Sinh danh sách test case có cấu trúc)

Phân tích tất cả input và lập bảng test case:

```
## Test Cases — <Feature Name>

| ID   | Tên test case            | Steps                                          | Expected Result            |
|------|--------------------------|------------------------------------------------|----------------------------|
| TC01 | Đăng nhập thành công      | 1. Vào /login 2. Nhập email+pass hợp lệ 3. Submit | Redirect về /dashboard  |
| TC02 | Sai mật khẩu             | 1. Vào /login 2. Nhập sai pass 3. Submit       | Hiện error message         |
| TC03 | Để trống email           | 1. Vào /login 2. Submit ngay                   | Hiện "Email là bắt buộc"   |
```

> ⚠️ **GATE 1 — BẮT BUỘC.** Hiển thị bảng và hỏi:
> "Danh sách test cases trên đã đúng chưa? Cần thêm/sửa/xóa case nào không?
> Xác nhận để tôi mở app và lấy selector thật."
>
> **Dừng lại, chờ tester xác nhận.** Không tự chuyển Phase 3.

---

## Phase 3 — Explore (Khám phá DOM thật qua Playwright MCP) 🔑

Đây là bước quyết định độ chính xác. **Không được đoán selector** — phải lấy từ app thật.

Với mỗi trang/màn hình liên quan trong các test case đã chốt:

1. `mcp__playwright__browser_navigate` tới `<baseUrl><path>` (vd `http://localhost:3000/login`).
2. `mcp__playwright__browser_snapshot` để lấy cây accessibility (role, name, ref của từng element).
3. Với từng element cần thao tác (input, button, message...), dùng
   `mcp__playwright__browser_generate_locator` để lấy **locator Playwright chuẩn** từ element thật.
4. Nếu một test case cần đi qua nhiều bước (vd phải submit mới hiện error), dùng
   `browser_type` / `browser_fill_form` / `browser_click` để **đi tới đúng trạng thái** rồi
   `browser_snapshot` lại để lấy selector của element xuất hiện sau đó (error message, modal...).
5. Ghi lại bảng ánh xạ: phần tử logic → locator thật. Ví dụ:

   ```
   Email field   → getByRole('textbox', { name: 'Email' })
   Password field→ getByLabel('Mật khẩu')
   Submit button → getByRole('button', { name: 'Đăng nhập' })
   Error message → getByText('Sai tên đăng nhập hoặc mật khẩu')
   ```

Quy tắc ưu tiên locator (theo Playwright best practice):
`getByRole` > `getByLabel` > `getByPlaceholder` > `getByText` > `getByTestId` > CSS.
Chỉ dùng CSS/XPath khi không còn cách nào khác, và ghi chú lý do.

> ⚠️ **Nếu không mở được app** (baseUrl sai, app chưa chạy, route không tồn tại):
> báo rõ cho tester, **không** sinh selector đoán mò. Hỏi tester sửa baseUrl / khởi động app.
>
> Đóng browser bằng `mcp__playwright__browser_close` khi khám phá xong.

### Nếu app yêu cầu đăng nhập (auth)

Khi feature cần test nằm sau màn hình login:

1. Khám phá selector trang login bằng MCP (như trên).
2. Cập nhật `tests/e2e/auth.setup.ts`: thay 3 dòng locator mặc định (Email / Mật khẩu /
   nút Đăng nhập) bằng **selector thật** vừa khám phá, và chỉnh `expectPathAfterLogin`.
3. Hỏi tester **tài khoản test** (không hardcode tài khoản thật). Hướng dẫn họ điền vào `.env`:
   `E2E_USER`, `E2E_PASSWORD`, `E2E_LOGIN_PATH`, `E2E_POST_LOGIN_PATH` (xem `.env.example`).
   Có `E2E_USER` → auth tự bật: project `setup` đăng nhập 1 lần, lưu `.auth/user.json`,
   các test sau tái dùng phiên (không login lại từng test).
4. Test mà bản thân nó cần trạng thái **CHƯA đăng nhập** (vd chính test login/logout):
   thêm ở đầu spec `test.use({ storageState: LOGGED_OUT })` (import `LOGGED_OUT` từ
   `../../fixtures/test`).

---

## Phase 4 — Generate (Sinh script với selector thật)

Sinh file theo cấu trúc:

```
tests/e2e/
├── pages/
│   ├── BasePage.ts            ← chỉ tạo nếu chưa có (đã có sẵn trong repo)
│   └── <Feature>Page.ts       ← Page Object, locator lấy từ Phase 3
├── specs/
│   └── <ticketId>/
│       └── <feature>.spec.ts  ← nhóm nhiều TCs (xem quy tắc bên dưới)
└── fixtures/test.ts           ← chỉ tạo nếu chưa có (đã có sẵn)
```

Khi được gọi từ `coverage-check` (Phase 2d), output vào `test-plan/test-scripts/`:

```
test-plan/test-scripts/
├── AD10_001.spec.ts   ← mỗi TC_ID một file riêng
├── AD10_002.spec.ts
└── ...
```

Quy tắc sinh code:

- **`<Feature>Page.ts`**: kế thừa `BasePage`. Khai báo tất cả `Locator` ở constructor,
  **dùng đúng locator lấy được ở Phase 3**. Mỗi hành động là một async method (vd `login()`).
  Tham khảo `templates/PageObject.example.ts` để biết style.

- **Đặt tên test function — bắt buộc chứa TC_ID:**
  ```typescript
  // ScreenID là mã màn hình/chức năng của từng dự án (AD10, LOGIN, ORD, ... — không cố định)
  test.describe('[ScreenID] — Tên màn hình', () => {
    test('[ScreenID_001] Mô tả scenario đầu tiên', async ({ page }) => {
      // ...
    })
    test('[ScreenID_002] Mô tả scenario thứ hai', async ({ page }) => {
      // ...
    })
  })
  ```
  Ví dụ thực tế (ScreenID tùy dự án): `[AD10_001]`, `[LOGIN_001]`, `[ORD_002]`

  Format bắt buộc: `'[TC_ID] Mô tả scenario'` — TC_ID phải nằm ở **đầu tên test**, trong dấu `[]`.
  Mục đích: khi chạy `--grep "AD10_001"` (hoặc TC_ID thực tế) sẽ select đúng test case, và kết quả map 1-1 với testcase document.

- **`<feature>.spec.ts`**: import `{ test, expect }` từ `../../fixtures/test`.
  dùng `test.describe('[ScreenID]', ...)` để nhóm. Assertion rõ ràng (`expect(...).toBeVisible()`...).
- **Không tạo lại** `BasePage.ts` / `playwright.config.ts` / `fixtures/test.ts` /
  `auth.setup.ts` / `support/auth.ts` nếu đã tồn tại (kiểm tra bằng `Glob` trước) —
  với `auth.setup.ts` chỉ **sửa locator/đường dẫn**, không ghi đè toàn bộ.

### Incremental Update — chỉ generate scripts mới hoặc đã thay đổi

Khi được gọi với danh sách TC_IDs (từ `coverage-check` hoặc sau khi TESTER update testcases):

1. **Kiểm tra scripts đã tồn tại:** `Glob` tìm các file trong `test-plan/test-scripts/*.spec.ts`
2. **Phân loại từng TC_ID:**
   - Nếu `[TC_ID].spec.ts` **chưa tồn tại** → `[NEW]` — sinh script mới
   - Nếu `[TC_ID].spec.ts` **đã tồn tại** và TC nằm trong danh sách "đã thay đổi" (do TESTER chỉ định) → `[UPDATED]` — regenerate, overwrite
   - Nếu `[TC_ID].spec.ts` **đã tồn tại** và không trong danh sách thay đổi → `[SKIP]` — không đụng vào
3. **Chỉ mở DOM/Playwright MCP cho TCs cần generate** (`[NEW]` và `[UPDATED]`)
4. **Báo cáo kết quả:**
   ```
   Script generation complete:
     [NEW]     AD10_005.spec.ts — created
     [UPDATED] AD10_002.spec.ts — regenerated (TC content changed)
     [SKIP]    AD10_001.spec.ts — unchanged, kept existing
     [SKIP]    AD10_003.spec.ts — unchanged, kept existing
   ```

Khi TESTER không chỉ định danh sách thay đổi (chạy lần đầu từ `coverage-check`), generate TẤT CẢ TCs có `Type = Auto` trong `final-testcases.md` mà chưa có script.

---

## Phase 5 — Execute

Set `BASE_URL` = `baseUrl` khi chạy để Playwright dùng đúng app.

### autoRun = false (default)
In hướng dẫn chạy thủ công:
```
✅ Đã sinh script xong!

Chạy test:
  BASE_URL=<baseUrl> npx playwright test tests/e2e/specs/<ticketId>/
  BASE_URL=<baseUrl> npx playwright test --headed     # hiện browser
  npx playwright test --ui                              # mở Playwright UI
Xem report:
  npx playwright show-report
```

### autoRun = true
1. Chạy bằng `Bash`:
   `BASE_URL=<baseUrl> npx playwright test tests/e2e/specs/<ticketId>/ <--headed nếu headed=true>`
2. Tóm tắt pass/fail cho tester (dễ đọc, không bắt đọc log thô).
3. Nếu có test fail: chỉ ra TC nào fail, lý do, và đề xuất sửa.
   In `npx playwright show-report` để mở HTML report.

---

## Nguyên tắc chung

- **Gate trước khi sinh code** (Phase 2) là bắt buộc — tránh sinh sai hàng loạt.
- **Selector phải đến từ DOM thật** (Phase 3) — đây là lý do tồn tại của skill này.
- Tên test, comment viết theo ngôn ngữ của draft test case của tester (xem `custom/rules/output-language.md`) — draft tiếng Việt → tiếng Việt, draft tiếng Anh → tiếng Anh.
- Nếu app yêu cầu đăng nhập trước, hỏi tester credentials test (đừng hardcode tài khoản thật).
