Вступление
Вы вообще в курсе, что такое CSS и XPath селекторы? Ну конечно в курсе — раз уж кликнули на эту статью, наверняка пережили хотя бы один из тех унылых споров в духе «а что лучше: CSS или XPath?» Спойлер: ни то, ни другое. Все эти разговоры — просто шум, рожденный из некомпетентности. Вот эти бесконечные обсуждения — «а XPath может по тексту», «а CSS быстрее», «а вот тут индекс нужен»... Да какая, к чёрту, разница, когда можно просто использовать тестовые идентификаторы?
Серьёзно. Если у элемента есть тестовый идентификатор — всё, точка. Найти его можно быстро, стабильно и без плясок с бубном вокруг селекторов. А выбор между CSS и XPath становится вообще неважным. Это уже вкусовщина, типа «а ты как макароны солишь — до или после закипания воды?». Умным людям не до таких споров.
Возникает логичный вопрос: если всё так круто с тестовыми идентификаторами, почему о них не кричат с каждого угла? Почему в 2025-м году тесты всё ещё ломаются от смены цвета кнопки? Ответ простой: либо люди не знают, либо не умеют. Иногда и то, и другое. В этой статье мы как раз это исправим. Покажем, как правильно и без боли внедрять тестовые идентификаторы в фронтенд‑приложении. Без теории ради теории — только практика и здравый смысл.
И давайте скажем это вслух: если ваши автотесты сыпятся из‑за смены классов, цвета, или позиции элемента — виноваты не разработчики, не продукт, не космос, не ретроградный Меркурий. Виноваты вы. Да‑да, вы, QA Automation инженер, который решил «ну тут же можно взять по div:nth-child(42)
, норм же работает». Нет, не норм. Вы создали флак, вы допустили нестабильность, вы наплодили технический долг в автотестах. Хотите стабильности? Учитесь писать тесты правильно. Хотите гордиться своими автотестами, а не прятать их отчеты под стол? Добро пожаловать — сейчас научим.
Эта статья — для всех: от автоматизаторов, уставших от флаков, до фронтендеров, которые хотят помочь команде и не видеть, как в pull request прилетает «починил тест» вместо нормального фидбека.
Что круче — CSS или XPath?
Поехали без прелюдий. Вот эти бесконечные дискуссии «а что круче — CSS или XPath?» — это как спорить, чем лучше копать яму: ложкой или вилкой. Забавно? Вот и мне смешно. Потому что оба варианта — костыли, если вы изначально подходите к задаче неправильно.
Ни CSS, ни XPath не являются оптимальными инструментами для UI‑автотестов. Вообще. Забудьте. Правильный, взрослый, стабильный подход — это кастомные атрибуты типа data-test-id
. И когда вы их используете, становится абсолютно по барабану, пишете вы CSS или XPath — элемент будет найден быстро, чётко, без танцев и отрыжек селекторной магии. Тут уже дело не в типе селектора, а в том, на что вы этот селектор навешиваете.
Если вы всё ещё цепляетесь за текст, классы, nth-child
, динамически сгенерированные ID, то, простите, не нужно потом жаловаться, что у вас «тесты флакают», «после рефакторинга всё упало» или «а что случилось, я ничего не менял». Сюрприз: вы сами себе враг. Вы выбрали путь страдания.
Возьмите мобильную автоматизацию, например. Там никто даже не думает писать тесты без тестовых идентификаторов. Просто потому, что нельзя. А в вебе исторически можно схалтурить: закинуть на скорую руку CSS или XPath локаторы и вроде как работает. Ключевое слово — «вроде». Работает — пока не перестаёт. А потом боль, костыли, и попытка понять, почему из‑за смены названия класса у вас теперь валится весь регресс.
И чем дальше, тем хуже. Проект обрастает десятками «временно рабочих» CSS/XPath локаторов, и в какой‑то момент вы уже не можете их нормально отрефакторить. Потому что, внимание, тесты становятся заложниками качества и стабильности селекторов. В итоге даже самый простой тест превращается в квест с подземельями, багами и слезами автоматизатора.
CSS и XPath: как НЕ надо
Я не буду тратить ваше и своё время на бессмысленное «сравнение плюсов и минусов» CSS и XPath. Это всё разговоры ради разговоров. Хотите правду? Они оба — не то, чем вы должны пользоваться. Вместо цирка про «что быстрее», давайте честно пройдёмся по главным болячкам этих подходов. Спойлер: всё плохо.
1. Завязка на текст: «А где кнопка?»
//button[text()='Отправить']
О, классика! Работает… до первой правки текста. А потом:
-
дизайнер захотел вместо «Отправить» — «Отослать»
-
UX‑специалист вспомнил про «Повышение конверсии»
-
продакт‑менеджер включил английский язык
-
а вы — включили панику
Итог: тесты отвалились. Причём интерфейс-то не поменялся. Только слова. А вы всё ещё ищете кнопку по её бывшему имени, как бывшую — по старому нику в Telegram.
2. Глубокая вложенность: «Интерфейс немного изменился — тесты умерли»
div.container > div.section > div.actions > button.primary
Красота? Нет. Хрупкий кусок боли. Вставили лишний div
? Удалили лишний блок? Изменили порядок? Поздравляю, вы снова чините автотест, который не обязан был сломаться.
3. Классы и стили: «Они же стабильны… ага, конечно»
button.primary.large
Никогда не забывайте: классы не для тестов, они для внешнего вида. Они меняются. Часто. Не потому что кнопка исчезла, а потому что large
больше никому не нравится. А вы снова страдаете. Потому что решили строить автотесты на песке.
4. Случайные ID: «Каждый билд как русская рулетка»
#react-17gh2j93-btn
Сначала кажется: «О, id
— уникально, надёжно!» Ага. Только вот React, Vue и другие милые ребята генерируют их каждый раз заново. Сюрприз: селектор работает один раз. Потом — идите курить логи.
5. Читаемость: «Что это вообще такое?»
div:nth-child(3) > button.btn-lg.primary
Даже если этот локатор работает — понять, что он делает, невозможно.
А теперь посмотрите на это:
[data-test-id="create-course-button"]
Видите разницу? Один селектор — как шифр из «Игры в кальмара». Другой — понятен даже вашему проджекту.
Что такое тестовые идентификаторы?
Всё просто: тестовые идентификаторы — это специальные атрибуты в HTML, которые нужны не для фронтенда, не для верстки, а исключительно для автотестов.
Они не влияют на внешний вид, поведение или UX. Их задача — быть якорем. Стабильным, читаемым и предсказуемым.
<button data-test-id="submit-button">Отправить</button>
Как их называть? Да как хотите:
-
data-test-id
-
data-testid
-
qa-id
-
test-id
-
data-qa
-
хоть
data-holy-button
Главное — чтобы было:
-
однозначно (один идентификатор — один элемент)
-
стабильно (не зависит от текста, стилей, порядков)
-
понятно (без
m7fg9btn99
иabc123
)
Некоторые фреймворки даже подсказывают «дефолтные» названия. Например, Playwright из коробки умеет работать с data-testid
. Но и это не догма — в конце концов, вы не роботы. Вы инженеры. Назовите как угодно — главное, начните использовать.
Почему все про них знают, но почти никто не использует?
Потому что, чёрт возьми, это не халява. Нужно:
-
понять, как работает фронт
-
договориться с командой
-
договориться с самимсобой, что «быстро и криво» — это путь в ад
А ещё потому что проще натыкать 20 XPath'ов и свалить с работы пораньше. Только потом те же 20 XPath'ов разнесёт первым же редизайном. А виноват кто? Правильно — вы.
Это как раз и отличает настоящего QA Automation‑инженера от человека, который «просто пишет тесты» — желание строить фундамент, а не временные палатки.
Зачем это вообще?
Вставлять data-test-id
в интерфейс — это не «ещё одна фича», это фундамент надёжной автоматизации UI‑тестов. Если вам нужны стабильные, предсказуемые и легко поддерживаемые тесты — это не рекомендация, это must-have.
Вот зачем это работает и почему без него — боль:
1. Стабильность и предсказуемость
CSS/XPath-подходы — как игра в рулетку: чуть поменяли DOM или порядок элементов — всё, локатор умер. А data-test-id
живёт, пока вы явно не убрали элемент. Падает только по делу, а не из-за новой обёртки в div
.
2. Не зависят от внешнего вида
Классы меняются — стили меняются — верстальщик захотел «посвежее» — CSS/XPath‑селекторы снова мертвы. Атрибут data-test-id
изолирован от всего этого. Он не для дизайна. Он для тестов. Его не трогают «просто так».
3. Устойчивость к рефакторингу
Если фронтендер переносит кнопку в другой компонент, но оставляет data-test-id
— тест даже не заметит изменений. Без него — «404 Not Found», даже если кнопка визуально на месте.
4. Улучшает коммуникацию
Когда разработчиквидит в DOM data-test-id
, он сразу понимает: «Окей, это тестовый якорь, трогаю аккуратно или обсуждаю с QA». Это культура. Это осознанность. Это уменьшает количество внезапных «почему всё упало?!».
5. Чистота и читаемость
Что выглядит понятнее?
div:nth-child(4) > .card .actions > button.btn.primary
против:
[data-test-id="delete-course-button"]
Первые — магия и боль. Второй — понятный, читаемый якорь, даже без документации. Такие локаторы приятно писать и удобно поддерживать.
6. Экономия времени на дистанции
Да, на старте CSS/XPath кажутся быстрее. Но дальше — начинается ад: правка верстки → 15 упавших тестов → день на фикс.
С data-test-id
:
-
тесты не разваливаются
-
не приходится «охотиться» за элементами заново
-
не нужна реанимация локаторов после каждого спринта
Это инвестиция, которая окупается с первых недель и сохраняет нервы всей команде.
Безопасно ли это?
Ах да, классический довод: «Не‑не‑не, не трогай DOM! Это сломает рендеринг, просадит перформанс и ухудшит UX!»
Перевод: »Я просто не хочу, чтобы QA лез в мой священный фронтенд.»
Ну давайте разложим по фактам. Потому что истина тут простая, как <div>
без class
.
1. «Сломает стили»
Как?! Это обычный data-
атрибут. Он не влияет ни на CSS, ни на JS, ни на цвета кнопки в Вальгалле. Если у вас стили завязаны на data-test-id
, то… ну тут даже тесты не помогут.
2. «Просадит перформанс»
Серьёзно? Атрибут весом в 15 байт замедлит ваше React-приложение? Вы там случайно не через useEffect рисуете галактику? Измеряем в наносекундах, а обсуждаем как будто у вас сервер под нагрузкой рухнет от одного data-test-id
.
3. «Ухудшит UX»
А, ну конечно, пользователь такой открывает страницу… и испытывает острый приступ UX-дискомфорта от атрибута, который он даже не видит. Зато мы все знаем: если у UX что-то не так — это точно data-test-id
виноват, а не логика или контент.
4. «Это видно поисковикам! SEO пострадает!»
Во-первых, не пострадает. Во-вторых, если очень хочется — можно даже сделать умный шаблон, где data-test-id
→ aria-label, и будет только лучше. Так что это уже не про SEO, а про отмазки.
5. А что на самом деле?
-
Не влияет на стили
-
Не влияет на производительность
-
Не отображается пользователям
-
Не мешает разработке
-
Не ломает компоненты
-
Не вызывает глобальное потепление
Это спокойный, неагрессивный, предельно предсказуемый способ дать автотестам якорь. И если ваш фреймворк умеет в HTML — значит он точно умеет в data-test-id
.
React, Vue, Angular, SolidJS, Svelte, Web Components, даже чистый HTML с ванильным JS —
везде работает. Везде безопасно.
Если разработчик против data-test-id
— скорее всего, он просто не хочет, чтобы вы могли зацепиться за его DOM. Но это уже не про производительность. Это про территорию. И да, она теперь и ваша тоже.
Как расставить тестовые идентификаторы?
Окей, пора перейти от слов к делу. Как именно ставить эти ваши data-test-id
, если вы, скажем, не фронтенд-разработчик, а просто честный автоматизатор с хорошими намерениями?
На первый взгляд может показаться, что это что-то из разряда «требуется знание React на уровне Senior++», но на деле — это базовый скилл, как открыть DevTools или запустить тест.
Правда жизни
Каждый день тысячи QA Automation инженеров по всему миру просто открывают HTML, вставляют data-test-id="что-то-понятное"
— и всё работает. Даже в мобильной разработке (да-да, с этими вашими Flutter, Jetpack Compose и SwiftUI), где всё как будто из магии и анимаций, идентификаторы всё равно ставят. Это уже не «хак», это индустриальный стандарт.
Шаг 1. Установка Node.js
Да, давайте начнём с азов. Если вы собираетесь трогать хоть один байт фронтенд-кода — вам нужен Node.js. Почему?
Потому что:
-
Вы разрабатываете фронтенд? → Нужен Node.js
-
Вы собираете фронтенд? → Нужен Node.js
-
Вы просто хотите поставить себе линтер? → Сюрприз: Node.js
-
Вы хотите посмотреть что-то в UI? → Тоже, мать его, Node.js
Node.js — это платформа, которая позволяет запускать JavaScript вне браузера. И всё, что связано с современными фронтендами (React, Vue, Angular, Svelte, SolidJS, Astro, you name it) — работает через него.
Как установить Node.js?
Не буду пересказывать документацию (она шикарна, кстати). Просто заходите сюда: https://nodejs.org/en/download. Выбираете свою ОС, кликаете мышкой — и всё. Node.js сам всё поставит, и бонусом даст вам npm — пакетный менеджер.
Шаг 2. Установка проекта
Ну что, пора, наконец, залезть в проект. Потому что прежде чем что-то в нём менять — будь то стили, компоненты или наши драгоценные data-test-id
— надо его хотя бы скачать. Да-да, просто «что‑то поменять на проде» не получится. Сначала к себе на комп, пожалуйста.
Клонируем проект
Для примера будем использовать учебный проект: https://github.com/Nikita-Filonov/qa-automation-engineer-ui-course. Клонируем его как нормальные люди:
git clone https://github.com/Nikita-Filonov/qa-automation-engineer-ui-course.git
cd qa-automation-engineer-ui-course
Установка зависимостей
Перед тем как нажимать на кнопочки и запускать фронт, надо сначала оживить проект. Потому что пока это просто папка с непонятным node_modules
в надежде, что они появятся по магии. Не появятся. Устанавливаем зависимости.
Используем правильный пакетный менеджер. Тут главное не тупить. У проекта может быть yarn или npm. Как определить?
-
Если там package-lock.json → значит, используем npm
Примеры:
Для yarn:
yarn install
Для npm:
npm install
И не надо мешать npm и yarn в одном проекте. Это не коктейль. Это грех.
Ждём немного... Да, установка может занять минутку. Иногда — три. Иногда — вечность, если у вас Wi-Fi в стиле «ловлю соседа на лестничной клетке». Но это нормально. Главное — после установки у вас появится волшебная папка node_modules
, и проект оживёт.
Запускаем проект
И вот тут начинается «веселье». Возможно, вы ожидали какую‑то одну универсальную команду вроде npm start, и она сработает. Иногда. Если звёзды сойдутся. Но чаще всего:
-
В проекте используется нестандартная структура
-
У команды совсем другое имя (например,
app:start
,dev
,run
,frontend:start
) -
Включён TypeScript, монорепа или ещё какое-нибудь веселье
Что делать? Открываем package.json. Это священный грааль для любого Node.js-проекта. В нём есть блок scripts
— и именно там указаны все команды, которые можно запускать. Вот как он может выглядеть:
"scripts": {
"start": "react-scripts start",
"app:start": "vite --host",
"build": "vite build"
}
Если не знаете, что запускать — не страдайте, просто спросите у фронтенд-разработчиков. Серьёзно, они не кусаются (если спросить вежливо). Это лучше, чем устраивать шаманские танцы вокруг терминала.
Для нашего проекта запускаем команду:
yarn start
После этого у вас локально поднимется фронтенд-приложение по адресу: http://localhost:3000. Вот теперь у вас есть живая страница, которую можно щупать, трогать, ломать, ломать ещё раз — и, конечно же, ставить на неё data-test-id
как боженька тестирования.
Шаг 3. Учимся расставлять тестовые идентификаторы
Что такое JSX?
JSX (JavaScript XML) — это такая хитрая смесь HTML и JavaScript, которую придумали для React. Выглядит как HTML, но внутри работает как настоящий JavaScript. Прямо как бургер с сюрпризом: сверху булочка — вроде знакомо, а внутри... JavaScript.
Пример:
<button>Нажми меня</button>
На вид — обычная кнопка. Но это уже не HTML, а JSX. Его фишка в том, что вы можете писать такие элементы прямо внутри JavaScript-файлов, и React будет знать, что с ними делать.
Отличия JSX от HTML
JSX похож на HTML, но не вздумайте писать как в старой школе, есть нюансы:
-
class
нельзя. Вместоclass="btn"
пишемclassName="btn"
, потому чтоclass
— это уже ключевое слово в JavaScript, и он вас просто пошлёт компиляться с ошибкой. -
Обработчики событий = camelCase. Вместо
onclick="..."
пишемonClick={...}
. БольшаяC
— это не ошибка, это стиль JSX. Привыкайте. -
Один родитель сверху. JSX не может вернуть два соседних тега. Всё должно быть в одном корневом контейнере:
Так нельзя:
<h1>Привет</h1> <p>Мир</p>
А так можно:
<div> <h1>Привет</h1> <p>Мир</p> </div>
-
Все теги должны быть закрыты. Даже
<input>
! Забудьте про HTML-расслабон.<input /> // правильно <input> // ошибка
Что такое компонент?
Компонент — это просто кусок интерфейса. Кнопка, форма, модалка, карточка товара — всё это компоненты. Каждый компонент — мини-приложение внутри большого приложения. Он самодостаточный, переиспользуемый и легко тестируемый.
Пример функционального компонента:
export const LoginButton = () => {
return <button data-test-id="login-page-submit-button">Войти</button>;
};
Вот и всё. Это уже компонент. Можно вставлять его в любом месте вот так:
<LoginButton />
React сам вставит кнопку с нужным data-test-id
, и ваш автотест её найдет за миллисекунду.
Компоненты бывают:
-
Функциональные (используются в 95% случаев) — как выше.
-
Классовые — это старьё. Если вы их видите — бегите или зовите на помощь. Скорее всего, вам они не понадобятся.
Где указывать data-test-id?
Просто: на том JSX-элементе, к которому должен обратиться автотест. Вот и вся философия. Пример:
<input
type="text"
placeholder="Введите email"
data-test-id="subscribe-form-email-input"
/>
Никакой магии. Теперь автотест найдёт этот input
и будет знать, что именно он — поле для email.
Как найти нужный элемент в коде?
Вы открыли фронт и видите: «Ага, вот тут есть кнопка 'Отправить', а вот заголовок 'Welcome to UI Course application!'». Но возникает логичный вопрос: «А где это в коде, мать его?». Именно туда и надо добавить data-test-id
, но сначала это нужно найти.
Ниже — три проверенных способа, которые реально работают и экономят кучу времени.
Способ 1: Поиск по видимому тексту
Прямолинейно, быстро, эффективно. Берете текст с экрана, прокидываете его в поиск по проекту и — бам! — находите нужный компонент.
Вы видите в браузере:
<h5>Welcome to UI Course application!</h5>
Берете "Welcome to UI Course application!"
, вбиваете в глобальный поиск (например, в VSCode), и если текст захардкожен, вы сразу окажетесь в JSX-файле, где он написан.
Плюсы:
-
Быстро.
-
Не требует танцев с бубном.
-
Идеально работает в простых проектах.
Минусы:
-
Не сработает, если используется локализация (i18n) — там текстов в JSX просто нет.
Способ 2: Поиск по ключу локализации (если проект на i18n)
Если проект многоязычный, то видимого текста в коде вы не найдёте. Вместо этого будет что-то вроде:
<h5>{t("ui_course_welcome_page_title")}</h5>
Такой код берёт текст из отдельного файла с переводами. Ваша задача:
-
Посмотрите текст в браузере.
-
Найдите его в одном из
.json
или.ts
файлов локализации. -
Посмотрите, какой у него ключ (
ui_course_welcome_page_title
). -
По этому ключу ищите в проекте, чтобы понять, где он используется в коде.
Плюсы:
-
Работает в 100% i18n проектов.
-
Даёт точный ответ — в каком компоненте вы находитесь.
Минусы:
-
Надо понимать, как устроены файлы локализации.
-
Ключи могут быть переиспользуемыми или составными, так что надо смотреть внимательно.
Способ 3: Просто спросить у фронтендера
Да, это самый очевидный, но почему-то забываемый способ. Подходите (или пишите в чат) и говорите:
“Бро, где живёт кнопка 'Отправить'? Мне туда
data-test-id
воткнуть надо.”
И вуаля — разработчик:
-
Говорит, как называется компонент (
WelcomePage
,LoginForm
,SubmitButton
, что угодно); -
Присылает путь к файлу;
-
Может даже сам навесить
data-test-id
по доброте душевной.
Почему это работает:
-
Разработчик знает структуру лучше всех.
-
Экономит вам часы бестолкового блуждания.
-
Заодно улучшите коммуникацию внутри команды.
Лайфхак: Если вы QA в команде — договоритесь с фронтендерами, чтобы сразу ставили data-test-id
при создании компонентов. Всё равно потом ставить придётся — лучше сразу.
Как правильно расставлять data-test-id: практические кейсы
Окей, вы нашли нужный элемент в коде. Теперь следующий шаг — пометить его для автотестов. Но делать это нужно не как попало, а с умом. Ниже — три ситуации из реальной практики и чёткий план, как работать с каждой из них.
Случай 1: Простой компонент — просто добавляем data-test-id
Если вы контролируете компонент и элемент доступен напрямую — добавляете идентификатор прямо в JSX. Никаких сложностей.
Пример:
export const WelcomeView = () => {
return (
<div>
<h5 data-test-id={'welcome-title'}>Welcome to UI Course application!</h5>
</div>
);
};
Что происходит:
-
У вас есть заголовок с текстом.
-
Вы добавляете
data-test-id="welcome-title"
— и теперь автотест легко найдёт этот элемент. -
Название делаем по шаблону:
{контекст}-{тип_элемента}
. Например:welcome-title
.
Когда так делать:
-
Элемент находится в вашем коде, а не во вложенном компоненте.
-
Компонент простой, без магии.
-
Вы контролируете разметку.
Случай 2: Вложенные компоненты — нужно идти внутрь
Когда нужный элемент внутри другого компонента — просто так data-test-id
не добавить. Нужно открыть вложенный компонент и разметить его там.
Пример:
export const FormView = () => {
return (
<div>
<label>Email</label>
<input data-test-id={'form-email-input'} />
<button data-test-id={'form-login-button'}>Login</button>
</div>
);
};
export const WelcomeView = () => {
return (
<div>
<h5 data-test-id={'welcome-title'}>Welcome to UI Course application!</h5>
<FormView />
</div>
);
};
Что происходит:
-
В
WelcomeView
есть компонентFormView
. -
Но
input
иbutton
живут внутриFormView
. -
Мы не размечаем их снаружи, а добавляем
data-test-id
внутри самогоFormView
.
Когда так делать:
-
Элемент спрятан во вложенном компоненте.
-
Вы можете открыть компонент и изменить его.
-
Важно сохранить локальный контекст — каждый компонент отвечает за свои
data-test-id
.
Случай 3: Переиспользуемые компоненты — передаём testId через props
Если у вас компонент, который юзается в разных местах — хардкодить data-test-id
в нём нельзя. Он потеряет уникальность. Нужно передавать часть ID снаружи, как параметр.
Пример:
type ListItemProps = {
title: string;
description: string;
testId?: string;
};
export const ListItem: FC<ListItemProps> = ({ title, description, testId }) => {
return (
<div data-test-id={`${testId}-list-item-container`}>
<h6 data-test-id={`${testId}-list-item-title`}>{title}</h6>
<p data-test-id={`${testId}-list-item-description`}>{description}</p>
</div>
);
};
export const WelcomeView = () => {
return (
<div>
<h5 data-test-id={'welcome-title'}>Welcome to UI Course application!</h5>
<ListItem testId={'playwright'} title={'Playwright'} description={'For Python'} />
<ListItem testId={'pydantic'} title={'Pydantic'} description={'For Python'} />
<ListItem testId={'httpx'} title={'HTTPX'} description={'For Python'} />
</div>
);
};
Что происходит:
-
Компонент
ListItem
используется несколько раз. -
В каждом вызове мы передаём уникальный
testId
. -
Внутри компонента формируются уникальные
data-test-id
— вродеplaywright-list-item-title
.
Что такое props
:
-
Это просто входные параметры компонента.
-
Через них мы можем гибко конфигурировать поведение и разметку.
-
В нашем случае — передаём
testId
.
Когда так делать:
-
Компонент универсальный и используется в разных контекстах.
-
Нужно сохранить уникальность
data-test-id
. -
У компонента нет контекста сам по себе, его дают извне.
Пример с индексами
Если рендерится список, и у вас нет уникального ID — можно использовать индекс:
{items.map((item, index) => (
<ListItem
key={item.id}
testId={`course-list-${index}`}
title={item.title}
description={item.description}
/>
))}
Но! Если есть item.id
— лучше использовать его, а не индекс: testId={course-${item.id}}
Почему?
-
Индекс может меняться при изменении списка.
-
ID — стабильнее, надёжнее, точнее.
Ставим свой первый тестовый идентификатор
Окей, мы разобрались с базой. Теперь пора поставить свой первый data-test-id
. В тестовом приложении, которое мы устанавливали ранее, есть специальная страница:
http://localhost:3000/#/welcome — и на ней пока не расставлены data-test-id
.
Давайте попробуем сделать это пошагово.
Шаг 1: Найти нужный элемент
На странице /welcome
вы увидите крупный заголовок: Welcome to UI Course application!

Он расположен в самом верху, внутри компонента с белым фоном (обёрнут в Paper) и визуально является главным заголовком страницы.
Шаг 2: Найти компонент в коде
Этот заголовок находится в компоненте WelcomeView
, который расположен по пути: /src/Views/Welcome/WelcomeView.tsx. Фрагмент кода до изменений:
import { BasePaper } from '../../Components/Views/BasePaper';
import { Grid2, List, Typography } from '@mui/material';
import { WelcomeTopicListItem } from '../../Components/ListItems/Welcome/WelcomeTopicListItem';
export const WelcomeView = () => {
return (
<BasePaper sx={{ mt: 3 }}>
<Grid2 container spacing={2}>
<Grid2 sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }} size={{ md: 5, xs: 12 }}>
<Typography variant={'h3'}>
Welcome to UI Course application!
</Typography>
</Grid2>
<Grid2 size={{ md: 7, xs: 12 }}>
<Typography variant="h6">What you'll learn:</Typography>
<List dense>
<WelcomeTopicListItem title={'Playwright'} />
<WelcomeTopicListItem title={'PageObject, PageComponent, PageFactory'} />
<WelcomeTopicListItem title={'UI Coverage Tool'} />
<WelcomeTopicListItem title={'CI/CD'} />
<WelcomeTopicListItem title={'Strategy of data-test-id'} />
<WelcomeTopicListItem title={'Allure'} />
<WelcomeTopicListItem title={'And more!'} />
</List>
</Grid2>
</Grid2>
</BasePaper>
);
};
Открываем этот файл в редакторе и видим знакомую структуру JSX.
Шаг 3: Добавить data-test-id
Теперь добавим data-test-id
к элементу <Typography>
, который содержит заголовок.
Было:
<Typography variant={'h3'}>
Welcome to UI Course application!
</Typography>
Стало:
<Typography variant={'h3'} data-test-id={'welcome-view-main-title'}>
Welcome to UI Course application!
</Typography>
Почему welcome-view-main-title?
Разберём идентификатор по частям:
-
welcome-view
— контекст, в котором находится элемент. -
main-title
— тип и смысл элемента: это основной заголовок страницы.
Такой шаблон делает data-test-id
:
-
читаемым,
-
понятным,
-
уникальным в пределах проекта.
Это лучше, чем просто main-title
или title
, так как изолирует идентификатор в контексте WelcomeView
.
Шаг 4: Проверить в DOM
Сохраняем изменения, обновляем страницу в браузере и открываем инструменты разработчика (DevTools). Ищем элемент с data-test-id="welcome-view-main-title"
:

Если вы его видите — значит всё сделано правильно.
Вывод
Как видите, поставить data-test-id
— это просто. В большинстве случаев вам нужно:
-
Найти нужный элемент в интерфейсе.
-
Найти соответствующий компонент в коде.
-
Добавить
data-test-id
, опираясь на контекст и роль элемента.
Со временем это станет автоматическим навыком, который занимает буквально пару секунд.
Как обстоят дела на самом деле в реальных проектах?
Реальность: data-test-id? Ха! Его почти никогда нет
Добро пожаловать в настоящий мир. В большинстве реальных проектов — особенно если вы подключаетесь не с первого дня — тестовые идентификаторы отсутствуют напрочь. Вообще. Ни одного.
Почему? Потому что (внимание, шок-контент) продукт делают для пользователей, а не для ваших автотестов. Вот так поворот.
Так что если вы видите чистый, идентификаторно-девственный DOM — это не баг, это фича.
Что с этим делать? Брать ситуацию в свои руки
Да, вам, скорее всего, придётся инициировать обсуждение. Да, возможно, даже сделать презентацию. Но кто, если не вы?
Вот что реально работает:
-
Проведите 15-минутный митинг. Без скучного буллшита, только боль и правда.
-
Покажите разваливающиеся локаторы на CSS или XPath. Чем больше — тем больнее.
-
Объясните,сколько часов жизни уходит на «починил → снова сломалось».
-
Скажите магическую фразу: «Мы будем чинить старьё вместо того, чтобы покрывать новое. Это не автотесты, это технический долг с таймером.»
Уверяю, после этих слов хотя бы один человек в зале начнёт молча гуглить data-test-id best practices
.
Как внедрять data-test-id и не развалить проект?
Спокойно. Никто не говорит "стопаем разработку и начинаем добавлять ID-шники". Всё можно делать аккуратно и параллельно:
-
Пока нет автотестов — вы всё равно делаете ручное тестирование.
-
Потратьте 1–2 часа в день на расстановку ID в ключевых местах.
-
Когда основные зоны покрыты — начинайте писать тесты.
-
Самые жирные точки входа — формы, списки, кнопки. Туда — в первую очередь.
Подключайте фронтенд-разработчиков (сюрприз: они не против)
Серьёзно, для фронтенда поставить data-test-id
— это даже не задача. Это полсекунды внимания.
-
Не требует архитектуры.
-
Не трогает бизнес-логику.
-
Помогает сразу понять, какие элементы проверяются в тестах.
-
Идеально живёт рядом с рефакторингом.
Хотите, чтобы они помогали? Дайте им гайд на одну страницу. Или бросьте ссылку в Confluence. Всё.
Важно понимать
data-test-id
— не панацея. Тесты всё равно будут падать: баги, переделки, жизнь. Но! Вы минимизируете 95% случайных падений, которые происходят потому что:
-
поменяли текст,
-
стиль ушёл на три пикселя влево,
-
элемент телепортировался в другой угол DOM.
Это не спасёт от апокалипсиса, но спасёт от тысячи мелких проблем.
Вывод
Хотите стабильные автотесты — возьмите data-test-id
под контроль. Не ждите, пока кто-то вспомнит о тестах в пятницу вечером перед релизом. Сделайте это сами, сделайте красиво.
Общий принцип именования data-test-id
Вы, конечно, можете назвать кнопку btn123
или elementX
. А можете — как человек, который уважает своих будущих коллег (и себя через неделю).
Базовый шаблон:
{context}-{element-type}-{index или id (если нужно)}
Компонент |
Пример |
---|---|
|
login-page, course-view, navbar |
|
title, button, input, link |
|
0, 1, user-42, item-3 |
Главное правило: уникальность в рамках страницы. Не устраивайте "битву кнопок" с одинаковыми ID.
Шаблоны и примеры
Тип элемента |
Шаблон |
Примеры |
---|---|---|
Заголовки ( |
|
login-page-title, dashboard-title |
Параграфы ( |
|
user-bio-text, offer-description-text |
Кнопки |
|
login-submit-button, cart-clear-button |
Ссылки |
|
navbar-home-link, support-link |
Поля ввода |
|
login-email-input, search-input |
Textarea |
|
feedback-textarea, course-desc-textarea |
Чекбоксы |
|
terms-accept-checkbox, filter-active-checkbox |
Радио-кнопки |
|
gender-male-radio, delivery-standard-radio |
Выпадающие списки |
|
country-select, lang-select |
Элементы списка |
|
faq-list-item-0, topic-list-item-3 |
Табличные строки |
|
user-table-row-123, product-table-row-5 |
Карточки / контейнеры |
|
lesson-card-42, checkout-container |
Изображения |
|
profile-image, banner-image |
Рекомендации
-
Используйте kebab-case (через дефис), а не
camelCase
или, прости господи,PascalCase
. -
Делайте ID осмысленным.
login-page-submit-button
гораздо понятнее, чемbtn5
. -
Не пишите ерунду вроде
button-button
илиinput-input-field
— вы не робот. -
Для повторяющихся элементов всегда указывайте индекс или ID — это спасёт от ада при кликах по спискам.
Запомните мантру: Контекст + Суть + Уникальность = Надёжный data-test-id
. Потом скажете себе спасибо. Или хотя бы не будете себя проклинать на ретро.
Пример: как Playwright дружит с data-test-id
Если вы используете Playwright — поздравляю, вы в хорошей компании. И вот приятная новость: он по умолчанию заточен под работу с data-testid
. Но если у вас в проекте решили изобрести своё — типа data-qa-id
, data-pw
, qa-id
или test-id
— не проблема.
Вот как сделать всё красиво:
from playwright.sync_api import sync_playwright
with sync_playwright() as playwright:
browser = playwright.chromium.launch()
page = browser.new_page()
# Говорим Playwright: "мы не такие, у нас свой data-атрибут"
playwright.selectors.set_test_id_attribute("data-qa-id")
page.goto("https://example.com")
# Теперь можно писать чистые и надёжные локаторы
login_button = page.get_by_test_id("login-button")
login_button.click()
Магия в том, что
get_by_test_id(...)
теперь будет искать поdata-qa-id="..."
, а неdata-testid
.
И да, это работает с любыми кастомными вариантами:
-
data-test-id
-
data-qa
-
data-id
-
qa-id
-
data-pw
Главное — задать атрибут один раз, и пользоваться как человек.
Зачем вообще это нужно?
Чтобы не городить XPath на полэкрана или кликать по «второму div
, где третий span
, у которого сосед справа с классом .active
». Вместо этого:
page.get_by_test_id("checkout-submit-button")
Просто. Прозрачно. Поддерживаемо. Прямо как вы хотели, когда только мечтали писать автотесты.
Заключение
Если вы всё ещё думаете, что data-test-id
— это какая‑то «штука для автоматизаторов», давайте скажу прямо: это часть инженерной культуры. Такой же как читаемый код, линтер, или адекватные названия переменных.
Писать автотесты без data-test-id
— это как строить небоскрёб без лифта: можно, но по лестнице долго и устаёшь.
И помните:
-
data-test-id
— это не костыль, а инструмент предсказуемости. -
Это не «сделка с совестью», а договор между тестами и интерфейсом.
-
Это не про «удобно тестерам», а про меньше багов в проде, меньше хаоса в команде.
CSS и XPath — это прошлое. А data-test-id
— это то, что делает автотесты надёжными, предсказуемыми и стабильными. Хотите вы этого или нет — он победит.
Автор: sound_right