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

Vọc Vạch Playwright

[Vọc Playwright] Assertion

https://playwright.dev/docs/test-assertions

Giới thiệu

  • Playwright sử dụng expect để thực hiện assertion.
  • Sử dụng đơn giản:expect(value) và chọn một “matcher” theo mong muốn. VD: toEqual, toContain, toBeTruthy
expect(success).toBeTruthy();
  • Ngoài ra thì Playwright cũng có các async matcher (bất đồng bộ), giúp kiểm tra các phần tử trên trang một cách hiệu quả hơn
await expect(page.getByTestId('status')).toHaveText('Submitted');
  • Trong ví dụ trên thì Playwright sẽ tự test lại phần tử có test-id là “status” cho tới khi nó có text là “Submitted” thì thôi.
  • Nó sẽ retry liên tục cho tới lúc timeout thì thôi.
  • Có thể cài đặt thời gian timeout trong testConfig.expect.

Auto retry assertions

  • Đây chính là các async matcher mà mình nói phía trên.
  • Vì nó bất đồng bộ ~> bạn cần phải thêm await phía trước.
Assertion Description
await expect(locator).toBeAttached() Phần tử được attached vào DOM
await expect(locator).toBeChecked() Checkbox được checked
await expect(locator).toBeDisabled() Phần tử được disable
await expect(locator).toBeEditable() Phần tử editable
await expect(locator).toBeEmpty() Phần tử empty
await expect(locator).toBeEnabled() Phần tử enable
await expect(locator).toBeFocused() Phần tử focused
await expect(locator).toBeHidden() Phần tử không visible (bị ẩn đi)
await expect(locator).toBeInViewport() Phần tử nằm trong viewport
await expect(locator).toBeVisible() Phần tử hiển thị (trái ngược với toBeHidden)
await expect(locator).toContainText() Phần tử chứa text
await expect(locator).toHaveAccessibleDescription() Phần tử chứa accessible description
await expect(locator).toHaveAccessibleName() Phần tử chứa accessible name
await expect(locator).toHaveAttribute() Phần tử chứa DOM attribute
await expect(locator).toHaveClass() Phần tử chứa class
await expect(locator).toHaveCount() Phần tử chứa chính xác số lượng
await expect(locator).toHaveCSS() Phần tử chứa thuộc tính CSS
await expect(locator).toHaveId() Phần tử chứa id
await expect(locator).toHaveJSProperty() Phần tử chứa Javascript property
await expect(locator).toHaveRole() Element has a specific ARIA role
await expect(locator).toHaveScreenshot() Element has a screenshot
await expect(locator).toHaveText() Element matches text
await expect(locator).toHaveValue() Input has a value
await expect(locator).toHaveValues() Select has options selected
await expect(page).toHaveScreenshot() Page has a screenshot
await expect(page).toHaveTitle() Page has a title
await expect(page).toHaveURL() Page has a URL
await expect(response).toBeOK() Response has an OK status

Non-retrying assertion

  • Đây là những assertion sẽ kiểm tra ngay lập tức giá trị mà bạn mong muốn. Không có retry đâu.
  • Thường thì nên dùng retrying assertion phía trên nhé anh em. Non-retry hay làm cho test flaky lắm.
  • Toàn cái dễ nên mình để nguyên tiếng Anh đọc nhé anh em 😉
Assertion Description
expect(value).toBe() Value is the same
expect(value).toBeCloseTo() Number is approximately equal
expect(value).toBeDefined() Value is not undefined
expect(value).toBeFalsy() Value is falsy, e.g. false, 0, null, etc.
expect(value).toBeGreaterThan() Number is more than
expect(value).toBeGreaterThanOrEqual() Number is more than or equal
expect(value).toBeInstanceOf() Object is an instance of a class
expect(value).toBeLessThan() Number is less than
expect(value).toBeLessThanOrEqual() Number is less than or equal
expect(value).toBeNaN() Value is NaN
expect(value).toBeNull() Value is null
expect(value).toBeTruthy() Value is truthy, i.e. not false, 0, null, etc.
expect(value).toBeUndefined() Value is undefined
expect(value).toContain() String contains a substring
expect(value).toContain() Array or set contains an element
expect(value).toContainEqual() Array or set contains a similar element
expect(value).toEqual() Value is similar – deep equality and pattern matching
expect(value).toHaveLength() Array or string has length
expect(value).toHaveProperty() Object has a property
expect(value).toMatch() String matches a regular expression
expect(value).toMatchObject() Object contains specified properties
expect(value).toStrictEqual() Value is similar, including property types
expect(value).toThrow() Function throws an error
expect(value).any() Matches any instance of a class/primitive
expect(value).anything() Matches anything
expect(value).arrayContaining() Array contains specific elements
expect(value).closeTo() Number is approximately equal
expect(value).objectContaining() Object contains specific properties
expect(value).stringContaining() String contains a substring
expect(value).stringMatching() String matches a regular expression

Negating matcher

  • Hiểu đơn giản là làm cho nó kiểm tra ngược lại.
  • Ví dụ: đang kiểm tra bằng (0), thêm not vào, thành kiểm tra khác 0
expect(value).not.toEqual(0);
await expect(locator).not.toContainText('some text');

Soft assertions

  • Mặc định, nếu assertion fail sẽ dừng test lại.
  • Playwright cung cấp khái niệm soft assertion, giúp test không dừng chạy khi fail. Và khi kết thúc test, vẫn sẽ đánh dấu test là fail.
// Make a few checks that will not stop the test when failed...
await expect.soft(page.getByTestId('status')).toHaveText('Success');
await expect.soft(page.getByTestId('eta')).toHaveText('1 day');

// ... and continue the test to check more things.
await page.getByRole('link', { name: 'next page' }).click();
await expect.soft(page.getByRole('heading', { name: 'Make another order' })).toBeVisible();
  • Có thể kết hợp cả soft assertionassertion
// Make a few checks that will not stop the test when failed...
await expect.soft(page.getByTestId('status')).toHaveText('Success');
await expect.soft(page.getByTestId('eta')).toHaveText('1 day');

// Avoid running further if there were soft assertion failures.
expect(test.info().errors).toHaveLength(0);

Custom expect message

  • Tức là bạn thay đổi cái message hiển thị của Playwright lúc chạy test.
  • Nếu pass hay fail thì nó sẽ hiện message này.
  • Ví dụ:
await expect(page.getByText('Name'), 'should be logged in').toBeVisible();
  • Nếu chạy pass thì sẽ hiển thị như sau:
✅ should be logged in    @example.spec.ts:18
  • Nếu chạy fail thì sẽ hiển thị như sau:
    Error: should be logged in

    Call log:
      - expect.toBeVisible with timeout 5000ms
      - waiting for "getByText('Name')"


      2 |
      3 | test('example test', async({ page }) => {
    > 4 |   await expect(page.getByText('Name'), 'should be logged in').toBeVisible();
        |                                                                  ^
      5 | });
      6 |
  • Có thể config custom expect message cho cả soft assertion nha
expect.soft(value, 'my soft assertion').toBe(56);

expect.configure

  • Có thể tạo ra các expect theo mong muốn
const slowExpect = expect.configure({ timeout: 10000 });
await slowExpect(locator).toHaveText('Submit');

// Always do soft assertions.
const softExpect = expect.configure({ soft: true });
await softExpect(locator).toHaveText('Submit');

expect.poll

  • Dùng để convert một expect thông thường thành async expect sử dụng hàm expect.poll:
await expect.poll(async () => {
  const response = await page.request.get('https://api.example.com');
  return response.status();
}, {
  // Custom expect message for reporting, optional.
  message: 'make sure API eventually succeeds',
  // Poll for 10 seconds; defaults to 5 seconds. Pass 0 to disable timeout.
  timeout: 10000,
}).toBe(200);
  • Trong ví dụ trên, hàm poll sẽ gọi lại cho tới khi giá trị giống như trong toBe thì thôi.
  • Bạn có thể setting để cho gọi lại theo thời gian mong muốn
await expect.poll(async () => {
  const response = await page.request.get('https://api.example.com');
  return response.status();
}, {
  // Probe, wait 1s, probe, wait 2s, probe, wait 10s, probe, wait 10s, probe
  // ... Defaults to [100, 250, 500, 1000].
  intervals: [1_000, 2_000, 10_000],
  timeout: 60_000
}).toBe(200);

expect.toPass

  • Bạn có thể retry một đoạn code cho tới khi nó success:
await expect(async () => {
  const response = await page.request.get('https://api.example.com');
  expect(response.status()).toBe(200);
}).toPass();
  • Bạn cũng có thể thay đổi timeout và interval giống expect.poll vậy
await expect(async () => {
  const response = await page.request.get('https://api.example.com');
  expect(response.status()).toBe(200);
}).toPass({
  // Probe, wait 1s, probe, wait 2s, probe, wait 10s, probe, wait 10s, probe
  // ... Defaults to [100, 250, 500, 1000].
  intervals: [1_000, 2_000, 10_000],
  timeout: 60_000
});

Tạo ra một matcher riêng theo ý muốn

  • Khá đơn giản, bạn chỉ cân extend expect là được
import { expect as baseExpect } from '@playwright/test';
import type { Page, Locator } from '@playwright/test';

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

export const expect = baseExpect.extend({
  async toHaveAmount(locator: Locator, expected: number, options?: { timeout?: number }) {
    const assertionName = 'toHaveAmount';
    let pass: boolean;
    let matcherResult: any;
    try {
      await baseExpect(locator).toHaveAttribute('data-amount', String(expected), options);
      pass = true;
    } catch (e: any) {
      matcherResult = e.matcherResult;
      pass = false;
    }

    const message = pass
      ? () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) +
          '\n\n' +
          `Locator: ${locator}\n` +
          `Expected: ${this.isNot ? 'not' : ''}${this.utils.printExpected(expected)}\n` +
          (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : '')
      : () =>  this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) +
          '\n\n' +
          `Locator: ${locator}\n` +
          `Expected: ${this.utils.printExpected(expected)}\n` +
          (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : '');

    return {
      message,
      pass,
      name: assertionName,
      expected,
      actual: matcherResult?.actual,
    };
  },
});
  • Sử dụng trong code
import { test, expect } from './fixtures';

test('amount', async () => {
  await expect(page.locator('.cart')).toHaveAmount(4);
});
  • ** Lưu ý **: không được nhầm lẫn giữa Playwright expectthư viện expect nhé bạn. Chú ý để ý import không lại nhầm.

  • Gộp matcher từ nhiều modules khác nhau

// fixture.ts

import { mergeTests, mergeExpects } from '@playwright/test';
import { test as dbTest, expect as dbExpect } from 'database-test-utils';
import { test as a11yTest, expect as a11yExpect } from 'a11y-test-utils';

export const expect = mergeExpects(dbExpect, a11yExpect);
export const test = mergeTests(dbTest, a11yTest);

  • Trong file test
import { test, expect } from './fixtures';

test('passes', async ({ database }) => {
  await expect(database).toHaveDatabaseUser('admin');
});

Trả lời