iT邦幫忙

2025 iThome 鐵人賽

DAY 13
0
Software Development

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

Day 13:影分身之術|用參數化測試 Parameterized Tests 一次挑戰多個敵人

  • 分享至 

  • xImage
  •  

上一篇我們成功地運用 Hooks 收納重複執行的程式碼,只需編寫一次就可重複使用,大大提升了可讀性,但是,各位玩家們請仔細看一下 describe 內的測試:

test.describe('Cat Quest', () => {
  // 第一個測試:找到第 1 隻貓咪
  test('find the cat 1', async ({ page }) => {
    await page.getByRole('button', { name: "Cat Zone" }).click();
    await expect(page.getByRole('img', { name: "first cat" })).toBeVisible();
  });

  // 第二個測試:找到第 2 隻貓咪
  test('find the cat 2', async ({ page }) => {
    await page.getByRole('button', { name: "Cat Zone" }).click();
    await expect(page.getByRole('img', { name: "second cat" })).toBeVisible();
  });

  // 接下來還要找第 3 隻貓咪、第 4 隻貓咪....
});

test.describe('Dog Quest', () => {
  // 第三個測試:找到第 1 隻狗狗
  test('find the dog 1', async ({ page }) => {
    await page.getByRole('button', { name: "Dog Zone" }).click();
    await expect(page.getByRole('img', { name: "first dog" })).toBeVisible();
  });

  // 接下來還要找第 2 隻狗狗、第 3 隻狗狗....
});

這兩個 test.describe 看起來是不是也在重複執行相同的程式碼?先點擊 Cat Zone 或 Dog Zone 的按鈕,接著確認畫面顯示相對應的照片。

相信聰明的玩家立刻想起令人頭痛的維護地獄,別擔心!我們可以應用 參數化測試 (Parameterized Tests) 的概念來分離資料與邏輯,讓測試更加純粹與專注,我們立刻動手吧!

提取資料

  1. 首先觀察程式碼中不同的資料:
    https://ithelp.ithome.com.tw/upload/images/20250922/20168913mQEmgDIH7X.png
  2. 以 array 方式表達:
    // Cat Quest
    [
      { name: 'Cat 1', category: 'Cat', expected: 'first cat' },
      { name: 'Cat 2', category: 'Cat', expected: 'second cat' },
    ]
    // Dog Quest
    [
      { name: 'Dog 1', category: 'Dog', expected: 'first dog' },
    ]
    

參數化設置

接著我們就能將資料與程式碼組裝起來,有 2 個方式可以選擇:
【方法一】以變數存取資料

test.describe('Cat Quest', () => {
  const testCases = [
  { name: 'Cat 1', category: 'Cat', expected: 'first cat' },
  { name: 'Cat 2', category: 'Cat', expected: 'second cat' },
];

  for (const { name, category, expected } of testCases) {
    test(`find the ${name}`, async ({ page }) => {
      await page.getByRole('button', { name: `${category} Zone` }).click();
      await expect(page.getByRole('img', { name: ${expected} })).toBeVisible();
    });
  }
});

【方法二】測試層級:以 forEach 方式組合

[
  { name: 'Cat 1', category: 'Cat', expected: 'first cat' },
  { name: 'Cat 2', category: 'Cat', expected: 'second cat' },
].forEach(({ name, expected }) => {
    test.describe(`${category} Quest`, () => {
  test(`find the ${name}`, async ({ page }) => {
      await page.getByRole('button', { name: `${category} Zone` }).click();
      await expect(page.getByRole('img', { name: ${expected} })).toBeVisible();
    });
  });
})

專案層級的參數化設置

上述兩個參數化測試的方法適合單純資料不同的測試使用,但如果測試環境配置不同,例如以下情境:

  • 多人身分測試:模擬不同帳號、權限登入後測試,例如 Admin、User、Guest。
  • 不同語言設定:設定locale: 'en-US'locale: 'zh-TW',讓同一個測試跑多語系版本
  • 不同環境設定:如果有不同測試環境,如 develop、staging、production,換掉 baseURL 就能共用測試程式碼。

這時候就適合在專案層級設定參數,讓我們以在不同環境當中測試登入流程來舉例:

  1. 擴充自訂 option 預設值(可以在專案中被覆蓋)

    // test/env.ts
    
    // 從 Playwright 測試框架匯入原始的 test 函式,並命名為 base
    // (這樣做是為了後面能用 base.extend() 來擴充成自訂版的 test)
    import { test as base } from '@playwright/test';
    
    // 定義測試參數 (options),後續以透過 fixture 注入到測試中
    export type TestOptions = {
      environment: string;
      baseURL: string;
      username: string;
      password: string;
    };
    
    // 用 base.extend() 擴充出自訂版的預設 option,後續可以在 config 裡覆蓋  
    export const test = base.extend<TestOptions>({
      environment: ['development', { option: true }],
      baseURL: ['development url', { option: true }],
      username: ['development username', { option: true }],
      password: ['development password', { option: true }],
    });
    
  2. playwright.spec.tsproject 當中設置不同環境的參數

    // playwright.spec.ts
    import { defineConfig, devices } from '@playwright/test';
    import type { TestOptions } from './tests/env';
    
    export default defineConfig<TestOptions>({
      projects: [
        {
          name: 'development',
          use: { ...devices['Desktop Chrome'],
            environment: 'development',
            baseURL: 'development url',
            username: 'development username',
            password: 'development password',
           },
        },
        {
          name: 'staging',
          use: { ...devices['Desktop Chrome'],
            environment: 'staging',
            baseURL: 'staging url',
            username: 'staging username',
            password: 'staging password',
          },
        },
        {
          name: 'production',
          use: { ...devices['Desktop Chrome'],
            environment: 'production url',
            baseURL: 'production',
            username: 'production username',
            password: 'production password',
          },
        },
      ]
    });
    
  3. 透過 fixture 注入到測試中

    // test/login.spec.ts
    import { expect } from '@playwright/test';
    import { test } from './env';
    
    test('login', async ({ page, environment, baseURL, username, password }) => {
      await page.goto(baseURL);
      await page.locator('input[name="txtAccount"]').fill(username);
      await page.locator('input[name="txtPassword"]').fill(password);
      await page.locator('button[type="submit"]').click();
      await expect(page).toHaveURL(environment);
      console.log(environment, 'Login successful');
    });
    

📌 Did You Know?
專案層級的參數化設置,是透過參數注入到測試中才能使用,因此無法像使用變數或 forEach 那樣,直接動態改變測試名稱。

執行結果
https://ithelp.ithome.com.tw/upload/images/20250922/20168913QLx94grmb6.png

由上面的執行結果我們可以看到,只要執行 login 這個測試,就會自動跑 developmentstagingproduction 三個環境的測試,不須手動切換測試環境。透過這種方式,我們能一次驗證多個環境,大幅減少重複的程式碼,除了增加可維護性,也讓測試流程更加自動化與可靠。


到這裡,我們已經掌握了參數化測試的技巧,能夠靈活應用在大量資料或多種環境的情境中,藉由將資料與測試邏輯分離,使腳本更為精簡且易於維護。接下來,我們將進一步探討定位技巧,當頁面上出現重複元素或是標籤內沒有屬性可供定位時,該如何精準鎖定目標。


上一篇
Day 12:戰鬥儀式|Hooks:before/describe/after 的佈置
系列文
Playwright 玩家攻略:從新手村到魔王關13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言