$npx -y skills add anhtester/codex-testing-kit --skill generate-locatorSinh locator ổn định cho UI element. Hỗ trợ Playwright, Selenium, Appium.
| 1 | # $generate-locator — Sinh Locator Ổn Định Cho UI Automation |
| 2 | |
| 3 | > User cung cấp element cần tìm locator (mô tả, screenshot, URL, hoặc HTML snippet). |
| 4 | > AI inspect DOM/UI hierarchy thực tế, sinh locator ổn định theo priority chuẩn, verify uniqueness, trả về kết quả. |
| 5 | |
| 6 | > **BẮT BUỘC (MANDATORY):** Trước khi bắt đầu, PHẢI nạp và đọc kỹ: |
| 7 | > - **Skill:** `.agents/skills/smart-locator-agent/SKILL.md` — Quy trình sinh locator |
| 8 | > - **Skill:** `.agents/skills/ui-debug-agent/SKILL.md` — Quy trình inspect DOM |
| 9 | > - **Rule:** `.agents/rules/locator_strategy.md` — Bản đồ ưu tiên locator |
| 10 | > - **Rule:** `.agents/rules/<framework>_rules.md` — Quy tắc riêng framework đang dùng |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | ## Input cần từ User |
| 15 | |
| 16 | | Input | Bắt buộc | Mô tả | |
| 17 | |-------|----------|-------| |
| 18 | | Mô tả element | ✅ | VD: "nút Login", "dropdown chọn Country", "ô nhập Email" | |
| 19 | | URL trang chứa element | ✅ | Để AI navigate và inspect DOM thực tế | |
| 20 | | Framework | ✅ | `playwright`, `selenium`, hoặc `appium` | |
| 21 | | HTML snippet | ❌ | Nếu User đã có sẵn DOM context — AI dùng để phân tích nhanh | |
| 22 | | Page class đích | ❌ | File Page class mà locator sẽ được thêm vào | |
| 23 | | Login yêu cầu | ❌ | Nếu trang yêu cầu đăng nhập — User cho biết cách login | |
| 24 | |
| 25 | > **Lưu ý về Login:** Nếu trang yêu cầu đăng nhập, User PHẢI chỉ rõ cách login (fixture, script, URL login...). AI KHÔNG ĐƯỢC tự đọc `.env` hay đoán credentials. |
| 26 | |
| 27 | --- |
| 28 | |
| 29 | ## Các bước thực hiện |
| 30 | |
| 31 | ### Phase 1: Phân tích yêu cầu |
| 32 | |
| 33 | 1. **Hiểu element cần tìm** — Xác định rõ: |
| 34 | - **Loại element:** button, input, link, dropdown, dialog, table row, checkbox, radio... |
| 35 | - **Context:** Nằm trong page chính, dialog/modal, sidebar, table, iframe? |
| 36 | - **Thao tác:** click, fill, select, hover, verify text, verify visibility? |
| 37 | |
| 38 | 2. **Xác định framework và đọc rules tương ứng:** |
| 39 | |
| 40 | | Framework | Rule file | |
| 41 | |-----------|-----------| |
| 42 | | Playwright | `.agents/rules/playwright_rules.md` | |
| 43 | | Selenium | `.agents/rules/selenium_rules.md` | |
| 44 | | Appium | `.agents/rules/appium_rules.md` | |
| 45 | |
| 46 | 3. **Kiểm tra Page class hiện tại (nếu User chỉ định):** |
| 47 | - Đọc file Page class → biết locator đã có sẵn |
| 48 | - Tránh trùng lặp hoặc xung đột naming |
| 49 | |
| 50 | --- |
| 51 | |
| 52 | ### Phase 2: Inspect DOM / UI Hierarchy thực tế |
| 53 | |
| 54 | > ⚠️ **NGUYÊN TẮC BẤT DI BẤT DỊCH: KHÔNG BAO GIỜ ĐOÁN LOCATOR. PHẢI INSPECT THỰC TẾ.** |
| 55 | |
| 56 | #### 2A. Web (Playwright MCP) |
| 57 | |
| 58 | 4. **Navigate đến trang chứa element:** |
| 59 | ``` |
| 60 | browser_navigate(url=<target_url>) |
| 61 | ``` |
| 62 | |
| 63 | 5. **Resize viewport (BẮT BUỘC):** |
| 64 | ``` |
| 65 | browser_resize(width=1920, height=1080) |
| 66 | ``` |
| 67 | |
| 68 | 6. **Capture DOM:** |
| 69 | ``` |
| 70 | browser_snapshot() |
| 71 | ``` |
| 72 | |
| 73 | 7. **Phân tích element trong snapshot:** |
| 74 | - Tìm ref element trong DOM tree |
| 75 | - Ghi lại **tất cả attributes có giá trị**: `role`, `aria-label`, `aria-labelledby`, `data-testid`, `data-test`, `data-qa`, `id`, `name`, `placeholder`, `type`, `href`, text content |
| 76 | - Ghi lại **parent context** (dialog? table? sidebar? iframe?) |
| 77 | |
| 78 | 8. **Nếu element bị ẩn** (dropdown menu, modal, tooltip...): |
| 79 | - Thực hiện action mở element: `browser_click(ref=<trigger>)` |
| 80 | - Capture lại: `browser_snapshot()` |
| 81 | |
| 82 | #### 2B. Mobile (Appium) |
| 83 | |
| 84 | 4. **Dùng Appium Inspector** hoặc `page_source` để lấy UI hierarchy |
| 85 | 5. **Ghi lại attributes:** `accessibility-id`, `resource-id`, `content-desc`, `text`, `class`, `bounds` |
| 86 | 6. **Nếu element nằm trong scroll view** → scroll đến element trước khi inspect |
| 87 | |
| 88 | --- |
| 89 | |
| 90 | ### Phase 3: Sinh locator theo Priority |
| 91 | |
| 92 | 9. **Áp dụng bản đồ ưu tiên (Master Priority Map):** |
| 93 | |
| 94 | | # | Loại | Khi nào dùng | |
| 95 | |---|------|-------------| |
| 96 | | 1 | Accessibility / Aria | Element có `role`, `aria-label` rõ ràng | |
| 97 | | 2 | Test attribute | Element có `data-testid`, `data-test`, `data-qa` | |
| 98 | | 3 | ID / name | Element có `id` hoặc `name` ổn định (không auto-generated) | |
| 99 | | 4 | Framework semantic | Playwright: `getByRole`, `getByLabel`... | |
| 100 | | 5 | CSS Selector | Dùng attribute cụ thể, tránh class động | |
| 101 | | 6 | XPath | Lựa chọn cuối cùng — chỉ dùng XPath tương đối | |
| 102 | |
| 103 | 10. **Sinh locator theo framework:** |
| 104 | |
| 105 | **Playwright (TypeScript/JavaScript):** |
| 106 | ```typescript |
| 107 | // Priority 1: Role-based |
| 108 | page.getByRole('button', { name: 'Submit' }) |
| 109 | |
| 110 | // Priority 2: Test ID |
| 111 | page.getByTestId('submit-btn') |
| 112 | |
| 113 | // Priority 3: Label / Placeholder |
| 114 | page.getByLabel('Email') |
| 115 | page.getByPlaceholder('Enter your password') |
| 116 | |
| 117 | // Priority 4: Text |
| 118 | page.getByText('Submit') |
| 119 | |
| 120 | // Priority 5: CSS |
| 121 | page.locator('#submit-button') |
| 122 | page.locator('[data-testid="submit-btn"]') |
| 123 | |
| 124 | // Priority 6: XPath (cuối cùng) |
| 125 | page.locator('//button[@type="submit"]') |
| 126 | ``` |
| 127 | |
| 128 | **Playwright (Python):** |
| 129 | ```python |
| 130 | # Priority 1: Role-based |
| 131 | page.get_by_role("button", name="Submit") |
| 132 | |
| 133 | # Priority 2: Test ID |
| 134 | page.get_by_test_id("submit-btn") |
| 135 | |
| 136 | # Priority 3: Label / Placeholder |
| 137 | page.get_by_label("Email") |
| 138 | page.get_by_placeholder("Enter your password") |
| 139 | |
| 140 | # Priority 4: Text |
| 141 | page.get_by_text("Submit") |