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
| View | State | Function |
|---|---|---|
| Template List | view=list | All checklist templates · project/WBS filter · industry standards + custom |
| Create/Edit Designer | view=designer | Sections + Questions builder · buttons inline with title (v4.0) |
| Mobile Preview panel | side panel | Tab strip (sections) · circle stepper (questions) · type-aware answer widget |
| Verdict Simulator | preview mode | Try-answer widget · shows criteria verdict (pass/warn/fail/auto_ncr) |
| Save Confirm Modal | modal | Summary before save |
| Delete Confirm | modal | Strong warning · double confirm |
❓ 10 Question Types
| Type | Widget | Answer Options | Criteria Support |
|---|---|---|---|
| number | Numeric input + unit | Value + unit (e.g. "Ω", "MΩ", "bar") | le · ge · between (auto_ncr on fail) |
| text | Short input | Single-line text | manual |
| longtext | Auto-grow textarea | Multi-line text | manual |
| passfail | 2 large buttons (full-width) | Pass / Fail | manual (auto_ncr on Fail) |
| radio | Radio group | Options (e.g. ใช่/ไม่/อื่นๆ) | Match specific value |
| checkbox | Multi-checkbox | Multiple options | All required · at-least-N |
| photo | Camera CTA + counter | 1+ photos (minPhotos) | Count-based |
| date | Date picker | Date value | Before/after target |
| signature | Draw signature | SVG/canvas signature | Presence required |
| color | HEX picker | Hex color value | Match ΔE ≤ threshold |
🌱 Seeded Templates (v8.0+)
| ID | Name TH | Sections | Questions | Types Used |
|---|---|---|---|---|
ck-001 | ตรวจงานสี · Paint Inspection | 1 | 4 | number · color · radio · photo |
ck-002 | ตรวจงานคอนกรีต · Concrete (v15.0 expanded) | 5 | 14 | text · date · longtext · number · radio · passfail · checkbox · photo |
ck-003 | ตรวจระบบ MEP · MEP System (v15.0 expanded) | 6 | 16 | All 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
| Type | Objective | Coverage |
|---|---|---|
| Positive | Create/Edit/Save/Delete · all 10 question types | ~35% |
| Negative | Empty sections · duplicate questions · invalid criteria | ~20% |
| Boundary | 0/1 section · 100+ questions · min/max photos · ΔE bounds | ~10% |
| UI/UX | Mobile Preview render · circle stepper · rule chip · scroll bar | ~15% |
| Integration | Verdict simulator · auto_ncr · localStorage · Task Mgmt link | ~12% |
| Accessibility | Keyboard · 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 ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-01 | เข้าหน้า Checklist Builder | 1. Login 2. Navigate Master Data → Checklist |
AppShell · breadcrumb · title "✅ Checklist Builder" · list of templates | P1 | Pos |
| TC-02 | 3 CK_SEED + 5 CK_TEMPLATES render | 1. View list | 8 template cards: ck-001, ck-002, ck-003 + 5 industry standards | P1 | Pos |
| TC-03 | Title/name column shown · Project/WBS columns REMOVED (v3.0) | 1. Check table columns | Title/version/status/updated — no Project/WBS columns | P2 | UI |
| TC-04 | Version and roles columns REMOVED (v4.0) | 1. Check list layout | No version chip · no roles column in list | P2 | UI |
| TC-05 | Create button opens designer | 1. Click "➕ สร้าง Checklist ใหม่" | Designer opens empty · sections + questions builder | P1 | Pos |
4.2 · ➕ Create Template
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-06 | Empty designer state | 1. Click Create | Empty form · 0 sections · 0 questions · action buttons inline with title (v4.0) | P1 | Pos |
| TC-07 | Buttons inline with title (v4.0) | 1. View header row | Save · Cancel buttons on same row as title (not separate footer) | P3 | UI |
| TC-08 | Required · name TH empty | 1. Skip name 2. Save |
Error · block save | P1 | Neg |
| TC-09 | Name TH + EN bilingual | 1. Enter both | Both saved · shown per language toggle | P2 | Pos |
| TC-10 | Description accepts multi-line | 1. Type long description | Textarea auto-grows · saves multi-line | P2 | Pos |
4.3 · ✏️ Edit Existing Template
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-11 | Load ck-001 pre-filled | 1. Click ck-001 (Paint) | 1 section + 4 questions pre-loaded · types match | P1 | Pos |
| TC-12 | Save updates same ID | 1. Edit label · Save | Same ID · updated in list | P1 | Pos |
| TC-13 | Unsaved changes warning | 1. Edit · click back | "มีการแก้ไข · ออกจริงไหม?" | P2 | Neg |
4.4 · 📑 Sections Editor
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-14 | Add section | 1. Click "+ Section" | New section · auto-id "s1", "s2" · name field | P1 | Pos |
| TC-15 | Section name TH + EN | 1. Enter nameTh + nameEn | Bilingual saved · Mobile Preview tab uses current language | P2 | Pos |
| TC-16 | Section description | 1. Enter descTh + descEn | Long description saved · shown at top of section in preview | P3 | Pos |
| TC-17 | Multi-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 | P1 | Pos |
| TC-18 | Delete section · cascade questions | 1. Delete section with 5 questions | Warning · confirm cascade · all 5 questions removed | P1 | Neg |
| TC-19 | Reorder sections · drag/drop | 1. Drag s2 above s1 | Order updates · Mobile Preview tab order matches | P2 | Pos |
4.5 · ❓ Questions Editor
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-20 | Add question in section | 1. Click "+ Question" in section | New question · auto-id "q1", "q2" · type dropdown | P1 | Pos |
| TC-21 | Question label TH + EN | 1. Enter labelTh + labelEn | Bilingual saved · declarative rule text (v10.0) | P2 | Pos |
| TC-22 | Help text TH + EN | 1. Enter helpTh + helpEn | Tooltip/subtitle · reference material (e.g. "อ้างอิง มยผ. 1101-64") | P2 | Pos |
| TC-23 | Required toggle | 1. Toggle required=true | * indicator in preview · block navigation if unanswered | P1 | Pos |
| TC-24 | Delete question | 1. Click delete on q1 | Row removed · related answers cleared | P1 | Pos |
| TC-25 | Reorder questions in section | 1. Drag q2 above q1 | Order updates · stepper circles order matches | P2 | Pos |
| TC-26 | Move question between sections | 1. Drag q3 from s1 to s2 | Question moves · parent updates · order recalculated | P2 | Pos |
4.6 · 🎨 10 Question Types
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-27 | number · numeric input + unit | 1. Add question type=number 2. Set unit "Ω" 3. Enter value in preview |
Numeric input · unit chip · rejects non-numeric | P1 | Pos |
| TC-28 | text · single-line input | 1. Add type=text | Single-line input · accepts any text | P2 | Pos |
| TC-29 | longtext · auto-grow textarea | 1. Add type=longtext 2. Type multi-line |
Textarea grows with content | P2 | UI |
| TC-30 | passfail · 2 full-width buttons | 1. Add type=passfail 2. View preview |
2 large buttons: Pass (green) · Fail (red) · full width in 2 columns | P1 | UI |
| TC-31 | radio · options list | 1. Add type=radio 2. Set options TH ["ใช่","ไม่","อื่นๆ"] |
Radio group · single selection · options bilingual | P1 | Pos |
| TC-32 | checkbox · multi-select | 1. Add type=checkbox 2. Set options |
Multiple checkboxes · multi-select | P2 | Pos |
| TC-33 | photo · camera CTA + counter | 1. Add type=photo 2. Set minPhotos=2 |
Camera button · counter "0 of 2 photos" · min enforced | P1 | UI |
| TC-34 | date · date picker | 1. Add type=date | Native date picker · saves ISO date | P2 | Pos |
| TC-35 | signature · draw signature | 1. Add type=signature | Canvas · finger/mouse draw · save as SVG/PNG | P2 | UI |
| TC-36 | color · HEX picker + ΔE | 1. Add type=color 2. Enter HEX #FF5733 |
Color picker · displays swatch · optional ΔE compare | P2 | Pos |
| TC-37 | All 10 types render in ck-003 (MEP) | 1. Open ck-003 | All 10 types appear across 6 sections · widget renders correctly per type | P1 | Pos |
4.7 · ⚖️ Criteria + Operator UI (v7.0+)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-38 | Operator "le" (≤) · number type | 1. type=number 2. Set operator=le, max=2.0 3. Preview: enter 1.5 |
Verdict = Pass · green | P1 | Int |
| TC-39 | Operator "le" fail · enter 2.5 | 1. Same as above · enter 2.5 | Verdict = Fail · red · triggers onFail | P1 | Int |
| TC-40 | Operator "ge" (≥) · Ground resistance ≤ 5 Ω | 1. type=number, ge, min=5 2. Preview enter 3 |
Fail (3 < 5) | P1 | Int |
| TC-41 | warnAbove/warnBelow · yellow zone | 1. le max=2.0, warnAbove=1.8 2. Enter 1.9 |
Yellow warning (between warn and fail) | P2 | Int |
| TC-42 | onFail = auto_ncr | 1. Set onFail=auto_ncr, severity=critical 2. Fail in preview |
Verdict shows "จะสร้าง NCR อัตโนมัติ" + severity chip | P1 | Int |
| TC-43 | onFail = warn (no NCR) | 1. Set onFail=warn, severity=minor 2. Fail |
Warning message only · no NCR triggered | P2 | Int |
| TC-44 | Severity chip color coded | 1. View criteria | critical=red · major=orange · minor=yellow | P3 | UI |
4.8 · 📷 Required Comment + Photo (v2.0)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-45 | requirePhoto flag · shows camera | 1. Set requirePhoto=true | Camera button appears under question in preview | P2 | Pos |
| TC-46 | requireComment flag · shows textarea | 1. Set requireComment=true | Comment textarea appears · block advance if empty | P2 | Pos |
| TC-47 | Photo counter min enforced | 1. minPhotos=2 · upload 1 photo | Warning · block advance until 2 uploaded | P2 | Bnd |
4.9 · 📱 Mobile Preview (v8.0+ · v16.0)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-48 | Preview panel renders alongside designer | 1. Open designer | Phone frame · shows current template in mobile-native UI | P1 | UI |
| TC-49 | Tab strip · one per section | 1. Load ck-003 (6 sections) | 6 tabs at top · click to switch | P1 | UI |
| TC-50 | Tab strip scroll bar on overflow (v16.0) | 1. 6+ sections | Horizontal scroll bar visible · smooth scroll | P2 | UI |
| TC-51 | Circle stepper · numbered | 1. View section with 4 questions | 4 numbered circles · click to jump | P1 | UI |
| TC-52 | Yellow ✓ badge on answered (v12.0) | 1. Answer q1 2. View stepper |
Circle 1 shows yellow ✓ · not filled bg | P2 | UI |
| TC-53 | Pin ✓ across navigation (v14.0) | 1. Answer q1 · go to q2 · back to q1 | Yellow ✓ still on q1 · state preserved | P2 | Pos |
| TC-54 | Only mark done if answered (v13.0) | 1. Open q1 but skip · go to q2 | Circle 1 · no ✓ badge (not marked done) | P2 | Pos |
| TC-55 | Rule chip · declarative statement (v10.0-11.0) | 1. View question in preview | Chip shows rule text (e.g. "ค่าความต้านทานสายดิน ≤ 5 Ω") · not "answer the following" | P2 | UI |
| TC-56 | Type-aware widget rendering | 1. Cycle through all 10 types | Each type shows correct mobile widget (number keyboard · passfail buttons · etc.) | P1 | UI |
4.10 · 🎯 Verdict Simulator (v7.0+)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-57 | Try-answer widget in preview | 1. Enter test value | Simulator evaluates criteria · shows verdict | P1 | Int |
| TC-58 | Pass verdict · green | 1. Enter passing value | Verdict: "✅ ผ่านเกณฑ์" · green background | P1 | Int |
| TC-59 | Warn verdict · yellow zone | 1. Enter warn-zone value | Verdict: "⚠️ ใกล้เกณฑ์" · yellow | P2 | Int |
| TC-60 | Fail verdict · red · auto_ncr note | 1. Enter failing value | Verdict: "❌ ไม่ผ่าน" · red · "จะสร้าง NCR อัตโนมัติ" | P1 | Int |
| TC-61 | Manual criteria (passfail) · direct fail | 1. type=passfail · press Fail button | Verdict red · onFail triggered | P1 | Int |
4.11 · 💾 Save + Delete Confirm (v5.0)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-62 | Save confirm popup | 1. Click Save | Popup summary: name · #sections · #questions · Cancel/Confirm | P1 | Pos |
| TC-63 | Confirm · save + toast + redirect | 1. Confirm | Saved · toast success · redirect to list | P1 | Pos |
| TC-64 | Strengthened Delete confirm (v5.0) | 1. Click Delete | Strong warning · "ไม่สามารถกู้คืนได้" · double confirm required | P1 | Neg |
| TC-65 | Delete removes from list | 1. Confirm delete | Card removed · toast success | P1 | Pos |
4.12 · ↕️ Expand/Collapse All (v5.0)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-66 | Expand All button | 1. Click "Expand All" | All sections expanded · all questions visible | P3 | UI |
| TC-67 | Collapse All button | 1. Click "Collapse All" | All sections collapsed · headers only | P3 | UI |
| TC-68 | Position · below Delete (v6.0) | 1. Check button order | Expand/Collapse is below Delete button | P3 | UI |
4.13 · 🌐 Language Toggle TH/EN
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-69 | Toggle TH → EN | 1. Click EN flag | All labels · sections · questions text switch to EN | P1 | Pos |
| TC-70 | EN i18n fix (v9.0) | 1. Toggle EN · check data | All templates render (no missing i18n block bug) | P1 | Pos |
4.14 · 💾 Data Persistence
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-71 | 3 CK_SEED + 5 CK_TEMPLATES persist | 1. F5 refresh | All 8 templates still shown | P1 | Int |
| TC-72 | Custom template persists | 1. Create custom · reload | Custom still in list | P1 | Int |
| TC-73 | Answer state persists in preview | 1. Answer q1 · switch section · come back | Answer + ✓ badge preserved | P2 | Int |
4.15 · ♿ Accessibility
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-74 | Keyboard nav · designer | 1. Tab through sections → questions → save | Focus visible · logical order | P2 | A11y |
| TC-75 | Mobile Preview · keyboard nav stepper | 1. Tab to circle stepper · arrow keys | Arrow keys move between circles · Enter answers | P2 | A11y |
| TC-76 | Widget ARIA · passfail buttons announced | 1. Screen reader on preview | "Pass · button · press to select" announced correctly | P2 | A11y |
📊
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
- Install:
npm init playwright@latest - Fixture reset: seed CK_SEED + CK_TEMPLATES before each test
- Widget testing: Query per-type widgets · use
data-qtype,data-question-id - 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 interactions06Appendix
📌 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