Playwright基礎篇(三):測試案例實作

testing

實作第一個測試:Apple網站購物流程


流程分析與測試目標


在開始編寫測試腳本前,我們需要先清楚定義測試目標和流程。對於本範例,我們將模擬用戶在 Apple 官網選購 iPad 的完整流程:

  1. 訪問 Apple 官網
  2. 導航至 iPad 產品頁面
  3. 選擇特定 iPad 型號(11吋 iPad)
  4. 選擇產品規格(顏色、儲存容量等)
  5. 加入購物車
  6. 驗證購物車內容是否正確

這個測試案例涵蓋了典型的電子商務網站核心功能,對於小企業的網站測試有很高的參考價值。

前置準備工作

在開始編寫測試前,我們需要完成以下準備工作:


  1. 建立專案結構:我們將採用頁面物件模式(Page Object Model)來組織代碼,這樣可以提高代碼的可維護性和重用性。
  2. 頁面物件分析:識別測試中涉及的主要頁面元素,例如:
    • 顏色選擇器
    • 儲存容量選項
    • 連線類型選項(Wi-Fi)
    • 各種附加選項(雕刻、Apple Pencil、鍵盤等)
    • 購買選項
    • 「加入購物袋」按鈕
  3. 建立適當的文件結構:
my-playwright-project/
├── tests/
│   └── example.spec.ts  # 測試腳本
├── pages/
│   └── ProductPage.ts   # 產品頁面物件
└── ...

編寫測試腳本

讓我們看看如何實現整個測試流程。

訪問 Apple 官網與導航

首先,我們需要建立測試的基本結構並導航至 iPad 產品頁面:

// tests/example.spec.ts

import { test, expect } from '@playwright/test';
import { ProductPage } from '../pages/ProductPage.ts';

test('Add to Bag Flow', async ({ page }) => {
  const productPage = new ProductPage(page);

  // Step 1: Go to iPad page
  await page.goto('https://www.apple.com/');
  await page.getByLabel('iPad', { exact: true }).click();
  await page.getByRole('link', { name: 'iPad New' }).click();
  await page.getByRole('link', { name: 'Pre-order iPad 11-inch' }).click();

  // 後續步驟將在這裡加入...
});

這段代碼做了以下事情:

  • 導入必要的 Playwright 測試工具
  • 導入我們自定義的 ProductPage 頁面物件
  • 創建一個名為 ‘Add to Bag Flow’ 的測試
  • 訪問 Apple 官網
  • 依次點擊導航至 iPad 產品頁面的元素

產品頁面物件定義

在編寫測試腳本之前,我們先定義產品頁面物件,以便更好地組織和重用代碼:

// pages/ProductPage.ts
import { expect, Locator, Page } from '@playwright/test';

export class ProductPage {
  readonly page: Page;
  readonly colorLocator: Locator;
  readonly storageLocator: Locator;
  readonly wifiLocator: Locator;
  readonly engravingLocator: Locator;
  // 依照命名規則向下完成readonly的定義...

  constructor(page: Page) {
    this.page = page;
    this.colorLocator = page.locator('#root').locator('text="Pink"'); 
    this.storageLocator = page.locator('#root').locator('text="256GB"');
    this.wifiLocator = page.locator('#root').locator('text="Wi-FiEvery iPad can connect"');
    this.engravingLocator = page.locator('text="No engraving"');
  }

  // 方法定義將在下方繼續...
}

頁面物件包含了頁面上的所有關鍵元素定位器,這些定位器使用了 Playwright 的多種選擇器方式,如文本、角色等。


選擇產品規格

現在,讓我們實現產品頁面物件中的方法,用於選擇各種產品規格:


// ProductPage.ts 中的方法
async selectColor(color: string) {
  const colorLocator = this.page.locator(`text="${color}"`).first();
  await colorLocator.waitFor({ state: 'visible', timeout: 10000 });
  await colorLocator.click();
}

async selectStorageOption(storage: string) {
  // 等待存儲選項出現
  await this.page.waitForSelector(`input[name="dimensionCapacity"][value="${storage.toLowerCase()}"]`, { timeout: 20000 });
  // 獲取存儲容量的 radio 按鈕元素
  const storageLocator = this.page.locator(`input[name="dimensionCapacity"][value="${storage.toLowerCase()}"]`);
  // 確保元素可見
  await storageLocator.waitFor({ state: 'visible', timeout: 20000 });
  // 確保元素滾動到視口內
  await storageLocator.scrollIntoViewIfNeeded();
  // 強制點擊(如果正常點擊不起作用時使用)
  await storageLocator.click({ force: true });
}

async selectWiFiOption() {
  try {
    // 使用 input 元素的 value 屬性定位 Wi-Fi 選項
    const wifiLocator = this.page.locator('input[name="dimensionConnection"][value="wifi"]');
    // 等待元素可見
    await wifiLocator.waitFor({ state: 'visible', timeout: 20000 });
    // 滾動元素到視口內
    await wifiLocator.scrollIntoViewIfNeeded();
    // 確保元素可見
    await wifiLocator.waitFor({ state: 'visible', timeout: 20000 });
    // 確保元素滾動到視口內
    await wifiLocator.scrollIntoViewIfNeeded();
    // 強制點擊(如果正常點擊不起作用時使用)
    await wifiLocator.click({ force: true });
  } catch (error) {
    console.error('Error selecting Wi-Fi option:', error);
    throw error;
  }
}

// 其他選項的方法...
async selectEngravingOption() {
  await this.engravingLocator.click();
}
// 依此類推...

這些方法包含了:

  • 智能等待(確保元素加載和可見)
  • 錯誤處理
  • 滾動頁面(確保元素在視口內)

選擇付款選項並加入購物袋

接下來,實現選擇付款方式和加入購物袋的方法:

// ProductPage.ts 中的更多方法
async selectPurchaseOption() {
  // 等待 "fullprice" 選項可見,確保頁面完全加載
  const purchaseLocator = this.page.locator('input[name="purchase_option_group"][value="fullprice"]');
  await purchaseLocator.waitFor({ state: 'visible', timeout: 20000 });
  // 確保購買選項滾動到視口內
  await purchaseLocator.scrollIntoViewIfNeeded();
  // 點擊該購買選項
  await purchaseLocator.check(); // 適用於 radio 按鈕
}

async selectAppleCareOption() {
  await this.appleCareLocator.click();
}

async addToBag() {
  const addToBagButton = this.page.getByRole('button', { name: 'Add to Bag' });
  await addToBagButton.waitFor({ state: 'visible', timeout: 10000 });
  await addToBagButton.click();
}

async verifyProductInBag(productName: string) {
  await expect(this.page.locator(`text="${productName}"`)).toBeVisible();
}

這些方法處理了:


  • 選擇全額購買選項(而非分期付款)
  • 選擇是否添加 AppleCare+
  • 點擊「加入購物袋」按鈕
  • 驗證產品是否成功加入購物袋

完整測試流程

現在,我們將所有步驟組合起來,完成整個測試腳本:

// tests/example.spec.ts 完整版
import { test, expect } from '@playwright/test';
import { ProductPage } from '../pages/ProductPage.ts';

test('Add to Bag Flow', async ({ page }) => {
  const productPage = new ProductPage(page);

  // Step 1: Go to iPad page
  await page.goto('https://www.apple.com/');
  await page.getByLabel('iPad', { exact: true }).click();
  await page.getByRole('link', { name: 'iPad New' }).click();
  await page.getByRole('link', { name: 'Pre-order iPad 11-inch' }).click();

  // Step 2: Perform actions on ProductPage
  await productPage.selectColor('Pink');  // 選擇粉色
  await productPage.selectStorageOption('512GB');  // 選擇 512GB 儲存容量
  await productPage.selectWiFiOption();  // 選擇 Wi-Fi 選項
  
  // 驗證 Wi-Fi 選項是否已選中
  const wifiInput = page.locator('input[name="dimensionConnection"][value="wifi"]:checked');
  await expect(wifiInput).toBeChecked();
  
  await productPage.selectEngravingOption();  // 選擇不刻字
  await productPage.selectApplePencilOption();  // 選擇不購買 Apple Pencil
  await productPage.selectKeyboardOption();  // 選擇不購買鍵盤
  await productPage.selectTradeInOption();  // 選擇不折抵
  
  // 選擇「全額付款」購買選項
  await productPage.selectPurchaseOption();
  
  // 驗證「全額付款」選項是否已選中
  const purchaseInput = page.locator('input[name="purchase_option_group"][value="fullprice"]:checked');
  await expect(purchaseInput).toBeChecked();
  
  await productPage.selectAppleCareOption();  // 選擇不購買 AppleCare+
  await productPage.addToBag();  // 加入購物袋

  // Step 3: 驗證產品是否已成功加入購物袋
  await productPage.verifyProductInBag('11-inch iPad Wi-Fi 512GB - Pink');

  // 截圖
  const generateScreenshotName = () => `screenshot_${Date.now()}.png`;
  await page.screenshot({ path: generateScreenshotName() });
});

測試執行與觀察


執行測試腳本


要執行我們剛剛創建的測試,請在終端機中輸入:

npx playwright test

如果你希望以視覺化方式觀察測試過程,可以添加 --headed 選項:

npx playwright test --headed

對於調試目的,你還可以使用 --debug 選項,這將允許你逐步執行測試:

npx playwright test --debug

理解自動化測試流程


讓我們分析一下這個測試流程中的關鍵部分:


  1. 頁面導航:使用特定的選擇器定位和點擊元素,模擬用戶在網站上的導航。
  2. 等待機制:注意我們經常使用 waitFor() 方法確保元素完全載入後再進行操作。這是自動化測試的關鍵,因為網頁載入速度可能因網絡狀況而異。
  3. 滾動頁面:使用 scrollIntoViewIfNeeded() 確保元素在視口內,這模擬了用戶查看長頁面時的滾動行為。
  4. 強制點擊:有時需要使用 { force: true } 選項來處理一些特殊情況,例如元素被其他元素部分遮擋時。
  5. 斷言(Assertions):使用 expect() 函數驗證操作結果,例如檢查選項是否被選中、產品是否成功加入購物袋。
  6. 錯誤處理:使用 try-catch 捕獲可能的錯誤,並提供有用的錯誤訊息。
  7. 截圖:測試結束時捕獲螢幕截圖,這對於調試非常有用。

這個測試示例展示了 Playwright 如何有效地自動化網頁交互流程,從產品選擇到加入購物車的整個過程。通過這種方式,你可以確保你的電子商務網站在各種瀏覽器和設備上都能正常運作。


小結與進階技巧


通過本教學,我們學習了如何:

  • 使用頁面物件模式結構化測試代碼
  • 在複雜網站上定位和操作元素
  • 處理動態加載的頁面內容
  • 驗證操作的結果


對於小型企業來說,這樣的自動化測試可以節省大量時間,並提高網站質量。即使是簡單的企業網站,也可以應用這些原則來測試關鍵流程,如聯繫表單、產品展示或預約功能。


進階提示


  1. 參數化測試: 嘗試使用不同的產品 規格(顏色、容量等)來測試同一流程
  2. 截圖比較: 使用 Playwright 的視覺比較功能來檢測 UI 變化
  3. 模擬不同設備: 配置測試以模擬手機、平板等不同設備上的使用體驗
  4. 持續集成: 將這些測試添加到你的 CI/CD 流程中,在每次代碼更改時自動執行

掌握了這些基礎之後,你已經準備好開始自動化測試你自己網站的關鍵功能了!

分享這篇文章: