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

Vọc Vạch Playwright

[Vọc Playwright] Accessibility testing

https://playwright.dev/docs/accessibility-testing

Giới thiệu

  • Playwright có thể được sử dụng để test các vấn đề liên quan đến khả năng truy cập (accessibility issues) của một trang web, chẳng hạn như:
  • Độ tương phản màu sắc giữa chữ và background, có thể gây khó đọc cho người khiếm thị
  • UI controls và các trường nhập liệu bị thiếu label
  • Các elements có ID trùng lặp có thể gây bối rối/nhầm lẫn
  • Các vấn đề về nêu trên có thể được hỗ trợ test qua package @axe-core/playwright, cho phép test khả năng truy cập như 1 phần của Playwright tests.
  • Lưu ý: Việc automation test cho accessibility chỉ có thể phát hiện một vào vấn đề cơ bản, ngoài ra có rất nhiều vấn đề khác chỉ có thể được tìm thấy khi thực hiện test manual.
  • Playwright khuyên chúng ta nên sử dụng kết hợp cả automation test và manual test để đảm bảo khả năng truy cập. Chúng ta có thể sử dụng Accessibility Insights for Web – một công cụ mã nguồn mở và miễn phí.

Ví dụ về accessibility testing

  • Accessibility testing cũng giống như một Playwright test bất kì.
  • Bạn có thể tạo một test riêng cho phần accessibility, hoặc update vào test đã có.
  • Dưới đây là một vài ví dụ điển hình

Kiểm tra accessibility trên toàn bộ trang web

  • Ví dụ dưới đây trình bày cách tự động quét toàn bộ trang web để phát hiện các vấn đề về khả năng truy cập:
    • Import package @axe-core/playwright
    • Định nghĩa test case bằng các cú pháp Playwright đơn giản
    • Truy cập trang cần test
    • Sử dụng AxeBuilder.analyze() để kiểm thử khả năng truy cập của trang web
    • Sử dụng Playwright Test assertions kiểm tra rằng trang web không gặp vấn đề về accessibility
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright'; // 1

test.describe('homepage', () => { // 2
  test('should not have any automatically detectable accessibility issues', async ({ page }) => {
    await page.goto('https://your-site.com/'); // 3

    const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); // 4

    expect(accessibilityScanResults.violations).toEqual([]); // 5
  });
});

Kiểm tra accessibility trên một phần của trang web

  • Bạn có thể dùng AxeBuilder.include() để kiểm tra accessibility trên một phần của trang
  • AxeBuilder.analyze() sẽ scan trang web khi được gọi. Giả sử trang web của bạn có các phần tử động, chỉ xuất hiện sau một số thao tác, thì bạn hãy thực hiện trước khi gọi hàm analyze()
test('navigation menu should not have automatically detectable accessibility violations', async ({
  page,
}) => {
  await page.goto('https://your-site.com/');

  await page.getByRole('button', { name: 'Navigation Menu' }).click();

  // It is important to waitFor() the page to be in the desired
  // state *before* running analyze(). Otherwise, axe might not
  // find all the elements your test expects it to scan.
  await page.locator('#navigation-menu-flyout').waitFor();

  const accessibilityScanResults = await new AxeBuilder({ page })
      .include('#navigation-menu-flyout')
      .analyze();

  expect(accessibilityScanResults.violations).toEqual([]);
});

Kiểm tra các vi phạm liên quan đến tiêu chuẩn WCAG

  • Mặc định, axe kiểm tra một loạt các quy tắc về khả năng truy cập. Một số quy tắc này tương ứng với các tiêu chí cụ thể từ Web Content Accessibility Guidelines (WCAG), trong khi các quy tắc khác được đúc kết từ thực tế, không phụ thuộc vào bất kì tiêu chuẩn WCAG nào.
  • Chúng ta có thể khoanh vùng việc quét khả năng truy cập của trang web, chỉ chạy những quy tắc được gắn thẻ dựa trên những tiêu chí của WCAG bằng cách sử dụng AxeBuilder.withTags()
  • Lưu ý rằng automation test không thể phát hiện hết tất cả các vi phạm dựa trên tiêu chí WCAG
  • Ví dụ, việc kiểm thử tự động Accessibility Insights for Web chỉ bao gồm kiểm tra việc vi phạm các tiêu chí WCAG A và AA, và để phù hợp với các tiêu chí đó, chúng ta sử dụng các thẻ wcag2a, wcag2aa, wcag21a và wcag21aa.
test('should not have any automatically detectable WCAG A or AA violations', async ({ page }) => {
  await page.goto('https://your-site.com/');

  const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
      .analyze();

  expect(accessibilityScanResults.violations).toEqual([]);
});

Xử lý các vấn đề đã được phát hiện

  • Đại khái là bạn biết là website của mình có lỗi accessibility rồi, và bạn cần ignore chúng đi.

Loại bỏ một phần tử khỏi quá trình scan

  • Dùng AxeBuilder.exclude().
  • Cách này đơn giản nhất, nhưng có một số nhược điểm:
    • exclude() sẽ bỏ qua element được chỉ định và tất cả các element con cháu của nó. Cần tránh sử dụng với những element có nhiều node con.
    • exclude() sẽ ignore TẤT CẢ các rules đối với một phần tử. Trường hợp bạn chỉ có một vài rules muốn ignore thì exclude sẽ không làm được
  • Ví dụ:
test('Bỏ qua phần tử element-with-known-issue', async ({
  page,
}) => {
  await page.goto('https://your-site.com/page-with-known-issues');

  const accessibilityScanResults = await new AxeBuilder({ page })
      .exclude('#element-with-known-issue')
      .analyze();

  expect(accessibilityScanResults.violations).toEqual([]);
});
  • Nếu element bạn muốn ignore dùng ở nhiều page, có thể sử dụng test fixture.

Vô hiệu hóa các rule scan

  • Phía trên là bạn không scan phần tử, còn ở đây thì bạn bỏ qua một số rules.
  • Sử dụng AxeBuilder.disableRules() để bỏ qua rule.
  • Bạn cần truyền id của các vi phạm mà bạn muốn bỏ qua vào hàm disableRules():
test('should not have any accessibility violations outside of rules with known issues', async ({
  page,
}) => {
  await page.goto('https://your-site.com/page-with-known-issues');

  const accessibilityScanResults = await new AxeBuilder({ page })
      .disableRules(['duplicate-id'])
      .analyze();

  expect(accessibilityScanResults.violations).toEqual([]);
});

Sử dụng snapshot để cho phép các sự cố đã biết

  • Sử dụng Snapshots giúp quản lý các vấn đề về khả năng truy cập được xác định từ trước, đảm bảo rằng các vi phạm trước đó không thay đổi, tránh các nhược điểm của việc sử dụng AxeBuilder.exclude().
  • Không nên sử dụng toàn bộ mảng accessibilityScanResults.violations trong snapshot, do mảng này chứa các chi tiết triển khai của phần mềm (chẳng hạn như đoạn mã HTML), và khi các thành phần này thay đổi sẽ dẫn đến fail test cases.
// Don't do this! This is fragile.
expect(accessibilityScanResults.violations).toMatchSnapshot();
  • Thay vào đó, bạn nên tạo một snapshot chỉ chứa các thông tin cần thiết để xác định các vi phạm đã biết mà không bao gồm các chi tiết có thể thay đổi một cách không liên quan:
// This is less fragile than snapshotting the entire violations array.
expect(violationFingerprints(accessibilityScanResults)).toMatchSnapshot();

// my-test-utils.js
function violationFingerprints(accessibilityScanResults) {
  const violationFingerprints = accessibilityScanResults.violations.map(violation => ({
    rule: violation.id,
    // These are CSS selectors which uniquely identify each element with
    // a violation of the rule in question.
    targets: violation.nodes.map(node => node.target),
  }));

  return JSON.stringify(violationFingerprints, null, 2);
} 

Xuất kết quả kiểm tra dưới dạng tệp đính kèm

  • Hầu hết các trường hợp kiểm tra khả năng truy cập tập trung chủ yếu vào việc trang web có vi phạm các quy tắc hay không. Tuy nhiên, kết quả quét chứa nhiều thông tin hơn chỉ là các vi phạm, chẳng hạn như thông tin về các quy tắc đã pass và về các phần tử mà không kết luận được dựa trên 1 vài quy tắc. Thông tin này có thể hữu ích cho việc debug khi kết quả test không như mong đợi.
  • Bạn có thể lưu toàn bộ kết quả quét như 1 một tệp đính kèm với testInfo.attach(). Reporters có thể nhúng hoặc link kết quả scan như 1 phần của test output
test('example with attachment', async ({ page }, testInfo) => {
  await page.goto('https://your-site.com/');

  const accessibilityScanResults = await new AxeBuilder({ page }).analyze();

  await testInfo.attach('accessibility-scan-results', {
    body: JSON.stringify(accessibilityScanResults, null, 2),
    contentType: 'application/json'
  });

  expect(accessibilityScanResults.violations).toEqual([]);
});

Sử dụng test fixture cho những cấu hình axe thông thường

  • Chúng ta có thể sử dụng test fixture để dùng lại các cài đặt AxeBuilder trong nhiều trường hợp kiểm thử. Nó sẽ hữu ích tring câc trường hợp:
    • Sử dụng 1 bộ quy tắc chung cho tất cả các test cases
    • Bỏ qua các vi phạm đã biết trong 1 element xuất hiện tại nhiều page
    • Đính kèm các báo cáo về khả năng truy cập của trang web trong nhiều lần test.
  • Ví dụ sau đây minh họa việc tạo và sử dụng test fixture cho kịch bản bên trên:

Tạo fixture

  • Fixture này tạo ra một đối tượng AxeBuilder, có sử dụng withTags()exclude
import { test as base } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

type AxeFixture = {
  makeAxeBuilder: () => AxeBuilder;
};

// Extend base test by providing "makeAxeBuilder"
//
// This new "test" can be used in multiple test files, and each of them will get
// a consistently configured AxeBuilder instance.
export const test = base.extend<AxeFixture>({
  makeAxeBuilder: async ({ page }, use, testInfo) => {
    const makeAxeBuilder = () => new AxeBuilder({ page })
        .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
        .exclude('#commonly-reused-element-with-known-issue');

    await use(makeAxeBuilder);
  }
});
export { expect } from '@playwright/test';

Sử dụng fixture

  • Để sử dụng fixture, thay thế new AxeBuilder({ page }) bằng makeAxeBuilder:
const { test, expect } = require('./axe-test');

test('example using custom fixture', async ({ page, makeAxeBuilder }) => {
  await page.goto('https://your-site.com/');

  const accessibilityScanResults = await makeAxeBuilder()
      // Automatically uses the shared AxeBuilder configuration,
      // but supports additional test-specific configuration too
      .include('#specific-element-under-test')
      .analyze();

  expect(accessibilityScanResults.violations).toEqual([]);
});

Trả lời