01บทนำ · About This Guide
Test scope · deliverables · references
เอกสารนี้เป็น Test Documentation สำหรับหน้าจอ WBS Template Management v13.0 ครอบคลุม:
- Test Scenarios · 14 areas — Template library · Create/Edit/Duplicate/Delete · WBS Builder
- Test Cases · 68 cases — Positive / Negative / Boundary / UI / Integration / A11y
- Playwright Automation Guide — POM · fixtures · sample scripts · CI/CD
- WBS-specific coverage — 3-level hierarchy (L1/L2/L3) · Ref Std dropdown · Risk (H/M/L) · Hold/Witness
📌
Reference file:
ConstructQ_WBSTemplate_v13.0.html · React SPA · AppShell wrapper · 8 seeded templates (คอนโด/บ้าน/ห้าง/สนง./โรงงาน/DC/Civil/รพ.) · List + Detail + Create + Edit + Duplicate views · Save Confirm popup (v12.0+)02Screen Overview
List · Detail · Form · 8 templates · WBS Builder
🗺️ Screen Map
| View | Route/State | Function |
|---|---|---|
| Template Library (List) | ctView=list | Grid of 8 seeded template cards · icon · name · description · version |
| Template Detail | ctView=detail | Selected template info · WBS tree · inline editing (admin_org only) |
| Create Form | ctView=form · __ctMode='create' | Empty form · WBS builder · circular back icon · "🚀 บันทึกเผยแพร่" |
| Edit Form | ctView=form · __ctMode='edit' | Pre-filled with existing template data · "💾 บันทึกการแก้ไข" |
| Duplicate Form | ctView=form · __ctMode='duplicate' | Pre-filled but new ID · "🚀 สร้างเป็นเทมเพลทใหม่" |
| Save Confirm Popup | modal · v12.0+ | Summary: name, version, L1/L2/L3 count · Cancel + Confirm |
| Delete Confirm Popup | modal | Warning + Cancel/Confirm |
🌳 8 Seeded Templates
| # | ID | Icon | Name (TH) | Name (EN) | Version |
|---|---|---|---|---|---|
| 1 | condo | 🏢 | คอนโด / High-rise | Condo / High-rise | 1.1 |
| 2 | lowrise | 🏘 | บ้านจัดสรร / Low-rise | Low-rise / Housing Estate | 1.0 |
| 3 | mixed | 🏬 | ห้าง / Mixed-use | Mixed-use / Retail | 1.0 |
| 4 | office | 🏢 | อาคารสำนักงาน | Office Tower | 1.0 |
| 5 | factory | 🏭 | โรงงาน / EPC | Factory / EPC | 1.0 |
| 6 | datacenter | 💾 | Data Center | Data Center | 1.0 |
| 7 | civil | 🛣 | ถนน / สะพาน / Civil | Civil · Road & Bridge | 1.0 |
| 8 | hospital | 🏥 | โรงพยาบาล / สถาบัน | Hospital / Healthcare | 1.0 |
🏗️ WBS Builder Structure (3 Levels)
- L1 (top level) — Category name only · e.g. "งานโครงสร้าง / Structure" · fields: code (1, 2, 3…), name
- L2 (work package) — Under L1 · e.g. "งานเสาเข็ม / Piling" · fields: code (1.1, 1.2), parent, name, refStd, risk
- L3 (activity) — Under L2 · e.g. "ตอกเสาเข็ม" · fields: code (1.1.1), parent, name, refStd, risk, hw (HOLD/WITNESS/blank)
📋 Ref Std Options (grouped by category)
Standards dropdown grouped by: ACI (Concrete) · ASTM (Testing) · AISC (Steel) · ASME (Piping) · วสท./มยผ. (Thai) · OSHA (Safety) · EIA/PUE (Data Center) · buildRefStdOptions() helper
⚠️ Risk Levels + Hold/Witness Points
- Risk: H (High · red) · M (Medium · amber) · L (Low · green) — L2 + L3 rows
- Hold/Witness (L3 only): HOLD (must stop for QC) · WITNESS (QC observes) · blank (routine)
03Test Strategy
Test types · priority · environments
🎯 Test Types
| Type | Objective | Coverage |
|---|---|---|
| Positive | Happy path · create/edit/duplicate/delete succeeds | ~40% |
| Negative | Invalid input · missing required · orphan L3 | ~20% |
| Boundary | 0 rows · 100+ rows · empty name · very long name | ~10% |
| UI/UX | Layout · animation · icons · responsive | ~12% |
| Integration | localStorage · backend save · Excel import | ~10% |
| Accessibility | Keyboard · ARIA · screen reader | ~8% |
⚡ Priority
- P1 — Critical · save · delete · data integrity · WBS hierarchy
- P2 — High · validation · duplicate · inline edit
- P3 — Medium · UI polish · language toggle · edge cases
🌐 Environments
- Local: localStorage-backed (dev fixture · 8 seeded templates)
- Staging: Backend + DB · seeded via
seed_wbs_templates_v2.0.py - UAT: Real user with admin_org role · sample data validation
04Test Scenarios + Test Cases
14 areas · 68 detailed test cases
4.1 · 📚 Template Library (List View)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-01 | เข้าหน้า WBS Template Library | 1. Login (admin_org) 2. Navigate to Master Data → WBS Template |
AppShell + breadcrumb · title "🌳 คลังเทมเพลต WBS" · 8 template cards | P1 | Pos |
| TC-02 | 8 template cards render | 1. View list | 8 cards · แต่ละใบมี icon · name TH+EN · description · version chip · "แก้ไข/ทำซ้ำ" buttons | P1 | Pos |
| TC-03 | Template card icons unique | 1. View card icons | 🏢 (condo) · 🏘 (lowrise) · 🏬 (mixed) · 🏢 (office) · 🏭 (factory) · 💾 (DC) · 🛣 (civil) · 🏥 (hospital) | P3 | UI |
| TC-04 | Click card → Detail view | 1. Click "คอนโด" card | Transition to detail view · WBS tree ของ condo template แสดง | P1 | Pos |
| TC-05 | "➕ สร้างเทมเพลทใหม่" button | 1. Click "สร้างใหม่" | Empty create form · circular back icon · "🚀 บันทึกเผยแพร่" primary button | P1 | Pos |
| TC-06 | Search/Filter templates | 1. Type "คอนโด" in search | Filter to matching cards only | P2 | Pos |
4.2 · 🔍 Template Detail View
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-07 | Detail header · name + version | 1. Open template detail | Template name · version chip · description · row count summary | P1 | Pos |
| TC-08 | WBS tree structure L1→L2→L3 | 1. View WBS tree | Hierarchical rows · L1 bold · L2 indent · L3 further indent · code + name visible | P1 | UI |
| TC-09 | Risk chip color coding | 1. View L2/L3 rows | H = red · M = amber · L = green chip | P2 | UI |
| TC-10 | Hold/Witness badge on L3 | 1. View L3 rows | HOLD (red) · WITNESS (blue) · blank rows show nothing | P2 | UI |
| TC-11 | Ref Std link/reference visible | 1. View L2/L3 rows with refStd | Ref Std code (e.g. "ACI 336") shown as chip | P2 | Pos |
| TC-12 | "แก้ไข / Edit" button in detail | 1. Click Edit | Navigate to Edit form · pre-filled with template data | P1 | Pos |
| TC-13 | "ทำซ้ำ / Duplicate" button | 1. Click Duplicate | Navigate to form · pre-filled · new ID · title "ทำซ้ำเทมเพลท" | P1 | Pos |
4.3 · ✏️ Inline Edit (v10.0+)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-14 | Click template name → editable input | 1. Detail · click name text | Text swaps to <input> · focus + select · save on Enter/blur |
P2 | Pos |
| TC-15 | Click description → editable textarea | 1. Click description | <textarea> · save on Ctrl+Enter or blur |
P2 | Pos |
| TC-16 | Click version → editable input | 1. Click version chip | Input · save on Enter | P2 | Pos |
| TC-17 | Inline edit · admin_org role only | 1. Login as non-admin (e.g. qc_insp) 2. Click name |
No edit mode · read-only | P1 | Neg |
| TC-18 | Cancel edit · Escape key | 1. Open inline edit 2. Press Esc |
Reverts to original value · no save | P2 | Pos |
4.4 · ➕ Create Template
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-19 | Create form empty state | 1. Click "สร้างใหม่" | Empty form · circular ◀ back icon · title "สร้างเทมเพลท WBS ใหม่" · empty WBS builder | P1 | Pos |
| TC-20 | Circular back icon (v12.0+) | 1. View form header | Circular button ◀ (40×40 px) · not square · before title | P3 | UI |
| TC-21 | Card 1 · basic info fields | 1. View Card 1 | Icon dropdown · name TH · name EN · description · version · Default Ref Std | P2 | Pos |
| TC-22 | Required · name TH empty | 1. Skip name TH 2. Click Save |
Error under field · block save | P1 | Neg |
| TC-23 | Version format default 1.0 | 1. Leave version empty 2. Save |
Defaults to "1.0" | P2 | Bnd |
| TC-24 | Icon dropdown | 1. Open icon dropdown | Emoji list (🏢🏘🏬🏭💾🛣🏥🌉 etc.) · click to select | P3 | UI |
4.5 · 📝 Edit Existing Template
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-25 | Edit mode · data pre-filled | 1. Click "แก้ไข" on condo card | All fields + WBS rows pre-loaded · title "แก้ไขเทมเพลท" | P1 | Pos |
| TC-26 | Primary button "บันทึกการแก้ไข" | 1. View footer buttons | 💾 บันทึกการแก้ไข (not เผยแพร่) | P2 | UI |
| TC-27 | Save updates existing · same ID | 1. Edit name · Save | List view · card shows new name · same template ID (not new) | P1 | Pos |
| TC-28 | Unsaved changes · back button warning | 1. Edit · click ◀ back | Confirm dialog "มีการแก้ไข · ยังไม่บันทึก · ออกจริงไหม?" | P2 | Neg |
4.6 · 📋 Duplicate Template
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-29 | Duplicate mode · pre-filled | 1. Click "ทำซ้ำ" on condo | Form pre-filled · name = "คอนโด (สำเนา)" · new ID · title "ทำซ้ำเทมเพลท" | P1 | Pos |
| TC-30 | Primary button "สร้างเป็นเทมเพลทใหม่" | 1. View footer | 🚀 สร้างเป็นเทมเพลทใหม่ | P2 | UI |
| TC-31 | Save · creates new template (9th total) | 1. Save duplicate | List shows 9 cards · new template with new ID · original unchanged | P1 | Pos |
4.7 · 🏗️ WBS Builder (L1/L2/L3)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-32 | Add L1 row | 1. Click "+ เพิ่ม L1" | New L1 row · code auto-generated (1, 2, 3...) · empty name field · no parent | P1 | Pos |
| TC-33 | Add L2 row under L1 | 1. Click "+ L2" on L1 row #1 | New L2 row · code "1.1" · parent=1 · fields: name, refStd, risk | P1 | Pos |
| TC-34 | Add L3 row under L2 | 1. Click "+ L3" on L2 row 1.1 | New L3 row · code "1.1.1" · parent=1.1 · fields: name, refStd, risk, hw | P1 | Pos |
| TC-35 | Auto-code generation · unique | 1. Add multiple L2 under L1 2. Verify codes |
1.1, 1.2, 1.3 auto-generated · no duplicates | P1 | Pos |
| TC-36 | Delete L1 · cascade L2/L3 | 1. Delete L1 row #1 | Warning "จะลบ L2/L3 ทั้งหมด" · confirm → cascade delete | P1 | Neg |
| TC-37 | Delete L2 · cascade L3 | 1. Delete L2 row 1.1 | Warning · confirm → cascade L3 under 1.1 | P2 | Neg |
| TC-38 | Reorder rows · drag/drop or up/down | 1. Move L1 #2 above #1 | Row order updates · codes recalculate if enabled | P2 | Pos |
| TC-39 | Editable name · inline input | 1. Click L2 name 2. Type |
Inline editable · save on blur/Enter | P1 | Pos |
| TC-40 | Risk dropdown · H/M/L | 1. Click risk dropdown on L2 | Options: H (red) · M (amber) · L (green) | P2 | Pos |
| TC-41 | Hold/Witness dropdown · L3 only | 1. View L2 vs L3 columns | H/W column only appears on L3 rows · options: HOLD, WITNESS, blank | P2 | UI |
| TC-42 | 100+ rows · performance | 1. Add 100+ WBS rows 2. Scroll |
No lag · virtualization if needed · save still works | P2 | Bnd |
4.8 · 📋 Ref Std Dropdown (v11.0+)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-43 | Ref Std options grouped by category | 1. Click Ref Std dropdown on L2 | Grouped: ACI · ASTM · AISC · ASME · วสท./มยผ. · OSHA · EIA/PUE | P2 | Pos |
| TC-44 | Ref Std on L2 + L3 (same options) | 1. Open dropdown on L2 vs L3 | Same options list | P3 | Pos |
| TC-45 | Custom Ref Std · type new value | 1. Type "ISO 9001" | Accept custom · save as text (helper: buildRefStdOptions preserves) | P2 | Pos |
4.9 · 💾 Save Confirm Popup (v12.0+)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-46 | Create mode · confirm popup shows | 1. Fill create form · click "🚀 บันทึกเผยแพร่" | Popup opens · title "ยืนยันการบันทึก" · summary: name, version, L1/L2/L3 count | P1 | Pos |
| TC-47 | Edit mode · confirm popup | 1. Edit · click "บันทึกการแก้ไข" | Same popup · summary reflects edited data | P1 | Pos |
| TC-48 | Duplicate mode · confirm popup | 1. Duplicate · click "สร้างเป็นเทมเพลทใหม่" | Popup · summary + note "จะสร้างเป็นเทมเพลทใหม่" | P1 | Pos |
| TC-49 | Popup summary · L1/L2/L3 count accuracy | 1. Add 3 L1 + 5 L2 + 10 L3 2. Open popup |
Summary shows "3 · 5 · 10" exact count | P2 | Bnd |
| TC-50 | Cancel popup · no save | 1. Open popup · click Cancel | Popup closes · form still open · data unchanged | P1 | Pos |
| TC-51 | Confirm popup · save + redirect | 1. Click Confirm | Actual save · toast success · redirect to list · new/updated card shows | P1 | Pos |
4.10 · 🗑️ Delete Confirm
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-52 | Delete template · confirm dialog | 1. Delete condo template | Modal "ลบเทมเพลท? · จะกู้ไม่ได้" · Cancel/Confirm | P1 | Pos |
| TC-53 | Cancel delete | 1. Click Cancel | Modal closes · template ยังอยู่ | P1 | Pos |
| TC-54 | Confirm delete · removed from list | 1. Confirm delete | Template หายจาก list · toast success | P1 | Pos |
| TC-55 | Delete seeded template · restore not possible | 1. Delete "condo" · refresh | Still gone (unless re-seed script run) | P2 | Neg |
4.11 · 🌐 Language Toggle TH/EN
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-56 | Toggle TH → EN | 1. Click EN flag in AppShell topbar | Title · buttons · labels · placeholders update to EN · TH template names unchanged | P1 | Pos |
| TC-57 | Data preserved on toggle | 1. Fill create form · toggle EN · toggle back | Data intact | P1 | Pos |
4.12 · 🔐 RBAC (admin_org only)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-58 | admin_org · full access | 1. Login as admin_org | Can view · create · edit · duplicate · delete · inline edit | P1 | Pos |
| TC-59 | Non-admin · read-only | 1. Login as qc_insp 2. Try Edit |
Buttons disabled/hidden · inline edit disabled · read-only view | P1 | Neg |
| TC-60 | Menu not visible for non-admin | 1. Login as ceo/qc_insp | WBS Template menu hidden from sidebar (or visible read-only) | P2 | Neg |
4.13 · 💾 Data Persistence
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-61 | Refresh · seed 8 templates persist | 1. F5 reload | 8 seed templates still shown (from localStorage or backend) | P1 | Int |
| TC-62 | Create new · persists after refresh | 1. Create custom · reload | Custom template still in list | P1 | Int |
| TC-63 | Delete · persists after refresh | 1. Delete · reload | Template stays deleted | P1 | Int |
| TC-64 | localStorage clear · fallback to 8 seed | 1. DevTools · clear localStorage · reload | Reverts to 8 seeded templates | P3 | Int |
4.14 · ♿ Accessibility
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-65 | Keyboard navigation full flow | 1. Tab through list → detail → form | Focus visible · logical order · Enter activates | P2 | A11y |
| TC-66 | WBS Builder · keyboard add/delete rows | 1. Tab to "+ L1" · press Enter | Row added via keyboard | P2 | A11y |
| TC-67 | Inline edit · keyboard trigger | 1. Tab to name · press Enter | Enter edit mode · Esc to cancel | P2 | A11y |
| TC-68 | Contrast · WCAG AA | 1. Run axe-core | All text ≥ 4.5:1 contrast · risk chips visible | P2 | A11y |
📊
Test Case Summary: รวม 68 test cases · P1 = 28 · P2 = 30 · P3 = 10 · Positive 33 · Negative 8 · Boundary 4 · Integration 4 · UI 9 · A11y 4
05Playwright Automation Guide
Setup · POM · sample tests · CI/CD
🚀 Setup
- Install:
npm init playwright@latest· TypeScript - Structure:
tests/wbs-template/,pages/,fixtures/templates.json - Seed data fixture: Use
seed_wbs_templates_v2.0.pybefore test run - Reset before each test: Clear localStorage or reset DB to 8 seed templates
- Auth: Login as admin_org (has full access to WBS Template)
📁 Project Structure
treetests/wbs-template/ ├── list.spec.ts # TC-01 to TC-06 ├── detail.spec.ts # TC-07 to TC-13 ├── inline-edit.spec.ts # TC-14 to TC-18 ├── create.spec.ts # TC-19 to TC-24 ├── edit.spec.ts # TC-25 to TC-28 ├── duplicate.spec.ts # TC-29 to TC-31 ├── wbs-builder.spec.ts # TC-32 to TC-42 ├── ref-std.spec.ts # TC-43 to TC-45 ├── save-confirm.spec.ts # TC-46 to TC-51 ├── delete.spec.ts # TC-52 to TC-55 ├── language.spec.ts # TC-56, TC-57 ├── rbac.spec.ts # TC-58 to TC-60 ├── persistence.spec.ts # TC-61 to TC-64 ├── a11y.spec.ts # TC-65 to TC-68 └── e2e-full-flow.spec.ts # Composite smoke pages/ ├── WBSTemplateListPage.ts ├── WBSTemplateFormPage.ts ├── WBSBuilder.ts └── SaveConfirmModal.ts fixtures/ ├── templates.json # 8 seeded templates └── refstd-options.json playwright.config.ts
🎯 Page Object Model · WBSTemplateFormPage
TypeScriptimport { Page, Locator, expect } from '@playwright/test'; export class WBSTemplateFormPage { readonly page: Page; readonly btnBack: Locator; readonly nameInputTh: Locator; readonly nameInputEn: Locator; readonly versionInput: Locator; readonly descTextarea: Locator; readonly btnAddL1: Locator; readonly btnPrimary: Locator; constructor(page: Page) { this.page = page; this.btnBack = page.locator('.ct-back-icon'); this.nameInputTh = page.locator('#ctNameTh'); this.nameInputEn = page.locator('#ctNameEn'); this.versionInput = page.locator('#ctVersion'); this.descTextarea = page.locator('#ctDesc'); this.btnAddL1 = page.getByRole('button', { name: /เพิ่ม L1|Add L1/ }); this.btnPrimary = page.locator('.ct-btn-primary'); } async fillBasicInfo(data: { nameTh: string; nameEn: string; version?: string; desc?: string }) { await this.nameInputTh.fill(data.nameTh); await this.nameInputEn.fill(data.nameEn); if (data.version) await this.versionInput.fill(data.version); if (data.desc) await this.descTextarea.fill(data.desc); } async addL1Row(name: string) { await this.btnAddL1.click(); const lastL1 = this.page.locator('.wb-row.L1').last(); await lastL1.locator('.wb-name-input').fill(name); } async addL2Row(l1Code: string, name: string, refStd?: string, risk?: 'H'|'M'|'L') { const l1 = this.page.locator(`.wb-row.L1[data-code="${l1Code}"]`); await l1.getByRole('button', { name: /\+ L2/ }).click(); const lastL2 = this.page.locator('.wb-row.L2').last(); await lastL2.locator('.wb-name-input').fill(name); if (refStd) await lastL2.locator('.wb-refstd').selectOption(refStd); if (risk) await lastL2.locator('.wb-risk').selectOption(risk); } async addL3Row(l2Code: string, name: string, opts: { refStd?: string; risk?: 'H'|'M'|'L'; hw?: 'HOLD'|'WITNESS' } = {}) { const l2 = this.page.locator(`.wb-row.L2[data-code="${l2Code}"]`); await l2.getByRole('button', { name: /\+ L3/ }).click(); const lastL3 = this.page.locator('.wb-row.L3').last(); await lastL3.locator('.wb-name-input').fill(name); if (opts.refStd) await lastL3.locator('.wb-refstd').selectOption(opts.refStd); if (opts.risk) await lastL3.locator('.wb-risk').selectOption(opts.risk); if (opts.hw) await lastL3.locator('.wb-hw').selectOption(opts.hw); } async submitSave() { await this.btnPrimary.click(); // wait for Save Confirm popup (v12.0+) await this.page.getByRole('dialog').waitFor({ state: 'visible' }); } }
✅ Sample Tests
TypeScriptimport { test, expect } from '@playwright/test'; import { WBSTemplateFormPage } from '../pages/WBSTemplateFormPage'; test.describe('WBS Template · E2E', () => { test.beforeEach(async ({ page }) => { await page.goto('/master-data/wbs-template'); }); test('TC-02 · 8 seeded templates render', async ({ page }) => { const cards = page.locator('.ct-card'); await expect(cards).toHaveCount(8); for (const id of ['condo', 'lowrise', 'mixed', 'office', 'factory', 'datacenter', 'civil', 'hospital']) { await expect(page.locator(`[data-template-id="${id}"]`)).toBeVisible(); } }); test('TC-19+46 · Create template + Save Confirm popup', async ({ page }) => { await page.getByRole('button', { name: /สร้างเทมเพลทใหม่|New Template/ }).click(); const form = new WBSTemplateFormPage(page); await form.fillBasicInfo({ nameTh: 'โรงเรียน / School', nameEn: 'School Building', version: '1.0', desc: 'อาคารเรียน 4 ชั้น' }); await form.addL1Row('งานโครงสร้าง'); await form.addL2Row('1', 'งานเสาเข็ม', 'ACI 336', 'H'); await form.addL3Row('1.1', 'ตอกเสาเข็ม', { refStd: 'ACI 336', risk: 'H', hw: 'WITNESS' }); await form.submitSave(); // Save Confirm popup (v12.0+) const dialog = page.getByRole('dialog'); await expect(dialog).toContainText('ยืนยันการบันทึก'); await expect(dialog).toContainText('โรงเรียน'); await expect(dialog).toContainText('1 · 1 · 1'); // L1/L2/L3 count await dialog.getByRole('button', { name: /ยืนยัน/ }).click(); // List view shows 9 cards await expect(page.locator('.ct-card')).toHaveCount(9); await expect(page.getByText('โรงเรียน')).toBeVisible(); }); test('TC-29 · Duplicate condo template', async ({ page }) => { await page.locator('[data-template-id="condo"]') .getByRole('button', { name: /ทำซ้ำ|Duplicate/ }).click(); await expect(page.getByText(/ทำซ้ำเทมเพลท|Duplicate Template/)).toBeVisible(); const nameField = page.locator('#ctNameTh'); await expect(nameField).toHaveValue(/สำเนา/); const form = new WBSTemplateFormPage(page); await form.submitSave(); await page.getByRole('button', { name: /ยืนยัน/ }).click(); await expect(page.locator('.ct-card')).toHaveCount(9); }); test('TC-59 · Non-admin role · read-only', async ({ page, context }) => { // Set role cookie/session to qc_insp await context.addCookies([{ name: 'role', value: 'qc_insp', url: 'http://localhost:3000' }]); await page.reload(); const editBtn = page.locator('[data-template-id="condo"]') .getByRole('button', { name: /แก้ไข|Edit/ }); await expect(editBtn).toBeHidden(); // or toBeDisabled() }); test('TC-36 · Delete L1 cascades L2/L3', async ({ page }) => { await page.locator('[data-template-id="condo"]') .getByRole('button', { name: /แก้ไข/ }).click(); const initialL2Count = await page.locator('.wb-row.L2').count(); const initialL3Count = await page.locator('.wb-row.L3').count(); await page.locator('.wb-row.L1').first() .getByRole('button', { name: /ลบ|Delete/ }).click(); await page.getByRole('button', { name: /ยืนยัน|Confirm/ }).click(); await expect(async () => { const l2 = await page.locator('.wb-row.L2').count(); const l3 = await page.locator('.wb-row.L3').count(); expect(l2).toBeLessThan(initialL2Count); expect(l3).toBeLessThan(initialL3Count); }).toPass(); }); });
💾 localStorage Fixture Reset (before each test)
TypeScript// tests/setup.ts import seed from '../fixtures/templates.json'; test.beforeEach(async ({ page }) => { await page.addInitScript((seedData) => { localStorage.setItem('wbs_templates', JSON.stringify(seedData)); }, seed); await page.goto('/master-data/wbs-template'); });
💡
Best Practices: Use
data-template-id attribute for reliable selectors · POM per view (List/Form/Builder/Modal) · verify counts before/after operations · reset localStorage before each test · use Save Confirm popup as a checkpoint (dialog role)06Appendix
📌 Version History
- ConstructQ_WBSTemplate_v13.0.html · source · Save Confirm reliability fix
- เอกสารนี้ v1.0 · 13 ก.ค. 2569 · Initial release · 68 test cases · Playwright automation
🔗 References
- Playwright Docs:
https://playwright.dev - Related file:
seed_wbs_templates_v2.0.py·ConstructQ_WBSTemplate_Tables_v13.0.xlsx·ConstructQ_WBSTemplate_ER_v13.0.html - Excel companion:
ConstructQ_WBSTemplate_TestScenario_v1.0.xlsx
🎯 8 Seeded Templates Reference
| ID | Name TH | Est. WBS Rows |
|---|---|---|
condo | คอนโด / High-rise | ~40 rows |
lowrise | บ้านจัดสรร / Low-rise | ~30 rows |
mixed | ห้าง / Mixed-use | ~35 rows |
office | อาคารสำนักงาน | ~35 rows |
factory | โรงงาน / EPC | ~45 rows |
datacenter | Data Center | ~50 rows |
civil | ถนน / สะพาน / Civil | ~30 rows |
hospital | โรงพยาบาล / สถาบัน | ~40 rows |