Вступление
Вы вообще в курсе, что такое 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
— это то, что делает автотесты надёжными, предсказуемыми и стабильными. Хотите вы этого или нет — он победит.
Комментарии (25)
nin-jin
27.05.2025 07:54Ок, HabrGPT, держи ответ на этот пассаж:
Ни CSS, ни XPath не являются оптимальными инструментами для UI-автотестов. Вообще. Забудьте. Правильный, взрослый, стабильный подход — это кастомные атрибуты типа
data-test-id
Детский сад какой-то. Современные интерфейсы строятся из компонент. Отличительной особенностью компонент является возможность использования их в разных местах. Поэтому в них принципиально не возможно захардкодить эти ваши глобально уникальные data-test-id - их надо генерировать на основе иерархии владения. Как выглядят генерируемые глобально уникальные человекопонятные идентификаторы можно глянуть в любом приложении на $mol, где ни программистам, ни тестировщикам вообще не нужно об этом думать. Например:
$hyoo_mol.Root(0).Apps().Filter().Clear()
- кнопка очистки поля фильтрации в галерее приложений.
VanKrock
27.05.2025 07:54Возникают вопросы:
Как быть если вы используете UI kit где нет test-id?
Почему используется кастомный атрибут, а например не name?sound_right Автор
27.05.2025 07:54В статье как раз используется Material UI — один из самых популярных, удобных, функциональных и классных UI Kit'ов. Это доказывает, что использование
data-test-id
никак не ограничено выбором библиотеки компонентов.Даже если компоненты приходят из UI Kit'а, вы можете:
прокинуть пропсы с
data-test-id
черезslotProps
,InputProps
,ButtonProps
,componentsProps
, короче есть разные варианты, почти все нормальные библиотеки это поддерживают;создать враппер-компонент с нужными атрибутами;
или просто установить
data-test-id
на обёртывающий элемент, не нарушая логику библиотеки.
Почему используется кастомный атрибут, а например не name?
Потому что
data-test-id
:не мешает CSS/JS;
не ломает семантику;
не конфликтует с другими системами;
и явно сигнализирует, что атрибут используется только для автотестов — это отличная практика.
Можно конечно и
name
,id
, что угодно. Главный посыл стати в том, что используйте тестовые идентификаторы, а как вы уже будете это реализовывать — дело ваше.
aamonster
27.05.2025 07:54Очень много текста, а всё, по большому счёту, сводится к тому, что
Через специально оставленные API тестировать удобнее (кто бы сомневался)
Даём тестировщикам доступ к репе, пусть сами себе их делают (сомнительно... Обычно принято делать наоборот: тестировщики дают программерам заказ на нужные идентификаторы или что там, а не лезут в код сами).
Ну и ещё: такие тесты, конечно, будут реже разваливаться и программистам будет удобнее читать об ошибке, но внезапно есть ситуации, где такой тест пройдет успешно, невзирая на ошибку. Например, элемент с этим id переставили в какое-то место, где юзер его не найдёт никогда. Так что тесты "не через специально оставленную для них дырочку" тоже нужны.
sound_right Автор
27.05.2025 07:54Кажется, вы немного неверно интерпретировали суть статьи:
Через специально оставленные API тестировать удобнее (кто бы сомневался)
Это всё равно что сказать, что «все советы по инженерной практике сводятся к “пишите хороший код”». Простые идеи — не значит бесполезные. Важно не что, а как. В статье как раз и разбирается, как внедрить подход с
data-test-id
в реальные проекты. Несмотря на кажущуюся очевидность, это до сих пор не становится нормой — что и делает статью актуальной.Даём тестировщикам доступ к репе, пусть сами себе их делают (сомнительно... Обычно принято делать наоборот: тестировщики дают программерам заказ на нужные идентификаторы или что там, а не лезут в код сами).
Попробуйте мыслить шире. Современный QA-инженер — это не «человек, который кликает по кнопкам». Это инженер, который готовит инфраструктуру для автотестов, в том числе и тестовые API. Добавление
data-test-id
— это такой же элемент тестовой инфраструктуры, как и factory-функции или фикстуры. При этом в статье явно указано, что это не обязанность QA, и добавлять такие идентификаторы могут (и должны) фронтенд-разработчики — особенно если они используют компоненты повторно.Ну и ещё: такие тесты, конечно, будут реже разваливаться и программистам будет удобнее читать об ошибке, но внезапно есть ситуации, где такой тест пройдет успешно, невзирая на ошибку. Например, элемент с этим id переставили в какое-то место, где юзер его не найдёт никогда. Так что тесты "не через специально оставленную для них дырочку" тоже нужны.
Именно. Никто не говорил, что
data-test-id
— это серебряная пуля. В статье отдельно подчёркнуто, что это способ сделать тесты более стабильными, но не единственный инструмент контроля качества. Локаторы — это лишь часть UI-тестов, а не вся валидация UX.aamonster
27.05.2025 07:54Речь не про суть (там трудно спорить). Речь про то, что вы написали целую статью там, где достаточно одного абзаца.
Насчёт "тестовой инфраструктуры" – обычно она отделена от основного кода и является зоной ответственности команды QA. QA не лезут в код самого проекта, программисты не лезут в тесты. Разумный предел – читать код друг друга, но не модифицировать самим.
sound_right Автор
27.05.2025 07:54Речь не про суть (там трудно спорить). Речь про то, что вы написали целую статью там, где достаточно одного абзаца.
Если вы не заметили, в статье не просто озвучена мысль "нужны тестовые идентификаторы" — она наполнена практикой: от установки и настройки, до готовых шаблонов, примеров, рекомендаций и таблиц. Я детально разобрал, как и зачем их внедрять, какие есть варианты, где проставлять, какие ошибки допускают и почему это важно. Свести всё это к одному абзацу — это всё равно что сказать "тестирование важно" и выкинуть все курсы и практики по тестированию. По вашей логике, почти любую инженерную тему можно уместить в пару строк. Но зачем тогда вообще писать статьи, документацию и делиться опытом? Тем более для новичков, это может быть очень полезно, когда вообще не знаешь откуда начать. Не у всех 10 лет опыта в индустрии и годы практики за плечами. Для меня тоже все эти вещи просты и очевидны, но это лишь субъективный взгляд.
Насчёт "тестовой инфраструктуры" – обычно она отделена от основного кода и является зоной ответственности команды QA. QA не лезут в код самого проекта, программисты не лезут в тесты. Разумный предел – читать код друг друга, но не модифицировать самим.
Это довольно устаревший взгляд. QA-инженеры уже давно не ограничиваются «тестированием интерфейса по кнопочкам». Они пишут интеграционные и изоляционные тесты внутри проекта, настраивают тестовую инфраструктуру, пайплайны, метрики, триггеры, покрытие, нагрузку, пишут кастомные ассерт-библиотеки и даже юнит-тесты — и делают это прямо в коде.
Вы описали не "правило", а модель взаимодействия, и таких моделей десятки. Всё зависит от зрелости команды, договорённостей и культуры. И если QA может внести улучшение в код — ничего страшного, если он это сделает. Это не вторжение, а вклад. Если разработчик против и может сделать все это сам — пожалуйста, флаг в руки.
AlexKMK
27.05.2025 07:54У нас в gfn.am используются транзитивные идентификаторы, назвали dbs-codes (design breakout system) которые идут от фигмы, через реакт, и до самого гуглоаналитика.
Чтоб по айдишнику всегда можно было найти элемент где угодно.
Принцип тот же самый - котенкст-сабконтекст-контрол
sound_right Автор
27.05.2025 07:54Почему бы и нет. Если это можно делать автоматически + заюзать для аналитики, вообще отлично
supercat1337
27.05.2025 07:54Прочитал с большим интересом! Кстати, у нас с вами идеи довольно похожи. Вот тут я писал о кастомных идентификаторах и скоупах: https://habr.com/ru/articles/892326/. Будет интересно, загляните.
Vitaly_js
27.05.2025 07:54Попробую дополнить некоторые моменты для этого пройдусь по статье сверху вниз.
Завязка на текст: "А где кнопка?"
Текст - это не чья-то хотелка, а полноценный элемент дизайна системы. Что и куда должно быть вставлено и в каком виде определятеся требованиями к системе. А с точки зрения потока работ изменение текста - это то, что оформляется отдельной задачей и должно иметь обоснование. И, соответственно, вопрос, а надо ли такую задачу тестировать? И кто это должен делать?
На мой взгляд, получается так, что все равно в тестах (в чьих?) текст должен быть. И, соответственно, должны быть способы получить элемент по тексту.
Если разработчик против data-test-id — скорее всего, он просто не хочет, чтобы вы могли зацепиться за его DOM. Но это уже не про производительность. Это про территорию. И да, она теперь и ваша тоже.
Тут сразу такой вопрос возникает, если это все не нужно для продакшена, то зачем это туда посылать? Более того, какой-нибудь злонамеренный код получает удобную возможность парсить такой документ. Я не говорю, что все это серьезные проблемы, но вопросы такие напрашиваются.
Окей, пора перейти от слов к делу. Как именно ставить эти ваши data-test-id ...
На первый взгляд может показаться, что это что-то из разряда «требуется знание React на уровне Senior++», ...
Мне на первый взгляд приходит, что это организационная и техническая задача. Кто должен это делать? Как должен это делать? Когда должен это делать? Потому, что это отдельная ответственность и должен быть выработан подход внутри команды как решать эту задачу. Все упирается далеко не только, что бы навалить какого-то исходного кода в проект.
Каждый день тысячи QA Automation инженеров по всему миру просто открывают HTML, вставляют data-test-id="что-то-понятное" — и всё работает
Если честно, за десятилетие разработки такого ни разу не встречал. Просто для примера, что бы исходному коду попасть в ветку разработчика он должен пройти ревью. К репозиторию допущено только ограниченное количество сотрудников. Ни разу не видел, что бы тестеров добавляли в бэк или фрон репозитории просто для того, что бы он там добавлял локаторы.
Плюс, не забываем, что data-test-id может использоваться и на фронте, когда другие методы исчерпаны, а значит от них будут зависеть юнит-тесты, что тестер по определению трогать не должен.
Далее вы начинаете описывать, на мой взгляд, узкий кейс, который и обнажает недостаточную проработку деталей.
Установка Node.js
Могу посоветовать устанавливать ноду через менеджеры, например,
nvm
Установка зависимостей
Тут имеет смысл, если не знакомы с фронтовыми проектами просто побеседовать с фронт-разработчиком или прочитать readme.md. Потому что то, что вы написали очень узкий кейс. Запустив
yarn install
на современном проекте без должно подготовки приведет не к установки зависимостей, а к получению подробной подсказки что нужно сделать, что бы эту подготовку произвести.Ждём немного... Да, установка может занять минутку
В данном конкретном случае, если бы вы использовали не 1.22 версию, то установка заняла бы в 2 раза меньше времени =Р. А может и в 10 раз.
Главное — после установки у вас появится волшебная папка node_modules, и проект оживёт.
На самом деле необязательно. Если проект поддерживает pnp загрузку моделей, то этой папки не будет и искать ее не нужно.
Поэтому, я и говорю, что опустил бы все такие детали и просто дал отсылку на инструкцию как установить ваш конкретный тестовый проект.
Почему это работает:
Разработчик знает структуру лучше всех.
Экономит вам часы бестолкового блуждания.
Думаю, дело не столько в знании структуры. Просто у разработчика стоят инструменты разработчика для реакта, вью и т.п. И он ищет нужный компонент по имени.
По сути, вот там выше вам тоже нужно было этот пункт добавить.
Случай 1: Простой компонент — просто добавляем data-test-id
Допустим, довили вы
data-test-id={'welcome-title'}
и что вы собрались тут тестировать? Где сам тест?Просто, что-то мне подсказывает, что где-то есть задача, в которой ясно указано какой текст должен быть на экране. И собственно в этом и есть часть задачи тестирования, т.е. проверить что бы такой текст попал на экран.
Как обстоят дела на самом деле в реальных проектах?
Почему? Потому что (внимание, шок-контент) продукт делают для пользователей, а не для ваших автотестов. Вот так поворот.
Сейчас под пользователями могут понимать довольно широкую аудиторию. Например, в том числе и людей с ограниченными возможностями. И сразу же вопрос, а вы с помощью тестовый идентификаторов будете и для них тестировать приложение?
Там тоже это будет лучшей практикой? Или автотесты игнорируют эту часть приложения?
Как внедрять data-test-id и не развалить проект?
Т.е. все внедрение, думаете, упирается только в добавление исходного кода, а кто, как, когда эти вопросы сами могут решиться?
Подключайте фронтенд-разработчиков (сюрприз: они не против)
Серьёзно, для фронтенда поставить data-test-id — это даже не задача. Это полсекунды внимания.
Это когда процесс определен и внедрен.
Не требует архитектуры.
Как все что добавляется в репозиторий - это так же требует архитектуры.
Не трогает бизнес-логику.
Тесты должны тестировать бизнес-логику. И если какую-то бизнес-логику протестировать не получается, очевидно, ее нужно "поменять" так, что бы она была тестируемой.
Общий принцип именования data-test-id
По сути, вы заменили неудобные селекторы на удобные. Но процесс выбора никак не опирается на семантику того, что происходит на экране. И в селекторе вы стараетесь повторить роли элементов DOM.
Поэтому сразу хочется спросить, а точно нет другого выбора?
Я вот открываю доку на плэйрайт и там в [первых же пунктах](https://playwright.dev/docs/writing-tests#first-test)
First test
Take a look at the following example to see how to write a test.
import { test, expect } from '@playwright/test'; test('has title', async ({ page }) => { await page.goto('https://playwright.dev/'); // Expect a title "to contain" a substring. await expect(page).toHaveTitle(/Playwright/); }); test('get started link', async ({ page }) => { await page.goto('https://playwright.dev/'); // Click the get started link. await page.getByRole('link', { name: 'Get started' }).click(); // Expects page to have a heading with the name of Installation. await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); });
Тут нет тестовых идентификаторов, а селекторы опираются на семантику того с чем взаимодействует пользователь. И проверяется ровно то, что находится (должно там находится) в описании к задаче
А
data-test-id
— это то, что делает автотесты надёжными, предсказуемыми и стабильными. Хотите вы этого или нет — он победит.Что-то мне подсказывает, что в том виде в котором вы его продвигаете он уже себя отжил. Сейчас тестовые идентификаторы используют только когда другие способы исчерпаны.
sound_right Автор
27.05.2025 07:54Текст - это не чья-то хотелка, а полноценный элемент дизайна системы. Что и куда должно быть вставлено и в каком виде определятеся требованиями к системе. А с точки зрения потока работ изменение текста - это то, что оформляется отдельной задачей и должно иметь обоснование. И, соответственно, вопрос, а надо ли такую задачу тестировать? И кто это должен делать?
На мой взгляд, получается так, что все равно в тестах (в чьих?) текст должен быть. И, соответственно, должны быть способы получить элемент по тексту.
Конечно, надо. Если текст влияет на поведение пользователя — он часть функциональности, и как любая функциональность, должен покрываться тестами. Но вот что важно: тестировать по тексту — не значит привязываться к нему. Тексты меняются, локализуются, управляются вне продуктовой команды — и именно поэтому привязка к тексту делает тесты хрупкими. Это не про "хотелки", это про устойчивость автотестов и здравую архитектуру.
Тут сразу такой вопрос возникает, если это все не нужно для продакшена, то зачем это туда посылать? Более того, какой-нибудь злонамеренный код получает удобную возможность парсить такой документ. Я не говорю, что все это серьезные проблемы, но вопросы такие напрашиваются.
Во-первых, "не нужно для продакшена" — это спорное утверждение. Автотесты — это часть продукта, обеспечивающая его качество. Во-вторых, если вы опасаетесь, что
data-test-id
могут быть использованы злоумышленниками — тогда стоит обеспокоиться всем HTML-кодом. Это публичная часть системы, и для защиты от парсинга или сканирования есть отдельные механизмы (например, Cloudflare, WAF и т.д.), а не отказ от удобств для QA.Мне на первый взгляд приходит, что это организационная и техническая задача. Кто должен это делать? Как должен это делать? Когда должен это делать? Потому, что это отдельная ответственность и должен быть выработан подход внутри команды как решать эту задачу. Все упирается далеко не только, что бы навалить какого-то исходного кода в проект.
Согласен. Именно поэтому в статье и написано, как выстраивать этот процесс. Удивительно, что вы, видимо, этого не заметили. Вопрос не в том, чтобы "накидать кода", а в том, чтобы договориться в команде, зачем и как использовать
data-test-id
— с учётом архитектуры, CI/CD и нужд тестирования.Если честно, за десятилетие разработки такого ни разу не встречал. Просто для примера, что бы исходному коду попасть в ветку разработчика он должен пройти ревью. К репозиторию допущено только ограниченное количество сотрудников. Ни разу не видел, что бы тестеров добавляли в бэк или фрон репозитории просто для того, что бы он там добавлял локаторы.
То, что вы этого не видели — не делает это редкостью. В мобильной разработке, например, это повсеместная практика. Во многих веб-проектах — тоже. Всё зависит от культуры команды. Если у вас доступы к Postman дают через 2 месяца, а к репозиторию — через 6, это не аргумент, это сигнал о проблемах в организации, а не в
data-test-id
.Могу посоветовать устанавливать ноду через менеджеры, например,
nvm
Всё это уже рассмотрено в статье. Упомянуты варианты установки, необходимость обсуждений с фронтендом и чтение
README.md
. Если вы это не заметили — возможно, стоит перечитать внимательнее.Допустим, довили вы
data-test-id={'welcome-title'}
и что вы собрались тут тестировать? Где сам тест?Это статья не про сами тесты, а про то, как сделать проект удобным для написания устойчивых тестов. Не нужно подменять тему статьи.
Сейчас под пользователями могут понимать довольно широкую аудиторию. Например, в том числе и людей с ограниченными возможностями. И сразу же вопрос, а вы с помощью тестовый идентификаторов будете и для них тестировать приложение?
Если вы действительно озабочены доступностью, то да, используйте aria-labels, роли и тесты с user-facing локаторами. Но это никак не отменяет использования
data-test-id
— это не конкурирующие подходы, а взаимодополняющие.Я вот открываю доку на плэйрайт и там в [первых же пунктах](https://playwright.dev/docs/writing-tests#first-test)
Playwright рекомендует в зависимости от целей: если вам важен текст — используйте текст. Если вы хотите стабильные тесты — используйте
data-test-id
. Это не противопоставление, это инструмент под нужду. Просто откройте правильный раздел документации. И там четко написано:Шах:
Testing by test ids is the most resilient way of testing
И мат:
QA's and developers should define explicit test ids
Вы просто выдрали из контекста и пытаетесь выдать это за истину. Очевидно никто не будет в самой первой странице доки сразу грузить про тестовые идентификаторы, PageObject, PageFactory, PageComponent и прочие прелести. Это очевидно.
По итогу. Вы поднимаете понятные, но давно разобранные вопросы. Ваш опыт — это ваш опыт, но он не универсален. Реальный масштаб и зрелость QA-инфраструктуры как раз и проявляются в таких деталях, как
data-test-id
. Это вопрос культуры качества, а не вкусовщины.Vitaly_js
27.05.2025 07:54Конечно, надо. Если текст влияет на поведение пользователя — он часть функциональности, и как любая функциональность, должен покрываться тестами. Но вот что важно: тестировать по тексту — не значит привязываться к нему. Тексты меняются, локализуются, управляются вне продуктовой команды — и именно поэтому привязка к тексту делает тесты хрупкими. Это не про "хотелки", это про устойчивость автотестов и здравую архитектуру.
Как это управляются вне продуктовой команды? Они продуктовой командой управляются.
Что значит не привязываться к нему? И почему тогда нельзя так же не привязываться при выборке? Что значит устойчивость автотестов и здравая архитектура в данном случае?
Приведите пример в случае с тем же заголовком. Мне действительно интересно узнать, что тут имеется в виду относительно всего того, что вы написали.Во-первых, "не нужно для продакшена" — это спорное утверждение.
Да? А какие тут спорные моменты?
Автотесты — это часть продукта, обеспечивающая его качество.
И что из этого следует? Например, юнит тесты тоже часть продукта обеспечивающие его качество.
Во-вторых, если вы опасаетесь, что
data-test-id
могут быть использованы злоумышленниками — тогда стоит обеспокоиться всем HTML-кодом. Это публичная часть системы, и для защиты от парсинга или сканирования есть отдельные механизмы (например, Cloudflare, WAF и т.д.), а не отказ от удобств для QA.Я в данном случае просто поставил вопросы. Я так и не увидел зачем это в проде. А оставлять рудементы разработки, просто, зачем?
Согласен. Именно поэтому в статье и написано, как выстраивать этот процесс. Удивительно, что вы, видимо, этого не заметили.
Именно как выстраивать процесс в статье очень мало. Есть пример каких-то техник, которые крайне слабо детализированы. И в таком виде не могут служить руководством. А вот как информация к размышлению вполне.
Вопрос не в том, чтобы "накидать кода", а в том, чтобы договориться в команде, зачем и как использовать
data-test-id
— с учётом архитектуры, CI/CD и нужд тестирования.Да, если использовать, то договориться нужно. Но это не выстроенный процесс.
То, что вы этого не видели — не делает это редкостью. В мобильной разработке, например, это повсеместная практика. Во многих веб-проектах — тоже. Всё зависит от культуры команды. Если у вас доступы к Postman дают через 2 месяца, а к репозиторию — через 6, это не аргумент, это сигнал о проблемах в организации, а не в
data-test-id
.Вы путаете онбординг и вот эту вашу инициативу. Всем ответственным доступы дают сразу. А тестеру в исходном коде делать нечего. И по моему, это уже больше похоже на бардак, если локаторы в исходный код добавляет тестер.
Всё это уже рассмотрено в статье. Упомянуты варианты установки, необходимость обсуждений с фронтендом и чтение
README.md
. Если вы это не заметили — возможно, стоит перечитать внимательнее.Нет там этого, поэтому и предложил. Вы просто предложили сказать с сайта ноды.
Это статья не про сами тесты, а про то, как сделать проект удобным для написания устойчивых тестов. Не нужно подменять тему статьи.
А почему тогда на месте не привести пример этого самого устойчивого теста. Ну, т.е. вот вы ввели свой тест айди и получился вот такой вот тест. А то в вашем примере мне не понятно, что вы собрались тестировать и как. А отсюда непонятно, зачем вам тогда в данном примере тест айди.
Я именно поэтому предложил. Это точно приводит к устойчивому тесту? Просто, по тому что в статье это совсем не очевидно. Теста то нет.
Если вы действительно озабочены доступностью, то да, используйте aria-labels, роли и тесты с user-facing локаторами. Но это никак не отменяет использования
data-test-id
— это не конкурирующие подходы, а взаимодополняющие.Использование тест айди действительно дополняет семантичное тестирование. Но само семантичное тестирование - это прямой конкурент тестирования только с помощью тест айди.
У использования тест айди сейчас есть своя ниша - это применение тогда, когда другие способы исчерпаны.
Playwright рекомендует в зависимости от целей: если вам важен текст — используйте текст. Если вы хотите стабильные тесты — используйте
data-test-id
. Это не противопоставление, это инструмент под нужду. Просто откройте правильный раздел документации. И там четко написано:Шах:
Testing by test ids is the most resilient way of testing
И мат:
QA's and developers should define explicit test ids
Так а вы внимательно это все прочитали? Там же ясно написано, что если вам нужны тесты ориентированные на пользователя ваш рекомендуется подход рассчитанный на семантичные локаторы. Вот тестируете вы пользовательский интерфейс как если бы его использовал пользователь и рекомендуются семантичные локаторы.
Вот же вся история целиком, зачем ее на части дробить?Testing by test ids is the most resilient way of testing as even if your text or role of the attribute changes, the test will still pass. QA's and developers should define explicit test ids and query them with page.get_by_test_id(). However testing by test ids is not user facing. If the role or text value is important to you then consider using user facing locators such as role and text locators.
Для тестирования синтетики для которой не важна семантика никакого смысла использовать семантичные локаторы нет. А вот когда вы тестируете бизнес логику семантика элементов на экране важна. Собственно вы ее и тестируете.
Поэтому никаких шах и матов тут нет. Если вам при постановке задачи прямо написали, что семантика не важна тогда вам нужны несемантичные локаторы.
Вы просто выдрали из контекста и пытаетесь выдать это за истину. Очевидно никто не будет в самой первой странице доки сразу грузить про тестовые идентификаторы, PageObject, PageFactory, PageComponent и прочие прелести. Это очевидно.
Простите, а почему? Если это основная практика как вы заявляете, значит ей место на первой странице. Это специфические случаи можно оставить для отдельных разделов документации, а на поверхности всегда общие случаи. Если дока, конечно, в мясину не устарела.
По итогу. Вы поднимаете понятные, но давно разобранные вопросы. Ваш опыт — это ваш опыт, но он не универсален. Реальный масштаб и зрелость QA-инфраструктуры как раз и проявляются в таких деталях, как
data-test-id
. Это вопрос культуры качества, а не вкусовщины.Тогда, наверное, стоило копать глубже. И не на словах, а на примерах показать конкурентные подходы. Мне теперь вообще тогда непонятно, а когда стоит использовать семантичный подход по вашему? И что конкретно тогда вы тестируете, если для вас судя по тому, что написано в документации на которую Вы же и дали ссылку сказано, что ваш подход нужен для когда тесты не ориентированы на пользователя, т.е. не тестируют то как пользователь взаимодействует с системой. Вы что тестируете?
sound_right Автор
27.05.2025 07:54Интересный стиль у вас — вы сперва ставите под сомнение очевидные вещи, а потом доказываете, что они действительно очевидны.
В любом случае. Спасибо за поток мыслей — местами занятно, но всё же давайте на этом остановимся. У меня нет времени на бесконечные теоретические споры. Если вы действительно знаете, как правильно — покажите это делом: кодом, подходом, опытом, проектом, статьей "как надо". Пока что всё это выглядит как пустой трёп с претензией на истину.
На этом предлагаю поставить точку.
Vitaly_js
27.05.2025 07:54Я поставил под сомнение ваше предложение. И оно не очевидно. Есть узкий круг задач для которых ваше решение подходит. По ссылке которую вы дали, эти задачи обозначены. В вашей статье вообще непонятно что и как вы тестируете, т.е. прямо вот вообще.
И причем тут теория? Вот ссылка на локаторы плэйрайта Locators | Playwright Python которую вы дали. Там полно локаторов и резонно возникает вопрос, почему в статье о них ни слова? Хотя бы намеком обозначили почему они вам не подошли.
Если вам все это не интересно, ну, ладно.
LoAdInG_FoRvAtEr
27.05.2025 07:54Спасибо за статью!
Идея добавления тестовых атрибутов напрямую силами AQA действительно интересная, но, на мой взгляд, слабо применима в условиях enterprise-проектов. В таких компаниях команды разработки и тестирования, как правило, разделены, и внесение изменений в кодовую базу (особенно фронта или монорепозитория) требует утверждения и ревью, на которое вряд ли кто-то согласится — не из вредности, а из-за процессов и приоритетов.
Как альтернатива — можно формализовать необходимость добавления таких атрибутов на этапе постановки требований. Например, на основании макетов или заранее подготовленных тест-кейсов закрепить требование на добавление подобных атрибутов как часть спецификации. В таком случае реализация ляжет на разработчика, и всё будет прозрачно и официально.
P.S.
Безопасно ли это?
Также из интересных причин, по которым не хотят внедрять подобный подход, которые я слышал - это "мы упростим работу ботам, которые парсят наш сайт"))
sound_right Автор
27.05.2025 07:54Идея добавления тестовых атрибутов напрямую силами AQA действительно интересная, но, на мой взгляд, слабо применима в условиях enterprise-проектов. В таких компаниях команды разработки и тестирования, как правило, разделены, и внесение изменений в кодовую базу (особенно фронта или монорепозитория) требует утверждения и ревью, на которое вряд ли кто-то согласится — не из вредности, а из-за процессов и приоритетов.
Согласен, в условиях enterprise-проектов часто бывают серьёзные ограничения на любые изменения в кодовой базе, особенно если фронт — это монорепозиторий, с отдельной очередью на ревью, приоритетами и безопасностью. Бывает, что на пользование курлом нужно два месяца доступ ждать. Именно поэтому я в статье и акцентировал, что можно договориться с разработчиками: договориться — ключевое слово.
Как альтернатива — можно формализовать необходимость добавления таких атрибутов на этапе постановки требований. Например, на основании макетов или заранее подготовленных тест-кейсов закрепить требование на добавление подобных атрибутов как часть спецификации. В таком случае реализация ляжет на разработчика, и всё будет прозрачно и официально.
Да, это хороший способ. Такой подход работает хорошо в продуктовых командах, где требования и UI-дизайн формализуются заранее. При этом даже если QA не правит код напрямую, он может быть инициатором таких изменений и участником обсуждения, вплоть до постановки задач фронтам.
Также из интересных причин, по которым не хотят внедрять подобный подход, которые я слышал - это "мы упростим работу ботам, которые парсят наш сайт"))
Это классика. Но если кто-то действительно захочет парсить сайт — наличие или отсутствие
data-test-id
вообще ни на что не повлияет. Это ложное чувство безопасности. А вот от настоящих угроз спасают инструменты вроде Cloudflare, WAF и капч.
edta_ff
27.05.2025 07:54с Playwright понятно. а как использовать эти аттрибуты в Java/Python + Selenium? мне пока приходится писать xpath'ы типа
//div[@data-testid='username_textfield']
sound_right Автор
27.05.2025 07:54Спасибо за вопрос!
Да, в Selenium нет нативной поддержки
data-testid
— это просто кастомный атрибут, как и любойdata-*
. Но обращаться к нему можно абсолютно спокойно, обычными средствами:driver.find_element(By.CSS_SELECTOR, "[data-testid='username_textfield']")
Или:
driver.findElement(By.xpath("//div[@data-testid='username_textfield']"));
Если надоело писать XPath руками — просто оберните в helper:
def get_by_test_id(driver, test_id): return driver.find_element(By.CSS_SELECTOR, f"[data-testid='{test_id}']")
Да, не Playwright, но работает. Либо можно использовать подход с PageFactory и инкапсулировать все это безобразие на уровне элементов. У меня было несколько статей на эту тему:
Пишем UI авто тесты на TypeScript с использованием Page Object, Page Factory
UI автотесты на Python с запуском на CI/CD и Allure отчетом. PageObject, PageComponent, PageFactory
Там везде используется Playwright, но и для Selenium с PageFactory пример имеется https://github.com/Nikita-Filonov/selenium_python
polRk
27.05.2025 07:54Пожалуйста, не вводите людей в заблуждение. НИКОГДА нельзя использовать xpath, сss selectors, data-attributes при тестировании. Это больше подходит под создание быстрых автоматизаций. на сайте playwright даже об этом говориться. Лучше и правильнее тестировать поведение, видимое пользователю. Например, вместо xpath до кнопки использовать page.getByRole - это гарантирует то, что мы пытаемся найти кнопку на которую может кликнуть пользователь (она видна, она доступна), и не важно где она находится.
sound_right Автор
27.05.2025 07:54Спасибо за мнение! Напомню, что статья посвящена техническим аспектам расстановки
test-id
, а не подходам к тестированию UI — вы, похоже, спорите с темой, которой в статье даже не было.Будет здорово увидеть вашу статью, примеры кода и аргументацию на тему "как правильно тестировать". Пока её нет — позволю себе отнестись к вашему комментарию как к пустому трёпу, не более.
Но если появится содержательный материал — рад обсудить по делу.
LyuMih
Полуавтомтический
data-test-id='clients-search__SearchClientInput-lastName--item'
У нас nx-монорепозиторий. Есть соглашение, что в каждом модуле (1-2 страницы со своим api) вложенность компонентов только на 1 уровне - т.к. нет components/a/b/c.
Делюсь рабочим простым, но эффективным рабочим кодом по генерации test-id. Его положили модуль library.
Для каждого модуля создаём функцию, которая содержит имя модуля. Например, для модуля clients-search.
И в самом модуле, в компонент используем useTestId() для компонентов.
Плюсы такой функции - утилиты:
Минимальное количество сил для уникальных названий, не надо думать! Достаточно, чтобы в 1 компоненте префикс не повторялся
testId('префикс').
При том очень стабильное поведение. Изменяется только когда сам компонент (его название) меняет название. А это как правило в следствии меняющих требования доработок.
Уникальность работает в рамках нашей архитектуры, где только 1 уровень вложенности. Кстати, как показывает время - очень простое и удобное решение (по сравнению с FSD xD )
sound_right Автор
Спасибо за комментарий! Интересный подход