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 configuse.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']+storageStatetrỏ 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 configuse: { storageState: '...' }cho cả project. - Setup project: trong
playwright.config.tsthê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ặctest.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: truetrong 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/*.jsonchứ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
addCookiesdễ 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.
