Section 04 · Module 03 · Lesson 02 · ~15 นาที

Authentication Flows

เวลาเรียน: 15 นาที ต้องรู้มาก่อน: Module 03, Lesson 01: Navigation และ Forms รูปแบบ: อ่าน + แบบฝึกสั้น

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

  • ทดสอบ 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

  1. Admin เห็นทุกหน้า รวมถึง /admin/settings
  2. Editor เห็น /editor/posts แต่เข้า /admin/settings ไม่ได้
  3. 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 — ต้องมั่นใจว่าคนที่ไม่มีสิทธิ์เข้าไม่ได้