Тестирование доступности
Введение
Playwright может использоваться для тестирования вашего приложения на наличие различных проблем с доступностью.
Некоторые примеры проблем, которые можно выявить:
- Текст, который будет трудно читать пользователям с нарушениями зрения из-за плохого контраста с фоном
- Элементы управления интерфейсом и формы без меток, которые может идентифицировать экранный диктор
- Интерактивные элементы с дублирующимися ID, которые могут запутать вспомогательные технологии
Следующие примеры полагаются на пакет Maven com.deque.html.axe-core/playwright
, который добавляет поддержку запуска движка тестирования доступности axe в рамках ваших тестов Playwright.
Отказ от ответственности
Автоматизированные тесты доступности могут обнаружить некоторые распространенные проблемы с доступностью, такие как отсутствующие или недействительные свойства. Но многие проблемы с доступностью можно обнаружить только с помощью ручного тестирования. Мы рекомендуем использовать комбинацию автоматизированного тестирования, ручных оценок доступности и инклюзивного тестирования пользователей.
Для ручных оценок мы рекомендуем Accessibility Insights for Web, бесплатный и открытый инструмент для разработчиков, который поможет вам оценить веб-сайт на соответствие WCAG 2.1 AA.
Примеры тестов доступности
Тесты доступности работают так же, как и любой другой тест Playwright. Вы можете либо создать для них отдельные тестовые случаи, либо интегрировать сканирование доступности и утверждения в ваши существующие тестовые случаи.
Следующие примеры демонстрируют несколько базовых сценариев тестирования доступности.
Пример 1: Сканирование всей страницы
Этот пример демонстрирует, как протестировать всю страницу на наличие автоматически обнаруживаемых нарушений доступности. Тест:
- Импортирует пакет
com.deque.html.axe-core/playwright
- Использует обычный синтаксис JUnit 5
@Test
для определения тестового случая - Использует обычный синтаксис Playwright для открытия браузера и перехода на тестируемую страницу
- Вызывает
AxeBuilder.analyze()
, чтобы запустить сканирование доступности страницы - Использует обычные утверждения тестов 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());
Использование отпечатков нарушений для конкретных известных проблем
Если вы хотите разрешить более детализированный набор известных проблем, вы можете использовать следующий шаблон:
- Выполните сканирование доступности, которое, как ожидается, обнаружит некоторые известные нарушения
- Преобразуйте нарушения в объекты "отпечатков нарушений"
- Утвердите, что набор отпечатков эквивалентен ожидаемым
Этот подход избегает недостатков использования 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 и многого другого.