iT邦幫忙

2025 iThome 鐵人賽

DAY 29
2
Software Development

Playwright 玩家攻略:從新手村到魔王關系列 第 29

Day 29:VIP 通行證 (三)|藉由 storageState 實現秒速進入戰場:多角色應用

  • 分享至 

  • xImage
  •  

雖然直接呼叫 API 獲取 Token 是一個可行方案,但考量到 E2E 測試的核心是模擬使用者行為,加上若 API 回傳的資料格式(非 Cookie)將增加測試前置作業的複雜性,因此我們選擇了實際跑過登入流程後以拿到驗證資料這個更貼近真實情境的作法。

多角色驗證資料

實際測試時,可能必須登入多個角色,驗證不同角色是否依據其權限顯示不同的畫面,有不同的操作流程,例如測試流程需要三個角色:

  • Admin - ada001
  • User - ada002
  • Editor - ada003

這時,先新增多角色驗證登入驗證流程,並各自儲存到對應的檔案:

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

// 建立 Admin 登入狀態檔的路徑
const adminAuth = path.join(__dirname, '../.auth/admin.json');

setup('admin authenticate', async ({ page }) => {
  // 執行 ada001 登入驗證步驟,請替換成你所需的流程
  // ...
    
  // 將 Admin 的驗證資訊儲存到 adminAuth
  await page.context().storageState({ path: adminAuth });
});

// 建立 User 登入狀態檔的路徑
const userAuth = path.join(__dirname, '../.auth/user.json');

setup('user authenticate', async ({ page }) => {
  // 執行 ada002 登入驗證步驟,請替換成你所需的流程
  // ...
    
  // 將 User 的驗證資訊儲存到 userAuth
  await page.context().storageState({ path: userAuth });
});

// 建立 Editor 登入狀態檔的路徑
const editorAuth = path.join(__dirname, '../.auth/admin.json');

setup('authenticate', async ({ page }) => {
  // 執行 ada003 登入驗證步驟
  // ...
    
  // 將 Editor 的驗證資訊儲存到 adminAuth
  await page.context().storageState({ path: editorAuth });
});

💡 Tips:
在提交測試程式之前,請務必將機密資料(如帳號、密碼、Token 等)改以環境變數方式設定,例如存放在 .env 檔案中,避免機密資訊被意外提交而造成外洩風險。

執行 setup 後就能看見目錄產生對應的資料:
https://ithelp.ithome.com.tw/upload/images/20251008/20168913pwduQY2eaj.png

在 test 中指定角色驗證資料

  1. project 不指定 storageState 路徑
    如果同一個 project 裡,會有不同角色登入,這時候就不在 config 的 project 裡指定 storageState 路徑(將原本設定的 storageState: './.auth/admin.json' 刪除),但仍可保留 dependencies: ['setup'],讓每次專案開始測試前執行 setup 以取得驗證資料。

  2. 在進入測試頁面之前加上 storageState 路徑:

    test.use({ storageState: 'tests/.auth/editor.json' });
    
  3. 該測試就能使用指定的角色登入,並進行後續測試。

在同一個 test 中使用不同角色驗證資料進行測試

有時可能需要在同一個測試流程中由不同角色登入,執行不同的測試步驟以觀察不同角色間交互作用是否正確,例如以下順序:

  1. User 登入 → 可操作 A 功能
  2. Admin 登入 → 關閉 User 的 A 功能權限
  3. User 登入 → 不可操作 A 功能

那我們就可以依照各使用者設置個別頁面,並將使用者名稱印出來查看:

test('multiple test', async ({ browser }) => {
  // 設置以 admin 驗證登入頁面的 browser
  const adminContext = await browser.newContext({ storageState: 'tests/.auth/admin.json' });
  const adminPage = await adminContext.newPage();
  await adminPage.goto('/');
  const userDiv = await adminPage.locator('div[id="username"]').innerText();
  const userid = userDiv.split('\n')[1];
  console.log('admin test:', userid);

  // 設置以 user 驗證登入頁面的 browser
  const userContext = await browser.newContext({ storageState: 'tests/.auth/user.json' });
  const userPage = await userContext.newPage();
  await userPage.goto('/');
  const userDiv2 = await adminPage.locator('div[id="username"]').innerText();
  const userid2 = userDiv2.split('\n')[1];
  console.log('user test:', userid2);

  // 設置以 editor 驗證登入頁面的 browser
  const editContext = await browser.newContext({ storageState: 'tests/.auth/editor.json' });
  const editPage = await editContext.newPage();
  await editPage.goto('/');
  const userDiv3 = await adminPage.locator('div[id="username"]').innerText();
  const editId = editDiv.split('\n')[1];
  console.log('editor test:', editId);
    
  // 關閉 browser
  await adminContext.close();
  await userContext.close();
  await editContext.close();
});

執行結果可以看見,以上面的架構,能夠實現在同一個測試中以不同角色驗證登入頁面的結果:
https://ithelp.ithome.com.tw/upload/images/20251008/201689135DyeyFw19M.png

整合進 POM (Page Object Model) 中

如果每次都需要設置新的 browser 似乎不太方便,撰寫測試的速度沒有較快,可讀性與可維護性也不佳,因此,將多角色驗證整合進 POM (Page Object Model) 裡,是更有效率的作法。

這邊我們就以兩個角色來實驗,先在 POM 資料夾裡新增用來定義特定使用者的檔案,將特定使用者使用特定驗證 storageState() 資料的擴展定義進去,順手將我們定位使用者名稱的定位器也定義在 POM 裡:

// pages/fixtures.ts
import { test as base, type Page, type Locator } from '@playwright/test';

// 「admin」頁面的 POM,可以新增特定於該使用者頁面的定位器和輔助方法
class AdminPage {
  // 以「admin」身份登入的頁面
  page: Page;

  // 用戶名稱定位器
  userID: Locator;

  constructor(page: Page) {
    this.page = page;
    this.userID = page.locator('div[id="username"]');
  }
}

// 「user」頁面的 POM,可以新增特定於該使用者頁面的定位器和輔助方法
class UserPage {
  // 以「user」身份登入的頁面。
  page: Page;

  // 用戶名稱定位器
  userID: Locator;

  constructor(page: Page) {
    this.page = page;
    this.userID = page.locator('div[id="username"]');
  }
}

// 宣告佈置類型
type MyFixtures = {
  adminPage: AdminPage;
  userPage: UserPage;
};

// 擴展基礎測試以包含我們的自定義佈置
export * from '@playwright/test';
export const test = base.extend<MyFixtures>({
  adminPage: async ({ browser }, use) => {
    const context = await browser.newContext({ storageState: '../.auth/admin.json' });
    const adminPage = new AdminPage(await context.newPage());
    await use(adminPage);
    await context.close();
  },

  userPage: async ({ browser }, use) => {
    const context = await browser.newContext({ storageState: '../.auth/user.json' });
    const userPage = new UserPage(await context.newPage());
    await use(userPage);
    await context.close();
  },
});

定義好之後,我們就可以在測試裡使用 POM:

import { test, expect } from './pages/fixture';

test.beforeEach(async ({ adminPage, userPage }) => {
  await adminPage.page.goto('/');
  await userPage.page.goto('/');
});

// 在測試中使用 adminPage 和 userPage 佈置。
test('admin', async ({ adminPage, userPage }) => {
  // adminPage
  const adminDiv = await adminPage.userID.innerText();
  const adminId = adminDiv.split('\n')[1];
  console.log('admin test:', adminId);

  // userPage
  const userDiv = await userPage.userID.innerText();
  const userId = userDiv.split('\n')[1];
  console.log('user test:', userId);
});

整體測試步驟看起來簡潔清楚了不少,現在來檢視一下最終執行結果:
https://ithelp.ithome.com.tw/upload/images/20251008/20168913pL8EOSTIHD.png


到這裡,我們成功地實現以 POM 管理多角色登入驗證的測試流程,除了運用 storageState() 儲存驗證資料以加速測試流程,POM 的設計模式也讓程式結構更加清晰,大幅提升可讀性與可維護性,最後,我們來談談如何面對自動化測試中常見的難題 「Flaky Tests(不穩定測試)」,並分享一些實用的策略以及小技巧!


上一篇
Day 28:VIP 通行證 (二)|藉由 storageState 實現秒速進入戰場:進階應用
系列文
Playwright 玩家攻略:從新手村到魔王關29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言