Wiki

← Tất cả bài wiki

storageState & Authentication (Đăng nhập 1 lần, dùng cho mọi test)

automationplaywright

storageState là snapshot của cookies + localStorage (và IndexedDB từ bản mới) của một browser context, lưu ra file JSON và nạp lại khi tạo context mới. Playwright dùng nó để giải bài toán kinh điển: đăng nhập 1 lần duy nhất, mọi test sau start với session đã login — suite nhanh và ổn định hơn hẳn so với login qua UI từng test.

Khái niệm & Bản chất

  • storageState = file JSON chứa cookies + origins (localStorage theo từng origin) của context. Đây là toàn bộ thứ browser dùng để nhận diện 'bạn đã đăng nhập'.
  • context.storageState({ path }): gọi sau khi login → dump session ra file. browser.newContext({ storageState: 'file.json' }) hoặc config use.storageState → context mới mang sẵn session đó.
  • Setup project pattern: định nghĩa project 'setup' chạy đầu tiên (login + lưu state), các project test dependencies: ['setup'] + storageState trỏ file đã lưu. Playwright chạy setup trước, test sau.
  • Bản chất: thay vì test 'mở browser → điền form login' (vài giây, dễ flaky), test chỉ 'mở browser đã có cookie' — giống copy session từ trình duyệt thật.
  • Session expiry là giới hạn tự nhiên: cookie token hết hạn sau X giờ → file state cũ không còn dùng được, phải login lại.

Cách hoạt động & API chính

  • Lưu: await context.storageState({ path: 'playwright/.auth/user.json' }) — gọi sau khi đã verify login thành công.
  • Nạp: browser.newContext({ storageState: '.auth/user.json' }) hoặc trong config use: { storageState: '...' } cho cả project.
  • Setup project: trong playwright.config.ts thêm { name: 'setup', testMatch: /.*\.setup\.ts/ } + dependencies ở project khác.
  • Multi-role: lưu nhiều file (admin.json, user.json) → project/test khác nhau dùng state khác nhau; hoặc test.use({ storageState: 'admin.json' }) trong 1 file.
  • Xoá session (test logged-out): test.use({ storageState: { cookies: [], origins: [] } }) — context sạch hoàn toàn.
  • IndexedDB: từ v1.43+ có option indexedDB: true trong storageState nếu app lưu auth ở IndexedDB thay vì localStorage/cookie.

Ví dụ & Cú pháp thực tế

  • ```ts // auth.setup.ts — chạy 1 lần trước toàn bộ test import { test as setup, expect } from '@playwright/test'; const authFile = 'playwright/.auth/user.json'; setup('đăng nhập và lưu session', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').fill('qa@hoctest.com'); await page.getByLabel('Mật khẩu').fill('123456'); await page.getByRole('button', { name: 'Đăng nhập' }).click(); // Chờ tới trang sau login — verify đã vào đúng await page.waitForURL('**/dashboard'); await expect(page.getByText('Xin chào')).toBeVisible(); // Lưu toàn bộ cookie + localStorage ra file await page.context().storageState({ path: authFile }); });
  • ```ts // playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ projects: [ { name: 'setup', testMatch: /.*\.setup\.ts/ }, { name: 'chromium', dependencies: ['setup'], // chạy setup trước use: { storageState: 'playwright/.auth/user.json', // mọi test có session sẵn }, }, ], });
  • ```ts // Test file — KHÔNG cần login, start ngay tại dashboard import { test, expect } from '@playwright/test'; test('xem danh sách đơn hàng', async ({ page }) => { await page.goto('/orders'); // đã login sẵn nhờ storageState await expect(page.locator('.order-row').first()).toBeVisible(); });
  • ```ts // Test KHÔNG login (guest) — override storageState rỗng test.describe('guest flow', () => { test.use({ storageState: { cookies: [], origins: [] } }); test('guest bị redirect về login', async ({ page }) => { await page.goto('/orders'); await expect(page).toHaveURL(/.*login/); }); });
  • ```ts // Login bằng API rồi lưu state — nhanh hơn qua UI setup('login qua API', async ({ request }) => { const res = await request.post('/api/auth/login', { data: { email: 'qa@hoctest.com', password: '123456' }, }); const { token } = await res.json(); // Tự build storageState: set token vào localStorage origin const state = { cookies: [], origins: [{ origin: 'https://app.hoctest.com', localStorage: [{ name: 'token', value: token }], }], }; require('fs').writeFileSync('playwright/.auth/user.json', JSON.stringify(state)); });

Best practices & Khi nào dùng

  • Luôn dùng cho mọi suite có login: login qua UI từng test = chậm + flaky + phụ thuộc form. storageState là pattern chính thức Playwright khuyến nghị.
  • Verify login TRƯỚC khi lưu state: waitForURL + expect element sau login — nếu login fail mà vẫn storageState, file rỗng làm toàn bộ test fail khó hiểu.
  • Bảo mật file state: .auth/*.json chứa session token thật — thêm vào .gitignore, KHÔNG commit. CI thì login lại mỗi run.
  • Nhiều vai trò: 1 file state per role (admin/user/manager) — test phân quyền chỉ đổi file state, không đổi flow.
  • Login bằng API khi được: nhanh và ổn định hơn UI login; chỉ dùng UI login cho chính test-case-login.

Gotchas, So sánh & Lưu ý

  • Session hết hạn giữa chừng: file state lưu lúc 8h, test chạy 20h mà token sống 2h → toàn bộ test fail 401. Xử lý: chạy setup gần test (dependency tự lo), hoặc refresh token, hoặc catch 401 → re-login.
  • storageState KHÔNG chứa sessionStorage: app lưu token trong sessionStorage thì state không bắt được — phải check app lưu ở đâu (cookie/localStorage ok; sessionStorage phải inject bằng addInitScript).
  • Parallel + shared state: nhiều worker dùng chung file state OK (read-only), nhưng cùng 1 account test parallel có thể đạp nhau ở data (đơn hàng, giỏ hàng) — vấn đề của shared account, không phải của storageState.
  • So với cookie copy thủ công: storageState gồm cả localStorage + cookie + đúng domain/path/expires — tay bôi addCookies dễ thiếu, khó maintain.
  • httpOnly cookie vẫn lưu được: storageState đọc cookie ở tầng context nên bắt được cả httpOnly (thứ mà document.cookie không thấy).
  • Setup chạy lại mỗi lần playwright test: nếu muốn reuse file cũ khi debug local, tạm bỏ dependency — nhưng CI luôn nên setup fresh.