Automation testing

Soft Assertions trong Playwright: Khi Test Failures Không Nên Dừng Lại

Phong Do Phong Do · 04/09/2025 · 12 phút đọc

Soft Assertions là gì?

Soft assertions (hay còn gọi là “non-blocking assertions”) là một tính năng trong Playwright cho phép các assertions thất bại mà không dừng việc thực thi test case ngay lập tức. Thay vào đó, test sẽ tiếp tục chạy và thu thập tất cả các lỗi, sau đó báo cáo tổng hợp ở cuối test.

Tại sao cần Soft Assertions?

Trong testing thực tế, đặc biệt khi kiểm tra một form hoặc một trang có nhiều elements, bạn muốn:

  • Kiểm tra toàn bộ các điều kiện thay vì dừng lại ở lỗi đầu tiên
  • Thu thập đầy đủ thông tin về tất cả các vấn đề trong một lần chạy test
  • Giảm thời gian debug và fix bugs
  • Tối ưu business impact bằng cách phát hiện nhiều issues cùng lúc

So sánh Hard Assertions vs Soft Assertions

Hard Assertions (Mặc định)

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

test('Kiểm tra form đăng ký - Hard Assertions', async ({ page }) => {
  await page.goto('https://example.com/register');
  
  // Test dừng ngay tại assertion đầu tiên fail
  await expect(page.locator('#title')).toHaveText('Đăng ký tài khoản');
  await expect(page.locator('#username')).toBeVisible();
  await expect(page.locator('#email')).toBeEnabled();
  await expect(page.locator('#submit')).toHaveText('Đăng ký');
});

Soft Assertions

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

test('Kiểm tra form đăng ký - Soft Assertions', async ({ page }) => {
  await page.goto('https://example.com/register');
  
  // Tất cả assertions sẽ được thực thi dù có fail
  await expect.soft(page.locator('#title')).toHaveText('Đăng ký tài khoản');
  await expect.soft(page.locator('#username')).toBeVisible();
  await expect.soft(page.locator('#email')).toBeEnabled();
  await expect.soft(page.locator('#submit')).toHaveText('Đăng ký');
  
  // Test sẽ fail nếu có bất kỳ soft assertion nào fail
});

Cách sử dụng Soft Assertions

1. Syntax cơ bản

// Thêm .soft vào expect
await expect.soft(locator).toHaveText('expected text');
await expect.soft(locator).toBeVisible();
await expect.soft(locator).toHaveAttribute('href', '/home');

2. Kết hợp với Hard Assertions

Dùng kết hợp linh hoạt giữa hard assertion với soft assertion:

  • Hard assertion: dùng khi step này block tất cả các step sau
  • Soft assertion: dùng khi step này chỉ validate, không block các step sau
test('Kiểm tra trang sản phẩm', async ({ page }) => {
  await page.goto('https://shop.example.com/product/123');
  
  // Hard assertion - dừng test nếu trang không load. Nếu trang không load được thì các step sau cũng không validate được.
  await expect(page).toHaveTitle(/Product/);
  
  // Soft assertions - kiểm tra chi tiết sản phẩm
  await expect.soft(page.locator('.product-name')).toHaveText('Laptop Dell XPS 13');
  await expect.soft(page.locator('.price')).toContainText('$999');
  await expect.soft(page.locator('.availability')).toHaveText('Còn hàng');
  await expect.soft(page.locator('.rating')).toBeVisible();
  
  // Hard assertion - critical action phải thành công
  const addToCartButton = page.locator('#add-to-cart');
  await expect(addToCartButton).toBeEnabled();
  await addToCartButton.click();
});

3. Custom error messages

Có thể “đặt tên” cho error message, thay vì để throw message mặc định

test('Validate checkout form', async ({ page }) => {
  await page.goto('https://shop.example.com/checkout');
  
  await expect.soft(
    page.locator('#shipping-address'), 
    'Shipping address field should be visible'
  ).toBeVisible();
  
  await expect.soft(
    page.locator('#payment-method'),
    'Payment method dropdown should have correct options'
  ).toHaveCount(3);
  
  await expect.soft(
    page.locator('#total-amount'),
    'Total amount should match expected value'
  ).toHaveText('$1,299.00');
});

Ví dụ thực tế: Test E-commerce Product Page

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

class ProductPageValidator {
  constructor(private page: Page) {}
  
  async validateProductDetails(expectedProduct: {
    name: string;
    price: string;
    rating: number;
    inStock: boolean;
  }) {
    // Soft assertions cho non-critical elements
    await expect.soft(
      this.page.locator('[data-testid="product-name"]')
    ).toHaveText(expectedProduct.name);
    
    await expect.soft(
      this.page.locator('[data-testid="product-price"]')
    ).toHaveText(expectedProduct.price);
    
    await expect.soft(
      this.page.locator('[data-testid="product-rating"]')
    ).toHaveAttribute('data-rating', expectedProduct.rating.toString());
    
    if (expectedProduct.inStock) {
      await expect.soft(
        this.page.locator('[data-testid="stock-status"]')
      ).toHaveText('In Stock');
    } else {
      await expect.soft(
        this.page.locator('[data-testid="stock-status"]')
      ).toHaveText('Out of Stock');
    }
    
    // Validate tất cả product images
    const images = this.page.locator('[data-testid="product-image"]');
    const imageCount = await images.count();
    
    for (let i = 0; i < imageCount; i++) {
      await expect.soft(
        images.nth(i),
        `Product image ${i + 1} should be visible`
      ).toBeVisible();
    }
  }
  
  async validateReviewSection() {
    await expect.soft(
      this.page.locator('[data-testid="reviews-section"]')
    ).toBeVisible();
    
    await expect.soft(
      this.page.locator('[data-testid="review-count"]')
    ).toContainText(/\d+ reviews/);
    
    await expect.soft(
      this.page.locator('[data-testid="write-review-btn"]')
    ).toBeEnabled();
  }
}

test('Complete product page validation', async ({ page }) => {
  await page.goto('https://shop.example.com/product/laptop-dell-xps');
  
  // Hard assertion - trang phải load thành công
  await expect(page).toHaveURL(/.*product.*/);
  
  const validator = new ProductPageValidator(page);
  
  // Soft assertions cho product details
  await validator.validateProductDetails({
    name: 'Dell XPS 13 Laptop',
    price: '$1,299.00',
    rating: 4.5,
    inStock: true
  });
  
  // Soft assertions cho review section
  await validator.validateReviewSection();
  
  // Hard assertion - Add to cart button phải hoạt động
  const addToCart = page.locator('[data-testid="add-to-cart"]');
  await expect(addToCart).toBeEnabled();
  await addToCart.click();
  
  // Verify cart update
  await expect(page.locator('[data-testid="cart-count"]')).toHaveText('1');
});

Best Practices khi sử dụng Soft Assertions

1. Xác định rõ Critical vs Non-Critical Checks

test('User registration flow', async ({ page }) => {
  await page.goto('/register');
  
  // Critical - phải có form
  await expect(page.locator('#registration-form')).toBeVisible();
  
  // Non-critical - UI elements
  await expect.soft(page.locator('.logo')).toBeVisible();
  await expect.soft(page.locator('.help-text')).toHaveText(/Enter your details/);
  
  // Critical - submit action
  await page.fill('#email', 'test@example.com');
  await page.fill('#password', 'Test123!');
  await page.click('#submit');
  
  // Critical - registration success
  await expect(page).toHaveURL('/welcome');
});

Nhóm các assertion có liên quan thành một nhóm. Việc này giúp dễ tổ chức các loại assertion.

test('Dashboard validation', async ({ page }) => {
  await page.goto('/dashboard');
  
  // Group 1: Header validations
  await test.step('Validate header elements', async () => {
    await expect.soft(page.locator('.header-logo')).toBeVisible();
    await expect.soft(page.locator('.user-menu')).toBeVisible();
    await expect.soft(page.locator('.notifications')).toHaveCount(1);
  });
  
  // Group 2: Main content validations
  await test.step('Validate dashboard widgets', async () => {
    await expect.soft(page.locator('.widget-sales')).toBeVisible();
    await expect.soft(page.locator('.widget-orders')).toBeVisible();
    await expect.soft(page.locator('.widget-customers')).toBeVisible();
  });
});

3. Reporting và Logging

Có thể gom nhóm các softassertion lại rồi log ra thông tin cùng một lúc để đọc cho tiện.

test('Product search with detailed reporting', async ({ page }) => {
  const softErrors: string[] = [];
  
  await page.goto('/search?q=laptop');
  
  // Custom soft assertion với logging
  const validateElement = async (
    locator: string, 
    expectation: string
  ) => {
    try {
      await expect.soft(page.locator(locator)).toBeVisible();
    } catch (error) {
      softErrors.push(`Failed: ${expectation}`);
    }
  };
  
  await validateElement('.search-results', 'Search results container');
  await validateElement('.filter-panel', 'Filter panel');
  await validateElement('.sort-options', 'Sort options');
  
  // Log all soft errors for debugging
  if (softErrors.length > 0) {
    console.log('Soft assertion failures:', softErrors);
  }
});

Khi nào nên và không nên dùng Soft Assertions?

Nên dùng khi:

  • Validate nhiều UI elements trên cùng một page
  • Kiểm tra data consistency trên nhiều fields
  • Non-critical visual validations
  • Collecting comprehensive test feedback
  • Regression testing với nhiều check points

Không nên dùng khi:

  • Critical business logic validations
  • Security checks
  • Payment flow validations
  • Authentication/Authorization checks
  • Data integrity validations

Tìm hiểu thêm


Phong Do
Tác giả
Phong Do