- Data-Driven Testing with Playwright
- 2. เตรียมโปรเจกต์ Playwright
- 3. โครงสร้างโปรเจกต์ตัวอย่าง
- 4. เว็บไซต์สำหรับทดลอง
- 5. ตัวอย่างที่ 1: Data-Driven Testing ด้วย Array
- 6. ทำไมชื่อ Test Case ต้องไม่ซ้ำกัน
- 7. ตัวอย่างที่ 2: ใช้ test.describe() แยกกลุ่มข้อมูล
- 8. ตัวอย่างที่ 3: Data-Driven Testing จาก JSON
- 9. ตัวอย่างที่ 4: Data-Driven Testing จาก CSV
- 10. Array, JSON หรือ CSV ควรเลือกอะไร
- 11. ใช้ Page Object Model ร่วมกับ Data-Driven Testing
- 12. แยก Data และ Logic อย่างเป็นระบบ
- 13. ใช้ Fixtures เมื่อข้อมูลเป็น Test Context
- 14. Parameterized Projects
- 15. Data-Driven Testing กับ Boundary Value Analysis
- 16. Data-Driven Testing กับ Equivalence Partitioning
- 17. อย่าใส่ Password จริงไว้ใน Test Data
- 18. Configuration ที่เหมาะกับ Test Automation
- 19. รัน Test แบบต่าง ๆ
- 20. ใช้ Tag/Filter กับ Data-Driven Test
- 21. GitHub Actions สำหรับ Data-Driven Playwright Test
- 22. Parallel Execution กับ Data-Driven Testing
- 23. การออกแบบ Dataset ที่ดี
- 24. หลีกเลี่ยง Test Logic ใน Dataset
- 25. Best Practices
- 26. Data-Driven Testing ไม่ใช่การ Loop ภายใน Test เดียว
- 27. Architecture สำหรับโปรเจกต์จริง
- 28. ตัวอย่าง Data Loader
- 29. เมื่อใดควรใช้ Data-Driven Testing
- 30. เมื่อใดไม่ควรใช้ Data-Driven Testing
- 31. สรุป
- 32. คำสั่งสรุปสำหรับ Workshop
- References
#Data-Driven Testing with Playwright
การทดสอบระบบจริงมักไม่ได้มีเพียงข้อมูลชุดเดียว เช่น หน้า Login อาจต้องทดสอบทั้งผู้ใช้ที่ถูกต้อง รหัสผ่านผิด ช่องว่าง หรือข้อมูลหลายรูปแบบ หากเราเขียน Test Script แยกทีละกรณี โค้ดจะซ้ำและดูแลรักษายาก
Data-Driven Testing (DDT) คือแนวทางที่แยก Test Data ออกจาก Test Logic แล้วนำข้อมูลหลายชุดมารันกับขั้นตอนการทดสอบเดียวกัน
Playwright Test รองรับแนวทางนี้ได้โดยตรงผ่านการสร้าง Parameterized Tests และสามารถอ่านข้อมูลจาก Array, JSON, CSV หรือแหล่งข้อมูลอื่นผ่าน Node.js ได้
#1. Data-Driven Testing คืออะไร
แนวคิดหลักคือ
Test Data
│
├── Case 1
├── Case 2
├── Case 3
└── Case N
│
▼
Reusable Test Logic
│
▼
Playwright Test Runner
│
▼
Pass / Fail / Report
แทนที่จะเขียน
test('login user A', async ({ page }) => {
// ...
});
test('login user B', async ({ page }) => {
// ...
});
test('login user C', async ({ page }) => {
// ...
});
เราสามารถเก็บข้อมูลเป็นชุด
const testData = [
{ username: 'user-a', password: 'pass-a' },
{ username: 'user-b', password: 'pass-b' },
{ username: 'user-c', password: 'pass-c' },
];
จากนั้นสร้าง Test Case จากข้อมูลแต่ละรายการ
for (const data of testData) {
test(`login with ${data.username}`, async ({ page }) => {
// ใช้ Test Logic ชุดเดียวกัน
});
}
ข้อดีคือ
- ลดโค้ดซ้ำ
- เพิ่ม Test Case ได้ง่าย
- แยก Test Data ออกจาก Test Logic
- รองรับข้อมูลจำนวนมาก
- เหมาะกับ Regression Test
- เหมาะกับ Boundary Value และ Equivalence Partitioning
- รายงานผลแยกตามข้อมูลแต่ละชุดได้
- ใช้ร่วมกับ CI/CD ได้ง่าย
#2. เตรียมโปรเจกต์ Playwright
#2.1 สิ่งที่ควรมี
Playwright รุ่นปัจจุบันใช้ Node.js รุ่นที่รองรับตามเอกสารของ Playwright
ตรวจสอบ Node.js
node --version
npm --version
เริ่มโปรเจกต์ Playwright
npm init playwright@latest
ระหว่างติดตั้งสามารถเลือก
TypeScript
tests
GitHub Actions
Install Playwright browsers
หากมีโปรเจกต์ Node.js อยู่แล้ว สามารถติดตั้งเพิ่มได้ด้วย
npm install -D @playwright/test@latest
npx playwright install
ตรวจสอบเวอร์ชัน
npx playwright --version
#3. โครงสร้างโปรเจกต์ตัวอย่าง
playwright-data-driven/
├── data/
│ ├── login-data.json
│ └── login-data.csv
├── pages/
│ └── LoginPage.ts
├── tests/
│ ├── login-array.spec.ts
│ ├── login-json.spec.ts
│ ├── login-csv.spec.ts
│ └── login-pom.spec.ts
├── playwright.config.ts
├── package.json
└── tsconfig.json
แนวคิดการแบ่งความรับผิดชอบ
data/
เก็บ Test Data
pages/
เก็บ Page Object
tests/
เก็บ Test Scenario และ Assertions
playwright.config.ts
เก็บ Configuration
#4. เว็บไซต์สำหรับทดลอง
ตัวอย่างนี้ใช้
https://seleniumbase.io/simple/login
ข้อมูลเข้าสู่ระบบตัวอย่างคือ
Username: demo_user
Password: secret_pass
หน้าเว็บมี element หลักที่สามารถใช้ได้ เช่น
#username
#password
Sign in
#5. ตัวอย่างที่ 1: Data-Driven Testing ด้วย Array
สร้างไฟล์
tests/login-array.spec.ts
import { test, expect } from '@playwright/test';
type LoginData = {
caseName: string;
username: string;
password: string;
shouldLogin: boolean;
};
const loginData: LoginData[] = [
{
caseName: 'valid username and password',
username: 'demo_user',
password: 'secret_pass',
shouldLogin: true,
},
{
caseName: 'invalid password',
username: 'demo_user',
password: 'wrong_password',
shouldLogin: false,
},
{
caseName: 'invalid username',
username: 'wrong_user',
password: 'secret_pass',
shouldLogin: false,
},
{
caseName: 'empty username and password',
username: '',
password: '',
shouldLogin: false,
},
];
for (const data of loginData) {
test(`Login: ${data.caseName}`, async ({ page }) => {
await page.goto('https://seleniumbase.io/simple/login');
await page.locator('#username').fill(data.username);
await page.locator('#password').fill(data.password);
await page.getByRole('link', { name: 'Sign in' }).click();
if (data.shouldLogin) {
await expect(
page.getByRole('heading', { name: 'Welcome!' })
).toBeVisible();
} else {
await expect(
page.getByRole('heading', { name: 'Welcome!' })
).not.toBeVisible();
}
});
}
รัน
npx playwright test tests/login-array.spec.ts
รันแบบเห็น Browser
npx playwright test tests/login-array.spec.ts --headed
เปิด UI Mode
npx playwright test --ui
#6. ทำไมชื่อ Test Case ต้องไม่ซ้ำกัน
ชื่อ Test ควรบอกว่าข้อมูลชุดใดกำลังถูกทดสอบ
ตัวอย่างที่ดี
test(`Login: ${data.caseName}`, async ({ page }) => {
// ...
});
ผลใน Report จะอ่านง่าย เช่น
✓ Login: valid username and password
✓ Login: invalid password
✓ Login: invalid username
✓ Login: empty username and password
หากใช้เพียง
test('login test', ...)
กับข้อมูลทุกชุด การวิเคราะห์ผลจะทำได้ยากกว่า
#7. ตัวอย่างที่ 2: ใช้ test.describe() แยกกลุ่มข้อมูล
import { test, expect } from '@playwright/test';
const users = [
{
name: 'valid-user',
username: 'demo_user',
password: 'secret_pass',
expected: true,
},
{
name: 'wrong-password',
username: 'demo_user',
password: '123456',
expected: false,
},
];
for (const user of users) {
test.describe(`Login dataset: ${user.name}`, () => {
test.beforeEach(async ({ page }) => {
await page.goto('https://seleniumbase.io/simple/login');
});
test('should return expected login result', async ({ page }) => {
await page.locator('#username').fill(user.username);
await page.locator('#password').fill(user.password);
await page.getByRole('link', { name: 'Sign in' }).click();
const welcome = page.getByRole('heading', {
name: 'Welcome!',
});
if (user.expected) {
await expect(welcome).toBeVisible();
} else {
await expect(welcome).not.toBeVisible();
}
});
});
}
รูปแบบนี้เหมาะเมื่อต้องการ
- Hooks แยกตาม Dataset
- กลุ่ม Test ที่อ่านง่าย
- Setup/Teardown เฉพาะชุดข้อมูล
#8. ตัวอย่างที่ 3: Data-Driven Testing จาก JSON
เมื่อ Test Data มากขึ้น ควรแยกออกจาก Test Script
สร้าง
data/login-data.json
[
{
"caseName": "valid login",
"username": "demo_user",
"password": "secret_pass",
"shouldLogin": true
},
{
"caseName": "wrong password",
"username": "demo_user",
"password": "wrong_password",
"shouldLogin": false
},
{
"caseName": "wrong username",
"username": "guest_user",
"password": "secret_pass",
"shouldLogin": false
},
{
"caseName": "empty credentials",
"username": "",
"password": "",
"shouldLogin": false
}
]
สร้าง
tests/login-json.spec.ts
import { test, expect } from '@playwright/test';
import fs from 'node:fs';
import path from 'node:path';
type LoginData = {
caseName: string;
username: string;
password: string;
shouldLogin: boolean;
};
const filePath = path.resolve(
process.cwd(),
'data/login-data.json'
);
const loginData: LoginData[] = JSON.parse(
fs.readFileSync(filePath, 'utf-8')
);
for (const data of loginData) {
test(`JSON - ${data.caseName}`, async ({ page }) => {
await page.goto('https://seleniumbase.io/simple/login');
await page.locator('#username').fill(data.username);
await page.locator('#password').fill(data.password);
await page.getByRole('link', { name: 'Sign in' }).click();
const welcome = page.getByRole('heading', {
name: 'Welcome!',
});
if (data.shouldLogin) {
await expect(welcome).toBeVisible();
} else {
await expect(welcome).not.toBeVisible();
}
});
}
รัน
npx playwright test tests/login-json.spec.ts
ข้อดีของ JSON คือ
- อ่านง่าย
- รองรับ object ซ้อนกัน
- เหมาะกับ API/E2E Test
- ใช้ร่วมกับ JavaScript/TypeScript ได้ดี
- ไม่ต้องติดตั้ง Parser เพิ่ม
#9. ตัวอย่างที่ 4: Data-Driven Testing จาก CSV
CSV เหมาะเมื่อ Test Data ถูกจัดเตรียมโดย Tester, BA หรือผู้ใช้ที่ทำงานกับ Spreadsheet
ติดตั้ง Parser
npm install -D csv-parse
สร้าง
data/login-data.csv
caseName,username,password,shouldLogin
valid login,demo_user,secret_pass,true
wrong password,demo_user,wrong_password,false
wrong username,guest_user,secret_pass,false
empty credentials,,,false
สร้าง
tests/login-csv.spec.ts
import { test, expect } from '@playwright/test';
import fs from 'node:fs';
import path from 'node:path';
import { parse } from 'csv-parse/sync';
type CsvLoginData = {
caseName: string;
username: string;
password: string;
shouldLogin: string;
};
const filePath = path.resolve(
process.cwd(),
'data/login-data.csv'
);
const records = parse(
fs.readFileSync(filePath),
{
columns: true,
skip_empty_lines: true,
trim: true,
}
) as CsvLoginData[];
for (const data of records) {
test(`CSV - ${data.caseName}`, async ({ page }) => {
await page.goto('https://seleniumbase.io/simple/login');
await page.locator('#username').fill(data.username ?? '');
await page.locator('#password').fill(data.password ?? '');
await page.getByRole('link', { name: 'Sign in' }).click();
const welcome = page.getByRole('heading', {
name: 'Welcome!',
});
const shouldLogin =
data.shouldLogin.toLowerCase() === 'true';
if (shouldLogin) {
await expect(welcome).toBeVisible();
} else {
await expect(welcome).not.toBeVisible();
}
});
}
รัน
npx playwright test tests/login-csv.spec.ts
Playwright ทำงานบน Node.js จึงสามารถอ่านไฟล์ผ่าน File System และใช้ CSV library ของ npm ได้โดยตรง
#10. Array, JSON หรือ CSV ควรเลือกอะไร
| รูปแบบ | เหมาะกับ | จุดเด่น | ข้อควรระวัง |
|---|---|---|---|
| Array | ข้อมูลน้อย | เขียนง่ายที่สุด | Test Script โตเร็ว |
| JSON | ข้อมูลเชิงโครงสร้าง | Type-friendly, อ่านง่าย | ผู้ใช้ธุรกิจอาจแก้ไขไม่สะดวก |
| CSV | ตารางข้อมูลจำนวนมาก | เปิดด้วย Spreadsheet ได้ | Data Type เป็น string เป็นหลัก |
| Database | Integration/Enterprise | ใช้ข้อมูลจริงหรือ seeded data | ต้องควบคุม state |
| API | Dynamic test data | ทดสอบระบบแบบ end-to-end | เพิ่ม dependency ภายนอก |
สำหรับโปรเจกต์ทั่วไปสามารถเริ่มจาก
Array → JSON → CSV/Database
ตามขนาดและความซับซ้อนของข้อมูล
#11. ใช้ Page Object Model ร่วมกับ Data-Driven Testing
เมื่อ Test Script เริ่มใหญ่ ไม่ควรให้ selector และ action กระจายอยู่ในทุก Test File
สร้าง
pages/LoginPage.ts
import {
Page,
Locator,
expect,
} from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly username: Locator;
readonly password: Locator;
readonly signIn: Locator;
readonly welcomeHeading: Locator;
constructor(page: Page) {
this.page = page;
this.username = page.locator('#username');
this.password = page.locator('#password');
this.signIn = page.getByRole('link', {
name: 'Sign in',
});
this.welcomeHeading = page.getByRole(
'heading',
{ name: 'Welcome!' }
);
}
async goto() {
await this.page.goto(
'https://seleniumbase.io/simple/login'
);
}
async login(username: string, password: string) {
await this.username.fill(username);
await this.password.fill(password);
await this.signIn.click();
}
async expectLoginSuccess() {
await expect(this.welcomeHeading).toBeVisible();
}
async expectLoginFailure() {
await expect(this.welcomeHeading).not.toBeVisible();
}
}
จากนั้นสร้าง
tests/login-pom.spec.ts
import { test } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
const cases = [
{
name: 'valid credentials',
username: 'demo_user',
password: 'secret_pass',
expected: true,
},
{
name: 'invalid password',
username: 'demo_user',
password: 'invalid',
expected: false,
},
];
for (const data of cases) {
test(`POM - ${data.name}`, async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login(
data.username,
data.password
);
if (data.expected) {
await loginPage.expectLoginSuccess();
} else {
await loginPage.expectLoginFailure();
}
});
}
จะเห็นว่า Test Case อ่านง่ายขึ้นเป็น
Prepare Data
→ Open Page
→ Login
→ Verify Expected Result
#12. แยก Data และ Logic อย่างเป็นระบบ
โครงสร้างที่แนะนำ
┌──────────────────┐
│ Test Dataset │
│ JSON / CSV / DB │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Playwright Test │
│ Parameterize │
└────────┬─────────┘
│
┌───────┴────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Page Objects │ │ Fixtures │
└──────┬───────┘ └──────┬───────┘
└────────┬─────────┘
▼
┌──────────────────┐
│ Browser / System │
│ Under Test │
└────────┬─────────┘
▼
┌──────────────────┐
│ Assert + Report │
└──────────────────┘
#13. ใช้ Fixtures เมื่อข้อมูลเป็น Test Context
Data-Driven Testing ไม่ได้หมายความว่าข้อมูลทุกอย่างต้องมาจาก CSV/JSON
ข้อมูลที่เป็น Configuration หรือ Context เช่น
- role
- locale
- tenant
- environment
- default user
- API client
- authenticated session
เหมาะกับ Playwright Fixtures
ตัวอย่าง
import { test as base } from '@playwright/test';
type TestOptions = {
role: string;
};
export const test = base.extend<TestOptions>({
role: ['student', { option: true }],
});
จากนั้นใช้ใน Test
test('show dashboard by role', async ({
page,
role,
}) => {
console.log(role);
});
#14. Parameterized Projects
หากต้องการรัน Test ชุดเดียวกับ Configuration หลายแบบ สามารถใช้ projects
ตัวอย่าง
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'student',
use: {
locale: 'th-TH',
},
},
{
name: 'international',
use: {
locale: 'en-US',
},
},
],
});
เหมาะกับ
Browser
Environment
Locale
Device
Role
Tenant
Configuration
แต่ถ้าเป็น
username
password
product
quantity
search keyword
form input
ควรใช้ Dataset เพื่อสร้าง Parameterized Tests มากกว่า
#15. Data-Driven Testing กับ Boundary Value Analysis
DDT เหมาะมากกับการทดสอบขอบเขต
สมมติระบบรับอายุ
18 - 60 ปี
Dataset
const ageCases = [
{ age: 17, valid: false },
{ age: 18, valid: true },
{ age: 19, valid: true },
{ age: 59, valid: true },
{ age: 60, valid: true },
{ age: 61, valid: false },
];
Test
for (const data of ageCases) {
test(`age=${data.age}`, async ({ page }) => {
// กรอก age
// submit
// verify ตาม data.valid
});
}
นี่ทำให้ Test Design และ Automation เชื่อมกันโดยตรง
#16. Data-Driven Testing กับ Equivalence Partitioning
ตัวอย่าง Username
Valid:
- ตัวอักษร 6-20 ตัว
Invalid:
- น้อยกว่า 6
- มากกว่า 20
- ตัวอักษรต้องห้าม
- ค่าว่าง
Dataset
const usernameCases = [
{
value: 'tester01',
expected: true,
},
{
value: 'abc',
expected: false,
},
{
value: '',
expected: false,
},
{
value: 'user name',
expected: false,
},
];
ทำให้ 1 Test Logic สามารถครอบคลุมหลาย Partition ได้
#17. อย่าใส่ Password จริงไว้ใน Test Data
ตัวอย่างที่ไม่ควรทำ
{
"username": "admin",
"password": "ProductionPassword123!"
}
เพราะข้อมูลอาจถูก commit เข้า Git
ควรใช้ Environment Variables หรือ Secret Manager
ตัวอย่าง
const username = process.env.TEST_USERNAME;
const password = process.env.TEST_PASSWORD;
บน macOS/Linux
TEST_USERNAME=demo_user \
TEST_PASSWORD=secret_pass \
npx playwright test
PowerShell
$env:TEST_USERNAME="demo_user"
$env:TEST_PASSWORD="secret_pass"
npx playwright test
ใน GitHub Actions ควรเก็บใน
GitHub Secrets
แล้วส่งเป็น environment variable
#18. Configuration ที่เหมาะกับ Test Automation
ตัวอย่าง playwright.config.ts
import {
defineConfig,
devices,
} from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['list'],
['html', { open: 'never' }],
],
use: {
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
],
});
ข้อดีคือเมื่อ Data-Driven Test มีหลายกรณี หากบางรายการล้มเหลวจะมีหลักฐานช่วยวิเคราะห์ เช่น
- Screenshot
- Video
- Trace
- HTML Report
#19. รัน Test แบบต่าง ๆ
รันทั้งหมด
npx playwright test
รันไฟล์เดียว
npx playwright test tests/login-json.spec.ts
รัน Chromium
npx playwright test --project=chromium
รันแบบ Headed
npx playwright test --headed
เปิด UI Mode
npx playwright test --ui
Debug
npx playwright test --debug
เปิด HTML Report
npx playwright show-report
#20. ใช้ Tag/Filter กับ Data-Driven Test
สามารถตั้งชื่อหรือ Tag ให้ Test Case เพื่อเลือกรันเฉพาะกลุ่มได้
ตัวอย่างแนวคิด Dataset
const cases = [
{
name: 'valid login',
tag: '@smoke',
username: 'demo_user',
password: 'secret_pass',
},
{
name: 'wrong password',
tag: '@regression',
username: 'demo_user',
password: 'wrong',
},
];
สร้างชื่อ Test
for (const data of cases) {
test(`${data.tag} ${data.name}`, async ({ page }) => {
// ...
});
}
รันเฉพาะ Smoke Test
npx playwright test --grep @smoke
#21. GitHub Actions สำหรับ Data-Driven Playwright Test
ตัวอย่าง
.github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches:
- main
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 14
Pipeline
Push / Pull Request
│
▼
Install Dependencies
│
▼
Install Browsers
│
▼
Load Test Data
│
▼
Run Parameterized Tests
│
▼
Generate HTML Report
│
▼
Upload Artifact
#22. Parallel Execution กับ Data-Driven Testing
Playwright รองรับการรัน Test แบบ Parallel
เมื่อเราเขียน
for (const data of loginData) {
test(`Login ${data.caseName}`, async ({ page }) => {
// ...
});
}
ข้อมูลแต่ละรายการถูกสร้างเป็น Test Case แยกกัน จึงสามารถใช้ความสามารถของ Playwright Test Runner ในการจัดสรรงานให้ worker ได้ตาม configuration
อย่างไรก็ตาม ต้องระวังเมื่อ Test Data ใช้ Resource ร่วมกัน เช่น
บัญชีเดียวกัน
ข้อมูล Database record เดียวกัน
Order เดียวกัน
ไฟล์เดียวกัน
Shared state
แนวทางแก้ไข
ใช้ข้อมูลที่ unique
สร้างข้อมูลก่อน Test
ลบข้อมูลหลัง Test
ใช้ Fixture
ใช้ API สำหรับ seed data
แยก Database/Test Environment
#23. การออกแบบ Dataset ที่ดี
Dataset ควรมีอย่างน้อย
caseName
input
expected
ตัวอย่าง
{
"caseName": "Login with valid credentials",
"username": "demo_user",
"password": "secret_pass",
"expected": {
"loggedIn": true,
"heading": "Welcome!"
}
}
ข้อดีคือ Test Data อธิบาย Expected Result ได้ชัดเจน
#24. หลีกเลี่ยง Test Logic ใน Dataset
ไม่ควรเขียน Dataset แบบ
{
"action": "click",
"selector": "#login",
"wait": 3000,
"assert": "h1"
}
เพราะจะกลายเป็นการสร้าง Testing Framework ซ้อน Testing Framework และทำให้ Debug ยาก
Dataset ควรบอก
Input
Expected Result
Business Condition
ส่วน
Click
Fill
Navigation
Assertion implementation
ควรอยู่ใน Test Logic หรือ Page Object
#25. Best Practices
#25.1 ตั้งชื่อ Test Case ให้ระบุ Dataset
ดี
test(`Login - ${data.caseName}`, ...)
ไม่แนะนำ
test('login test', ...)
#25.2 ใช้ TypeScript Type
type LoginData = {
caseName: string;
username: string;
password: string;
shouldLogin: boolean;
};
ช่วยลดความผิดพลาดจาก Test Data
#25.3 Validate External Test Data
หากอ่านจาก CSV/JSON จำนวนมาก ควรตรวจสอบก่อนสร้าง Test
if (!data.caseName) {
throw new Error('caseName is required');
}
โปรเจกต์ขนาดใหญ่สามารถใช้ Schema Validator เช่น Zod
#25.4 อย่าใช้ index เป็นชื่อ Test เพียงอย่างเดียว
ไม่แนะนำ
case 1
case 2
case 3
แนะนำ
valid credentials
wrong password
empty username
username boundary 20 chars
#25.5 ทำให้แต่ละ Dataset Independent
Test Case ไม่ควรขึ้นกับผลของ Test ก่อนหน้า
ไม่ควรเป็น
Case 1 สร้าง User
Case 2 Login User จาก Case 1
Case 3 ลบ User จาก Case 1
ควรให้แต่ละ Test มี Setup ที่ชัดเจน หรือใช้ Fixture
#25.6 หลีกเลี่ยง Hard Wait
ไม่ควรใช้
await page.waitForTimeout(5000);
หากไม่ได้จำเป็นจริง
ควรใช้ Web-First Assertion
await expect(
page.getByRole('heading', {
name: 'Welcome!',
})
).toBeVisible();
#26. Data-Driven Testing ไม่ใช่การ Loop ภายใน Test เดียว
รูปแบบที่ไม่แนะนำ
test('login all users', async ({ page }) => {
for (const user of users) {
// test user
}
});
ปัญหาคือ
Dataset 1 PASS
Dataset 2 PASS
Dataset 3 FAIL
Dataset 4 ไม่ได้รัน
Report จะแสดงเป็น Test เดียว
ควรสร้าง Test แยกจาก Dataset
for (const user of users) {
test(`login ${user.name}`, async ({ page }) => {
// ...
});
}
ผลคือ
PASS user 1
PASS user 2
FAIL user 3
PASS user 4
วิเคราะห์ผลได้ง่ายกว่ามาก
#27. Architecture สำหรับโปรเจกต์จริง
แนวทางที่เหมาะกับโปรเจกต์ขนาดกลางถึงใหญ่
tests/
│
├── data/
│ ├── users.json
│ ├── products.json
│ └── orders.csv
│
├── fixtures/
│ ├── auth.fixture.ts
│ └── api.fixture.ts
│
├── pages/
│ ├── LoginPage.ts
│ ├── ProductPage.ts
│ └── CheckoutPage.ts
│
├── specs/
│ ├── login.spec.ts
│ ├── product.spec.ts
│ └── checkout.spec.ts
│
└── utils/
├── csv.ts
├── data-generator.ts
└── validation.ts
แนวคิดคือ
Data
↓
Fixtures / Data Loader
↓
Test Scenario
↓
Page Object / API Client
↓
System Under Test
↓
Assertions
↓
Report
#28. ตัวอย่าง Data Loader
สร้าง
utils/data-loader.ts
import fs from 'node:fs';
import path from 'node:path';
export function loadJson<T>(
relativePath: string
): T {
const filePath = path.resolve(
process.cwd(),
relativePath
);
return JSON.parse(
fs.readFileSync(filePath, 'utf-8')
) as T;
}
ใช้งาน
import { loadJson } from '../utils/data-loader';
type LoginData = {
caseName: string;
username: string;
password: string;
shouldLogin: boolean;
};
const users = loadJson<LoginData[]>(
'data/login-data.json'
);
ช่วยลดการเขียน File I/O ซ้ำหลาย Test File
#29. เมื่อใดควรใช้ Data-Driven Testing
เหมาะกับกรณี
#Login
Valid user
Invalid user
Locked user
Inactive user
Different roles
#Form Validation
Required field
Min length
Max length
Invalid format
Boundary values
#Search
Keyword
Empty query
Special characters
Unicode
Thai/English
#E-Commerce
Product
Quantity
Coupon
Shipping method
Payment method
#API Testing
Request body
Status code
Expected response
Validation rules
#Role-Based Access Control
Admin
Teacher
Student
Guest
#30. เมื่อใดไม่ควรใช้ Data-Driven Testing
ไม่จำเป็นต้องใช้ DDT หาก
- มีข้อมูลเพียง 1 ชุด
- Scenario แต่ละชุดมี Flow แตกต่างกันมาก
- Test Data มี dependency ซับซ้อน
- Test Logic ต้องเปลี่ยนตามข้อมูลแทบทั้งหมด
- การอ่าน Dataset ทำให้ Test เข้าใจยากกว่าการเขียนแยก
หลักการคือ
ใช้ Data-Driven Testing เมื่อ “ขั้นตอนเหมือนเดิม แต่ข้อมูลและ Expected Result เปลี่ยน”
#31. สรุป
Data-Driven Testing ใน Playwright สามารถเริ่มได้ง่ายจาก Parameterized Tests
Test Data
↓
for / forEach
↓
Playwright Test
↓
Page Object / Fixtures
↓
Assertions
↓
HTML Report / CI
แนวทางเลือกใช้
ข้อมูลน้อย
→ Array
ข้อมูลแยกเป็นไฟล์และมีโครงสร้าง
→ JSON
ข้อมูลตารางจำนวนมาก
→ CSV
Configuration หลายชุด
→ Projects / Option Fixtures
Setup และ Context ที่ reusable
→ Fixtures
โปรเจกต์ขนาดกลาง-ใหญ่
→ Data + Page Object + Fixtures + CI
จุดสำคัญที่สุดคือ สร้าง Test Case แยกสำหรับข้อมูลแต่ละชุด แทนการวน Loop ทุก Dataset ภายใน test() เดียว เพื่อให้ Playwright สามารถรายงานผล, retry, parallelize และ debug แต่ละกรณีได้อย่างชัดเจน
#32. คำสั่งสรุปสำหรับ Workshop
# สร้างโปรเจกต์
npm init playwright@latest
# เข้าโฟลเดอร์
cd playwright-data-driven
# ติดตั้ง CSV parser
npm install -D csv-parse
# รัน Test ทั้งหมด
npx playwright test
# รันแบบเห็น Browser
npx playwright test --headed
# เปิด UI Mode
npx playwright test --ui
# Debug
npx playwright test --debug
# เปิด HTML Report
npx playwright show-report
#References
-
Playwright — Parameterize tests
https://playwright.dev/docs/test-parameterize -
Playwright — Fixtures
https://playwright.dev/docs/test-fixtures -
Playwright — Projects
https://playwright.dev/docs/test-projects -
Playwright — Best Practices
https://playwright.dev/docs/best-practices -
Playwright — Running and Debugging Tests
https://playwright.dev/docs/running-tests -
SeleniumBase — Simple Login Testing Page
https://seleniumbase.io/simple/login -
SeleniumBase Documentation
https://seleniumbase.io/