English
testover · แผนที่ระบบเทส

เทสในโปรเจกต์นี้มีกี่ชั้น
แต่ละชั้นกันอะไร

หน้านี้ไล่ให้เห็นทั้งระบบ automated test: มีเทสแบบไหนบ้าง ใช้เครื่องมืออะไร รันตอนไหน และ contract กับ characterization test ต่างจากเทสทั่วไปอย่างไร เขียนให้คนที่ยังไม่เคยทำ QA อัตโนมัติอ่านเข้าใจ

127เทสที่ใช้เบราว์เซอร์จริง (Playwright)
42เทสที่ไม่ใช้เบราว์เซอร์ (node:test)
3ขอบเขตที่มี contract ตรวจ
2workflow บน GitHub Actions

1ชั้นของเทส

ยิ่งเทสอยู่ด้านบนยิ่งเร็วและชี้จุดพังได้ตรง ยิ่งอยู่ด้านล่างยิ่งเหมือนผู้ใช้จริงแต่ช้ากว่า ทั้งหกชั้นนี้เสริมกัน ไม่ได้แทนกัน

42เคส

Unit test — ทดสอบโค้ดชิ้นเล็กโดยตรง

เรียกฟังก์ชันหรือเซิร์ฟเวอร์ตรงๆ ไม่เปิดเบราว์เซอร์ จึงจบในเสี้ยววินาที เช่น เช็กว่าเซิร์ฟเวอร์ตอบ 200/301/304/400/404/405/500 ถูกต้อง และสคริปต์แจ้งผลสร้างข้อความ PASS/FAIL ได้ตรง

unit/server.test.mjs (15) · unit/notify.test.mjs (16) · unit/contracts.test.mjs (11) · รัน npm run test:unit
6 + 11เคส

Contract test — ข้อตกลงเรื่องรูปร่างข้อมูล

กำหนด "รูปร่างที่ข้อมูลต้องเป็น" ไว้ที่เดียว (schema) แล้วเช็กทุกจุดที่ข้อมูลข้ามจากส่วนหนึ่งไปอีกส่วน ถ้าฝั่งหนึ่งเปลี่ยนรูปแล้วอีกฝั่งไม่รู้ เทสจะพังทันที ดูรายละเอียดในข้อ 2

contracts/schemas.mjs · tests/contracts/storage.spec.ts (6) · ส่วนที่เหลืออยู่ใน unit/contracts.test.mjs (11)
37เคส

Characterization test — จดว่าวันนี้แอปทำอะไร

ไม่ได้ถามว่า "ควรทำอะไร" แต่บันทึก "ตอนนี้ทำอะไรอยู่จริง" รวมถึงพฤติกรรมแปลกๆ ไว้เป็นหลักฐาน เพื่อให้รู้ทันทีเมื่อมีใครไปเปลี่ยนมันโดยไม่ตั้งใจ ดูรายละเอียดในข้อ 3

tests/characterization/validation.spec.ts · tests/characterization/resilience.spec.ts
70เคส

E2E regression — ใช้เว็บแทนผู้ใช้จริง

เปิดเบราว์เซอร์จริง คลิก กรอก แล้วเช็กผล แบ่งตามส่วนของแอป: สมัคร/login 27, งาน 27, โปรไฟล์ 10 และเส้นทางเต็มหลายหน้า 6 ข้อมูลที่ใช้เป็นผู้ใช้สมมติ (demo, alice)

login.spec.ts · tasks.spec.ts · profile.spec.ts · journey.spec.ts · Page Object อยู่ที่ tests/support/pages.ts
14เคส

Accessibility test — คนใช้ได้ทุกแบบไหม

ให้ axe-core สแกนทุกหน้าและทุกสถานะ (ปกติ, มี error, กำลังแก้, dark mode) ตามมาตรฐาน WCAG 2.1 ระดับ A และ AA และมีเคสพิเศษที่ยัดข้อผิดพลาดเข้าไปเองเพื่อพิสูจน์ว่าตัวสแกนจับได้จริง ไม่ผ่านแบบเงียบๆ

tests/a11y.spec.ts · เคสที่ติด tag @a11y รวม 16 (รวมเคสคีย์บอร์ดและ title ในไฟล์อื่น)
5เคส

Smoke test — ชุดเร็วเช็กว่ายังไม่พังหนัก

ไม่ใช่เทสชนิดใหม่ แต่เป็นการเลือก 5 เคสจาก E2E ที่เป็นเส้นทางสำคัญที่สุดมารันก่อน (ติด tag @smoke) ใช้กับทุก push และ pull request

รัน npm run test:smoke · workflow smoke.yml
ส่วนของโปรเจกต์มีไว้ทำอะไร
app/index.html, app/server.mjsแอป Task Board และเซิร์ฟเวอร์ static ที่เป็นเป้าหมายของเทสทั้งหมด
tests/support/pages.tsPage Object: รวมวิธี "กดปุ่มนี้ กรอกช่องนั้น" ไว้ที่เดียว เทสจึงอ่านง่ายและแก้ที่เดียวเมื่อหน้าตาเปลี่ยน
tests/support/storage.tsตัวช่วยอ่าน/เขียน localStorage ตรงๆ: seedStorage (ยัดข้อมูลแล้ว reload), readJson, readRaw, dumpStorage
tests/fixtures/reports/รายงานจำลอง 3 แบบ (pass, fail-nested, empty) ให้ unit test ใช้ทดสอบสคริปต์แจ้งผล โดยไม่ต้องรัน Playwright จริง
contracts/schemas.mjsที่เดียวที่นิยามรูปร่างข้อมูลทั้ง 3 ขอบเขต ทั้ง Playwright และ node:test import ไปใช้ร่วมกัน

2Contract test: ข้อตกลงระหว่างสองฝั่ง

เปรียบเหมือนแบบฟอร์มที่สองแผนกตกลงกันว่าต้องกรอกช่องอะไรบ้าง ถ้าแผนกหนึ่งเปลี่ยนแบบฟอร์มโดยไม่บอก อีกแผนกจะอ่านไม่ออก contract test คือคนตรวจแบบฟอร์มทุกครั้งที่ส่งต่อ

ข้อมูลข้ามขอบเขตตรงไหนบ้างในโปรเจกต์นี้

แอปapp/index.html
C1
localStoragetb.users · tb.session · tb.theme · tb.tasks.<user>
Playwrightผลการรัน
C2
results.jsontest-results/
อ่านโดย
notify.mjsสรุปผล
C3
webhook bodycontent + text
ส่งให้
Discord / Slack
ชื่อข้อตกลงว่าอย่างไรตรวจโดย
C1 localStorage แอปเขียนได้เฉพาะ key ที่กำหนด และรูปข้อมูลต้องตรง เช่น งานแต่ละชิ้นต้องเป็น { id: จำนวนเต็ม, title: ข้อความ, priority: low|medium|high, done: จริง/เท็จ } id ห้ามซ้ำ session ต้องชี้ไปผู้ใช้ที่มีอยู่จริง และงานต้องเป็นของผู้ใช้ที่มีอยู่ tests/contracts/storage.spec.ts ขับ UI จริง (สมัคร เพิ่ม/แก้/ลบงาน เปลี่ยนโปรไฟล์ ลบบัญชี สลับผู้ใช้) แล้วตรวจ storage หลังแต่ละขั้น
C2 รายงาน Playwright ไฟล์ results.json ต้องมี stats (expected, unexpected, flaky, skipped เป็นจำนวนเต็มไม่ติดลบ) และ suites ที่มี title เพราะ notify.mjs พึ่งพาแค่ส่วนนี้ unit/contracts.test.mjs ตรวจกับ fixture 3 แบบ และกับรายงานจากการรันจริงครั้งล่าสุด (ถ้ามีไฟล์) พร้อมเคสที่ต้องถูกปฏิเสธ
C3 body ที่ส่ง webhook ต้องมี content (Discord) และ text (Slack) เป็นข้อความไม่ว่างและเหมือนกัน ห้ามมี field เกิน unit/contracts.test.mjs ยกเซิร์ฟเวอร์ปลอมขึ้นมารับ แล้วรัน notify.mjs จริง เก็บ body ที่ส่งมาตรวจกับ schema

จุดเด่นคือ schema อยู่ที่เดียว (contracts/schemas.mjs เขียนด้วย zod) ทั้งเทสในเบราว์เซอร์และเทส node ใช้ไฟล์เดียวกัน จึงไม่มีทางที่สองฝั่งจะเข้าใจ "รูปร่างที่ถูก" ไม่ตรงกัน และ state ที่ตรวจเกิดจากการขับ UI จริง ไม่ได้สร้างขึ้นเองด้วยมือ

3Characterization test: จดสิ่งที่เป็นอยู่

เหมือนถ่ายรูปห้องก่อนย้ายของ ไม่ได้บอกว่าห้องควรหน้าตาแบบนี้ แต่ถ้าวันหลังมีอะไรหายไปจะรู้ทันที เหมาะกับโค้ดที่ยังไม่มีสเปกเขียนไว้ชัด หรือก่อนจะแก้ของเก่าที่ไม่แน่ใจว่าอะไรผูกกันอยู่

Contract

"ข้อมูลต้องเป็นแบบนี้"

คำถามที่ตอบ
สองฝั่งยังคุยกันรู้เรื่องไหม
ตัวอย่างในโปรเจกต์
งานทุกชิ้นต้องมี priority เป็น low, medium หรือ high เท่านั้น
ถ้าเทสพัง
ส่วนใหญ่คือบั๊ก หรือมีคนเปลี่ยนรูปข้อมูลฝั่งเดียว ให้แก้ให้ตรงกัน หรือถ้าตั้งใจเปลี่ยน ให้แก้ schema ที่เดียว
Characterization

"วันนี้มันทำแบบนี้"

คำถามที่ตอบ
พฤติกรรมเปลี่ยนไปจากเมื่อวานหรือเปล่า
ตัวอย่างในโปรเจกต์
username 20 ตัวอักษรผ่าน 21 ไม่ผ่าน และข้อความ error ออกมาเรียงลำดับตายตัว
ถ้าเทสพัง
ยังไม่ต้องตกใจ ถามก่อนว่า "ตั้งใจเปลี่ยนไหม" ถ้าตั้งใจ ให้แก้ค่าที่คาดไว้ ถ้าไม่ตั้งใจ ให้แก้โค้ด

ในโปรเจกต์ครอบคลุมอะไรบ้าง

ไฟล์จดไว้ว่า
validation.spec.tsขอบเขตของทุกฟอร์ม: ความยาว username (3 ถึง 20 ตัว, ใช้ได้เฉพาะตัวอักษร ตัวเลข underscore), รหัสผ่านขั้นต่ำ 8 ตัว, ชื่องานไม่เกิน 80 ตัว, ลำดับการตรวจ (ตรวจ username ก่อนรหัสผ่าน ตรวจรหัสเดิมก่อนความยาวก่อนการยืนยัน), username ไม่สนตัวพิมพ์เล็ก/ใหญ่ แต่รหัสผ่านสนใจ, login ผิดทุกกรณีใช้ข้อความเดียวกัน
resilience.spec.tsแอปบูตอย่างไรเมื่อ storage ผิดปกติ (JSON พัง, ข้อมูลผิดชนิด, งานผิดรูปปนกับงานดี, theme แปลก) และกฎการ redirect ของแต่ละ route รวมถึง title ของแต่ละหน้า
unit/contracts.test.mjsข้อความที่ notify.mjs สร้างจากรายงานแต่ละแบบ ตรงตามตัวอักษร และรายงานที่ไม่มีเทสเลยถูกนับเป็น PASS
เคสที่ขึ้นต้นด้วย QUIRK คือพฤติกรรมที่น่าจะไม่ตั้งใจ แต่จดไว้ตามจริงก่อน ตอนนี้มี 4 เคส: เมื่อแก้บั๊กเหล่านี้ในอนาคต เคส QUIRK จะพัง ซึ่งเป็นสัญญาณที่ดี ให้เปลี่ยนค่าที่คาดไว้เป็นพฤติกรรมใหม่ที่ถูกต้อง

4เครื่องมือที่ใช้อยู่และหน้าที่ของแต่ละตัว

รายการนี้ดึงจาก package.json, playwright.config.ts และไฟล์ workflow ที่ใช้งานจริง

Playwright Test

@playwright/test 1.49.1
ตัวรันเทสหลัก

เปิด Chromium จริง ควบคุมเบราว์เซอร์ให้คลิกและกรอกข้อมูล มีระบบ assert, retry และรันขนานในตัว ตั้งให้เปิดเว็บเทสให้เองก่อนรัน

ไฟล์ตั้งค่า playwright.config.ts · retry 2 ครั้งและ 2 workers บน CI · เก็บ screenshot/video/trace เฉพาะเคสที่พัง

node:test

มากับ Node.js
ตัวรัน unit test

test runner ที่มีในตัว Node ไม่ต้องติดตั้งเพิ่ม ใช้กับเทสที่ไม่ต้องเปิดเบราว์เซอร์ ผลจึงออกเร็ว มีโหมดวัด coverage ในตัว

npm run test:unit · npm run test:unit:coverage

zod

zod 4.6.7
นิยาม contract

ไลบรารีเขียน schema ของข้อมูลแล้วตรวจว่าข้อมูลจริงตรงไหม ใช้เป็นภาษากลางของ contract ทั้งสามขอบเขต

contracts/schemas.mjs

axe-core

@axe-core/playwright 4.14.0
ตรวจ accessibility

สแกนหน้าเว็บหา violation ตาม WCAG 2.1 A/AA เช่น ภาพไม่มี alt, สีตัวอักษรตัดกับพื้นไม่พอ, ฟอร์มไม่มี label

tests/a11y.spec.ts

Node HTTP server

app/server.mjs
เว็บเป้าหมายของเทส

เซิร์ฟเวอร์ static ที่เขียนเอง ไม่ต้องติดตั้งอะไรเพิ่ม Playwright เปิดให้อัตโนมัติ (พอร์ต 3000 เปลี่ยนได้ด้วย PORT) หรือชี้ไปเว็บอื่นด้วย BASE_URL

ตัวเซิร์ฟเวอร์เองก็มี unit test (unit/server.test.mjs)

Playwright reporters

list · html · json
รายงานผลระหว่างรัน

list พิมพ์ผลในเทอร์มินัล html สร้างรายงานเปิดดูด้วย npm run report json เขียน test-results/results.json ซึ่งสคริปต์แจ้งผลอ่านต่อ

ถ้าสั่ง --reporter เองจะไม่มี results.json และแจ้งผลผิดเป็น FAIL

Allure

allure-playwright 3.1.0 · allure-commandline 2.46.1
รายงานเชิงลึก

เก็บผลดิบเป็น allure-results แล้วสร้างรายงานที่แบ่งตามพฤติกรรมและ suite ดูย้อนหลังได้ ต้องมี Java ในเครื่องตอนสร้างรายงาน

npm run allure:generate · allure:open · allure:serve

GitHub Actions

checkout / setup-node / upload-artifact v4
ตัวสั่งรันอัตโนมัติ

รันบนเครื่อง ubuntu และ Node 22 มี 2 workflow: Smoke ทุก push ไป main และทุก PR, Weekend E2E ตามเวลาเสาร์-อาทิตย์ แล้วอัปโหลดรายงานเก็บไว้ (14 วัน)

.github/workflows/smoke.yml · weekend-e2e.yml

notify.mjs + Webhook

Discord / Slack
แจ้งผลเข้าแชต

สคริปต์อ่าน results.json สรุปเป็นข้อความ ✅/❌ แล้ว POST เข้า webhook ที่เก็บเป็นความลับใน GitHub Secrets (WEBHOOK_URL) รันทุกครั้งแม้เทสพัง

scripts/notify.mjs

5รันตอนไหน ผลไปไหน

เส้นทางของ workflow เต็มตอนเสาร์-อาทิตย์ ส่วน Smoke ใช้เฉพาะขั้น 1 ถึง 4 กับเทส 5 เคส

  1. GitHub Actions ปลุก workflow ตามเวลา (เสาร์และอาทิตย์ 09:00 เวลาไทย) หรือมีคนกดรันเอง
  2. ติดตั้งของ: npm ci และ npx playwright install --with-deps chromium
  3. Playwright เปิดเว็บ Task Board แล้วรันเทส 127 เคส (ผิดพลาดจะ retry ได้ 2 ครั้ง)
  4. อัปโหลด playwright-report และ test-results เป็น artifact
  5. ติดตั้ง Java แล้วสร้างรายงาน Allure และอัปโหลดเป็น artifact
  6. notify.mjs อ่าน results.json ส่งข้อความสรุปเข้า Discord หรือ Slack พร้อมลิงก์ไปดู run

6แก้อะไรอยู่ ควรรันอะไร

สถานการณ์คำสั่ง
เพิ่งแก้นิดเดียว อยากเช็กเร็วnpm run test:smoke
แก้สคริปต์แจ้งผลหรือเซิร์ฟเวอร์npm run test:unit
แก้รูปแบบข้อมูลที่เก็บใน storagenpx playwright test --grep @contract
แก้กฎตรวจข้อมูลหรือข้อความ errornpx playwright test --grep @characterization
แก้หน้าตา เลย์เอาต์ สีnpx playwright test --grep @a11y
ก่อน releasenpm run test:unit แล้ว npm test
อยากเห็นว่าคำสั่งจะรันอะไรโดยไม่รันnpx playwright test --list --grep @contract

Tag ลักษณะเคสที่ใช้อยู่: @smoke @validation @security @persistence @resilience @destructive @a11y @responsive @contract @characterization และ tag ตามพื้นที่ @auth @tasks @profile @journey เคสหนึ่งมีได้หลาย tag

7สิ่งที่ควรรู้: ช่องว่างตอนนี้

Unit test ยังไม่รันใน CI ทั้ง smoke.yml และ weekend-e2e.yml รันเฉพาะ Playwright เท่านั้น เทส 42 เคสใน unit/ (รวม contract C2 และ C3) ต้องสั่ง npm run test:unit เองในเครื่อง ถ้าอยากให้ถูกจับอัตโนมัติ ให้เพิ่ม step นี้ใน workflow
ยังไม่มี heartbeat ถ้า schedule ของ GitHub ถูกปิดหรือล่ม จะไม่มีข้อความเข้าเลยและไม่มีใครรู้ README แนะนำ healthchecks.io แต่ยังไม่ได้ต่อ
เทส Chromium อย่างเดียว ยังไม่ครอบคลุม Firefox หรือ Safari

อ่านต่อ