CupCode.cc
Section 05 · Module 03 · Lesson 01 · ~24–28 นาที

ใช้ Copilot และ Claude สร้าง Playwright Script

บทนี้สอนวิธีใช้ AI assistant เขียน Playwright test ที่อ่านง่ายและเสถียร โดยให้ AI ทำส่วน boilerplate ส่วนคุณดูแลส่วน assertion และ logic หลัก

เวลาเรียน: 24–28 นาที ต้องรู้มาก่อน: Section 4 - Playwright E2E และพื้นฐานการใช้ AI assistant รูปแบบ: อ่าน + ตัวอย่าง prompt → script + แบบฝึก compare output

จบบทนี้แล้วคุณจะ...

  • ใช้ AI ช่วยเขียน Playwright test ได้เร็วกว่าเขียนเอง
  • รู้วิธี prompt ให้ AI เลือก locator ที่ stable
  • จับ pattern script ที่ AI ชอบเขียนผิด เช่น hard-coded wait, weak assert
  • ตัดสินใจได้ว่าตรงไหนให้ AI ทำ ตรงไหนต้องเขียนเอง
1 · จุดที่ AI ช่วยได้มากใน Playwright workflow

ส่วนซ้ำ ๆ คือจุดคุ้มที่สุด

AI ทำได้ดีในส่วนเหล่านี้:

  • Boilerplate: import, test.describe, fixture setup
  • Locator suggestion: แนะนำ selector หลายแบบจาก HTML
  • Pattern translation: แปลง manual steps เป็น Playwright API
  • Refactor to POM: รวม logic ที่ซ้ำเป็น Page Object
  • Add retries / timeouts: เพิ่ม config ที่จำเป็น

ส่วนที่ AI ทำได้ปานกลาง (ต้อง verify หนัก):

  • เลือก best locator (อาจเลือก css selector เปราะ)
  • เขียน assertion ที่มีความหมาย (มักเขียน toBeVisible แทนที่จะ assert ค่าจริง)
  • handle dynamic content / async race condition

ส่วนที่ AI พังบ่อย:

  • เดา selector จาก description ที่กำกวม
  • ใช้ waitForTimeout(1000) แทนการ wait แบบ deterministic
  • mock ข้อมูลที่ไม่ตรงกับ shape จริงของ API
2 · Prompt template สำหรับ Playwright script

โครงที่ใช้ได้กับ feature ทุกแบบ

You are a senior Playwright test engineer using TypeScript.

Context:
- Project uses Playwright Test, TypeScript, page object pattern
- Base URL: https://app.example.com
- Auth: shared storage state at .auth/user.json

HTML snippet of the page being tested:
[PASTE HTML]

User flow to automate:
1. Navigate to /products
2. Search for "laptop"
3. Click first result
4. Add to cart
5. Verify cart count = 1

Constraints:
- Use getByRole, getByLabel, getByTestId — avoid CSS selectors
- Use Playwright auto-waiting; do NOT use waitForTimeout
- Each test must have at least one meaningful assertion per step
- Keep test under 30 lines

Output: Single test file ready to drop into tests/.

หลัก prompt ที่ทำให้ AI gen script ดี:

  • ใส่ HTML snippet จริงให้เห็น (ไม่ใช่ description)
  • ระบุ locator strategy ที่ทีมใช้
  • ห้าม waitForTimeout explicit ตั้งแต่ prompt
  • บอก convention POM ของทีม
3 · เปรียบเทียบ prompt ที่ดี vs ไม่ดี

ดู output ต่างกันชัดเจน

Prompt แย่:

write playwright test for adding product to cart

Output:

test('add to cart', async ({ page }) => {
  await page.goto('/');
  await page.click('.product');
  await page.click('.add-to-cart');
  await page.waitForTimeout(2000);
  expect(true).toBeTruthy();
});

ปัญหา:

  • CSS selector เปราะ
  • ใช้ waitForTimeout
  • assertion = true.toBeTruthy() ไม่มีความหมาย
  • ไม่มี navigation flow ครบ

Prompt ดี (ใช้ template ข้างบน):

Output:

import { test, expect } from '@playwright/test';

test.describe('Product cart', () => {
  test('user can add a laptop to cart', async ({ page }) => {
    await page.goto('/products');

    await page.getByRole('searchbox').fill('laptop');
    await page.getByRole('button', { name: 'Search' }).click();

    const firstResult = page.getByTestId('product-card').first();
    await expect(firstResult).toBeVisible();
    await firstResult.click();

    await page.getByRole('button', { name: 'Add to cart' }).click();

    await expect(page.getByTestId('cart-count')).toHaveText('1');
  });
});

ความต่าง:

  • locator แบบ role-based, อ่านง่าย
  • ไม่มี hard-coded wait
  • assertion ตรงกับ business behavior
4 · Pattern ที่ AI ชอบทำผิด

รู้ pattern แล้วแก้ใน review ได้เร็ว

waitForTimeout(N): AI ชอบใส่เพื่อ "ความปลอดภัย" → เปลี่ยนเป็น expect(locator).toBeVisible() หรือ waitForResponse

Selector แบบ chain ยาว: .container > .row > div:nth-child(2) → เปลี่ยนเป็น getByRole หรือ getByTestId

Assertion อ่อน: expect(page).toHaveURL(/.+/) → เปลี่ยนเป็น URL เฉพาะหรือ assert content

Test ที่ยาวเกิน 50 บรรทัด: AI ชอบเขียนรวมหลาย scenario → split เป็นหลาย test

ไม่จัดการ async: ลืม await หน้า action → ตรวจ await ทุก action

Mock ที่ shape ไม่ตรง: route.fulfill({ json: { foo: 'bar' } }) แต่ frontend คาด schema เต็ม → ดึง shape จาก API จริงก่อน

5 · Workflow ที่ใช้งานได้จริงในทีม

4 ขั้น ใช้กับทุก feature ใหม่

  1. Spike แบบ manual: ลองทำ flow เองในเบราว์เซอร์ จด step ที่ทำ + selector ที่เห็น
  2. Prompt AI ด้วย context: ใส่ HTML, step, convention ทีม → AI gen draft
  3. Run + review: รัน test จริง ดูว่าผ่านไหม / locator ตรงไหม / assert มีความหมาย
  4. Refactor: เก็บส่วนที่ซ้ำเป็น Page Object, ลบ wait ที่ไม่จำเป็น

ระยะเวลา: feature เล็ก (1 flow) ~10-15 นาที vs เขียนเอง 30-45 นาที

Practice

แบบฝึก: ปรับปรุง script ที่ AI gen มา

ดู Playwright test ด้านล่างที่ AI gen มา ระบุปัญหาและเขียนเวอร์ชันที่ดีขึ้น:

import { test, expect } from '@playwright/test';

test('checkout', async ({ page }) => {
  await page.goto('https://shop.example.com');
  await page.click('.product:nth-child(1)');
  await page.waitForTimeout(3000);
  await page.click('button.btn-primary');
  await page.waitForTimeout(2000);
  await page.fill('#card', '4111111111111111');
  await page.click('.checkout-btn');
  await page.waitForTimeout(5000);
  expect(page.url()).toContain('success');
});
ดูคำตอบแนะนำ

ปัญหา:

  • ใช้ CSS selector แทน role-based
  • มี waitForTimeout 3 จุด รวม 10 วินาที
  • assertion ใช้ expect(page.url()).toContain ไม่ใช่ Playwright matcher
  • กรอกบัตรเครดิตจริง = security risk
  • ไม่มี await หน้า expect

เวอร์ชันที่ปรับปรุง:

import { test, expect } from '@playwright/test';

test('user completes checkout with test card', async ({ page }) => {
  await page.goto('/');

  await page.getByTestId('product-card').first().click();
  await expect(page.getByRole('heading', { level: 1 })).toBeVisible();

  await page.getByRole('button', { name: 'Add to cart' }).click();
  await expect(page.getByTestId('cart-count')).toHaveText('1');

  await page.getByRole('link', { name: 'Checkout' }).click();
  await page.getByLabel('Card number').fill('4242424242424242');
  await page.getByRole('button', { name: 'Pay now' }).click();

  await expect(page).toHaveURL(/\/order\/success/);
  await expect(page.getByText('Order confirmed')).toBeVisible();
});

ความต่าง: locator stable, ไม่มี hard wait, assert มีความหมาย, ใช้ test card stripe ไม่ใช่หมายเลขจริง

สิ่งที่ต้องเอาติดตัวไปจากบทนี้

  • AI ช่วยเขียน Playwright boilerplate ได้เร็ว แต่ assertion ต้องคนคุม
  • ใส่ HTML จริงและ convention ทีมใน prompt เพื่อลดการเดาของ AI
  • Pattern ที่ AI ชอบผิดได้แก่ waitForTimeout, weak assertion, CSS selector เปราะ
  • รัน test ที่ AI gen จริงทุกครั้ง อย่าเชื่อแค่ที่เห็นในหน้าจอ
  • workflow 4 ขั้น (spike → prompt → run → refactor) ใช้ได้กับทุก feature