🧪 QA · Version 1.0

WBS Template Management · Test Scenarios
+ Playwright Automation Guide

Comprehensive test documentation for ConstructQ WBS Template Library v13.0 · 14 test areas · 68 test cases · Playwright automation for List/Create/Edit/Duplicate/Delete/WBS Builder
📅 วันที่: 13 ก.ค. 2569 (2026-07-13) 👤 Owner: วุธ (PM/SA · QA Lead) 🎯 Audience: QA · Dev · Automation 📄 Source: ConstructQ_WBSTemplate_v13.0.html
14
Test Areas
68
Test Cases
8
Seeded Templates
3
Levels (L1/L2/L3)

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

ViewRoute/StateFunction
Template Library (List)ctView=listGrid of 8 seeded template cards · icon · name · description · version
Template DetailctView=detailSelected template info · WBS tree · inline editing (admin_org only)
Create FormctView=form · __ctMode='create'Empty form · WBS builder · circular back icon · "🚀 บันทึกเผยแพร่"
Edit FormctView=form · __ctMode='edit'Pre-filled with existing template data · "💾 บันทึกการแก้ไข"
Duplicate FormctView=form · __ctMode='duplicate'Pre-filled but new ID · "🚀 สร้างเป็นเทมเพลทใหม่"
Save Confirm Popupmodal · v12.0+Summary: name, version, L1/L2/L3 count · Cancel + Confirm
Delete Confirm PopupmodalWarning + Cancel/Confirm

🌳 8 Seeded Templates

#IDIconName (TH)Name (EN)Version
1condo🏢คอนโด / High-riseCondo / High-rise1.1
2lowrise🏘บ้านจัดสรร / Low-riseLow-rise / Housing Estate1.0
3mixed🏬ห้าง / Mixed-useMixed-use / Retail1.0
4office🏢อาคารสำนักงานOffice Tower1.0
5factory🏭โรงงาน / EPCFactory / EPC1.0
6datacenter💾Data CenterData Center1.0
7civil🛣ถนน / สะพาน / CivilCivil · Road & Bridge1.0
8hospital🏥โรงพยาบาล / สถาบันHospital / Healthcare1.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

TypeObjectiveCoverage
PositiveHappy path · create/edit/duplicate/delete succeeds~40%
NegativeInvalid input · missing required · orphan L3~20%
Boundary0 rows · 100+ rows · empty name · very long name~10%
UI/UXLayout · animation · icons · responsive~12%
IntegrationlocalStorage · backend save · Excel import~10%
AccessibilityKeyboard · 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 IDScenario StepsExpected Result PriorityType
TC-01เข้าหน้า WBS Template Library 1. Login (admin_org)
2. Navigate to Master Data → WBS Template
AppShell + breadcrumb · title "🌳 คลังเทมเพลต WBS" · 8 template cards P1Pos
TC-028 template cards render 1. View list 8 cards · แต่ละใบมี icon · name TH+EN · description · version chip · "แก้ไข/ทำซ้ำ" buttons P1Pos
TC-03Template card icons unique 1. View card icons 🏢 (condo) · 🏘 (lowrise) · 🏬 (mixed) · 🏢 (office) · 🏭 (factory) · 💾 (DC) · 🛣 (civil) · 🏥 (hospital) P3UI
TC-04Click card → Detail view 1. Click "คอนโด" card Transition to detail view · WBS tree ของ condo template แสดง P1Pos
TC-05"➕ สร้างเทมเพลทใหม่" button 1. Click "สร้างใหม่" Empty create form · circular back icon · "🚀 บันทึกเผยแพร่" primary button P1Pos
TC-06Search/Filter templates 1. Type "คอนโด" in search Filter to matching cards only P2Pos

4.2 · 🔍 Template Detail View

TC IDScenario StepsExpected Result PriorityType
TC-07Detail header · name + version 1. Open template detail Template name · version chip · description · row count summary P1Pos
TC-08WBS tree structure L1→L2→L3 1. View WBS tree Hierarchical rows · L1 bold · L2 indent · L3 further indent · code + name visible P1UI
TC-09Risk chip color coding 1. View L2/L3 rows H = red · M = amber · L = green chip P2UI
TC-10Hold/Witness badge on L3 1. View L3 rows HOLD (red) · WITNESS (blue) · blank rows show nothing P2UI
TC-11Ref Std link/reference visible 1. View L2/L3 rows with refStd Ref Std code (e.g. "ACI 336") shown as chip P2Pos
TC-12"แก้ไข / Edit" button in detail 1. Click Edit Navigate to Edit form · pre-filled with template data P1Pos
TC-13"ทำซ้ำ / Duplicate" button 1. Click Duplicate Navigate to form · pre-filled · new ID · title "ทำซ้ำเทมเพลท" P1Pos

4.3 · ✏️ Inline Edit (v10.0+)

TC IDScenario StepsExpected Result PriorityType
TC-14Click template name → editable input 1. Detail · click name text Text swaps to <input> · focus + select · save on Enter/blur P2Pos
TC-15Click description → editable textarea 1. Click description <textarea> · save on Ctrl+Enter or blur P2Pos
TC-16Click version → editable input 1. Click version chip Input · save on Enter P2Pos
TC-17Inline edit · admin_org role only 1. Login as non-admin (e.g. qc_insp)
2. Click name
No edit mode · read-only P1Neg
TC-18Cancel edit · Escape key 1. Open inline edit
2. Press Esc
Reverts to original value · no save P2Pos

4.4 · ➕ Create Template

TC IDScenario StepsExpected Result PriorityType
TC-19Create form empty state 1. Click "สร้างใหม่" Empty form · circular ◀ back icon · title "สร้างเทมเพลท WBS ใหม่" · empty WBS builder P1Pos
TC-20Circular back icon (v12.0+) 1. View form header Circular button ◀ (40×40 px) · not square · before title P3UI
TC-21Card 1 · basic info fields 1. View Card 1 Icon dropdown · name TH · name EN · description · version · Default Ref Std P2Pos
TC-22Required · name TH empty 1. Skip name TH
2. Click Save
Error under field · block save P1Neg
TC-23Version format default 1.0 1. Leave version empty
2. Save
Defaults to "1.0" P2Bnd
TC-24Icon dropdown 1. Open icon dropdown Emoji list (🏢🏘🏬🏭💾🛣🏥🌉 etc.) · click to select P3UI

4.5 · 📝 Edit Existing Template

TC IDScenario StepsExpected Result PriorityType
TC-25Edit mode · data pre-filled 1. Click "แก้ไข" on condo card All fields + WBS rows pre-loaded · title "แก้ไขเทมเพลท" P1Pos
TC-26Primary button "บันทึกการแก้ไข" 1. View footer buttons 💾 บันทึกการแก้ไข (not เผยแพร่) P2UI
TC-27Save updates existing · same ID 1. Edit name · Save List view · card shows new name · same template ID (not new) P1Pos
TC-28Unsaved changes · back button warning 1. Edit · click ◀ back Confirm dialog "มีการแก้ไข · ยังไม่บันทึก · ออกจริงไหม?" P2Neg

4.6 · 📋 Duplicate Template

TC IDScenario StepsExpected Result PriorityType
TC-29Duplicate mode · pre-filled 1. Click "ทำซ้ำ" on condo Form pre-filled · name = "คอนโด (สำเนา)" · new ID · title "ทำซ้ำเทมเพลท" P1Pos
TC-30Primary button "สร้างเป็นเทมเพลทใหม่" 1. View footer 🚀 สร้างเป็นเทมเพลทใหม่ P2UI
TC-31Save · creates new template (9th total) 1. Save duplicate List shows 9 cards · new template with new ID · original unchanged P1Pos

4.7 · 🏗️ WBS Builder (L1/L2/L3)

TC IDScenario StepsExpected Result PriorityType
TC-32Add L1 row 1. Click "+ เพิ่ม L1" New L1 row · code auto-generated (1, 2, 3...) · empty name field · no parent P1Pos
TC-33Add L2 row under L1 1. Click "+ L2" on L1 row #1 New L2 row · code "1.1" · parent=1 · fields: name, refStd, risk P1Pos
TC-34Add 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 P1Pos
TC-35Auto-code generation · unique 1. Add multiple L2 under L1
2. Verify codes
1.1, 1.2, 1.3 auto-generated · no duplicates P1Pos
TC-36Delete L1 · cascade L2/L3 1. Delete L1 row #1 Warning "จะลบ L2/L3 ทั้งหมด" · confirm → cascade delete P1Neg
TC-37Delete L2 · cascade L3 1. Delete L2 row 1.1 Warning · confirm → cascade L3 under 1.1 P2Neg
TC-38Reorder rows · drag/drop or up/down 1. Move L1 #2 above #1 Row order updates · codes recalculate if enabled P2Pos
TC-39Editable name · inline input 1. Click L2 name
2. Type
Inline editable · save on blur/Enter P1Pos
TC-40Risk dropdown · H/M/L 1. Click risk dropdown on L2 Options: H (red) · M (amber) · L (green) P2Pos
TC-41Hold/Witness dropdown · L3 only 1. View L2 vs L3 columns H/W column only appears on L3 rows · options: HOLD, WITNESS, blank P2UI
TC-42100+ rows · performance 1. Add 100+ WBS rows
2. Scroll
No lag · virtualization if needed · save still works P2Bnd

4.8 · 📋 Ref Std Dropdown (v11.0+)

TC IDScenario StepsExpected Result PriorityType
TC-43Ref Std options grouped by category 1. Click Ref Std dropdown on L2 Grouped: ACI · ASTM · AISC · ASME · วสท./มยผ. · OSHA · EIA/PUE P2Pos
TC-44Ref Std on L2 + L3 (same options) 1. Open dropdown on L2 vs L3 Same options list P3Pos
TC-45Custom Ref Std · type new value 1. Type "ISO 9001" Accept custom · save as text (helper: buildRefStdOptions preserves) P2Pos

4.9 · 💾 Save Confirm Popup (v12.0+)

TC IDScenario StepsExpected Result PriorityType
TC-46Create mode · confirm popup shows 1. Fill create form · click "🚀 บันทึกเผยแพร่" Popup opens · title "ยืนยันการบันทึก" · summary: name, version, L1/L2/L3 count P1Pos
TC-47Edit mode · confirm popup 1. Edit · click "บันทึกการแก้ไข" Same popup · summary reflects edited data P1Pos
TC-48Duplicate mode · confirm popup 1. Duplicate · click "สร้างเป็นเทมเพลทใหม่" Popup · summary + note "จะสร้างเป็นเทมเพลทใหม่" P1Pos
TC-49Popup summary · L1/L2/L3 count accuracy 1. Add 3 L1 + 5 L2 + 10 L3
2. Open popup
Summary shows "3 · 5 · 10" exact count P2Bnd
TC-50Cancel popup · no save 1. Open popup · click Cancel Popup closes · form still open · data unchanged P1Pos
TC-51Confirm popup · save + redirect 1. Click Confirm Actual save · toast success · redirect to list · new/updated card shows P1Pos

4.10 · 🗑️ Delete Confirm

TC IDScenario StepsExpected Result PriorityType
TC-52Delete template · confirm dialog 1. Delete condo template Modal "ลบเทมเพลท? · จะกู้ไม่ได้" · Cancel/Confirm P1Pos
TC-53Cancel delete 1. Click Cancel Modal closes · template ยังอยู่ P1Pos
TC-54Confirm delete · removed from list 1. Confirm delete Template หายจาก list · toast success P1Pos
TC-55Delete seeded template · restore not possible 1. Delete "condo" · refresh Still gone (unless re-seed script run) P2Neg

4.11 · 🌐 Language Toggle TH/EN

TC IDScenario StepsExpected Result PriorityType
TC-56Toggle TH → EN 1. Click EN flag in AppShell topbar Title · buttons · labels · placeholders update to EN · TH template names unchanged P1Pos
TC-57Data preserved on toggle 1. Fill create form · toggle EN · toggle back Data intact P1Pos

4.12 · 🔐 RBAC (admin_org only)

TC IDScenario StepsExpected Result PriorityType
TC-58admin_org · full access 1. Login as admin_org Can view · create · edit · duplicate · delete · inline edit P1Pos
TC-59Non-admin · read-only 1. Login as qc_insp
2. Try Edit
Buttons disabled/hidden · inline edit disabled · read-only view P1Neg
TC-60Menu not visible for non-admin 1. Login as ceo/qc_insp WBS Template menu hidden from sidebar (or visible read-only) P2Neg

4.13 · 💾 Data Persistence

TC IDScenario StepsExpected Result PriorityType
TC-61Refresh · seed 8 templates persist 1. F5 reload 8 seed templates still shown (from localStorage or backend) P1Int
TC-62Create new · persists after refresh 1. Create custom · reload Custom template still in list P1Int
TC-63Delete · persists after refresh 1. Delete · reload Template stays deleted P1Int
TC-64localStorage clear · fallback to 8 seed 1. DevTools · clear localStorage · reload Reverts to 8 seeded templates P3Int

4.14 · ♿ Accessibility

TC IDScenario StepsExpected Result PriorityType
TC-65Keyboard navigation full flow 1. Tab through list → detail → form Focus visible · logical order · Enter activates P2A11y
TC-66WBS Builder · keyboard add/delete rows 1. Tab to "+ L1" · press Enter Row added via keyboard P2A11y
TC-67Inline edit · keyboard trigger 1. Tab to name · press Enter Enter edit mode · Esc to cancel P2A11y
TC-68Contrast · WCAG AA 1. Run axe-core All text ≥ 4.5:1 contrast · risk chips visible P2A11y
📊
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

  1. Install: npm init playwright@latest · TypeScript
  2. Structure: tests/wbs-template/, pages/, fixtures/templates.json
  3. Seed data fixture: Use seed_wbs_templates_v2.0.py before test run
  4. Reset before each test: Clear localStorage or reset DB to 8 seed templates
  5. 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

IDName THEst. WBS Rows
condoคอนโด / High-rise~40 rows
lowriseบ้านจัดสรร / Low-rise~30 rows
mixedห้าง / Mixed-use~35 rows
officeอาคารสำนักงาน~35 rows
factoryโรงงาน / EPC~45 rows
datacenterData Center~50 rows
civilถนน / สะพาน / Civil~30 rows
hospitalโรงพยาบาล / สถาบัน~40 rows