บทที่ 16 · Part 4 — Reliability and Operations

Debugging and Reporting

ใช้ UI Mode, Inspector, trace, HTML report, steps และ redacted attachments เพื่อหาสาเหตุจาก artifact เดียว

Debugging and Reporting

Failure ที่บอกเพียง expected 201, received 422 บังคับคนแก้ rerun และหวังว่าอาการจะเกิดซ้ำ Test artifact ที่ดี ตอบได้ว่า test ทำอะไร, browser เห็นอะไร, request/response เป็นอย่างไร และ failure อยู่ phase ไหน โดยไม่เก็บ secret มากกว่าที่จำเป็น

จบบทนี้คุณจะ

ใช้ UI Mode, Inspector, codegen, trace และ reporters เลือก artifact ตาม failure mode สร้าง redacted API attachment เฉพาะเมื่อ fail และวาง workflow triage ที่เริ่มจาก evidence

Local Authoring Tools

npx playwright test --ui
npx playwright test tests/ui/reservations/reserve.spec.ts --debug
npx playwright codegen http://127.0.0.1:4173/workshops

UI Mode ให้ filter ตาม project/tag/status, watch rerun, timeline, DOM snapshots, network, console และ attachments เหมาะกับ authoring/reproduce Inspector จาก --debug ช่วย step/pick locator กับ live page ส่วน codegen record actions และเสนอ role/text/test-id locator เป็น draft

Codegen ไม่รู้ risk, test data lifecycle, oracle หรือ abstraction ของ project ต้อง refactor title, setup, steps, locators และ assertions ก่อน commit ไม่เก็บ generated mega-flow เป็น final test

Trace บน CI

use: {
  trace: 'on-first-retry',
  screenshot: 'only-on-failure',
}

Trace บันทึก timeline, actions, DOM snapshots, network, console และ assertions เปิด local artifact ด้วย:

npx playwright show-trace path/to/trace.zip

on-first-retry ทำให้ first execution เร็วและเก็บ trace ตอน retry ที่ต้องวิเคราะห์ หาก failure เกิดเฉพาะ attempt แรก อาจต้องเปิด retain-on-failure ชั่วคราวใน diagnosis branch แต่ trace ทุก test เพิ่ม storage/เวลาและข้อมูลอ่อนไหว

Steps และ Boxed Location

test.step() ทำ report เป็น narrative และรองรับ box เพื่อให้ stack ชี้ call site ของ high-level action:

await test.step(
  'Reserve the selected workshop',
  async () => {
    await catalog.actions.reserve(workshop.title)
    await reservation.assertions.verifyConfirmed(workshop.title)
  },
  { box: true },
)

ใช้ box เมื่อ helper ภายในมีหลาย locators แต่ caller action สำคัญ อย่าห่อทุกบรรทัดเพราะ report จะกลายเป็น noise

Redacted API Attachment Fixture

ตัวอย่างนี้ให้ wrapper method เดียว fetch เพื่อให้ type/behavior ชัด และอ่าน APIResponse.body() ตาม API ของ Playwright โดยไม่สมมติว่าเป็น browser Fetch Response:

import {
  test as base,
  type APIRequestContext,
  type APIResponse,
} from '@playwright/test'

type FetchOptions = Parameters<APIRequestContext['fetch']>[1]
type Exchange = { method: string; url: string; status: number; request: unknown; response: unknown }
type RecordedApi = { fetch: (url: string, options?: FetchOptions) => Promise<APIResponse> }

const sensitive = new Set(['authorization', 'cookie', 'password', 'token'])
const redact = (value: unknown): unknown => {
  if (Array.isArray(value)) return value.map(redact)
  if (value && typeof value === 'object') {
    return Object.fromEntries(
      Object.entries(value as Record<string, unknown>).map(([key, item]) =>
        sensitive.has(key.toLowerCase()) ? [key, '[REDACTED]'] : [key, redact(item)],
      ),
    )
  }
  return value
}

export const test = base.extend<{ api: RecordedApi }>({
  api: async ({ playwright, baseURL }, use, testInfo) => {
    const context = await playwright.request.newContext({ baseURL })
    const exchanges: Exchange[] = []

    await use({
      fetch: async (url, options) => {
        const response = await context.fetch(url, options)
        const raw = (await response.body()).toString('utf8')
        let responseBody: unknown = raw
        try { responseBody = JSON.parse(raw) } catch { /* keep text */ }
        exchanges.push({
          method: options?.method ?? 'GET',
          url: response.url(),
          status: response.status(),
          request: redact(options?.data ?? null),
          response: redact(responseBody),
        })
        return response
      },
    })

    if (testInfo.status !== testInfo.expectedStatus && exchanges.length > 0) {
      await testInfo.attach('http-exchanges.json', {
        body: JSON.stringify(exchanges, null, 2),
        contentType: 'application/json',
      })
    }
    await context.dispose()
  },
})

Project จริงต้องขยาย sensitive keys ตาม data classification และ redact nested headers/query/body ก่อนเก็บ ตัวอย่างไม่บันทึก request headers เพื่อลด risk Passing runs ไม่แนบไฟล์เพื่อลด noise/storage

Reporter ตามผู้บริโภค

Reporterเหมาะกับ
list/lineconsole feedback ระหว่าง run
HTMLhuman exploration, steps, traces, screenshot diffs
JSON/JUnitmachine/CI/test management integration
blobintermediate result สำหรับ merge หลาย shards

Reporter หลายตัวใช้พร้อมกันได้ HTML เปิด never บน CI Blob ไม่ใช่ report ปลายทางสำหรับคน แต่เก็บ attachments เพื่อ merge ภายหลัง

Triage Workflow

  1. อ่าน test title, project, retry classification และ failing step
  2. เปิด trace ดู actionability, DOM snapshot, console และ network ณ failure
  3. ตรวจ screenshot/visual diff ถ้า claim เกี่ยว layout
  4. ตรวจ redacted HTTP exchange และ correlate request id กับ server logs
  5. rerun test เดี่ยว --repeat-each แล้วเปรียบเทียบ workers/projects
  6. จัดประเภท product defect, test defect, environment, data collision หรือ known dependency
  7. แก้ root cause และเพิ่ม assertion/artifact เฉพาะที่ช่วยป้องกัน recurrence

ห้าม rerun pipeline จนเขียวโดยไม่บันทึก first failure เพราะทำลาย evidence ที่ดีที่สุด

Artifact policy ต้องมีเจ้าของ

Trace/screenshot/video/HAR/attachment อาจบันทึก DOM, session, URL และข้อมูลผู้ใช้ ระยะเก็บ, สิทธิ์เข้าถึง, การ redact และการลบต้องมาจาก security/data owner พร้อมชื่อและวันที่ ไม่ให้ผู้เขียน test เดาตัวเลขเอง

รายการตรวจสอบ

  • Codegen output ถูก review เป็น behavior test ไม่ใช่ commit ตรง
  • CI เก็บ trace/screenshot ตาม failure/retry policy
  • Steps ชี้ phase และไม่ละเอียดจน noise
  • HTTP attachment redact ก่อน serialize และไม่บันทึก headers โดยไม่จำเป็น
  • Reporter ตรงกับ human/machine/merge consumer
  • Triage เริ่มจาก first-failure evidence ไม่ใช่ blind rerun

สรุปบทนี้

Report ที่ดีลดเวลาจาก red build ถึง root cause Steps บอก where, trace บอก what happened และ redacted exchange บอก boundary response โดย artifact security เป็นส่วนของ test design ตั้งแต่ต้น

อ่านเพิ่มเติม