Playwright як інструмент для автоматизації тестування доступності

💡 Усі статті, обговорення, новини про тестування — в одному місці. Приєднуйтесь до QA спільноти!

Усім привіт! Мене звати Віктор і я займаюсь автоматизацією тестування за допомогою Playwright, використовуючи мову програмування TypeScript. Нещодавно на проєкті, де я працюю, виникла потреба автоматизувати процес тестування доступності. Це цікава тема, яку я вирішив дослідити та зрештою впровадити процес на практиці.

В Levi9 регулярно проводяться мітапи для розробників різних напрямів, як-от Python, Java, QA тощо. На одному з них я якраз ділився своїм досвідом з автоматизації тестування, щоб допомогти колегам полегшити роботу та знайти нові ідеї. Готуючи презентацію, я оцінив ситуацію з іншої сторони і в результаті покращив попередні рішення.

В цій статті хочу поділитися набутим досвідом. А оскільки в роботі я використовую TypeSctipt, то всі представлені в статті фрагменти коду будуть написані на ньому.

1. Тестування доступності

Загалом тестування доступності (Accessibility Testing) — це велика та важлива тема для компаній і проєктів, які піклуються про своїх клієнтів, тому якщо ви зовсім не знайомі з тестуванням доступності, то раджу ознайомитись із цією темою.

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

2. Базові налаштування

Щоб тестувати доступність за допомогою Playwright, потрібно додатково встановити бібліотеку @axe-core/playwright за допомогою якої і відбуватиметься сканування.

npm i @axe-core/playwright 

Після того як бібліотеку встановлено, можна написати перший тест.

 //test-file.spec.ts
  import { test, expect } from '@playwright/test';
  import AxeBuilder from '@axe-core/playwright';
    
   test('Test name', async ({ page }, testInfo) => {
     await page.goto('https://your-site.com/');
     const scanResults = await new AxeBuilder({ page }).analyze();
     expect(scanResults.violations).toEqual([]);
   });  

Як бачимо, в тесті все відбувається в 3 кроки:

1) перехід на потрібну сторінку;
2) сканування за допомогою AxeBuilder();
3) порівняння отриманих та очікуваних результатів.

Важливо розуміти, що сканування відбувається за принципом фотографії: якщо на сторінці є модальні вікна чи списки, що випадають, їх потрібно попередньо відкрити, щоб протестувати.

Крім того, варто враховувати, що результати сканування містять не лише порушення, тому для коректного порівняння в expect() потрібно передавати violations.

Якщо є порушення, то після тесту ми отримаємо звіт з детальною інформацією про порушені правила та місця, де це відбулося, а також інформацію щодо можливих варіантів вирішення.

 [
     Object {
         "description": "Ensures the contrast between foreground and background colors meets WCAG 2 AA contrast ratio thresholds",
         "help": "Elements must have sufficient color contrast",
         "helpUrl": "https://dequeuniversity.com/rules/axe/4.6/color-contrast?application=playwright",
         "id": "color-contrast",
         "impact": "serious",
         "nodes": Array[
             Object {
         "all": Array[],
         "any": Array[
             Object {
         "data": Object {
         "bgColor": "#ebedf0",
         "contrastRatio": 2.27,
         "expectedContrastRatio": "4.5:1",
         "fgColor": "#969faf",
         "fontSize": "12.0pt (16px)",
         "fontWeight": "normal",
         "messageKey": null,
         "shadowColor": undefined,
     },
     "id": "color-contrast",
     "impact": "serious",
     "message": "Element has insufficient color contrast of 2.27 (foreground color: #969faf, background color: #ebedf0, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1",
     "relatedNodes": Array[
     Object {
         "html": "<button type=\"button\" class=\"DocSearch DocSearch-Button\" aria-label=\"Search\">",
         "target": Array[
             ".DocSearch",
              ],
     },
 ],
        },
      ],
 "failureSummary": "Fix any of the following:
 Element has insufficient color contrast of 2.27(foreground color: #969faf, background color: #ebedf0, font size: 12.0pt(16px), font weight: normal).Expected contrast ratio of 4.5: 1",
 "html": "<span class=\"DocSearch-Button-Placeholder\">Search</span>",
     "impact": "serious",
         "none": Array[],
             "target": Array[
                 ".DocSearch-Button-Placeholder",
      ],
    },
  ],
 "tags": Array[
     "cat.color",
     "wcag2aa",
     "wcag143",
     "ACT",
  ],
 },
 ]

Загалом інформації у звіті достатньо, щоб локалізувати проблему та зрозуміти можливі шляхи її розв’язання.

3. Конфігурування аналізатора

Щоб отримати оптимальний ефект від тестування, аналіз можна досить гнучко конфігурувати. Цей ефект досягається використанням методів, які дозволяють встановлювати рівень рекомендацій чи набір правил, корегуючи їх шляхом виключення з перевірки неактуальних правил.

Зручно також виключити з перевірки певну частину сторінки і тестувати лише ту, що необхідна. Для себе я обрав наступну конфігурацію, де в include() залежно від конкретного тесту буде передано необхідний селектор або body за замовчуванням.

 //test-file.spec.ts
  test('Test name', async ({ page }) => {
	await page.goto('https://your-site.com/);
	const scanResults = await new AxeBuilder({ page })
	.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) // набір правил
	.disableRules(['color-contrast']) // виключення з перевірки певного правила
	.include('selector') // обмеження частини сторінки для перевірки
	.analyze();
	expect(scanResults.violations).toEqual([]);
 });  

4. Оптимізація використання

Для оптимізації використання аналізатора в тестах розробники Playwright пропонують використовувати фікстури. Проте, експериментуючи, я знайшов зручніший варіант.

Оскільки Playwright дозволяє розширити expect, то я вирішив зробити метод toBeAccessible(). В результаті це дає зручніший і лаконічніший синтаксис для написання тестів, а саме:

//test-file.spec.ts
test('Test name', async ({ page }, testInfo) => {
    await page.goto('https://your-site.com/);
    await expect(page).toBeAccessible(testInfo, 'selector');
}); 

Для цього потрібно змінити 3 файли. Перший — playwright.config.ts.

// playwright.config.ts
 import { expect, PlaywrightTestConfig } from '@playwright/test';
  
 expect.extend({
     async toBeAccessible(page: Page, testInfo: TestInfo, include?: string) {
         const scanResults = await new AxeBuilder({ page })
             .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
             .disableRules(['color-contrast'])
             .include(include || 'body')
             .analyze();
  
         if (scanResults.violations.length == 0) {
             return {
                 message: () => 'pass',
                 pass: true,
             };
         } else {
             await testInfo.attach('accessibility-scan-results', {
                 body: JSON.stringify(scanResults.violations, null, 2),
                 contentType: 'application/json',
             });
             return {
                 message: () => `${scanResults.violations.length} violated rules were found.`,
                 pass: false,
             };
         }
     },
 });  

Як і при використанні фікстур, така конструкція дозволяє змінити конфігурацію аналізатора під конкретний тест. Додатково «під капотом» вона приховує крок зі збереженням звіту про порушення у вигляді застосунку до основного звіту.

Це значно полегшує роботу зі звітами, якщо порушень більше одного, адже їхній розмір може бути й на сотні рядків.

І оскільки я використовую TypeScript, то для коректної типізації додатково потрібно змінити ще й файл global.d.ts

//global.d.ts
 import { TestInfo } from '@playwright/test';
  
 declare global {
     namespace PlaywrightTest {
         interface Matchers<R> {
             toBeAccessible(testInfo: TestInfo, include?: string): Promise<R>;
         }
     }
 }

А також tsconfig.json, щоб IDE розпізнавала ці типи:

 //tsconfig.json
 {
     "compilerOptions": {
         "typeRoots": [
             "global.d.ts"
         ]
     }
 }  

5. Використання тестів

Тести по перевірці доступності можна використовувати окремо чи міксувати з основними. Я вирішив виокремити їх в блок, використовуючи тег @a11y, щоб простіше маніпулювати наборами тестів для різних потреб.

Доцільним для таких тестів є також використання кроків разом з expect.soft(). Таке рішення дозволяє покроково сканувати сторінку без втрати порушення всередині кроків, а також суттєво економить час. Прикладом такого використання є:

test('Test name @a11y', async ({ page, somePage }, testInfo) => {
    await page.goto('https://your-site.com/);
    await test.step('Scan whole page', async () => {
        await expect.soft(page).toBeAccessible(testInfo);
    });
    await test.step('Scan modal window', async () => {
        await somePage.modalWindow.open();
        await expect.soft(page).toBeAccessible(testInfo, 'modal-window-selector');
    });
}); 

На цьому прикладі також показано перевагу використання обмеженого сканування частини сторінки. І якщо буде знайдено порушення для 2-го кроку, то вони стосуватимуться лише модального вікна та не дублюватимуть інші порушення зі сторінки.

6. Складнощі

При написанні тестів я виявив проблему, яку вже частково описували розробники Playwright: для коректного сканування певної частини сторінки потрібно дочекатися поки вона стане видимою.

Для цього вони пропонують використовувати waitFor().

await page.locator('selector').waitFor();

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

Проте якщо елементи цього модального вікна підвантажуються асинхронно, то система перевірить їх некоректно чи взагалі просканує пустий контейнер. В обох випадках результати тестування будуть хибні.

Висновки

Playwright в поєднанні з бібліотекою @axe-core — досить гнучкий і зручний інструмент для автоматизованого тестування доступності, хоча й не покриває всі його потреби. І це варто враховувати, якщо ваш продукт має такі зобов’язання.

Проте варто зважити, що ефективність використання будь-якого інструменту здебільшого залежить від того, хто його використовує, тому завжди потрібно думати про можливі способи покращення.

👍ПодобаєтьсяСподобалось19
До обраногоВ обраному6
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Ніхто не протестує функціонал вашого додатка так, як я. Тим більше, що я це зроблю задарма, просто заради відгука.

Хочу почати вивчати данний інструмент але в напрямку саме мобільної автоматизації, як на адроїд так і айос, але зараз саме на стадії вивчення мови програмування а саме С#, які порадите інструменти для мобільної автотамотизації?
Які вона взагалі підтримує і що саме потрібно знати?

Вітаю!
Я спеціалізуюсь на тестуванні веб-продуктів (UI та API) з використанням TS/JS та Python, а тому не можу давати рекомендації щодо використання C# чи тестування мобільних додатків.
А стосовно використання Playwright, то однозначно варто спробувати.
Як на мене то для ознайомлення найкраще купити курс на Udemy, наприклад цей www.udemy.com/...​rials-automation-testing, або інший.
Зараз вартість вказаного курсу — $74.99, але періодично з’являється знижка і можна буде купити за $8.
Окрім цього є Oleksandr Khotemskyi, який проводить оффлайн (а може і онлайн) майстер класи по Playwright — це гарний варіант якщо є потреба в «живому» фідбекові.
Якщо витрачати кошти на курси не хочеться або немає можливості, то можна знайти аналогічні курси на ютубі, але зазвичай вони менш деталізовані і можуть бути застарілими.
Враховуючи курси та вичерпну офіційну документацію (playwright.dev/docs/intro) можна швидко зробити тестовий проєкт і зрозуміти ви підходить цей інтрмент для поставлених завдань.

Проте якщо елементи цього модального вікна підвантажуються асинхронно, то система перевірить їх некоректно чи взагалі просканує пустий контейнер. В обох випадках результати тестування будуть хибні.

Нема варіанту чекати на нетворк реквести чи на елементи модального вікна?

+1

Можна через waitForRequest шось зробити спробувати

Такі можливості є, оскільки Playwright має великий вибір вейтрів, але універсального рішення немає, тому кожем випадок потрібно розглядати окремо і на це можна витрати більше часу а ніж хотілося б.
Приклад з власного досвіду.Є модалка, дані для якої підвантажились на етапі завантаження сторінки, тому перевіряти запити-відповіді не має сенсу, але при її відкритті виконується анімація для відображення кнопок. Використання «.waitFor();», як рекомендують розробники Playwright, не допомогає, оскільки метод поверне true (що елемент видимий) ще в процесі анімації, не дочекавшись її завершення, а тому буде розпочато a11y-сканування і буде зафіксовано порушення правила контрасту (співідношення кольорів фону кнопки і тексту та розміру тексту).
Тому я і зробив акцент на тому що не варто довіряти «.waitFor();» бо це ненадійний варіант.

В цьому випадку можна очікувати, коли елемент перестане змінювати позицію / opacity etc.
В загалом, більшість подібних кейсів вирішується очікуванням певного атрібуту, проперті або незмінності позиції.
Сам писав окрему лібу, щоб хендлити ці кейси в Puppeteer.
Тобто, Playwright теж має з цим проблеми.

Якщо в вас є якісь критерії за якими можна зрозуміти що застосунок не завантажено — то це не є проблема Playwright та a11y. Якісь анімації завантажуються в там мають якісь проперті в before та after стейті. То до перевірки можна юзати expect та poll замість waitFor, хоча у чому його нестабільність — так і не зрозуміло.

Хоча яка різніця, якщо це проблема застосунку та його load критеріїв.

Влучне зауваження, дякую.
То я неточно сформував свою думку.

Чи є варіант отримати ці результати якось красиво у виді html репорту?

«Із коробки» такої можливості немає.
Я планував реалізувати такий варіант, але через брак часу зупинився на проміжному варіанті, а саме — дворівневий список (порушене правило -> селектори порушених елементів) + скрін всієї сторінки з виділенням порушень як в браузерному розширені Axe + атач json репорту. Такий варіант виявився досить вдалим за рахунок поєднання лаконічності та інформативності, тому поки реалізацію html-репорту відклав.
В статті цього не має, бо доробив уже після написання статті, але можу швидко оформити і зробити як продовження цієї статті.
По html-репорту — бачив одну репу, але мені не сподобалось тому не зберіг посилання. Якщо цікаво, то можу пошукати по історії.

Я зараз для lighthouse report який я генерую з playwright тестів просто додаю HTML файл з репортом аттачментом до тесту. Маякни в телегу покажу як виглядає якщо цікаво

На практиці зазвичай воно не дуже сильно допомагає, все рівно у вас повинні бути аксесабіліті тестери, а також ревю відповідних команд, які займаються оцінкою доступності вашого додатку. Хоча розвиток Axe за останні роки трохи замітний.Також і питання: навіщо той же Playwright, коли є більш потужний( в більшості критеріїв ) Cypress

Cypress вже вміє в айфрейми?

насмішили з «більш потужний Cypres»

Бачу, вже навчився. Але це виглядає більш як костиль, а нe як stable solution.

Моя улюблена сторінка в документації cypress — «Як оголосити змінну?»
docs.cypress.io/...​pts/variables-and-aliases

а Playwright може нормально тестувати електрон апки, які не піднімаються в браузері???

ПС: можливо ви і праві в цьому плані, тому що я говорю лише з точки зору розробника і команди розробників, яким набагато зручніше на сайпресі писати і е2е тести, і інтеграційні(а також доволі прикольна функція компонент тестування з рендер функцією для кожного фреймворку)

Микола, для енжинірінг лiда з 4ма роками досвiду ви забагато самовпевнені. Дуже сумніваюся, що ви використовували усі існуючі фреймворки для автоматизації щоб казати що і де є зручнішими для е2е та інтеграційних тестів, та що є

більш потужний

;)

Звісно може. Це ще і на вебдрайвері років 6 тому можна було робити.

playwright.dev/docs/api/class-electron

Коментар порушує правила спільноти і видалений модераторами.

playwright можна писати на python разом з pytest не обмежуючись ні у чому, що курив коли писав за старий cypress?

Плейврайт зараз один з найсучасніших інструментів для автоматизації тестування. Мабуть тому ;)

1) проведення такого виду тестування було вимогою клієнта, а тому і розробники і тестувальники мають розуміння і досвід повʼязаний із аксесібіліті;
2) звісно що такий варіант не покриває усі необхідні аспекти, але суттєво зменшує кількість мануальних перевірок;
3) на момент впровадження цього тестування ми уже використовували playwright.

В ідеальному світі тако воно, мабуть, і є, якщо компанія має бюджет на експерта з тестування доступності на повну ставку, який буде цим постійно займатися, консультувати команди. Інший підхід — найняти підрядника, який проведе аксесибіліті аудит і навчить розробників, тестувальників і дизайнерів як все виправити та розробляти продукти із «accessibility in mind». Проте світ не ідеальний, бюджети не безмежні, тому аудити проводяться силами самих команд, які навчаються на власному досвіді, матеріалах з воркшопів та курсів, впроваджують автоматизоване тестування на основі наявних інструментів аби підвищити якість продукту та скоротити час на мануальні перевірки.

Підписатись на коментарі