🧪 QA · Version 1.0

Checklist Builder · Test Scenarios
+ Playwright Automation Guide

Comprehensive test documentation for ConstructQ Checklist v16.0 · 15 test areas · 76 test cases · 10 question types · 5 industry-standard templates (มยผ./วสท./OSHA-TH/EIA) · Mobile Preview + verdict simulator
📅 วันที่: 13 ก.ค. 2569 (2026-07-13) 👤 Owner: วุธ (PM/SA · QA Lead) 🎯 Audience: QA · Dev · Automation 📄 Source: ConstructQ_Checklist_v16.0.html
15
Test Areas
76
Test Cases
10
Question Types
5+3
Industry + Sample

01บทนำ · About This Guide

Test scope · deliverables · references

เอกสารนี้เป็น Test Documentation สำหรับหน้าจอ Checklist Builder v16.0 ครอบคลุม:

  • Test Scenarios · 15 areas — List · Designer · Sections · Questions · Types · Criteria · Preview · Simulator
  • Test Cases · 76 cases — Positive / Negative / Boundary / UI / Integration / A11y
  • Playwright Automation Guide — POM + 5 sample test scripts · question-type widgets · verdict simulator
  • Industry standard coverage — 5 templates: มยผ. · วสท. · OSHA-TH · EIA · Concrete/MEP multi-section
📌
Reference file: ConstructQ_Checklist_v16.0.html · React SPA · 3 CK_SEED sample + 5 CK_TEMPLATES industry standards · 10 question types · Mobile Preview with circle stepper + type-aware widgets · verdict simulator (v7.0+)

02Screen Overview

Template list · designer · Mobile Preview · 10 question types

🗺️ Screen Map

ViewStateFunction
Template Listview=listAll checklist templates · project/WBS filter · industry standards + custom
Create/Edit Designerview=designerSections + Questions builder · buttons inline with title (v4.0)
Mobile Preview panelside panelTab strip (sections) · circle stepper (questions) · type-aware answer widget
Verdict Simulatorpreview modeTry-answer widget · shows criteria verdict (pass/warn/fail/auto_ncr)
Save Confirm ModalmodalSummary before save
Delete ConfirmmodalStrong warning · double confirm

❓ 10 Question Types

TypeWidgetAnswer OptionsCriteria Support
numberNumeric input + unitValue + unit (e.g. "Ω", "MΩ", "bar")le · ge · between (auto_ncr on fail)
textShort inputSingle-line textmanual
longtextAuto-grow textareaMulti-line textmanual
passfail2 large buttons (full-width)Pass / Failmanual (auto_ncr on Fail)
radioRadio groupOptions (e.g. ใช่/ไม่/อื่นๆ)Match specific value
checkboxMulti-checkboxMultiple optionsAll required · at-least-N
photoCamera CTA + counter1+ photos (minPhotos)Count-based
dateDate pickerDate valueBefore/after target
signatureDraw signatureSVG/canvas signaturePresence required
colorHEX pickerHex color valueMatch ΔE ≤ threshold

🌱 Seeded Templates (v8.0+)

IDName THSectionsQuestionsTypes Used
ck-001ตรวจงานสี · Paint Inspection14number · color · radio · photo
ck-002ตรวจงานคอนกรีต · Concrete (v15.0 expanded)514text · date · longtext · number · radio · passfail · checkbox · photo
ck-003ตรวจระบบ MEP · MEP System (v15.0 expanded)616All 10 types · covers all widgets

Plus 5 industry-standard CK_TEMPLATES (v8.0): มยผ. 1101-64 · วสท. 3001-56 · วสท. 4001-59 · OSHA-TH · EIA/ASHRAE — pre-written as declarative rule statements (v10.0)

📋 Criteria + Operator UI (v7.0+)

  • Operators: le (≤) · ge (≥) · between · manual (for passfail)
  • onFail action: warn (show warning) · auto_ncr (auto-create NCR)
  • severity: critical · major · minor
  • warnAbove/warnBelow: intermediate zone (yellow warning before fail)

📱 Mobile Preview (v8.0+ · v16.0 · scroll bar)

  • Tab strip: One tab per section · horizontal scroll bar when overflow (v16.0)
  • Circle stepper: Numbered circles · yellow ✓ badge on answered (v12.0)
  • Rule chip: Displays declarative rule (v10.0-11.0) · not "try to answer" widget
  • Type-aware widget: Different UI per question type
  • Only marks done if answered (v13.0)
  • Pin ✓ badge across navigation (v14.0)

03Test Strategy

Test types · priority · environments

🎯 Test Types

TypeObjectiveCoverage
PositiveCreate/Edit/Save/Delete · all 10 question types~35%
NegativeEmpty sections · duplicate questions · invalid criteria~20%
Boundary0/1 section · 100+ questions · min/max photos · ΔE bounds~10%
UI/UXMobile Preview render · circle stepper · rule chip · scroll bar~15%
IntegrationVerdict simulator · auto_ncr · localStorage · Task Mgmt link~12%
AccessibilityKeyboard · ARIA · widget semantics · touch target~8%

⚡ Priority

  • P1 — Critical · save · question types · criteria enforcement · Mobile Preview
  • P2 — High · sections · verdict simulator · duplicate · reorder
  • P3 — Medium · language toggle · UI polish · edge cases

04Test Scenarios + Test Cases

15 areas · 76 detailed test cases

4.1 · 📚 Landing + Template List

TC IDScenario StepsExpected Result PriorityType
TC-01เข้าหน้า Checklist Builder 1. Login
2. Navigate Master Data → Checklist
AppShell · breadcrumb · title "✅ Checklist Builder" · list of templates P1Pos
TC-023 CK_SEED + 5 CK_TEMPLATES render 1. View list 8 template cards: ck-001, ck-002, ck-003 + 5 industry standards P1Pos
TC-03Title/name column shown · Project/WBS columns REMOVED (v3.0) 1. Check table columns Title/version/status/updated — no Project/WBS columns P2UI
TC-04Version and roles columns REMOVED (v4.0) 1. Check list layout No version chip · no roles column in list P2UI
TC-05Create button opens designer 1. Click "➕ สร้าง Checklist ใหม่" Designer opens empty · sections + questions builder P1Pos

4.2 · ➕ Create Template

TC IDScenario StepsExpected Result PriorityType
TC-06Empty designer state 1. Click Create Empty form · 0 sections · 0 questions · action buttons inline with title (v4.0) P1Pos
TC-07Buttons inline with title (v4.0) 1. View header row Save · Cancel buttons on same row as title (not separate footer) P3UI
TC-08Required · name TH empty 1. Skip name
2. Save
Error · block save P1Neg
TC-09Name TH + EN bilingual 1. Enter both Both saved · shown per language toggle P2Pos
TC-10Description accepts multi-line 1. Type long description Textarea auto-grows · saves multi-line P2Pos

4.3 · ✏️ Edit Existing Template

TC IDScenario StepsExpected Result PriorityType
TC-11Load ck-001 pre-filled 1. Click ck-001 (Paint) 1 section + 4 questions pre-loaded · types match P1Pos
TC-12Save updates same ID 1. Edit label · Save Same ID · updated in list P1Pos
TC-13Unsaved changes warning 1. Edit · click back "มีการแก้ไข · ออกจริงไหม?" P2Neg

4.4 · 📑 Sections Editor

TC IDScenario StepsExpected Result PriorityType
TC-14Add section 1. Click "+ Section" New section · auto-id "s1", "s2" · name field P1Pos
TC-15Section name TH + EN 1. Enter nameTh + nameEn Bilingual saved · Mobile Preview tab uses current language P2Pos
TC-16Section description 1. Enter descTh + descEn Long description saved · shown at top of section in preview P3Pos
TC-17Multi-section · concrete/MEP (v15.0) 1. Load ck-002 or ck-003 5 sections (ck-002) or 6 sections (ck-003) · sections tab strip renders P1Pos
TC-18Delete section · cascade questions 1. Delete section with 5 questions Warning · confirm cascade · all 5 questions removed P1Neg
TC-19Reorder sections · drag/drop 1. Drag s2 above s1 Order updates · Mobile Preview tab order matches P2Pos

4.5 · ❓ Questions Editor

TC IDScenario StepsExpected Result PriorityType
TC-20Add question in section 1. Click "+ Question" in section New question · auto-id "q1", "q2" · type dropdown P1Pos
TC-21Question label TH + EN 1. Enter labelTh + labelEn Bilingual saved · declarative rule text (v10.0) P2Pos
TC-22Help text TH + EN 1. Enter helpTh + helpEn Tooltip/subtitle · reference material (e.g. "อ้างอิง มยผ. 1101-64") P2Pos
TC-23Required toggle 1. Toggle required=true * indicator in preview · block navigation if unanswered P1Pos
TC-24Delete question 1. Click delete on q1 Row removed · related answers cleared P1Pos
TC-25Reorder questions in section 1. Drag q2 above q1 Order updates · stepper circles order matches P2Pos
TC-26Move question between sections 1. Drag q3 from s1 to s2 Question moves · parent updates · order recalculated P2Pos

4.6 · 🎨 10 Question Types

TC IDScenario StepsExpected Result PriorityType
TC-27number · numeric input + unit 1. Add question type=number
2. Set unit "Ω"
3. Enter value in preview
Numeric input · unit chip · rejects non-numeric P1Pos
TC-28text · single-line input 1. Add type=text Single-line input · accepts any text P2Pos
TC-29longtext · auto-grow textarea 1. Add type=longtext
2. Type multi-line
Textarea grows with content P2UI
TC-30passfail · 2 full-width buttons 1. Add type=passfail
2. View preview
2 large buttons: Pass (green) · Fail (red) · full width in 2 columns P1UI
TC-31radio · options list 1. Add type=radio
2. Set options TH ["ใช่","ไม่","อื่นๆ"]
Radio group · single selection · options bilingual P1Pos
TC-32checkbox · multi-select 1. Add type=checkbox
2. Set options
Multiple checkboxes · multi-select P2Pos
TC-33photo · camera CTA + counter 1. Add type=photo
2. Set minPhotos=2
Camera button · counter "0 of 2 photos" · min enforced P1UI
TC-34date · date picker 1. Add type=date Native date picker · saves ISO date P2Pos
TC-35signature · draw signature 1. Add type=signature Canvas · finger/mouse draw · save as SVG/PNG P2UI
TC-36color · HEX picker + ΔE 1. Add type=color
2. Enter HEX #FF5733
Color picker · displays swatch · optional ΔE compare P2Pos
TC-37All 10 types render in ck-003 (MEP) 1. Open ck-003 All 10 types appear across 6 sections · widget renders correctly per type P1Pos

4.7 · ⚖️ Criteria + Operator UI (v7.0+)

TC IDScenario StepsExpected Result PriorityType
TC-38Operator "le" (≤) · number type 1. type=number
2. Set operator=le, max=2.0
3. Preview: enter 1.5
Verdict = Pass · green P1Int
TC-39Operator "le" fail · enter 2.5 1. Same as above · enter 2.5 Verdict = Fail · red · triggers onFail P1Int
TC-40Operator "ge" (≥) · Ground resistance ≤ 5 Ω 1. type=number, ge, min=5
2. Preview enter 3
Fail (3 < 5) P1Int
TC-41warnAbove/warnBelow · yellow zone 1. le max=2.0, warnAbove=1.8
2. Enter 1.9
Yellow warning (between warn and fail) P2Int
TC-42onFail = auto_ncr 1. Set onFail=auto_ncr, severity=critical
2. Fail in preview
Verdict shows "จะสร้าง NCR อัตโนมัติ" + severity chip P1Int
TC-43onFail = warn (no NCR) 1. Set onFail=warn, severity=minor
2. Fail
Warning message only · no NCR triggered P2Int
TC-44Severity chip color coded 1. View criteria critical=red · major=orange · minor=yellow P3UI

4.8 · 📷 Required Comment + Photo (v2.0)

TC IDScenario StepsExpected Result PriorityType
TC-45requirePhoto flag · shows camera 1. Set requirePhoto=true Camera button appears under question in preview P2Pos
TC-46requireComment flag · shows textarea 1. Set requireComment=true Comment textarea appears · block advance if empty P2Pos
TC-47Photo counter min enforced 1. minPhotos=2 · upload 1 photo Warning · block advance until 2 uploaded P2Bnd

4.9 · 📱 Mobile Preview (v8.0+ · v16.0)

TC IDScenario StepsExpected Result PriorityType
TC-48Preview panel renders alongside designer 1. Open designer Phone frame · shows current template in mobile-native UI P1UI
TC-49Tab strip · one per section 1. Load ck-003 (6 sections) 6 tabs at top · click to switch P1UI
TC-50Tab strip scroll bar on overflow (v16.0) 1. 6+ sections Horizontal scroll bar visible · smooth scroll P2UI
TC-51Circle stepper · numbered 1. View section with 4 questions 4 numbered circles · click to jump P1UI
TC-52Yellow ✓ badge on answered (v12.0) 1. Answer q1
2. View stepper
Circle 1 shows yellow ✓ · not filled bg P2UI
TC-53Pin ✓ across navigation (v14.0) 1. Answer q1 · go to q2 · back to q1 Yellow ✓ still on q1 · state preserved P2Pos
TC-54Only mark done if answered (v13.0) 1. Open q1 but skip · go to q2 Circle 1 · no ✓ badge (not marked done) P2Pos
TC-55Rule chip · declarative statement (v10.0-11.0) 1. View question in preview Chip shows rule text (e.g. "ค่าความต้านทานสายดิน ≤ 5 Ω") · not "answer the following" P2UI
TC-56Type-aware widget rendering 1. Cycle through all 10 types Each type shows correct mobile widget (number keyboard · passfail buttons · etc.) P1UI

4.10 · 🎯 Verdict Simulator (v7.0+)

TC IDScenario StepsExpected Result PriorityType
TC-57Try-answer widget in preview 1. Enter test value Simulator evaluates criteria · shows verdict P1Int
TC-58Pass verdict · green 1. Enter passing value Verdict: "✅ ผ่านเกณฑ์" · green background P1Int
TC-59Warn verdict · yellow zone 1. Enter warn-zone value Verdict: "⚠️ ใกล้เกณฑ์" · yellow P2Int
TC-60Fail verdict · red · auto_ncr note 1. Enter failing value Verdict: "❌ ไม่ผ่าน" · red · "จะสร้าง NCR อัตโนมัติ" P1Int
TC-61Manual criteria (passfail) · direct fail 1. type=passfail · press Fail button Verdict red · onFail triggered P1Int

4.11 · 💾 Save + Delete Confirm (v5.0)

TC IDScenario StepsExpected Result PriorityType
TC-62Save confirm popup 1. Click Save Popup summary: name · #sections · #questions · Cancel/Confirm P1Pos
TC-63Confirm · save + toast + redirect 1. Confirm Saved · toast success · redirect to list P1Pos
TC-64Strengthened Delete confirm (v5.0) 1. Click Delete Strong warning · "ไม่สามารถกู้คืนได้" · double confirm required P1Neg
TC-65Delete removes from list 1. Confirm delete Card removed · toast success P1Pos

4.12 · ↕️ Expand/Collapse All (v5.0)

TC IDScenario StepsExpected Result PriorityType
TC-66Expand All button 1. Click "Expand All" All sections expanded · all questions visible P3UI
TC-67Collapse All button 1. Click "Collapse All" All sections collapsed · headers only P3UI
TC-68Position · below Delete (v6.0) 1. Check button order Expand/Collapse is below Delete button P3UI

4.13 · 🌐 Language Toggle TH/EN

TC IDScenario StepsExpected Result PriorityType
TC-69Toggle TH → EN 1. Click EN flag All labels · sections · questions text switch to EN P1Pos
TC-70EN i18n fix (v9.0) 1. Toggle EN · check data All templates render (no missing i18n block bug) P1Pos

4.14 · 💾 Data Persistence

TC IDScenario StepsExpected Result PriorityType
TC-713 CK_SEED + 5 CK_TEMPLATES persist 1. F5 refresh All 8 templates still shown P1Int
TC-72Custom template persists 1. Create custom · reload Custom still in list P1Int
TC-73Answer state persists in preview 1. Answer q1 · switch section · come back Answer + ✓ badge preserved P2Int

4.15 · ♿ Accessibility

TC IDScenario StepsExpected Result PriorityType
TC-74Keyboard nav · designer 1. Tab through sections → questions → save Focus visible · logical order P2A11y
TC-75Mobile Preview · keyboard nav stepper 1. Tab to circle stepper · arrow keys Arrow keys move between circles · Enter answers P2A11y
TC-76Widget ARIA · passfail buttons announced 1. Screen reader on preview "Pass · button · press to select" announced correctly P2A11y
📊
Test Case Summary: รวม 76 test cases · P1 = 34 · P2 = 32 · P3 = 10 · Positive 33 · Negative 5 · Boundary 2 · Integration 9 · UI 22 · A11y 3

05Playwright Automation Guide + 5 Sample Scripts

Setup · POM · sample tests for question types · criteria · Mobile Preview

🚀 Setup

  1. Install: npm init playwright@latest
  2. Fixture reset: seed CK_SEED + CK_TEMPLATES before each test
  3. Widget testing: Query per-type widgets · use data-qtype, data-question-id
  4. Verdict simulator: Verify text contains "ผ่าน" / "ไม่ผ่าน" / "auto NCR"

📁 Project Structure

treetests/checklist/
├── list.spec.ts                # TC-01 to TC-05
├── create.spec.ts              # TC-06 to TC-10
├── edit.spec.ts                # TC-11 to TC-13
├── sections.spec.ts            # TC-14 to TC-19
├── questions.spec.ts           # TC-20 to TC-26
├── question-types.spec.ts      # TC-27 to TC-37 (11 tests · 10 types)
├── criteria.spec.ts            # TC-38 to TC-44 (7 tests)
├── required-photo.spec.ts      # TC-45 to TC-47
├── mobile-preview.spec.ts      # TC-48 to TC-56 (9 tests)
├── verdict-simulator.spec.ts   # TC-57 to TC-61 (5 tests)
├── save-delete.spec.ts         # TC-62 to TC-65
├── expand-collapse.spec.ts     # TC-66 to TC-68
├── language.spec.ts            # TC-69, TC-70
├── persistence.spec.ts         # TC-71 to TC-73
├── a11y.spec.ts                # TC-74 to TC-76
└── e2e-full-flow.spec.ts       # Composite smoke
pages/
├── ChecklistListPage.ts
├── ChecklistDesignerPage.ts
├── SectionsEditor.ts
├── QuestionsEditor.ts
└── MobilePreviewPanel.ts
fixtures/
├── ck-seed.json
└── ck-templates.json
playwright.config.ts

🎯 Page Object Model · ChecklistDesignerPage

TypeScriptimport { Page, Locator, expect } from '@playwright/test';

export type QType =
  'number' | 'text' | 'longtext' | 'passfail' | 'radio'
  | 'checkbox' | 'photo' | 'date' | 'signature' | 'color';

export type Operator = 'le' | 'ge' | 'between' | 'manual';
export type OnFail  = 'warn' | 'auto_ncr';
export type Severity= 'critical' | 'major' | 'minor';

export class ChecklistDesignerPage {
  readonly page: Page;
  readonly nameTh: Locator;
  readonly nameEn: Locator;
  readonly btnAddSection: Locator;
  readonly btnSave: Locator;
  readonly btnDelete: Locator;
  readonly btnExpandAll: Locator;
  readonly btnCollapseAll: Locator;
  readonly sections: Locator;

  constructor(page: Page) {
    this.page = page;
    this.nameTh = page.locator('#ckNameTh');
    this.nameEn = page.locator('#ckNameEn');
    this.btnAddSection = page.getByRole('button', { name: /\+ Section/ });
    this.btnSave = page.getByRole('button', { name: /บันทึก|Save/ });
    this.btnDelete = page.getByRole('button', { name: /ลบ|Delete/ });
    this.btnExpandAll = page.getByRole('button', { name: /Expand All|กางทั้งหมด/ });
    this.btnCollapseAll = page.getByRole('button', { name: /Collapse All|ยุบทั้งหมด/ });
    this.sections = page.locator('.ck-section');
  }

  async addSection(nameTh: string, nameEn: string) {
    await this.btnAddSection.click();
    const last = this.sections.last();
    await last.locator('.ck-section-nameTh').fill(nameTh);
    await last.locator('.ck-section-nameEn').fill(nameEn);
    return last;
  }

  async addQuestion(sectionIdx: number, opts: {
    type: QType; labelTh: string; labelEn: string;
    required?: boolean; requirePhoto?: boolean; requireComment?: boolean;
    unit?: string; minPhotos?: number;
    criteria?: { operator: Operator; min?: number; max?: number;
                 warnBelow?: number; warnAbove?: number;
                 onFail: OnFail; severity: Severity };
  }) {
    const section = this.sections.nth(sectionIdx);
    await section.getByRole('button', { name: /\+ Question/ }).click();
    const last = section.locator('.ck-question').last();
    await last.locator('.ck-qtype-select').selectOption(opts.type);
    await last.locator('.ck-qlabel-th').fill(opts.labelTh);
    await last.locator('.ck-qlabel-en').fill(opts.labelEn);
    if (opts.required)       await last.locator('.ck-required').check();
    if (opts.requirePhoto)   await last.locator('.ck-req-photo').check();
    if (opts.requireComment) await last.locator('.ck-req-comment').check();
    if (opts.unit)           await last.locator('.ck-unit').fill(opts.unit);
    if (opts.minPhotos)      await last.locator('.ck-min-photos').fill(String(opts.minPhotos));

    if (opts.criteria) {
      await last.locator('.ck-criteria-op').selectOption(opts.criteria.operator);
      if (opts.criteria.min !== undefined) await last.locator('.ck-criteria-min').fill(String(opts.criteria.min));
      if (opts.criteria.max !== undefined) await last.locator('.ck-criteria-max').fill(String(opts.criteria.max));
      await last.locator('.ck-criteria-onfail').selectOption(opts.criteria.onFail);
      await last.locator('.ck-criteria-severity').selectOption(opts.criteria.severity);
    }
    return last;
  }

  // Mobile Preview helpers
  preview() { return this.page.locator('.ck-mobile-preview'); }

  async tapPreviewTab(sectionIdx: number) {
    await this.preview().locator('.tab-strip [role="tab"]').nth(sectionIdx).click();
  }

  async answerNumber(questionId: string, value: number) {
    await this.preview()
      .locator(`[data-question-id="${questionId}"] input[type="number"]`)
      .fill(String(value));
  }

  async answerPassFail(questionId: string, choice: 'pass' | 'fail') {
    await this.preview()
      .locator(`[data-question-id="${questionId}"] .ck-pf-${choice}`).click();
  }

  async getVerdict(questionId: string): Promise<string> {
    const verdict = this.preview().locator(`[data-question-id="${questionId}"] .ck-verdict`);
    return (await verdict.textContent()) ?? '';
  }
}

✅ Sample Test 1 · Add All 10 Question Types

TypeScriptimport { test, expect } from '@playwright/test';
import { ChecklistDesignerPage, QType } from '../pages/ChecklistDesignerPage';

test('TC-37 · All 10 question types render correctly', async ({ page }) => {
  await page.goto('/master-data/checklist');
  await page.getByRole('button', { name: /สร้าง Checklist ใหม่/ }).click();
  const designer = new ChecklistDesignerPage(page);
  await designer.nameTh.fill('ทดสอบทุก type');
  await designer.nameEn.fill('All Types Test');
  await designer.addSection('ครบทุก type', 'All Types');

  const types: QType[] = [
    'number', 'text', 'longtext', 'passfail', 'radio',
    'checkbox', 'photo', 'date', 'signature', 'color'
  ];
  for (const [i, t] of types.entries()) {
    await designer.addQuestion(0, {
      type: t,
      labelTh: `ข้อ ${i+1} (${t})`,
      labelEn: `Q${i+1} (${t})`,
    });
  }

  // Verify preview renders all 10 widgets
  for (const t of types) {
    await expect(designer.preview().locator(`[data-qtype="${t}"]`)).toBeVisible();
  }
});

✅ Sample Test 2 · Criteria + Verdict Simulator (Pass/Warn/Fail)

TypeScripttest.describe('Verdict Simulator', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/master-data/checklist');
    await page.getByRole('button', { name: /สร้าง Checklist ใหม่/ }).click();
    const designer = new ChecklistDesignerPage(page);
    await designer.nameTh.fill('Ground resistance test');
    await designer.addSection('ไฟฟ้า', 'Electrical');
    await designer.addQuestion(0, {
      type: 'number',
      labelTh: 'ค่าความต้านทานสายดิน ≤ 5 Ω',
      labelEn: 'Ground resistance ≤ 5 Ω',
      unit: 'Ω',
      required: true,
      criteria: { operator: 'le', max: 5, warnAbove: 4, onFail: 'auto_ncr', severity: 'critical' }
    });
  });

  test('TC-38 · Pass verdict (value=3 ≤ 5)', async ({ page }) => {
    const designer = new ChecklistDesignerPage(page);
    await designer.answerNumber('q1', 3);
    const verdict = await designer.getVerdict('q1');
    expect(verdict).toContain('ผ่าน');
    await expect(designer.preview().locator('[data-question-id="q1"] .ck-verdict.pass')).toBeVisible();
  });

  test('TC-41 · Warn verdict (value=4.5 · in warn zone)', async ({ page }) => {
    const designer = new ChecklistDesignerPage(page);
    await designer.answerNumber('q1', 4.5);
    const verdict = await designer.getVerdict('q1');
    expect(verdict).toContain('ใกล้เกณฑ์');
    await expect(designer.preview().locator('[data-question-id="q1"] .ck-verdict.warn')).toBeVisible();
  });

  test('TC-42 · Fail verdict + auto_ncr note (value=8 > 5)', async ({ page }) => {
    const designer = new ChecklistDesignerPage(page);
    await designer.answerNumber('q1', 8);
    const verdict = await designer.getVerdict('q1');
    expect(verdict).toContain('ไม่ผ่าน');
    expect(verdict).toContain('สร้าง NCR อัตโนมัติ');
    await expect(designer.preview().locator('[data-question-id="q1"] .ck-verdict.fail')).toBeVisible();
  });
});

✅ Sample Test 3 · Mobile Preview · Circle Stepper Yellow ✓ Badge (v12.0)

TypeScripttest('TC-52 · Yellow ✓ badge shows on answered questions', async ({ page }) => {
  await page.goto('/master-data/checklist');
  await page.getByText('ตรวจงานสี').click();
  const designer = new ChecklistDesignerPage(page);

  // Initial: no badges on stepper
  const stepper = designer.preview().locator('.ck-stepper');
  await expect(stepper.locator('.ck-stepper-check')).toHaveCount(0);

  // Answer q1 (number) with a passing value
  await designer.answerNumber('q1', 1.5);

  // Yellow ✓ badge should appear on circle 1
  const circle1 = stepper.locator('.ck-stepper-circle').first();
  await expect(circle1.locator('.ck-stepper-check')).toBeVisible();
  await expect(circle1.locator('.ck-stepper-check')).toHaveCSS('color', 'rgb(255, 210, 63)'); // yellow

  // Navigate away and back · badge should persist (TC-53 · v14.0)
  const circle2 = stepper.locator('.ck-stepper-circle').nth(1);
  await circle2.click();
  await circle1.click();  // back to q1
  await expect(circle1.locator('.ck-stepper-check')).toBeVisible();  // still there
});

✅ Sample Test 4 · Multi-Section Tab Strip Scroll (v16.0)

TypeScripttest('TC-50 · Tab strip · horizontal scroll bar on overflow (v16.0)', async ({ page }) => {
  await page.goto('/master-data/checklist');
  await page.getByText('ตรวจระบบ MEP').click();  // ck-003 · 6 sections

  const designer = new ChecklistDesignerPage(page);
  const tabStrip = designer.preview().locator('.tab-strip');

  // Verify 6 tabs
  await expect(tabStrip.locator('[role="tab"]')).toHaveCount(6);

  // Verify scroll bar visible (overflow-x auto or scroll)
  const overflowX = await tabStrip.evaluate((el) => window.getComputedStyle(el).overflowX);
  expect(['auto', 'scroll']).toContain(overflowX);

  // Verify tabs actually overflow
  const scrollWidth = await tabStrip.evaluate((el) => el.scrollWidth);
  const clientWidth = await tabStrip.evaluate((el) => el.clientWidth);
  expect(scrollWidth).toBeGreaterThan(clientWidth);

  // Scroll to last tab and verify it's now visible
  await tabStrip.evaluate((el) => { el.scrollLeft = el.scrollWidth; });
  const lastTab = tabStrip.locator('[role="tab"]').last();
  await expect(lastTab).toBeInViewport();
});

✅ Sample Test 5 · Full E2E · Build Custom Concrete Checklist

TypeScripttest('TC-E2E-01 · Build custom concrete checklist end-to-end', async ({ page }) => {
  await page.goto('/master-data/checklist');
  await page.getByRole('button', { name: /สร้าง Checklist ใหม่/ }).click();

  const designer = new ChecklistDesignerPage(page);
  await designer.nameTh.fill('ตรวจงานคอนกรีต (Custom)');
  await designer.nameEn.fill('Concrete Custom');

  // Section A · Reference (text · date · longtext)
  await designer.addSection('A. ข้อมูลอ้างอิง', 'A. Reference');
  await designer.addQuestion(0, { type: 'text', labelTh: 'เลขที่ Drawing', labelEn: 'Drawing No.', required: true });
  await designer.addQuestion(0, { type: 'date', labelTh: 'วันที่เทคอนกรีต', labelEn: 'Pour date', required: true });

  // Section B · Delivery Check (number × 2 · radio)
  await designer.addSection('B. ตรวจรับคอนกรีต', 'B. Delivery');
  await designer.addQuestion(1, {
    type: 'number',
    labelTh: 'Slump ≤ 12 cm ตามมาตรฐาน',
    labelEn: 'Slump ≤ 12 cm per spec',
    unit: 'cm', required: true,
    criteria: { operator: 'le', max: 12, warnAbove: 10, onFail: 'auto_ncr', severity: 'major' }
  });

  // Section C · Quality Control (passfail · photo)
  await designer.addSection('C. คุณภาพงาน', 'C. Quality');
  await designer.addQuestion(2, {
    type: 'passfail',
    labelTh: 'การเทต่อเนื่อง ไม่มี Cold Joint',
    labelEn: 'Continuous pour · no cold joint',
    required: true, requirePhoto: true,
    criteria: { operator: 'manual', onFail: 'auto_ncr', severity: 'critical' }
  });

  // Verify structure in preview
  await expect(designer.preview().locator('.tab-strip [role="tab"]')).toHaveCount(3);

  // Test verdict on slump = 15 (fail · auto_ncr major)
  await designer.tapPreviewTab(1);  // Section B
  await designer.answerNumber('q1', 15);   // q1 in section B
  const verdict = await designer.getVerdict('q1');
  expect(verdict).toContain('ไม่ผ่าน');
  expect(verdict).toMatch(/NCR|auto/i);

  // Save with confirm popup
  await designer.btnSave.click();
  const dialog = page.getByRole('dialog');
  await expect(dialog).toContainText('3 sections');
  await expect(dialog).toContainText('ตรวจงานคอนกรีต');
  await dialog.getByRole('button', { name: /ยืนยัน|Confirm/ }).click();

  // Verify in list
  await expect(page.getByText('ตรวจงานคอนกรีต (Custom)')).toBeVisible();
});
💡
Best Practices: Use data-qtype · data-question-id · data-section-id attributes · reset localStorage before each test · test verdict simulator via .ck-verdict.pass/.warn/.fail class · use POM helpers answerNumber/answerPassFail for widget interactions

06Appendix

📌 Version History

  • ConstructQ_Checklist_v16.0.html · source · scroll bar on Mobile Preview Tab strip
  • เอกสารนี้ v1.0 · 13 ก.ค. 2569 · 76 test cases · Playwright + 5 sample scripts

🔗 References

  • Playwright Docs: https://playwright.dev
  • Related file: ConstructQ_Checklist_ER_v16.0.html · Tables_v16.0.xlsx
  • Industry standards: มยผ. 1101-64 · วสท. 3001-56 · วสท. 4001-59 · OSHA-TH · EIA · ASHRAE
  • Excel companion: ConstructQ_Checklist_TestScenario_v1.0.xlsx