Wiki

← Tất cả bài wiki

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 goto sẽ đượ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 + (url HOẶC domain+path). expires là Unix timestamp (giây), -1 = session cookie.
  • context.clearCookies(options?): xoá cookie — từ v1.43 lọc được theo name/domain/path thay 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/addInitScript trước goto — 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ưng context.cookies() vẫn thấy vì đọc ở tầng context.
  • Domain/path sai → cookie không gửi: addCookies với domain .app.hoctest.com nhưng goto app.hoctest.com có thể lệch; dùng url cho 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.