# Kho tài liệu — Danh sách `data-testid` cần DEV bổ sung / sửa

> Tổng hợp từ đợt khảo sát Playwright MCP ngày **2026-07-27**, môi trường **sitdev**
>
> ✅ **Cập nhật 2026-07-30 (khảo sát lại bằng MCP)**: DEV **đã bổ sung testId cho modal "Thêm thư
> mục"** — mục B1 và B2 dưới đây **đã xong**, script đã chuyển sang dùng testId thật:
> `txt-ma-thu-muc` (Index), `txt-ten-thu-muc`, `tree-sel-thu-muc-cha`, `sel-do-mat`,
> `sel-trang-thai-hien-thi`, và 5 khối quyền `pp-multi-usersRight*`.
> Các mục còn lại **chưa kiểm tra lại**, có thể cũng đã được bổ sung.
> (`https://sunecm-dev.sharepoint.vn/#/managed-records`), tài khoản `ecm01`.
> Mục đích: giúp script tự động bám element ổn định thay vì dựa vào class/placeholder/thứ tự.

> 📌 **Phạm vi tài liệu**: chỉ liệt kê **testId thiếu / sai / trùng** trên màn hình. **Không** ghi
> logic test / kỳ vọng của case / quy tắc nghiệp vụ vào đây — chỗ của chúng là spec `tests/<mục>/`,
> `<mục>.steps.ts` và `<mục>.md`. Xem `KHO-TAI-LIEU.md` mục đầu file.

Quy ước prefix testId của dự án: `txt-` (text), `txa-` (textarea), `date-` (datepicker),
`sel-` (dropdown), `sel-tags-` (tags), `tree-sel-` (tree dropdown), `pp-multi-` (people picker),
`btn-` (nút), `lbl-` (label), `chk-` (checkbox), `mni-` (menu item), `tbl-` (bảng).

---

## 0. ⭐ Ưu tiên cao nhất — 3 vấn đề chặn nhiều nhất

| # | Vấn đề                                                                                     | Ảnh hưởng                                                                 |
| - | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| 1 | ~~Modal **Thêm thư mục**: 4 field chính + 5 people-picker phân quyền không có testId~~ | ✅ **ĐÃ XONG** (xác nhận 2026-07-30) — xem ghi chú đầu file |
| 2 | Modal con **"Phân quyền cấu trúc bộ hồ sơ"** và modal **"Thêm"** (cấp quyền) gần như **không có testId** | Đây là **nơi duy nhất** sửa được quyền sau khi tạo BHS, nhưng phải bám text tiếng Việt |
| 3 | `btn-more` **lặp 2 lần** trên cùng màn (header BHS + header bảng Cấu trúc)                  | Mọi `getByTestId("btn-more")` đều strict-mode violation                     |

---

## A. 🐞 BUG — testId sai, cần sửa

| # | Vị trí                                              | testId hiện tại                | Vấn đề                                                              | Đề xuất                                     |
| - | --------------------------------------------------- | ------------------------------ | --------------------------------------------------------------------- | ------------------------------------------- |
| 1 | Header bảng tab **Cấu trúc hồ sơ**; header tab **Lưu trữ**; tab **Lịch sử**; modal Tạo tài liệu | `undefined-title`              | Literal `"undefined"` — biến truyền vào bị `undefined`                | `lbl-cau-truc-ho-so-title`, `lbl-luu-tru-title`, … |
| 2 | Dropdown "Thêm thông tin liên quan"                 | `btn-options-relaled-record`   | **Gõ nhầm** `relaled` → `related`                                     | `btn-options-related-record`                |
| 3 | Modal Chọn người dùng — cây cơ cấu tổ chức          | `Tree-sel-co-cau-to-chuc`, `Tree-sel-item` | Viết hoa `T`, sai quy ước `tree-sel-`                     | `tree-sel-co-cau-to-chuc`, `tree-sel-item`  |
| 4 | Panel lọc tab Cấu trúc hồ sơ                        | `dtp-effective-date-tu` / `dtp-effective-date-den` | **Lặp 2 lần** trong cùng panel → strict-mode violation | Tách testId cho từng cặp                    |
| 5 | Modal Tạo tài liệu                                  | `datetime-ngay-ban-hanh`, `datetime-effective-date`, `dtp-ngay-het-han` | 3 datepicker cùng loại nhưng **3 prefix khác nhau** (`datetime-`, `dtp-`), lại không khớp quy ước `date-` | Thống nhất `date-` |
| 6 | Modal Tạo tài liệu                                  | `sel-tags-tu-khoa`             | Không đồng nhất với form BHS (`sel-tags-tuKhoa`)                      | Chọn 1 quy ước duy nhất                     |

---

## B. ❌ THIẾU testId — ưu tiên CAO (chặn viết script)

### B1 + B2. Modal "Thêm thư mục" — ✅ ĐÃ CÓ testId (xác nhận lại 2026-07-30)

| Field / khối quyền           | testId thật                                                |
| ---------------------------- | ---------------------------------------------------------- |
| Index thư mục (input disabled) | `txt-ma-thu-muc`                                          |
| Tên thư mục                  | `txt-ten-thu-muc`                                          |
| Thư mục / Hồ sơ cấp cha      | `tree-sel-thu-muc-cha` (tree-select)                       |
| Độ mật                       | `sel-do-mat`                                               |
| Trạng thái hiển thị          | `sel-trang-thai-hien-thi`                                  |
| 5 khối quyền tab Phân quyền  | `pp-multi-usersRightOwner/Add/Edit/Download/Viewers`       |

Còn thiếu ở modal này: **tên đầy đủ của người trong khối quyền** — khối chỉ render avatar **chữ viết
tắt**, phải **hover** mới có `.ant-popover` chứa tên. Đề xuất thêm `title`/`aria-label` (hoặc testId
kèm tên) cho `avatar-container` để script khỏi phải hover từng avatar.

### B3. Nút bật/tắt kế thừa quyền

| # | Vị trí                          | Locator tạm                                                       | Đề xuất                        |
| - | ------------------------------- | ------------------------------------------------------------------- | ------------------------------ |
| 1 | Modal thư mục — bật độc lập     | `.ant-alert-action` → `getByRole("button",{name:"Đặt quyền độc lập"})` | `btn-dat-quyen-doc-lap`        |
| 2 | Modal thư mục — khôi phục       | `getByRole("button",{name:"Khôi phục kế thừa"})`                     | `btn-khoi-phuc-ke-thua`        |
| 3 | Modal tài liệu — Break quyền riêng | `input#basic_hasUniquePermission`                                 | `chk-break-quyen-rieng`        |
| 4 | Modal con Phân quyền cấu trúc BHS | `getByRole("button",{name:"Ngắt kế thừa"})`                        | `btn-ngat-ke-thua`             |

### B4. Confirm dialog (`.ant-modal-confirm`)

Tất cả confirm dialog hiện chỉ bắt được bằng text nút. Đề xuất thêm testId cho 2 nút của
mọi `.ant-modal-confirm`: `btn-confirm-ok` / `btn-confirm-cancel`.

Các dialog đã gặp:

| Dialog                     | Nút                              |
| -------------------------- | -------------------------------- |
| `Chuyển hoạt động`         | `Hủy bỏ` / `Chuyển hoạt động`    |
| `Đặt quyền độc lập?`       | `Hủy` / `Xác nhận`               |
| `Ngắt kế thừa quyền`       | `Hủy bỏ` / `Xác nhận`            |
| `Xóa` (xoá item cấu trúc)  | ✅ **đã có** `btn-xoa-huy` / `btn-xoa-xac-nhan`, modal có `mdl-xoa-xac-nhan` (đo 2026-08-14) — **mẫu tốt, áp dụng cho các dialog còn lại** |

### B6. Modal "Khôi phục Cấu trúc hồ sơ" (`btn-more` → Quản lý cấu trúc đã xóa)

Khảo sát 2026-08-14: modal **chỉ có** `lbl-modal-title` + `btn-close-modal` (và `avatar-*` trong ô
"Người thực hiện"). Thiếu:

| Element                      | Đề xuất testId              |
| ---------------------------- | --------------------------- |
| Nút footer **Khôi phục**     | `btn-khoi-phuc`             |
| Nút footer **Đóng**          | `btn-dong`                  |
| Bảng danh sách item đã xoá   | `tbl-cau-truc-da-xoa`       |
| Từng dòng                    | `row-cau-truc-da-xoa-<id>`  |

Ngoài ra `lbl-modal-title` **trùng** với modal BHS phía sau (xem mục D) → script phải lọc theo
chuỗi tiêu đề `"Khôi phục Cấu trúc hồ sơ"`.

### B5. Nút footer modal Thêm thư mục

`Xác nhận` / `Hủy` hiện chỉ bắt bằng `getByRole("button", { name })`.
Đề xuất: `btn-xac-nhan` / `btn-huy`.

---

## C. ❌ THIẾU testId — ưu tiên TRUNG BÌNH

### C1. Pop-up "Tìm hồ sơ liên quan" / "Tìm tài liệu liên quan"

**Không có bất kỳ testId nào** trong 2 pop-up (chỉ nút `btn-add-related` xuất hiện sau khi tick dòng).

| Element                | Đề xuất                        |
| ---------------------- | ------------------------------ |
| Tiêu đề pop-up         | `lbl-modal-title`              |
| Ô tìm kiếm             | `txt-tim-ho-so-lien-quan` / `txt-tim-tai-lieu-lien-quan` |
| Bảng kết quả           | `tbl-ho-so-lien-quan` / `tbl-tai-lieu-lien-quan` |
| Checkbox chọn dòng     | `chk-chon-<id>`                |
| Nút Hủy                | `btn-huy`                      |

### C2. Bảng `related-item-table`

| Element                    | Locator tạm              | Đề xuất                    |
| -------------------------- | ------------------------ | -------------------------- |
| Nút **Loại bỏ liên kết**   | `div.buttonLoaiBoRelated` | `btn-loai-bo-related-<id>` |
| Ô mã HS/TL                 | `div.cssMaHSTLChild`      | `lbl-ma-related-<id>`      |

### C3. Modal con "Phân quyền cấu trúc bộ hồ sơ" + modal "Thêm" (ưu tiên CAO — xem mục 0)

Ngoài `lbl-modal-title` / `btn-close-modal` / `avatar-*` thì **không có testId nào**.

**Modal con "Phân quyền cấu trúc bộ hồ sơ":**

| Element                              | Locator tạm                                          | Đề xuất                       |
| ------------------------------------ | ------------------------------------------------------ | ----------------------------- |
| Cây item bên trái (mỗi item)         | `getByRole("button", { name: "A. Tên thư mục" })`      | `btn-item-<code>`             |
| Bảng quyền bên phải                  | `.ant-table` cuối modal                                 | `tbl-quyen-item`              |
| Mỗi dòng quyền                       | `tr.ant-table-row`                                      | `row-quyen-<userId>`          |
| Nút **Ngắt kế thừa**                 | `getByRole("button", { name: "Ngắt kế thừa" })`         | `btn-ngat-ke-thua`            |
| Nút **Kế thừa quyền**                | `getByRole("button", { name: "Kế thừa quyền" })`        | `btn-ke-thua-quyen`           |
| Nút **Thêm**                         | `getByRole("button", { name: "Thêm" })`                 | `btn-them-quyen`              |

**Modal "Thêm" (cấp quyền trực tiếp):**

| Element              | Locator tạm                                                       | Đề xuất                 |
| -------------------- | ------------------------------------------------------------------- | ----------------------- |
| Người nhận           | `.people-picker` `.first()`                                         | `pp-multi-nguoi-nhan`   |
| Quyền                | `.ant-select` + `filter({ hasText: "Chọn quyền" })`                 | `sel-quyen`             |
| Nội dung             | `textarea[placeholder="Nhập nội dung"]`                             | `txa-noi-dung`          |
| Nút **Gửi**          | `getByRole("button", { name: "Gửi" })`                              | `btn-gui`               |
| Nút **Đóng**         | `getByRole("button", { name: "Đóng" })`                             | `btn-dong`              |

### C4. Tab "Theo người dùng" của modal Phân quyền nâng cao

| Element                          | Locator tạm                                                | Đề xuất                       |
| -------------------------------- | ------------------------------------------------------------ | ----------------------------- |
| People picker "Người dùng/Nhóm"  | `.cssPeoplePickerPermissionTabModal.people-picker`           | `pp-multi-loc-nguoi-dung`     |
| Dropdown lọc **Quyền**           | `div.cssDropdownSelectField` + `filter({hasText:"Quyền"})`   | `sel-loc-quyen`               |
| Dropdown lọc **Phân loại**       | `div.cssDropdownSelectField` + `filter({hasText:"Phân loại"})` | `sel-loc-phan-loai`         |

### C4-1. Nút mở rộng / thu gọn của ô quyền (bảng Phân quyền nâng cao)

Ô quyền chỉ render 2 avatar, số còn lại gom vào 1 nút text `"+ 1 người khác"`; bấm vào thì
mở rộng ngay trong ô và hiện nút thu gọn. Cả 2 nút **không có `data-testid`**, class lại là
**CSS-module có hash** (`_expand__others_k9vgr_348`) — hash đổi mỗi lần build nên script chỉ
match được theo tiền tố class + text.

| Element        | Locator tạm                                                                   | Đề xuất                |
| -------------- | ------------------------------------------------------------------------------- | ---------------------- |
| Nút mở rộng    | `[class*="_expand__others"]` hoặc text `/\+\s*\d+\s*người khác/`               | `btn-xem-them-nguoi`   |
| Nút thu gọn    | `[class*="_collapse"]`                                                         | `btn-thu-gon-nguoi`    |
| 1 avatar trong ô | `[class*="_displayLimit"]` > `.ant-tag` > `[data-testid="avatar-container"]`  | (đã có avatar-container) |

> Ảnh hưởng: mọi assert "người X có quyền Y tại item Z" đều phải bấm mở rộng trước, vì tên người
> bị gom **không tồn tại trong DOM**. Xem `KHO-TAI-LIEU.MODAL-PHAN-QUYEN-NANG-CAO.md` mục 4a-1.

### C5. Màn danh sách Kho tài liệu

| Element               | Locator tạm                    | Đề xuất              |
| --------------------- | ------------------------------ | -------------------- |
| Nút **Tải lên**       | `getByText("Tải lên")`         | `btn-tai-len`        |
| Nút **Xuất excel**    | `getByText("Xuất excel")`      | `btn-xuat-excel`     |
| Nút mở panel lọc      | icon cuối toolbar (không có gì) | `btn-hien-thi-bo-loc` |

### C6. Tab "Cấu trúc hồ sơ"

| Element                            | Locator tạm                                          | Đề xuất                  |
| ---------------------------------- | ------------------------------------------------------ | ------------------------ |
| Mũi tên dropdown cạnh nút "Tạo mới" | `.ant-tabs-tabpane-active .ant-dropdown-trigger` `.first()` | `btn-tao-moi-options` |

### C7. Field read-only "Mã hồ sơ" / "Mã tài liệu"

| Element                 | Locator tạm              | Đề xuất            |
| ----------------------- | ------------------------ | ------------------ |
| Mã hồ sơ (form BHS)     | `span#basic_soHieuBoHoSo` | `lbl-ma-ho-so`     |
| Mã tài liệu (modal TL)  | *(không có id)*           | `lbl-ma-tai-lieu`  |

### C8. Upload file (tab "Tài liệu số")

| Element                        | Locator tạm                                | Đề xuất                     |
| ------------------------------ | -------------------------------------------- | --------------------------- |
| `input[type=file]` thật        | `pane.locator('input[type="file"]').first()` | `inp-file-tai-lieu`         |

⚠️ `file-upload-tai-lieu` hiện gắn trên **`div` nút "Tải lên"**, không phải trên `input[type=file]`
→ `setInputFiles` trên testid này báo lỗi. Nên chuyển testId sang chính `input`, hoặc thêm testId mới.
Ngoài ra có **2** `input[type=file]` trong cùng pane → cần phân biệt.

### C9. ~~Ô "Tên" của modal Thêm thư mục có 2 input~~ → ✅ ĐÃ XONG

2 input đã có testId riêng: `txt-ma-thu-muc` (disabled, mã prefix) + `txt-ten-thu-muc`.

---

## D. ⚠️ testId TRÙNG giữa nhiều vùng — cần rà soát

Script buộc phải scope thủ công, dễ sai:

| testId                                                                       | Xuất hiện ở                                                   |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `btn-more`                                                                   | Header modal BHS **và** header bảng tab Cấu trúc hồ sơ (2 element cùng lúc) |
| `btn-save`, `btn-close-modal`, `lbl-modal-title`                             | Mọi modal (BHS / tài liệu / phân quyền) — chồng nhau tới 3 lớp |
| `mni-them-thu-muc`, `mni-them-tai-lieu`, `mni-tao-ho-so-lien-quan`, `mni-xoa`, `mni-nhan-ban`, `mni-cap-nhat`, `mni-keo-tha`, `mni-chuyen-len`, `mni-chuyen-xuong` | Dropdown toolbar **và** dropdown từng dòng |
| `txt-so-hieu-tai-lieu`, `sel-dang-van-ban-tl`, `txt-don-vi-phe-duyet`, `txt-don-vi-xuat-ban` | Panel lọc tab Cấu trúc **và** modal Tạo tài liệu |
| `sel-hardCopyStatus`                                                         | Form BHS **và** modal Tạo tài liệu                            |
| `pp-multi-usersRight*`                                                       | Form BHS **và** modal Tạo tài liệu                            |
| `btn-kiem-tra-phan-quyen`, `btn-dong-bo-ocr`, `btn-xoa`                      | Menu BHS **và** menu Tài liệu                                 |
| `row-nguoi-dung`, `chk-chon-nguoi-dung`, `Tree-sel-item`                      | Mọi dòng trong modal Chọn người dùng                          |

**Đề xuất chung**: với element lặp theo dữ liệu, gắn hậu tố id — đúng như cách đã làm tốt ở
`lnk-ten-ho-so-tai-lieu-<id>`, `btn-more-action-<id>`, `tbl-row-<code>`, `cell-<field>-row-<code>`.

---

## E. ⚠️ Vấn đề KHÔNG phải testId — cần DEV/BA xác nhận là đúng thiết kế

| # | Quan sát                                                                                                       | Câu hỏi                                                                    |
| - | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| 1 | Trên **màn chi tiết BHS**, cột chứa khối **"Phân quyền truy cập"** được gắn thuộc tính `hidden` (cả 2 trạng thái Khai báo/Hoạt động, cả viewport 1200 và 1600) | Cố ý ẩn hẳn (bắt buộc sửa quyền qua modal Phân quyền nâng cao), hay là bug layout? |
| 2 | Thẻ `div` bọc 2 cột có class **`flexbg-gray-200`** — nhiều khả năng thiếu dấu cách (`flex bg-gray-200`)         | Nếu đúng là typo thì layout 2 cột đang không hoạt động như thiết kế           |
| 3 | Ngắt kế thừa / khôi phục kế thừa **không có toast**, cột "Kế thừa" ở bảng cha chỉ cập nhật sau khi đóng modal con | Có nên thêm toast + refresh bảng cha ngay không?                              |
| 4 | Upload file xong **không có toast**                                                                             | Có chủ ý không?                                                               |
| 5 | Bỏ tick "Có bản vật lý" làm `sel-hardCopyStatus` **biến mất khỏi DOM** (không chỉ disable)                      | Xác nhận đúng nghiệp vụ                                                       |

---

## F. ✅ Những chỗ DEV đã làm tốt (giữ nguyên)

- Bảng **Phân quyền nâng cao**: `tbl-row-<code>`, `cell-<field>-row-<code>`, `col-header-<field>` —
  đầy đủ, ổn định, script bám rất dễ.
- Bảng **Cấu trúc hồ sơ**: `lnk-ten-ho-so-tai-lieu-<id>`, `btn-more-action-<id>`.
- Bảng **file đính kèm**: `lbl-ten-tep-row-<uuid>`, `btn-menu-row-<uuid>`, `tag-ocr-status-row-<uuid>`… đầy đủ.
- **Modal Chọn người dùng**: gần như đủ testId cho mọi control.
- **Modal Tạo tài liệu**: toàn bộ field nghiệp vụ đều có testId.
- People-picker có bộ 3: `<id>`, `<id>-select`, `<id>-orgchart-btn`.
- Menu ngữ cảnh dùng prefix `mni-` nhất quán.

---

## G. Ghi chú riêng cho AUTOMATION (không phải việc của DEV)

1. Dòng `tbl-row-` (BHS gốc) là **tiền tố** của `tbl-row-A`, và `cell-title-row-A` là tiền tố của
   `cell-title-row-A.1` → **không dùng `^=`**, phải match chính xác.
2. Field bắt buộc đánh dấu bằng `span.icon-required` trong `label` — **không phải dấu `*`**.
3. Một số field bắt buộc **không có** `icon-required` mà chỉ lộ ra khi submit
   (vd `tree-sel-submissionUnit` của modal thư mục) → danh sách bắt buộc phụ thuộc cấu hình
   "Cấu trúc hồ sơ" của từng BHS.
4. Field metadata ẩn dùng class `ant-form-item-hidden` — **vẫn có trong DOM**, phải guard
   `isVisible()` chứ không dùng `count()`.
5. Tab của **màn chi tiết tài liệu** là `input[type=radio]` ẩn → click vào `label` bọc ngoài.
6. `page.goto()` chỉ đổi query sau `#` **không** reset SPA → phải đi 2 bước qua màn danh sách.
7. Cấu hình Playwright MCP trong `.claude/settings.json` đang đặt `--test-id-attribute=data-test`,
   trong khi app dùng `data-testid` → khi khảo sát bằng MCP phải viết CSS `[data-testid="..."]`,
   `getByTestId` sẽ không khớp. (Project `playwright.config.ts` **không** override nên spec thật
   dùng `getByTestId` bình thường.)
