Auto-waiting ทำงานอย่างไร
จบบทนี้แล้วคุณจะ...
- เข้าใจระบบ auto-waiting ของ Playwright
- รู้จัก actionability checks ที่ Playwright ทำอัตโนมัติ
- เปรียบเทียบ Playwright auto-wait กับ Selenium explicit wait
- รู้ว่าเมื่อไหร่ต้อง (และไม่ต้อง) เขียน manual wait
1 · Auto-waiting คืออะไร?
Auto-waiting คือความสามารถของ Playwright ที่จะรอ element พร้อมก่อนทำ action โดยอัตโนมัติ ไม่ต้องเขียน wait เอง
ตัวอย่าง: เมื่อคุณเขียน await page.click('button') Playwright จะ:
- รอให้ element ปรากฏใน DOM
- รอให้ element มองเห็นได้ (visible)
- รอให้ element หยุดเคลื่อนไหว (stable)
- รอให้ element enabled (ไม่ disabled)
- รอให้ไม่มี element อื่นบัง
- แล้วค่อยคลิก
ทั้งหมดนี้เกิดขึ้นอัตโนมัติ — ไม่ต้องเขียน code เพิ่ม
2 · Actionability Checks
Playwright ทำ actionability checks ต่อไปนี้ก่อนทำ action:
| Check | หมายความว่า | Actions ที่ต้องผ่าน |
|---|---|---|
| Attached | element อยู่ใน DOM | ทุก action |
| Visible | มองเห็นได้ (ไม่ hidden, ไม่มี size 0) | click, fill, hover |
| Stable | หยุดเคลื่อนไหว (animation จบ) | click |
| Enabled | ไม่ disabled | click, fill, selectOption |
| Receives Events | ไม่มี element อื่นบัง (overlay) | click |
| Editable | element แก้ไขได้ | fill |
3 · Playwright vs Selenium — เปรียบเทียบ
Selenium (ต้องเขียน wait เอง)
# Selenium — ต้อง explicit wait ทุกครั้ง
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
button = wait.until(
EC.element_to_be_clickable((By.ID, "submit"))
)
button.click()
Playwright (auto-wait ให้)
// Playwright — แค่บรรทัดเดียว
await page.getByRole('button', { name: 'Submit' }).click();
Playwright ทำ wait ให้อัตโนมัติ — code สั้นกว่า อ่านง่ายกว่า และเสถียรกว่า
4 · Timeout
ทุก action มี timeout (default 30 วินาที) ถ้า element ไม่พร้อมภายในเวลา จะ throw error:
// ใช้ default timeout (30 วินาที)
await page.getByRole('button', { name: 'Submit' }).click();
// กำหนด timeout เฉพาะ action นี้
await page.getByRole('button', { name: 'Submit' }).click({
timeout: 10000 // 10 วินาที
});
// ตั้ง timeout สำหรับทุก action ใน config
// playwright.config.ts
export default defineConfig({
use: {
actionTimeout: 15000, // 15 วินาที
},
});
5 · เมื่อไหร่ต้อง Wait เอง?
ส่วนใหญ่ไม่ต้องเขียน wait เอง แต่มีบางกรณีที่ต้อง:
กรณีที่ต้อง wait
// รอ URL เปลี่ยน (SPA)
await page.waitForURL('**/dashboard');
// รอ API response
await page.waitForResponse(resp =>
resp.url().includes('/api/data')
);
// รอ element หายไป (เช่น loading spinner)
await page.getByTestId('spinner').waitFor({ state: 'hidden' });
// รอ element ปรากฏ
await page.getByText('Data loaded').waitFor({ state: 'visible' });
กรณีที่ไม่ต้อง wait (Playwright ทำให้แล้ว)
// ไม่ต้อง wait ก่อน click — Playwright รอ element พร้อมเอง
await page.getByRole('button', { name: 'Submit' }).click();
// ไม่ต้อง wait ก่อน fill — Playwright รอ field editable เอง
await page.getByLabel('Email').fill('[email protected]');
// ไม่ต้อง wait ใน assertion — auto-retry ให้แล้ว
await expect(page.getByText('Success')).toBeVisible();
6 · Anti-pattern: อย่าใช้ page.waitForTimeout()
// ไม่ดี — hard-coded wait
await page.waitForTimeout(3000);
await page.getByRole('button', { name: 'Submit' }).click();
// ดี — Playwright auto-wait
await page.getByRole('button', { name: 'Submit' }).click();
page.waitForTimeout() เป็น anti-pattern เพราะ:
- ถ้า wait น้อยไป → test flaky
- ถ้า wait มากไป → test ช้า
- ไม่มีทางรู้ว่าต้อง wait เท่าไหร่ถึง "พอดี"
แบบฝึกหัด
วิเคราะห์ test ด้านล่าง แล้ว refactor ให้ใช้ auto-waiting แทน manual waits:
Before (ใช้ manual waits)
test('should load products', async ({ page }) => {
await page.goto('/products');
// รอ 3 วินาทีให้หน้าโหลด
await page.waitForTimeout(3000);
// รอให้ spinner หายไป
await page.waitForTimeout(2000);
// คลิกสินค้าแรก
await page.waitForTimeout(1000);
await page.locator('.product-card:first-child').click();
// ตรวจว่าไปหน้า detail
await page.waitForTimeout(2000);
const title = await page.locator('.product-title').textContent();
expect(title).not.toBeNull();
});
ดูคำตอบแนะนำ
After (ใช้ auto-waiting)
test('should load products', async ({ page }) => {
await page.goto('/products');
// รอ spinner หายไป (ถ้ามี)
await page.getByTestId('spinner').waitFor({ state: 'hidden' });
// คลิกสินค้าแรก — Playwright auto-wait ให้
await page.getByRole('listitem').first().click();
// ตรวจว่าไปหน้า detail — auto-retry assertion
await expect(page.getByRole('heading')).toBeVisible();
});
สิ่งที่เปลี่ยน:
- ลบ
waitForTimeout()ทั้งหมด - ใช้
waitFor({ state: 'hidden' })สำหรับ spinner - ใช้ getByRole แทน CSS selector
- ใช้ auto-retry assertion แทนการอ่าน textContent แล้ว assert
สรุปสิ่งที่ต้องจำ
- Playwright auto-wait ก่อนทุก action — รอ visible, stable, enabled
- ไม่ต้องเขียน wait สำหรับ click, fill, assertion — Playwright ทำให้แล้ว
- page.waitForTimeout() เป็น anti-pattern — อย่าใช้
- ใช้ waitForURL(), waitForResponse(), waitFor() เฉพาะเมื่อ auto-wait ไม่เพียงพอ
- ตั้ง actionTimeout ใน config แทนการกำหนด timeout ทีละ action