Học Playwright tiếng Việt, Cộng đồng Playwright cho người Việt

Random Q&A

[Q&A] Xử lý nhiều tab, mỗi tab 1 user/token khác nhau trong Playwright thế nào?

Hỏi

Xử lý nhiều tab, mỗi tab 1 user/token khác nhau trong Playwright thế nào?

Trả lời

Khi thực hiện automation testing cho các ứng dụng web, bạn có thể gặp trường hợp cần mở đồng thời nhiều tab với các tài khoản người dùng khác nhau. Ví dụ như test chức năng chat giữa hai user, hoặc test phân quyền giữa admin và user thường.

Vấn đề gặp phải

Playwright mặc định chia sẻ context và cookies giữa các tab trong cùng một browser instance. Điều này có nghĩa là khi bạn đăng nhập ở tab đầu tiên, token sẽ được lưu và áp dụng cho tất cả các tab khác.

Giải pháp: Sử dụng Browser Contexts riêng biệt

Cách 1: Tạo hai context độc lập

import { test, expect, Browser, BrowserContext, Page } from '@playwright/test';

test('Multi-tab với hai token khác nhau', async ({ browser }) => {
  // Tạo context đầu tiên cho user 1
  const context1: BrowserContext = await browser.newContext({
    storageState: undefined // Đảm bảo không có storage state từ trước
  });
  
  // Tạo context thứ hai cho user 2  
  const context2: BrowserContext = await browser.newContext({
    storageState: undefined
  });

  // Tạo page từ mỗi context
  const page1: Page = await context1.newPage();
  const page2: Page = await context2.newPage();

  // Đăng nhập user 1
  await page1.goto('https://example.com/login');
  await page1.fill('#username', 'user1@example.com');
  await page1.fill('#password', 'password1');
  await page1.click('#login-btn');
  
  // Đăng nhập user 2
  await page2.goto('https://example.com/login');
  await page2.fill('#username', 'user2@example.com');
  await page2.fill('#password', 'password2');
  await page2.click('#login-btn');

  // Thực hiện các hành động với hai user khác nhau
  await page1.goto('https://example.com/dashboard');
  await page2.goto('https://example.com/dashboard');

  // Verify
  await expect(page1.locator('[data-testid="user-name"]')).toContainText('User 1');
  await expect(page2.locator('[data-testid="user-name"]')).toContainText('User 2');

  // Cleanup
  await context1.close();
  await context2.close();
});

Cách 2: Sử dụng API token trực tiếp

import { test, expect } from '@playwright/test';

test('Multi-tab với API token', async ({ browser }) => {
  const token1 = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Token user 1
  const token2 = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Token user 2

  // Context 1 với token 1
  const context1 = await browser.newContext({
    extraHTTPHeaders: {
      'Authorization': `Bearer ${token1}`
    }
  });

  // Context 2 với token 2
  const context2 = await browser.newContext({
    extraHTTPHeaders: {
      'Authorization': `Bearer ${token2}`
    }
  });

  const page1 = await context1.newPage();
  const page2 = await context2.newPage();

  // Truy cập trang với token tương ứng
  await Promise.all([
    page1.goto('https://api.example.com/protected-page'),
    page2.goto('https://api.example.com/protected-page')
  ]);

  // Verify response với token khác nhau
  const response1 = await page1.waitForResponse('**/api/user-info');
  const response2 = await page2.waitForResponse('**/api/user-info');

  expect(response1.status()).toBe(200);
  expect(response2.status()).toBe(200);

  await context1.close();
  await context2.close();
});

Cách 3: Sử dụng Storage State được chuẩn bị sẵn

import { test } from '@playwright/test';

// Chuẩn bị storage states
test.beforeAll(async ({ browser }) => {
  // Tạo storage state cho user 1
  const context1 = await browser.newContext();
  const page1 = await context1.newPage();
  
  await page1.goto('https://example.com/login');
  await page1.fill('#username', 'user1@example.com');
  await page1.fill('#password', 'password1');
  await page1.click('#login-btn');
  await page1.waitForURL('**/dashboard');
  
  // Lưu storage state
  await context1.storageState({ path: 'user1-storage.json' });
  await context1.close();

  // Tương tự cho user 2
  const context2 = await browser.newContext();
  const page2 = await context2.newPage();
  
  await page2.goto('https://example.com/login');
  await page2.fill('#username', 'user2@example.com');
  await page2.fill('#password', 'password2');
  await page2.click('#login-btn');
  await page2.waitForURL('**/dashboard');
  
  await context2.storageState({ path: 'user2-storage.json' });
  await context2.close();
});

test('Sử dụng prepared storage states', async ({ browser }) => {
  // Sử dụng storage state đã chuẩn bị
  const context1 = await browser.newContext({
    storageState: 'user1-storage.json'
  });
  
  const context2 = await browser.newContext({
    storageState: 'user2-storage.json'
  });

  const page1 = await context1.newPage();
  const page2 = await context2.newPage();

  // Các page đã tự động được đăng nhập
  await page1.goto('https://example.com/dashboard');
  await page2.goto('https://example.com/dashboard');

  // Test interaction giữa hai user
  await page1.click('[data-testid="start-chat"]');
  await page1.fill('[data-testid="message-input"]', 'Hello from User 1');
  await page1.click('[data-testid="send-btn"]');

  // Verify message xuất hiện ở page2
  await expect(page2.locator('[data-testid="message"]')).toContainText('Hello from User 1');

  await context1.close();
  await context2.close();
});

Helper function để tái sử dụng

// utils/multi-user-helper.ts
import { Browser, BrowserContext } from '@playwright/test';

export class MultiUserHelper {
  static async createAuthenticatedContexts(
    browser: Browser,
    users: Array<{ username: string; password: string }>
  ): Promise<BrowserContext[]> {
    const contexts: BrowserContext[] = [];

    for (const user of users) {
      const context = await browser.newContext();
      const page = await context.newPage();

      await page.goto('https://example.com/login');
      await page.fill('#username', user.username);
      await page.fill('#password', user.password);
      await page.click('#login-btn');
      await page.waitForURL('**/dashboard');

      contexts.push(context);
      await page.close();
    }

    return contexts;
  }

  static async createTokenContexts(
    browser: Browser,
    tokens: string[]
  ): Promise<BrowserContext[]> {
    return Promise.all(
      tokens.map(token =>
        browser.newContext({
          extraHTTPHeaders: {
            'Authorization': `Bearer ${token}`
          }
        })
      )
    );
  }
}

// Sử dụng helper
test('Test với helper function', async ({ browser }) => {
  const users = [
    { username: 'user1@example.com', password: 'pass1' },
    { username: 'user2@example.com', password: 'pass2' }
  ];

  const contexts = await MultiUserHelper.createAuthenticatedContexts(browser, users);
  const [page1, page2] = await Promise.all([
    contexts[0].newPage(),
    contexts[1].newPage()
  ]);

  // Test logic here...

  // Cleanup
  await Promise.all(contexts.map(ctx => ctx.close()));
});

Lưu ý quan trọng

  1. Memory Usage: Mỗi context tiêu tốn bộ nhớ riêng biệt, hãy đóng context sau khi sử dụng
  2. Parallel Execution: Có thể chạy song song các hành động trên nhiều context để tăng tốc độ test
  3. Cookie Isolation: Mỗi context có cookie jar riêng biệt, đảm bảo isolation hoàn toàn

Bạn có câu hỏi hoặc yêu cầu Playwright Việt Nam làm về nội dung gì? Xin mời nhắn cho chúng mình ở đây: https://voz.ee/s/BBA-community-request

Bạn muốn tham gia forum Automation Testing trên Facebook Messenger? Mời tham gia ở đây: https://voz.ee/s/BBA-FB-messenger-automation-group

Trả lời