01บทนำ · About This Guide
Test scope · deliverables · mobile-specific coverage
เอกสารนี้เป็น Mobile Test Documentation สำหรับหน้าจอ Task Mobile v34.0 ครอบคลุม:
- Test Scenarios · 16 areas — 4 roles · Create Task · 3 pickers · Checklist wizard · NCR flow
- Test Cases · 82 cases — Positive / Negative / Boundary / UI / Mobile / Integration / A11y
- Playwright Mobile Guide — Pixel 5 + iPhone 14 emulation · POM · 5 sample test scripts
- Bottom-sheet Modal coverage — Project · Site/Zone · WBS with L1 tabs + horizontal scroll bar (v34.0)
📌
Reference file:
ConstructQ_TaskMobile_v34.0.html · React SPA · phone frame · 4 roles · offline-first · Checklist Preview UI ported from Checklist Template v16.0 · NCR confirm dialog · rule chip + type-aware widgets02Mobile Overview
Phone frame · 4 roles · 3 pickers · Checklist wizard
📱 Mobile Layout
- Fixed viewport: Portrait 375×812 (iPhone 14) or 393×851 (Pixel 5) · phone frame on desktop
- Status bar: Time · signal · battery · notch (iPhone)
- Topbar: Back icon · title · language toggle · action buttons (role-dependent)
- Content (scrollable): Task cards · list/detail · form fields
- Bottom nav: 64px height · fixed at bottom · role-based icons
- Bottom sheet modals: Project · Site/Zone · WBS · slide up · backdrop dim · constrained to phone frame (v32)
👥 4 Roles
| Role | Chip | Primary Actions | Screens Access |
|---|---|---|---|
| QCM · QC Manager | qcm | Create Task · assign inspector · approve NCR · publish workflow | List · Create · Detail · Approve NCR |
| QC Inspector | qi | Perform Checklist inspection · Pass/Fail per item · create NCR on fail | List · Detail · Checklist wizard · Create NCR |
| Fixer / Sub Contractor | fx | Receive NCR · fix on-site · request re-verify | List (NCRs assigned) · NCR detail · request verify |
| Executive · CEO | ceo | View-only · dashboard · overall progress | List (read-only) · Detail (read-only) |
➕ Create Task Form (QCM · v24.0 overhaul)
- Project field — inline dropdown with project data (v29) · gradient banner + code chip
- Site/Zone field — two-step bottom sheet Modal (v30-31)
- WBS/Activity field — bottom sheet Modal with L1 tabs (Struct · Finishing · MEP) + visible horizontal scroll bar (v34)
- Checklist field — link to template
- Workflow field — link to workflow template
- Inspector field — opens popup showing users in role (v24 rename)
- Approver field — separate popup
- Start Date + Duration · Ref Std · Risk (H/M/L)
- Cancel + Save buttons — at bottom of form (v7 fixed)
✅ Checklist Wizard (QC Inspector · v18-23)
- Tab strip per section (v21) · horizontal scroll on overflow
- Circle stepper per section · numbered
- Yellow ✓ badge on passed items (v20) · pinned across navigation
- Rule chip · declarative rule statement (from Checklist v16.0)
- Type-aware widgets · passfail · number+unit · radio · checkbox · text · longtext · photo · date · signature · color (v23)
- Pass/Fail 2 large buttons · full-width
- Wizard hides passed items (v15-16) · shows only uninspected
- Completion screen · after all items · summary
🚨 NCR Create Flow
- NCR Confirm dialog (v17) · on Fail tap · warns "จะสร้าง NCR"
- Confirm button opens Create-NCR form (v6/v18 fix)
- Cancel/Save buttons at bottom (v7)
- Owner picker · web-style horizontal table (v9)
- Sub Contractor auto-assigned based on WBS category
🎴 Card Layout (v12.0+ · matches web v28)
- Header: Task code first, then status pill
- Progress footer split · progress bar + %complete
- Lifecycle pills in Thai (v14): สร้าง · มอบหมาย · ตรวจ · รอตรวจรับ · ปิด · NCR
03Mobile Test Strategy
Devices · gestures · offline · role-based flows
📱 Target Devices
| Device | Viewport | Browser | Priority |
|---|---|---|---|
| iPhone 14 | 390×844 | Safari (WebKit) | P1 |
| Pixel 5 | 393×851 | Chrome | P1 |
| iPhone SE | 375×667 | Safari | P2 (small) |
| Samsung Galaxy S9+ | 320×658 | Chrome | P2 (narrow) |
🎯 Test Types
- Positive ~35% · Negative ~15% · Mobile ~20% · UI ~13%
- Integration ~10% · Boundary ~5% · A11y ~2%
⚡ Priority
- P1 — Critical · Create Task · Checklist wizard · NCR flow · touch input
- P2 — High · Pickers · role gating · yellow ✓ badge · language
- P3 — Medium · orientation · UI polish · edge devices
04Test Scenarios + Test Cases
16 areas · 82 test cases
4.1 · 🚀 App Launch + Role Switch
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M01 | Launch as QCM · task list | 1. Login as qcm role | Phone frame · topbar QCM · task list · Create FAB visible | P1 | Pos |
| TC-M02 | Launch as QC Inspector | 1. Login as qi | List filtered to assigned tasks · Create hidden | P1 | Pos |
| TC-M03 | Launch as Fixer | 1. Login as fx | List filtered to NCRs assigned · workflow context | P1 | Pos |
| TC-M04 | Launch as CEO · read-only | 1. Login as ceo | Read-only view · no Create · no edit actions | P1 | Pos |
| TC-M05 | Topbar language toggle | 1. Tap TH/EN flag | Entire page switches language · task cards preserve TH data | P1 | Pos |
4.2 · 📋 Task List Card (v12+ · matches web v28)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M06 | Card layout · code + status + progress | 1. View card | Task code first · status pill · progress footer split (bar + %) | P1 | UI |
| TC-M07 | Lifecycle pills · Thai (v14) | 1. View status pill | สร้าง · มอบหมาย · ตรวจ · รอตรวจรับ · ปิด · NCR · in Thai | P2 | UI |
| TC-M08 | Empty state · no tasks | 1. Filter to yield 0 results | Empty message + illustration · role-specific text | P2 | UI |
| TC-M09 | Card tap → detail view | 1. Tap task card | Full detail screen · back arrow · tabs (Overview / Checklist / NCR / Files) | P1 | Pos |
| TC-M10 | Pull-to-refresh | 1. Swipe down on list | Refresh indicator · new tasks appear | P2 | Mobile |
| TC-M11 | Infinite scroll · 20+ tasks | 1. Scroll to bottom | Load more · loading indicator · no hang | P2 | Mobile |
| TC-M12 | Fixed bottom nav · 64px | 1. View list | Bottom nav stays at bottom · touch target ≥ 44px | P1 | Mobile |
4.3 · 🔍 Filter + Search
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M13 | Filter tab · Search tab order (v11) | 1. View filter bar | Filter tab first · Search tab second (v11 swap) | P3 | UI |
| TC-M14 | Search · filter by code | 1. Type "TQ-2026-057" | List filters to matching · results update on debounce | P2 | Pos |
| TC-M15 | Filter by status · lifecycle chips | 1. Tap สร้าง chip | Only tasks with created status shown | P2 | Pos |
| TC-M16 | Filter by project | 1. Tap project filter | Only tasks under selected project | P2 | Pos |
4.4 · ➕ QCM · Create Task (v24 overhaul)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M17 | Open Create form (QCM only) | 1. QCM tap Create FAB | Full form · fields · Cancel/Save at bottom (v7) | P1 | Pos |
| TC-M18 | Non-QCM · Create hidden | 1. Login as qi/fx/ceo | Create FAB hidden or disabled | P1 | Neg |
| TC-M19 | Project field · inline dropdown (v29) | 1. Tap project field | Bottom-sheet modal · project cards with gradient banner + code chip | P1 | UI |
| TC-M20 | Required · Project + WBS + Checklist + Workflow | 1. Skip fields · Save | Error · block save · highlight missing | P1 | Neg |
| TC-M21 | Start Date + Duration inputs | 1. Set start · duration=7 | Calculate due date = start + 7 days | P2 | Pos |
| TC-M22 | Ref Std dropdown | 1. Open Ref Std | Grouped options (ACI · ASTM · วสท. · OSHA · etc.) | P2 | Pos |
| TC-M23 | Risk (H/M/L) chips | 1. Tap H chip | Selected · H=red · M=amber · L=green | P2 | UI |
| TC-M24 | Save valid task · success | 1. All fields · Save | Confirm popup · save · toast success · new card appears | P1 | Pos |
| TC-M25 | Cancel · discard changes | 1. Fill fields · Cancel | Warning "จะทิ้งข้อมูล?" · discard → back to list | P2 | Neg |
4.5 · 🏗️ Project Picker (v25-29)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M26 | Open picker from Project field | 1. Tap project field | Bottom-sheet slides up · project list · grouped by status | P1 | Mobile |
| TC-M27 | Project card · gradient + progress ring | 1. View project card | Color-per-project banner · code chip · circular progress ring · status badge · contract value · target date | P1 | UI |
| TC-M28 | Filter · all/active/near/recent (v26) | 1. Tap filter tab | List filters by state | P2 | Pos |
| TC-M29 | Select project · closes picker | 1. Tap project card | Picker closes · field shows selected project | P1 | Pos |
| TC-M30 | Backdrop dismiss | 1. Open picker · tap backdrop | Picker closes · no selection | P3 | Mobile |
4.6 · 🗺️ Site/Zone Picker (v30-31 · 2-step bottom sheet)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M31 | Open picker · Step 1 sites | 1. Tap Site/Zone field | Bottom sheet · list of sites for selected project | P1 | Mobile |
| TC-M32 | Step 2 · zones under site | 1. Tap site → Step 2 | Zone pills (v31) · tap to select | P1 | Pos |
| TC-M33 | Back button · Step 2 → Step 1 | 1. Tap ◀ in Step 2 | Return to sites list | P2 | Mobile |
| TC-M34 | Confirm selection | 1. Pick zone · Confirm | Picker closes · field shows "Site A / Zone B" | P1 | Pos |
4.7 · 🌳 WBS Picker (v33-34 · L1 tabs + scroll bar)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M35 | Open picker · shows L1 tabs | 1. Tap WBS field | Bottom sheet · 3 L1 tabs (Struct · Finishing · MEP) | P1 | UI |
| TC-M36 | L1 tab strip · horizontal scroll bar VISIBLE (v34) | 1. View L1 tabs | Scroll bar visible when overflow · not hidden | P1 | UI |
| TC-M37 | Tap L1 · shows L2/L3 tree | 1. Tap "Struct" | Hierarchical L2 (Piling · Concrete) · L3 activities visible | P1 | Pos |
| TC-M38 | Select L3 · confirms selection | 1. Tap L3 "ตอกเสาเข็ม" | Selected · Confirm → picker closes · field updated | P1 | Pos |
| TC-M39 | Modal constrained to phone frame (v32) | 1. Desktop preview · open picker | Modal stays within phone frame · does NOT overflow to browser | P1 | UI |
4.8 · 👤 Owner Picker (v8-10)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M40 | Owner picker · web-style horizontal table (v9) | 1. Tap Inspector field | Popup · horizontal table · sorted by availability (v8) | P1 | UI |
| TC-M41 | Owner picker · click handler works (v10 fix) | 1. Tap owner row | Selection registered · popup closes | P1 | Pos |
| TC-M42 | Owner picker shows role members only | 1. Inspector field → picker | Only users with "QC Inspector" role listed | P2 | Pos |
4.9 · ✅ Checklist Wizard (QC Inspector · v18-23)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M43 | Enter wizard mode | 1. QC Inspector · tap "เริ่มตรวจ Checklist" | Wizard opens · tab strip + circle stepper visible | P1 | Pos |
| TC-M44 | Screen renders (v22 fix) | 1. Enter wizard | Not blank · headerHtml renders · completion check works | P1 | Neg |
| TC-M45 | Tab strip per section (v21) | 1. Multi-section checklist | Tabs at top · horizontal scroll on overflow | P1 | UI |
| TC-M46 | Circle stepper · per section | 1. View section 4 questions | 4 circles numbered · current highlighted | P1 | UI |
| TC-M47 | Rule chip · declarative statement (v23) | 1. View question | Chip shows rule text (matches Checklist Template v16.0) | P2 | UI |
| TC-M48 | Type-aware widget · passfail | 1. type=passfail question | 2 large buttons: Pass (green) · Fail (red) · full width | P1 | UI |
| TC-M49 | Type-aware widget · number+unit | 1. type=number question | Numeric keyboard opens · unit chip visible | P1 | Mobile |
| TC-M50 | Wizard hides passed items (v15-16) | 1. Pass q1 · continue | Only uninspected items shown next · q1 hidden | P2 | Pos |
| TC-M51 | Completion screen | 1. Complete all items | Summary screen · Pass/Fail count · Save button | P1 | Pos |
| TC-M52 | Skip button REMOVED (v19) | 1. Check passfail widget | Only Pass/Fail · no Skip button | P3 | UI |
4.10 · 🟡 Yellow ✓ Badge on Stepper (v20)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M53 | Pass item · yellow ✓ badge shows | 1. Pass q1 | Circle 1 · yellow ✓ badge appears | P2 | UI |
| TC-M54 | Pin ✓ across navigation | 1. Pass q1 · go to q2 · back to q1 | Yellow ✓ still on q1 · state pinned | P2 | Pos |
| TC-M55 | Fail item · no yellow ✓ | 1. Fail q1 | Circle 1 · no ✓ · red styling | P2 | UI |
| TC-M56 | Unanswered · plain circle | 1. View unanswered question | Plain circle · no badge | P3 | UI |
4.11 · 🚨 NCR Create Flow (v6/v17/v18)
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M57 | Fail question · NCR confirm dialog opens (v17) | 1. Tap Fail on question | Dialog "จะสร้าง NCR ยืนยันไหม?" · Cancel/Confirm | P1 | Pos |
| TC-M58 | Confirm opens Create-NCR form (v6 · v18 fix) | 1. Tap Confirm on dialog | Create-NCR form opens · pre-filled with task context | P1 | Pos |
| TC-M59 | Cancel NCR dialog · returns to wizard | 1. Tap Cancel | Dialog closes · Fail not registered | P2 | Neg |
| TC-M60 | NCR form · required fields | 1. Skip title/desc · Save | Error · block save | P1 | Neg |
| TC-M61 | Cancel/Save buttons at bottom (v7) | 1. View form footer | Cancel + Save buttons fixed at bottom | P2 | UI |
| TC-M62 | Save NCR · assigns Sub Contractor | 1. Fill NCR · Save | Task status → NCR · Sub Contractor auto-assigned · toast success | P1 | Int |
4.12 · 📄 Task Detail
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M63 | Detail with tabs | 1. Tap task card | 4 tabs: Overview · Checklist · NCR · Files | P1 | Pos |
| TC-M64 | Overview tab · task fields | 1. Tap Overview | Code · status · dates · WBS · owner · risk | P1 | Pos |
| TC-M65 | Checklist tab · start inspection | 1. Tap Checklist | Preview + "เริ่มตรวจ" button (v4 · expand/collapse details) | P1 | Pos |
| TC-M66 | NCR tab · list NCRs | 1. Tap NCR | Show related NCRs · view button per row | P2 | Pos |
4.13 · 🌐 Language TH/EN
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M67 | Toggle TH → EN (v5 · smaller lang toggle) | 1. Tap EN flag | Full UI switches · TH task data preserved | P1 | Pos |
| TC-M68 | Preserve data on toggle | 1. Fill form · toggle · back | Data intact · no reset | P1 | Pos |
4.14 · 📡 Offline / Poor Network
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M69 | Offline · checklist writes queue locally | 1. Airplane mode · complete checklist | Answers saved locally · sync on reconnect | P1 | Mobile |
| TC-M70 | Slow 3G · loading state | 1. Throttle 3G · save task | Spinner · disable button · no double-submit | P2 | Mobile |
| TC-M71 | Photo upload · resume on reconnect | 1. Take photo offline · reconnect | Photo uploads · progress indicator · retry on failure | P2 | Int |
4.15 · 🔄 Safe Area + Orientation
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M72 | Portrait only | 1. Rotate landscape | Stay portrait or show hint · UI works | P3 | UI |
| TC-M73 | Safe area · notch/home indicator | 1. iPhone 14 with notch | Content respects safe-area-inset · no overlap | P2 | Mobile |
| TC-M74 | Modal constrained to phone frame (v32) | 1. Desktop · open any bottom sheet | Modal within frame · not full-window | P1 | UI |
4.16 · ♿ Mobile Accessibility
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M75 | Touch target · min 44×44 px | 1. Audit buttons | All ≥ 44px · Material 48dp | P2 | Mobile |
| TC-M76 | Tap ripple/feedback | 1. Tap Pass/Fail button | Visual feedback · immediate response | P2 | Mobile |
| TC-M77 | VoiceOver · button labels | 1. Enable VoiceOver · swipe | Buttons announced · Pass · Fail · Camera | P2 | A11y |
| TC-M78 | TalkBack · picker announces state | 1. TalkBack · open picker | "Bottom sheet · opened · project list" | P2 | A11y |
| TC-M79 | Contrast · WCAG AA | 1. Run axe scan | Text contrast ≥ 4.5:1 | P2 | A11y |
4.17 · 🎯 Bottom Nav + FAB
| TC ID | Scenario | Steps | Expected Result | Priority | Type |
|---|---|---|---|---|---|
| TC-M80 | Bottom nav tabs · role-specific | 1. QCM vs QI vs Fixer | Nav items differ by role · badge counts (NCR waiting) | P2 | Pos |
| TC-M81 | FAB · Create button (QCM only) | 1. QCM · FAB visible bottom right | Circular button · shadow · above bottom nav | P2 | UI |
| TC-M82 | FAB hidden for non-creators | 1. Inspector view | No FAB · no Create option | P2 | Neg |
📊
Test Case Summary: รวม 82 mobile test cases · P1 = 38 · P2 = 34 · P3 = 10 · Positive 27 · Negative 8 · Boundary 0 · Integration 3 · Mobile 15 · UI 24 · A11y 5
05Playwright Mobile Automation Guide + 5 Sample Scripts
Setup · POM · bottom sheet · checklist wizard · role emulation
🚀 Setup
- Install:
npm init playwright@latest+ Mobile Chrome + WebKit projects - Devices: Configure Pixel 5 + iPhone 14 in
playwright.config.ts - Touch enabled:
hasTouch: true·isMobile: true - Role fixture: Set cookie/localStorage for role (qcm/qi/fx/ceo)
- Seed data: Pre-populate tasks/projects/checklists · reset before each test
📁 Project Structure
treetests/task-mobile/ ├── launch.spec.ts # TC-M01 to TC-M05 ├── task-list.spec.ts # TC-M06 to TC-M12 ├── filter-search.spec.ts # TC-M13 to TC-M16 ├── create-task.spec.ts # TC-M17 to TC-M25 (QCM) ├── project-picker.spec.ts # TC-M26 to TC-M30 ├── site-zone-picker.spec.ts # TC-M31 to TC-M34 ├── wbs-picker.spec.ts # TC-M35 to TC-M39 (v34 scroll bar) ├── owner-picker.spec.ts # TC-M40 to TC-M42 ├── checklist-wizard.spec.ts # TC-M43 to TC-M52 (QC Inspector) ├── yellow-badge.spec.ts # TC-M53 to TC-M56 (v20) ├── ncr-create.spec.ts # TC-M57 to TC-M62 (Fail flow) ├── task-detail.spec.ts # TC-M63 to TC-M66 ├── language.spec.ts # TC-M67, TC-M68 ├── offline.spec.ts # TC-M69 to TC-M71 ├── orientation.spec.ts # TC-M72 to TC-M74 ├── a11y.spec.ts # TC-M75 to TC-M79 ├── bottom-nav.spec.ts # TC-M80 to TC-M82 └── e2e-full-flow.spec.ts # Composite QCM → QI → NCR pages/ ├── TaskListPage.ts ├── CreateTaskPage.ts ├── ProjectPicker.ts ├── SiteZonePicker.ts ├── WBSPicker.ts ├── ChecklistWizard.ts └── NCRCreateForm.ts playwright.config.ts
⚙️ playwright.config.ts
TypeScriptimport { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests/task-mobile', fullyParallel: true, use: { baseURL: process.env.BASE_URL ?? 'http://localhost:3000', trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', locale: 'th-TH', }, projects: [ { name: 'iPhone 14', use: { ...devices['iPhone 14'] }}, { name: 'Pixel 5', use: { ...devices['Pixel 5'] }}, { name: 'iPhone SE', use: { ...devices['iPhone SE'] }}, { name: 'Galaxy S9+',use: { ...devices['Galaxy S9+'] }}, ] });
🎯 POM · CreateTaskPage
TypeScriptimport { Page, Locator, expect } from '@playwright/test'; export type Role = 'qcm' | 'qi' | 'fx' | 'ceo'; export type Risk = 'H' | 'M' | 'L'; export class CreateTaskPage { readonly page: Page; readonly fabCreate: Locator; readonly projectField: Locator; readonly siteZoneField: Locator; readonly wbsField: Locator; readonly checklistField: Locator; readonly workflowField: Locator; readonly inspectorField: Locator; readonly btnSave: Locator; readonly btnCancel: Locator; constructor(page: Page) { this.page = page; this.fabCreate = page.locator('.tm-fab-create'); this.projectField = page.locator('[data-field="project"]'); this.siteZoneField = page.locator('[data-field="site-zone"]'); this.wbsField = page.locator('[data-field="wbs"]'); this.checklistField = page.locator('[data-field="checklist"]'); this.workflowField = page.locator('[data-field="workflow"]'); this.inspectorField = page.locator('[data-field="inspector"]'); this.btnSave = page.getByRole('button', { name: /บันทึก|Save/ }); this.btnCancel = page.getByRole('button', { name: /ยกเลิก|Cancel/ }); } async openCreateForm() { await this.fabCreate.tap(); } // Project picker (bottom sheet) async pickProject(projectCode: string) { await this.projectField.tap(); const sheet = this.page.locator('.pk-project-sheet'); await sheet.waitFor({ state: 'visible' }); await sheet.locator(`[data-project-code="${projectCode}"]`).tap(); } // Site/Zone 2-step picker async pickSiteZone(site: string, zone: string) { await this.siteZoneField.tap(); const sheet = this.page.locator('.pk-modal-sites'); await sheet.waitFor({ state: 'visible' }); await sheet.getByText(site).tap(); // Step 1 await sheet.locator('.pk-zone-pill').getByText(zone).tap(); // Step 2 await sheet.getByRole('button', { name: /ยืนยัน|Confirm/ }).tap(); } // WBS picker · L1 tabs + L3 (v34 scroll bar) async pickWBS(l1Category: 'Struct' | 'Finishing' | 'MEP', l3ActivityName: string) { await this.wbsField.tap(); const sheet = this.page.locator('.pk-wbsm'); await sheet.waitFor({ state: 'visible' }); await sheet.locator(`.pk-wbsm-l1s [data-l1="${l1Category}"]`).tap(); await sheet.locator('.pk-wbsm-l3').getByText(l3ActivityName).tap(); await sheet.getByRole('button', { name: /ยืนยัน|Confirm/ }).tap(); } async pickInspector(inspectorName: string) { await this.inspectorField.tap(); const popup = this.page.locator('.pk-owner-popup'); await popup.waitFor({ state: 'visible' }); await popup.getByText(inspectorName).tap(); } async setRisk(risk: Risk) { await this.page.locator(`.tm-risk-chip[data-risk="${risk}"]`).tap(); } async save() { await this.btnSave.tap(); await this.page.getByRole('button', { name: /ยืนยัน|Confirm/ }).tap(); } }
✅ Sample Test 1 · Role-Based Access (QCM vs QI vs CEO)
TypeScriptimport { test, expect } from '@playwright/test'; test.describe('Task Mobile · Role-based access', () => { test('TC-M01 · QCM · FAB visible + Create form works', async ({ page, context }) => { await context.addCookies([{ name: 'role', value: 'qcm', url: 'http://localhost:3000' }]); await page.goto('/task-mobile'); await expect(page.locator('.tm-fab-create')).toBeVisible(); }); test('TC-M18 · QC Inspector · FAB hidden', async ({ page, context }) => { await context.addCookies([{ name: 'role', value: 'qi', url: 'http://localhost:3000' }]); await page.goto('/task-mobile'); await expect(page.locator('.tm-fab-create')).toBeHidden(); }); test('TC-M04 · CEO · read-only view', async ({ page, context }) => { await context.addCookies([{ name: 'role', value: 'ceo', url: 'http://localhost:3000' }]); await page.goto('/task-mobile'); await expect(page.locator('.tm-fab-create')).toBeHidden(); await expect(page.locator('.tm-edit-btn')).toBeHidden(); }); });
✅ Sample Test 2 · WBS Picker (v34 scroll bar)
TypeScripttest('TC-M36 · WBS Modal L1 tabs · horizontal scroll bar visible (v34)', async ({ page }) => { const create = new CreateTaskPage(page); await page.goto('/task-mobile'); await create.openCreateForm(); await create.pickProject('PJ-001'); await create.wbsField.tap(); const tabs = page.locator('.pk-wbsm-l1s'); await expect(tabs).toBeVisible(); // Verify overflow-x auto/scroll · v34 requirement const overflowX = await tabs.evaluate((el) => window.getComputedStyle(el).overflowX); expect(['auto', 'scroll']).toContain(overflowX); // Verify tabs overflow the container width const scrollWidth = await tabs.evaluate((el) => el.scrollWidth); const clientWidth = await tabs.evaluate((el) => el.clientWidth); expect(scrollWidth).toBeGreaterThanOrEqual(clientWidth); // Verify all 3 L1 tabs present for (const cat of ['Struct', 'Finishing', 'MEP'] as const) { await expect(tabs.locator(`[data-l1="${cat}"]`)).toBeVisible(); } // Verify modal constrained to phone frame (v32) const box = await page.locator('.pk-wbsm').boundingBox(); const viewport = page.viewportSize()!; expect(box!.width).toBeLessThanOrEqual(viewport.width); });
✅ Sample Test 3 · Checklist Wizard with Yellow ✓ Badge (v20)
TypeScripttest('TC-M53 + TC-M54 · Pass item shows yellow ✓ pinned across nav', async ({ page, context }) => { await context.addCookies([{ name: 'role', value: 'qi', url: 'http://localhost:3000' }]); await page.goto('/task-mobile'); await page.locator('.tm-card').first().tap(); await page.getByRole('tab', { name: /Checklist/ }).tap(); await page.getByRole('button', { name: /เริ่มตรวจ Checklist/ }).tap(); // Wizard opens · press Pass on q1 (assumes passfail type) const passBtn = page.locator('.ck-pf-pass').first(); await passBtn.tap(); // Verify yellow ✓ badge appears on stepper circle 1 const circle1 = page.locator('.ck-stepper-circle').first(); const checkIcon = circle1.locator('.ck-stepper-check'); await expect(checkIcon).toBeVisible(); // Verify color is yellow (#FFD23F or equivalent) const color = await checkIcon.evaluate((el) => window.getComputedStyle(el).color); expect(color).toMatch(/rgb\(255,\s*210,\s*63\)|#FFD23F/i); // Navigate away (to q2) and back to q1 · verify ✓ still pinned (v14/v20) await page.locator('.ck-stepper-circle').nth(1).tap(); await page.locator('.ck-stepper-circle').first().tap(); await expect(checkIcon).toBeVisible(); // still there });
✅ Sample Test 4 · NCR Create Flow (Fail → Confirm → Form)
TypeScripttest('TC-M57 + TC-M58 · Fail opens NCR confirm · Confirm opens Create-NCR form', async ({ page, context }) => { await context.addCookies([{ name: 'role', value: 'qi', url: 'http://localhost:3000' }]); await page.goto('/task-mobile'); await page.locator('.tm-card').first().tap(); await page.getByRole('button', { name: /เริ่มตรวจ Checklist/ }).tap(); // Tap Fail on first passfail question await page.locator('.ck-pf-fail').first().tap(); // NCR Confirm dialog appears (v17) const dialog = page.getByRole('dialog'); await expect(dialog).toContainText(/จะสร้าง NCR|Create NCR/); // Confirm → opens Create-NCR form (v6 · v18 fix) await dialog.getByRole('button', { name: /ยืนยัน|Confirm/ }).tap(); const ncrForm = page.locator('.ncr-create-form'); await expect(ncrForm).toBeVisible(); // Verify Cancel + Save buttons at bottom (v7) await expect(page.locator('.ncr-create-form-footer button')).toHaveCount(2); // Fill NCR form await page.getByLabel(/หัวข้อ NCR|NCR Title/).fill('พบรอยแตกที่คอนกรีต'); await page.getByLabel(/รายละเอียด|Description/).fill('รอยแตก 3 มม. ที่ตำแหน่ง P-2'); // Save NCR await page.getByRole('button', { name: /บันทึก|Save/ }).tap(); await expect(page.getByText(/สร้าง NCR เรียบร้อย|NCR created/)).toBeVisible(); });
✅ Sample Test 5 · Full E2E · QCM Creates Task → Inspector Inspects → NCR Loop
TypeScripttest('TC-E2E-01 · Full lifecycle · QCM creates → QI inspects → NCR created', async ({ browser }) => { // === Step 1: QCM creates task === const qcmContext = await browser.newContext({ ...devices['Pixel 5'] }); await qcmContext.addCookies([{ name: 'role', value: 'qcm', url: 'http://localhost:3000' }]); const qcmPage = await qcmContext.newPage(); const create = new CreateTaskPage(qcmPage); await qcmPage.goto('/task-mobile'); await create.openCreateForm(); await create.pickProject('PJ-001'); await create.pickSiteZone('อาคาร A', 'Zone 1'); await create.pickWBS('Struct', 'ตอกเสาเข็ม'); await create.checklistField.tap(); await qcmPage.getByText('ตรวจงานคอนกรีต').tap(); await create.workflowField.tap(); await qcmPage.getByText('Standard + NCR Loop').tap(); await create.pickInspector('สมชาย ใจดี'); await create.setRisk('H'); await create.save(); await expect(qcmPage.getByText(/สร้างเรียบร้อย/)).toBeVisible(); const taskCode = await qcmPage.locator('.tm-card').first() .locator('.tm-code').textContent(); await qcmContext.close(); // === Step 2: QC Inspector inspects · fails · triggers NCR === const qiContext = await browser.newContext({ ...devices['Pixel 5'] }); await qiContext.addCookies([{ name: 'role', value: 'qi', url: 'http://localhost:3000' }]); const qiPage = await qiContext.newPage(); await qiPage.goto('/task-mobile'); await qiPage.getByText(taskCode!).tap(); await qiPage.getByRole('button', { name: /เริ่มตรวจ Checklist/ }).tap(); // Fail first question · trigger NCR await qiPage.locator('.ck-pf-fail').first().tap(); await qiPage.getByRole('button', { name: /ยืนยัน|Confirm/ }).tap(); // Fill NCR await qiPage.getByLabel(/หัวข้อ NCR/).fill('พบรอยแตก'); await qiPage.getByRole('button', { name: /บันทึก|Save/ }).tap(); // Verify NCR created + Sub Contractor auto-assigned await expect(qiPage.getByText(/NCR created|สร้าง NCR/)).toBeVisible(); await qiContext.close(); });
💡
Best Practices: Use
tap() not click() · use data-role, data-field, data-project-code attributes for reliable selectors · set role cookie before each test · use POM for each picker (Project/Site/WBS/Owner) · verify modal within phone frame via boundingBox width comparison06Appendix
📌 Version History
- ConstructQ_TaskMobile_v34.0.html · source · WBS modal L1 tabs · visible horizontal scrollbar
- เอกสารนี้ v1.0 · 13 ก.ค. 2569 · 82 test cases · 5 sample Playwright scripts
🔗 References
- Playwright Mobile:
https://playwright.dev/docs/emulation - Related file: web
ConstructQ_TaskManagement(QC+NCR)_v40.0.html· shares data structures - Checklist port:
ConstructQ_Checklist_v16.0.html(rule chip + type-aware widgets v23) - Excel companion:
ConstructQ_TaskMobile_TestScenario_v1.0.xlsx