Перейти к основному содержимому

Тестирование доступности

Введение

Playwright может использоваться для тестирования вашего приложения на наличие различных проблем с доступностью.

Некоторые примеры проблем, которые можно выявить:

  • Текст, который будет трудно читать пользователям с нарушениями зрения из-за плохого контраста с фоном
  • Элементы управления интерфейсом и формы без меток, которые может идентифицировать экранный диктор
  • Интерактивные элементы с дублирующимися ID, которые могут запутать вспомогательные технологии

Следующие примеры полагаются на пакет Maven com.deque.html.axe-core/playwright, который добавляет поддержку запуска движка тестирования доступности axe в рамках ваших тестов Playwright.

Отказ от ответственности

Автоматизированные тесты доступности могут обнаружить некоторые распространенные проблемы с доступностью, такие как отсутствующие или недействительные свойства. Но многие проблемы с доступностью можно обнаружить только с помощью ручного тестирования. Мы рекомендуем использовать комбинацию автоматизированного тестирования, ручных оценок доступности и инклюзивного тестирования пользователей.

Для ручных оценок мы рекомендуем Accessibility Insights for Web, бесплатный и открытый инструмент для разработчиков, который поможет вам оценить веб-сайт на соответствие WCAG 2.1 AA.

Примеры тестов доступности

Тесты доступности работают так же, как и любой другой тест Playwright. Вы можете либо создать для них отдельные тестовые случаи, либо интегрировать сканирование доступности и утверждения в ваши существующие тестовые случаи.

Следующие примеры демонстрируют несколько базовых сценариев тестирования доступности.

Пример 1: Сканирование всей страницы

Этот пример демонстрирует, как протестировать всю страницу на наличие автоматически обнаруживаемых нарушений доступности. Тест:

  1. Импортирует пакет com.deque.html.axe-core/playwright
  2. Использует обычный синтаксис JUnit 5 @Test для определения тестового случая
  3. Использует обычный синтаксис Playwright для открытия браузера и перехода на тестируемую страницу
  4. Вызывает AxeBuilder.analyze(), чтобы запустить сканирование доступности страницы
  5. Использует обычные утверждения тестов JUnit 5, чтобы убедиться, что в возвращенных результатах сканирования нет нарушений
import com.deque.html.axecore.playwright.*; // 1
import com.deque.html.axecore.utilities.axeresults.*;

import org.junit.jupiter.api.*;
import com.microsoft.playwright.*;

import static org.junit.jupiter.api.Assertions.*;

public class HomepageTests {
@Test // 2
void shouldNotHaveAutomaticallyDetectableAccessibilityIssues() throws Exception {
Playwright playwright = Playwright.create();
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();

page.navigate("https://your-site.com/"); // 3

AxeResults accessibilityScanResults = new AxeBuilder(page).analyze(); // 4

assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations()); // 5
}
}

Пример 2: Настройка axe для сканирования конкретной части страницы

com.deque.html.axe-core/playwright поддерживает множество параметров конфигурации для axe. Вы можете указать эти параметры, используя шаблон Builder с классом AxeBuilder.

Например, вы можете использовать AxeBuilder.include(), чтобы ограничить сканирование доступности только одной конкретной частью страницы.

AxeBuilder.analyze() будет сканировать страницу в ее текущем состоянии на момент вызова. Чтобы сканировать части страницы, которые отображаются в результате взаимодействия с интерфейсом, используйте Локаторы для взаимодействия со страницей перед вызовом analyze():

public class HomepageTests {
@Test
void navigationMenuFlyoutShouldNotHaveAutomaticallyDetectableAccessibilityViolations() throws Exception {
page.navigate("https://your-site.com/");

page.locator("button[aria-label=\"Navigation Menu\"]").click();

// Важно дождаться, чтобы страница была в нужном состоянии
// *перед* запуском analyze(). В противном случае axe может не
// найти все элементы, которые ваш тест ожидает сканировать.
page.locator("#navigation-menu-flyout").waitFor();

AxeResults accessibilityScanResults = new AxeBuilder(page)
.include(Arrays.asList("#navigation-menu-flyout"))
.analyze();

assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());
}
}

Пример 3: Сканирование на наличие нарушений WCAG

По умолчанию axe проверяет широкий спектр правил доступности. Некоторые из этих правил соответствуют конкретным критериям успеха из Руководства по доступности веб-контента (WCAG), а другие являются правилами "лучшей практики", которые не требуются ни одним критерием WCAG.

Вы можете ограничить сканирование доступности только теми правилами, которые "помечены" как соответствующие конкретным критериям успеха WCAG, используя AxeBuilder.withTags(). Например, Автоматические проверки Accessibility Insights for Web включают только правила axe, которые проверяют нарушения критериев успеха WCAG A и AA; чтобы соответствовать этому поведению, вы бы использовали теги wcag2a, wcag2aa, wcag21a и wcag21aa.

Обратите внимание, что автоматизированное тестирование не может обнаружить все типы нарушений WCAG.

AxeResults accessibilityScanResults = new AxeBuilder(page)
.withTags(Arrays.asList("wcag2a", "wcag2aa", "wcag21a", "wcag21aa"))
.analyze();

assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());

Вы можете найти полный список поддерживаемых axe-core тегов правил в разделе "Axe-core Tags" документации по API axe.

Обработка известных проблем

Один из распространенных вопросов при добавлении тестов доступности в приложение: "как подавить известные нарушения?" Следующие примеры демонстрируют несколько техник, которые вы можете использовать.

Исключение отдельных элементов из сканирования

Если ваше приложение содержит несколько конкретных элементов с известными проблемами, вы можете использовать AxeBuilder.exclude(), чтобы исключить их из сканирования, пока вы не сможете исправить проблемы.

Это обычно самый простой вариант, но у него есть некоторые важные недостатки:

  • exclude() исключит указанные элементы и всех их потомков. Избегайте использования его с компонентами, содержащими много дочерних элементов.
  • exclude() предотвратит выполнение всех правил для указанных элементов, а не только правил, соответствующих известным проблемам.

Вот пример исключения одного элемента из сканирования в одном конкретном тесте:

AxeResults accessibilityScanResults = new AxeBuilder(page)
.exclude(Arrays.asList("#element-with-known-issue"))
.analyze();

assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());

Если рассматриваемый элемент используется многократно на многих страницах, рассмотрите возможность использования тестового фикстура для повторного использования той же конфигурации AxeBuilder в нескольких тестах.

Отключение отдельных правил сканирования

Если ваше приложение содержит множество различных существующих нарушений конкретного правила, вы можете использовать AxeBuilder.disableRules(), чтобы временно отключить отдельные правила, пока вы не сможете исправить проблемы.

Вы можете найти идентификаторы правил, которые нужно передать в disableRules(), в свойстве id нарушений, которые вы хотите подавить. Полный список правил axe можно найти в документации axe-core.

AxeResults accessibilityScanResults = new AxeBuilder(page)
.disableRules(Arrays.asList("duplicate-id"))
.analyze();

assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());

Использование отпечатков нарушений для конкретных известных проблем

Если вы хотите разрешить более детализированный набор известных проблем, вы можете использовать следующий шаблон:

  1. Выполните сканирование доступности, которое, как ожидается, обнаружит некоторые известные нарушения
  2. Преобразуйте нарушения в объекты "отпечатков нарушений"
  3. Утвердите, что набор отпечатков эквивалентен ожидаемым

Этот подход избегает недостатков использования AxeBuilder.exclude() за счет небольшой дополнительной сложности и хрупкости.

Вот пример использования отпечатков, основанных только на идентификаторах правил и селекторах "целей", указывающих на каждое нарушение:

public class HomepageTests {
@Test
shouldOnlyHaveAccessibilityViolationsMatchingKnownFingerprints() throws Exception {
page.navigate("https://your-site.com/");

AxeResults accessibilityScanResults = new AxeBuilder(page).analyze();

List<ViolationFingerprint> violationFingerprints = fingerprintsFromScanResults(accessibilityScanResults);

assertEquals(Arrays.asList(
new ViolationFingerprint("aria-roles", "[span[role=\"invalid\"]]"),
new ViolationFingerprint("color-contrast", "[li:nth-child(2) > span]"),
new ViolationFingerprint("label", "[input]")
), violationFingerprints);
}

// Вы можете сделать ваш "отпечаток" настолько специфичным, насколько хотите. Этот считает нарушение
// "тем же самым", если оно соответствует тому же правилу Axe на том же элементе.
//
// Использование типа записи упрощает сравнение отпечатков с помощью assertEquals
public record ViolationFingerprint(String ruleId, String target) { }

public List<ViolationFingerprint> fingerprintsFromScanResults(AxeResults results) {
return results.getViolations().stream()
// Каждое нарушение относится к одному правилу и нескольким "узлам", которые его нарушают
.flatMap(violation -> violation.getNodes().stream()
.map(node -> new ViolationFingerprint(
violation.getId(),
// Каждый узел содержит "цель", которая является CSS-селектором, уникально идентифицирующим его
// Если страница включает iframes или теневые DOM, это может быть цепочка CSS-селекторов
node.getTarget().toString()
)))
.collect(Collectors.toList());
}
}

Использование тестового фикстура для общей конфигурации axe

Класс TestFixtures является хорошим способом поделиться общей конфигурацией AxeBuilder между многими тестами. Некоторые сценарии, где это может быть полезно, включают:

  • Использование общего набора правил среди всех ваших тестов
  • Подавление известного нарушения в общем элементе, который появляется на многих разных страницах
  • Последовательное прикрепление автономных отчетов о доступности для многих сканирований

Следующий пример демонстрирует расширение класса TestFixtures из примера Тестовые раннеры с новым фикстуром, содержащим некоторую общую конфигурацию AxeBuilder.

Создание фикстура

Этот пример фикстура создает объект AxeBuilder, который предварительно настроен с общей конфигурацией withTags() и exclude().

class AxeTestFixtures extends TestFixtures {
AxeBuilder makeAxeBuilder() {
return new AxeBuilder(page)
.withTags(new String[]{"wcag2a", "wcag2aa", "wcag21a", "wcag21aa"})
.exclude("#commonly-reused-element-with-known-issue");
}
}

Использование фикстура

Чтобы использовать фикстур, замените new AxeBuilder(page) из предыдущих примеров на вновь определенный фикстур makeAxeBuilder:

public class HomepageTests extends AxeTestFixtures {
@Test
void exampleUsingCustomFixture() throws Exception {
page.navigate("https://your-site.com/");

AxeResults accessibilityScanResults = makeAxeBuilder()
// Автоматически использует общую конфигурацию AxeBuilder,
// но также поддерживает дополнительную конфигурацию, специфичную для теста
.include("#specific-element-under-test")
.analyze();

assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());
}
}

Смотрите экспериментальную интеграцию JUnit для автоматической инициализации объектов Playwright и многого другого.