Вступление
Вы вообще в курсе, что такое 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
— это то, что делает автотесты надёжными, предсказуемыми и стабильными. Хотите вы этого или нет — он победит.