Authentication Flows
จบบทนี้แล้วคุณจะ...
- ทดสอบ login/logout flow
- ใช้ storageState บันทึกและนำ auth state กลับมาใช้ซ้ำ
- สร้าง global setup สำหรับ auth
- ทดสอบ unauthorized access
1 · ทำไม Auth Testing ถึงสำคัญ?
Authentication คือ user flow ที่สำคัญที่สุดของแอปส่วนใหญ่ — ถ้า login ใช้ไม่ได้ ผู้ใช้จะทำอะไรไม่ได้เลย นอกจากนี้ auth ยังเกี่ยวข้องกับ:
- Security — ต้องมั่นใจว่าคนที่ไม่มีสิทธิ์เข้าไม่ได้
- Session management — token/cookie ต้องทำงานถูกต้อง
- Multi-role — admin เห็นสิ่งที่ user ธรรมดาไม่เห็น
2 · ทดสอบ Login/Logout
import { test, expect } from '@playwright/test';
test.describe('Authentication', () => {
test('should login with valid credentials', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.getByText('Welcome back')).toBeVisible();
});
test('should show error with invalid credentials', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('wrongpassword');
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page.getByText('Invalid email or password')).toBeVisible();
await expect(page).toHaveURL(/.*login/);
});
test('should logout successfully', async ({ page }) => {
// login ก่อน
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign In' }).click();
// logout
await page.getByRole('button', { name: 'Logout' }).click();
await expect(page).toHaveURL(/.*login/);
});
});
3 · storageState — บันทึก Auth State
ปัญหา: ถ้าทุก test ต้อง login ใหม่ทุกครั้ง จะช้ามาก ทางออกคือ login ครั้งเดียว แล้วบันทึก state ไว้ใช้ซ้ำ
// global-setup.ts — รันก่อน test ทั้งหมด
import { chromium, FullConfig } from '@playwright/test';
async function globalSetup(config: FullConfig) {
const browser = await chromium.launch();
const page = await browser.newPage();
// login
await page.goto('http://localhost:3000/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign In' }).click();
// รอจน login สำเร็จ
await page.waitForURL('**/dashboard');
// บันทึก cookies + localStorage
await page.context().storageState({ path: './auth/user.json' });
await browser.close();
}
export default globalSetup;
4 · ใช้ storageState ใน Config
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
globalSetup: require.resolve('./global-setup'),
projects: [
{
name: 'authenticated',
use: {
storageState: './auth/user.json',
},
},
{
name: 'unauthenticated',
use: {
storageState: { cookies: [], origins: [] },
},
},
],
});
ตอนนี้ test ใน project "authenticated" จะเริ่มต้นด้วย login state พร้อมใช้ — ไม่ต้อง login ใหม่ทุกครั้ง
5 · Setup Projects (วิธีใหม่ที่แนะนำ)
Playwright มีวิธีที่ดีกว่า globalSetup คือใช้ setup project:
// playwright.config.ts
export default defineConfig({
projects: [
// setup project — login แล้ว save state
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
// test project — ใช้ state จาก setup
{
name: 'chromium',
dependencies: ['setup'],
use: {
storageState: './auth/user.json',
},
},
],
});
// tests/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign In' }).click();
await page.waitForURL('**/dashboard');
await page.context().storageState({ path: './auth/user.json' });
});
6 · ทดสอบ Unauthorized Access
test('should redirect to login when accessing protected page', async ({ browser }) => {
// สร้าง context ใหม่โดยไม่มี auth
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('/admin/settings');
await expect(page).toHaveURL(/.*login/);
await context.close();
});
แบบฝึกหัด
วิเคราะห์ auth testing strategy สำหรับแอปที่มี 3 roles: Admin, Editor, Viewer
- Admin เห็นทุกหน้า รวมถึง /admin/settings
- Editor เห็น /editor/posts แต่เข้า /admin/settings ไม่ได้
- Viewer เห็นแค่ /dashboard
คุณจะออกแบบ test strategy อย่างไร?
ดูคำตอบแนะนำ
// playwright.config.ts
export default defineConfig({
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'admin-tests',
dependencies: ['setup'],
use: { storageState: './auth/admin.json' },
},
{
name: 'editor-tests',
dependencies: ['setup'],
use: { storageState: './auth/editor.json' },
},
{
name: 'viewer-tests',
dependencies: ['setup'],
use: { storageState: './auth/viewer.json' },
},
],
});
// tests/auth.setup.ts
import { test as setup } from '@playwright/test';
const users = [
{ role: 'admin', email: '[email protected]', path: './auth/admin.json' },
{ role: 'editor', email: '[email protected]', path: './auth/editor.json' },
{ role: 'viewer', email: '[email protected]', path: './auth/viewer.json' },
];
for (const user of users) {
setup(`authenticate as ${user.role}`, async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill(user.email);
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign In' }).click();
await page.waitForURL('**/dashboard');
await page.context().storageState({ path: user.path });
});
}
สิ่งที่ต้องทดสอบ:
- Admin เข้า /admin/settings ได้
- Editor เข้า /editor/posts ได้ แต่ /admin/settings redirect ไป /unauthorized
- Viewer เข้า /editor/posts ไม่ได้ redirect ไป /unauthorized
สรุปสิ่งที่ต้องจำ
- Auth testing ครอบคลุม: login สำเร็จ, login ล้มเหลว, logout, unauthorized access
- storageState บันทึก cookies/localStorage เพื่อ reuse auth state
- ใช้ setup project แทน globalSetup — จัดการง่ายกว่า
- ทดสอบ multi-role ด้วย project แยกต่างหากสำหรับแต่ละ role
- อย่าลืมทดสอบ unauthorized access — ต้องมั่นใจว่าคนที่ไม่มีสิทธิ์เข้าไม่ได้