Cookies & local/session Storage (Đọc ghi trạng thái phía client)
automationplaywright
Playwright cho phép thao tác trực tiếp với cookie (context.cookies/addCookies/clearCookies) và storage của trang (localStorage/sessionStorage qua page.evaluate hoặc addInitScript). Đây là cách kiểm soát trạng thái client-side: set token/auth trực tiếp, verify app ghi đúng cookie, reset state giữa test — không cần qua UI.
Khái niệm & Bản chất
- Cookies thuộc về browser context, không phải page — một context có 1 cookie jar dùng chung cho mọi page/tab trong nó. API thao tác nằm ở
context. - localStorage / sessionStorage thuộc về origin trong page — truy cập bằng
page.evaluate(() => localStorage...)vì nó là JS của trang. - addCookies trước khi vào trang: cookie set vào context trước
gotosẽ được gửi kèm request ngay từ đầu — cách 'đăng nhập' không qua form. - addInitScript = chạy JS trước mọi script của trang, mỗi lần load — cách chuẩn để set localStorage (VD: token) trước khi app kịp đọc.
- Mối liên hệ storageState: storageState chính là dump của cookies + localStorage — hiểu 2 thứ này là hiểu cách session persist.
Cách hoạt động & API chính
- context.cookies(urls?): đọc cookie; truyền URL để lọc theo domain. Trả mảng cookie object:
name, value, domain, path, expires, httpOnly, secure, sameSite. - context.addCookies([...]): thêm cookie. Bắt buộc
name,value+ (urlHOẶCdomain+path).expireslà Unix timestamp (giây),-1= session cookie. - context.clearCookies(options?): xoá cookie — từ v1.43 lọc được theo
name/domain/paththay vì xoá trắng. - localStorage qua evaluate:
page.evaluate(() => localStorage.getItem('token')),setItem,removeItem,clear(). sessionStorage tương tự. - addInitScript:
context.addInitScript(() => localStorage.setItem('token', 'abc'))— chạy trước script app trên mọi navigation. - request.cookieHeader / route.request().headers()['cookie']: soi cookie thật gửi đi trong request.
Ví dụ & Cú pháp thực tế
- ```ts import { test, expect } from '@playwright/test'; test('set cookie để bypass consent banner', async ({ context, page }) => { await context.addCookies([ { name: 'cookie_consent', value: 'accepted', url: 'https://app.hoctest.com', // cách nhanh: url thay domain+path }, ]); await page.goto('https://app.hoctest.com'); // Banner cookie không xuất hiện nữa await expect(page.locator('.cookie-banner')).toBeHidden(); });
- ```ts test('verify app set cookie sau login', async ({ context, 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(); await page.waitForURL('**/dashboard'); const cookies = await context.cookies(); const session = cookies.find((c) => c.name === 'session_id'); expect(session).toBeTruthy(); expect(session?.httpOnly).toBe(true); // cookie auth phải httpOnly expect(session?.secure).toBe(true); expect(session?.sameSite).toBe('Lax'); });
- ```ts // Set token localStorage TRƯỚC khi app chạy — đăng nhập không qua UI test('inject auth token', async ({ context, page }) => { await context.addInitScript((token) => { localStorage.setItem('auth_token', token); localStorage.setItem('role', 'admin'); }, 'test-token-xyz'); await page.goto('/dashboard'); // app đọc token thấy đã login await expect(page.getByText('Dashboard')).toBeVisible(); });
- ```ts test('đọc và sửa localStorage trong lúc chạy', async ({ page }) => { await page.goto('/settings'); // Đọc giá trị app đang lưu const theme = await page.evaluate(() => localStorage.getItem('theme')); expect(theme).toBe('light'); // Ghi trực tiếp rồi reload để app áp dụng await page.evaluate(() => localStorage.setItem('theme', 'dark')); await page.reload(); await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); });
- ```ts // Reset state giữa test: xoá cookie + storage test.afterEach(async ({ context, page }) => { await context.clearCookies({ domain: 'app.hoctest.com' }); await page.evaluate(() => { localStorage.clear(); sessionStorage.clear(); }); });
Best practices & Khi nào dùng
- Khi nào dùng: verify app set cookie đúng thuộc tính bảo mật (httpOnly/secure/sameSite) — test bảo mật cơ bản QA nên có; bypass màn hình phụ (consent, onboarding) bằng cookie flag; inject token để login nhanh; kiểm tra feature theo localStorage (theme, ngôn ngữ, A/B flag).
- Set state TRƯỚC navigation:
addCookies/addInitScripttrướcgoto— set sau thì app đã đọc state cũ, phải reload mới có tác dụng. - addInitScript cho localStorage: vì localStorage chỉ tồn tại sau khi trang load, init script là cách duy nhất 'inject trước khi app đọc' — tốt hơn evaluate + reload.
- Dùng storageState khi cần cả cụm session: nếu mục tiêu là 'đã đăng nhập', storageState gọn hơn nhiều so với tự set từng cookie.
- Verify hành vi, không verify implementation: check cookie name/value quan trọng thôi — app đổi tên cookie nội bộ thì test vỡ không đáng.
Gotchas, So sánh & Lưu ý
- Cookie vs localStorage — đừng nhầm: cookie gửi kèm MỌI request tới domain (server đọc được), giới hạn ~4KB, có expires. localStorage chỉ nằm client, ~5-10MB, không tự gửi đi, không hết hạn. sessionStorage như localStorage nhưng chết khi đóng tab.
- httpOnly cookie không đọc được bằng JS:
page.evaluate(() => document.cookie)KHÔNG thấy httpOnly — nhưngcontext.cookies()vẫn thấy vì đọc ở tầng context. - Domain/path sai → cookie không gửi:
addCookiesvới domain.app.hoctest.comnhưng gotoapp.hoctest.comcó thể lệch; dùngurlcho chắc. Sai path tương tự. - sessionStorage per tab: mở tab mới = sessionStorage mới — test multi-tab chú ý state không share.
- So với storageState: cookies/storage API là thao tác từng mảnh (granular, runtime); storageState là snapshot nguyên cụm (lưu/nạp file). Thao tác tinh chỉnh dùng API trực tiếp, reuse session dùng storageState.
- evaluate chạy trong page: lỗi JS trong evaluate crash test đó; và mọi thao tác evaluate đều có thể bị CSP/closure của app cản — init script ổn định hơn cho setup.
