
Автотесты писали, чтобы экономить время. Но в какой-то момент поняли, что тратим на их обслуживание больше, чем экономим.
Тест упал в CI — открываешь Allure, идёшь на стенд через VPN, авторизуешься, лезешь в DOM, ищешь, какой локатор отвалился. Полчаса-час на один тест. Прошёл релиз, и ещё пара дней уходит на то, чтобы починить локаторы после того, как фронтенд поменял вёрстку. А когда с утра видишь 200 красных тестов при нетронутом коде, начинается расследование.
В этот момент хочется отдать всё это AI-ассистенту и заняться делом. Мы так и сделали, но панацеи не вышло. ИИ снимает часть рутины и экономит часы, но на сложных кейсах он ошибается, ходит по кругу и с уверенным видом выдаёт несуществующие локаторы за настоящие.
Всем привет! На связи Егор Лаптев — QA Fullstack Java в SENSE на проекте крупного российского банка.
В статье рассказываю, где ассистент помогает, а где создаёт больше проблем. Показываю, как он устроен внутри и разбираю шесть рабочих сценариев с кодом. Отдельно выделил ограничения и стоимость нововведений.
В примерах стек Selenide, Cucumber, REST Assured, Allure, Kafka и Moon в Kubernetes, но сами принципы переносятся на любую связку UI- и API-тестов.
С чем мы жили каждый день
В нашем проекте более 2000 автотестов: UI-сценарии на Selenide и Cucumber, API-тесты на REST Assured, интеграционные проверки с Kafka, инфраструктурный мониторинг. Всё это вшито в процессы команды: каждый прогон CI — это сотни сценариев, а каждый релиз — актуализация десятков тестов.
Боль 1: Упавший тест — это 30–60 минут рутины. Открыть Allure. Найти ошибку. Зайти на стенд через VPN. Авторизоваться. Найти страницу. Проверить DOM. Понять, что локатор сломался. Исправить. Запустить. Упало по-другому. Снова исправить. Снова запустить. Прошло. Следующий тест.
Боль 2: Релиз — это несколько дней актуализации. Фронтенд изменил data-test-id на 15 элементах. Нужно найти все 15, проверить DOM на стенде, обновить локаторы, запустить тесты. На каждый элемент — 10–15 минут. 15 элементов — 3–4 часа. А если ещё и вёрстка перестроилась — XPath тоже нужно переписывать.
Боль 3: Массовые падения — и непонятно почему. Утром пришёл — 200 тестов красных. Код не менялся. Что случилось? Moon лежит? DNS упал? Стенд перезапустили? Данные протухли? Нужно последовательно проверять каждую гипотезу — 20–30 минут на диагноз.
Боль 4: Написание нового теста — 2–3 часа на один сценарий. Прочитать тикет. Понять бизнес-логику. Написать feature-файл. Найти или создать Page Object. Написать шаги. Запустить. Упало — поправить локатор. Запустить. Упало — добавить wait. Запустить. Прошло.
Боль 5: Отладка iframe и перекрытых элементов — 1–2 часа на один кейс. Элемент «как бы есть» в DOM, но Selenide его не находит. Почему? iframe? Перекрытие? Не загрузился? Нет прав? Каждая гипотеза — 15–20 минут проверки на стенде.
ИИ берёт на себя рутину в этих пяти сценариях. Дальше разбираю подробнее каждый.

Архитектура: Supervisor + Workers + Skills
Ассистент работает по модели Supervisor-Worker с системой Skills:
Supervisor — оркестратор. Читает контекст, определяет тип задачи, делегирует работу конкретному воркеру
Workers — исполнители. Имеют доступ к файловой системе, терминалу, Allure-отчётам, Bitbucket, Confluence, Jira, TestOps
Skills — набор инструкций, которые определяют как делать задачи. Не общие знания, а конкретные протоколы под наш проект
Ключевое ограничение: Workers не видят историю чата. Supervisor обязан передавать весь контекст в каждом вызове. Пропущенная деталь — воркер работает без нужной информации.
Критическое правило инъекции: Supervisor обязан передавать содержимое релевантных Skills в каждом вызове воркеру. Не передал — воркер работает вслепую.
Практические сценарии
Процесс 1. Починка упавших UI-тестов
Задача: Тест упал с NoSuchElementException. Нужно понять причину и исправить.
Что делает ИИ:
Определяет тип ошибки по стектрейсу
Находит локатор в Page Object по аннотации @ElementName
Проверяет реальный DOM через playwright-cli
Сравнивает ожидаемый и реальный DOM, исправляет локатор
Запускает тест и проверяет результат
Пример кода — Page Object с аннотациями:
@PageName("Карточка организации") public class CompanyCardPage { @ElementName("Пользователи и роли") public SelenideElement usersAndRolesTile = $(byAttribute("data-test-id", "nibAdminViewButton")); @ElementName("Модальное окно Пользователи и роли") public SelenideElement usersAndRolesModal = $(byAttribute("data-test-id", "nibAdminModal")); }
Пример кода — Step, ищущий элемент по имени:
@Когда("кликаем на блок-кнопку {string}") public void кликаемНаБлокКнопку(String buttonName) { SelenideElement element = pageObjectFinder .getSelenideElementByName(buttonName, pageContext.getCurrentPage()); element.shouldBe(Condition.visible).click(); }
Пример кода — проверка реального DOM через playwright-cli:
# Открываем браузер playwright-cli open --headed about:blank # Обход SSL + авторизация через Keycloak SSO playwright-cli run-code --filename /tmp/pw_ssl_bypass.js async () => { const browser = page.context().browser(); const ctx = await browser.newContext({ignoreHTTPSErrors: true}); const p = await ctx.newPage(); await p.goto('https://app-int.moscow.internal.corp/...'); await p.waitForSelector('#username', {timeout: 10000}); await p.fill('#username', 'LOGIN'); await p.fill('#password', 'PASSWORD'); await p.click('#kc-login'); await p.waitForLoadState('networkidle', {timeout: 15000}); return p.url(); } // Извлекаем все data-test-id из DOM async () => { const result = await targetPage.evaluate(() => { const elements = []; document.querySelectorAll('[data-test-id]').forEach(el => { elements.push({ tag: el.tagName, dataTestId: el.getAttribute('data-test-id'), text: el.textContent.substring(0, 200).trim() }); }); return JSON.stringify(elements, null, 2); }); return result; }
Приоритет локаторов при исправлении:
Приоритет | Тип локатора | Почему |
|---|---|---|
1 | @data-test-id | Стабильный, не зависит от вёрстки |
2 | XPath с contains() | По тексту или стабильному атрибуту |
3 | CSS-классы через contains() | CSS-модули генерируют хеш-суффиксы |
Типичные проблемы и решения:
Проблема | Решение |
|---|---|
/span не находит | Заменить на //span (не прямой потомок) |
CSS-класс с хеш-суффиксом | contains(@class, 'base-name') |
Элемент не кликается | click(ClickOptions.usingJavaScript()) |
Модалка: несколько совпадений | Привязать к @data-test-id конкретной модалки |
Запуск и проверка:
./gradlew uiParallel -Dcucumber.filter.tags="@TICKET-3146 and not @Ignore"
Отладка сложных UI-флоу. Обычный сценарий: тест падает, инженер открывает стенд, видит, что элемент присутствует в DOM, но не кликается — и начинает разбираться. ИИ систематизирует этот процесс:
iframe (подсистемы внутри основного приложения). Фронтенд рендерит часть интерфейса в iframe — например, виджет из Подсистемы А внутри основного портала. Selenide по умолчанию ищет элементы в основном контексте, а элемент — в iframe. ИИI через playwright-cli переключает контекст, проверяет DOM внутри iframe и предлагает switchTo().frame() перед взаимодействием.
Перекрытые элементы. Элемент виден в DOM, но перекрыт другим — модалкой, тултипом, sticky-хедером. ИИ пробует разные стратегии клика:
click() — стандартный клик → падает, ElementClickInterceptedException
click(ClickOptions.usingJavaScript()) — JS-клик → может уйти не туда
scrollIntoView(true).click() — скролл + клик → если элемент за viewport
hover().click() — hover для убирания тултипа → иногда помогает
Если ни одна стратегия не работает — ИИ эскалирует на инженера. Проблема может быть не в локаторе, а в том, что модалка не закрылась из-за бага в приложении.
Race conditions. Тест падает через раз: иногда элемент успевает появиться, иногда нет. ИИ анализирует тайминги в Allure-отчёте — сколько шаг выполнялся, когда появился timeout — и предлагает wait-стратегии: shouldBe(visible) вместо shouldHave(text(...)), явные WaitFor с нужным условием, увеличение таймаута для конкретного шага.
Ограничение: ИИ не понимает ПОЧЕМУ элемент не появился. Он видит следствие — NoSuchElementException, ElementClickInterceptedException, timeout — но не причину. Причина может быть в бизнес-логике (роль не даёт доступ), в баге приложения (сервер вернул 500, данные не загрузились), в инфраструктуре (Moon-под завис, браузер не отвечает). AI видит только код и DOM, а не бизнес-контекст. Отладка — это партнёрство: ИИ работает с DOM, инженер принимает решение.
Процесс 2. Написание новых тестов
Задача: Написать новый автотест по описанию или тикету.
Что делает ИИ:
Читает тикет из Jira / описание задачи
Пишет feature-файл (Gherkin на русском)
Создаёт или дополняет Page Object
Пишет Step Definition
Запускает тест и проверяет
Пример кода — Feature-файл:
@CompanyCardGeneralInfo Функционал: Вкладка Общая информация карточки организации Предыстория: Пользователь входит на портал * переход на страницу "Страница входа" по ссылке "loginPage" * Пользователь с ролью "Role_DepartmentHead" авторизуется через логин * загрузка страницы "Поиск организаций" Сценарий: Отображение плиток при ролевой модели Подсистемы А * заполнено поле "Поиск компаний" со значением "тестовая компания" * нажата кнопка "Найти" * отображаются результаты поиска, содержащие компанию с названием "Тестовая организация 1" Тогда тег "Подсистема А" должен быть активен И отображается блок кнопка "Пользователи и роли" И элемент "Руководители" скрыт
Пример кода — Page Object:
@PageName("Карточка организации") public class CompanyCardPage { @ElementName("ИНН") public SelenideElement inn = $(byXpath( "//div[contains(@class, 'main-info-header_row')]" + "//span[contains(text(), 'ИНН:')]/following-sibling::span" )); @ElementName("Пользователи и роли") public SelenideElement usersAndRolesTile = $( byAttribute("data-test-id", "nibAdminViewButton") ); }
Никаких @FindBy — только $, $x, $$, $$x. Ленивая инициализация Selenide.
Пример кода — Step Definition с PicoContainer DI:
public class CompanyCardStep { private final PageObjectFinder pageObjectFinder; private final PageContext pageContext; public CompanyCardStep(PageObjectFinder pageObjectFinder, PageContext pageContext) { this.pageObjectFinder = pageObjectFinder; this.pageContext = pageContext; } @Когда("в хедере карточки компании отображаются нередактируемые поля:") public void вХедереОтображаютсяПоля(DataTable dataTable) { List<Map<String, String>> data = dataTable.asMaps(String.class, String.class); for (Map<String, String> row : data) { SelenideElement element = pageObjectFinder .getSelenideElementByName(row.get("Поле"), pageContext.getCurrentPage()); element.shouldBe(Condition.visible); } } }
Процесс 3. Анализ Allure-отчётов и умный реран
Задача: После прогона часть тестов упала по флаковым причинам. Нужно определить, какие стоит перезапустить, и сделать это автоматически.
Что делает ИИ:
Парсит JSON-результаты Allure из build/allure-results/
Разделяет упавшие тесты на параллельные (независимые) и процессные (с тегом @Sequential)
Генерирует rerun-parallel.txt и rerun-sequential.txt
Запускает только упавшие тесты через Gradle-таски
Пример кода — Gradle-таска рерана:
tasks.register('rerunParallel', Test) { filter { includeTestsMatching("ru.company.runner.UITestRunner") } ignoreFailures = true onlyIf { def rerunFile = file("${layout.buildDirectory.get()}/rerun-parallel.txt") rerunFile.exists() && !rerunFile.text.trim().isEmpty() } doFirst { def rerunPaths = file("${layout.buildDirectory.get()}/rerun-parallel.txt").text.trim() systemProperty "cucumber.features", rerunPaths.replaceAll('\\n', ',') } }
Ключевой нюанс: cucumber.features полностью переопределяет @SelectClasspathResource в Suite-раннере. Без onlyIf при пустом файле запустятся ВСЕ тесты.
Процесс 4. Диагностика Moon-кластера
Задача: UI-тесты не запускаются. Нужно понять, жив ли Moon-кластер (Aerokube) в Kubernetes.
Что делает ИИ:
Проверяет доступность Moon через /wd/hub/status
Пытается создать тестовую сессию Chrome
Пробует навигацию на целевой URL
Если навигация не работает — проверяет DNS внутри K8s
Пример кода — диагностика через curl:
# Проверка доступности curl -sk http://moon.test-cluster.internal.corp/wd/hub/status # Создание тестовой сессии Chrome curl -sk -X POST http://moon.test-cluster.internal.corp/wd/hub/session \ -H 'Content-Type: application/json' \ -d '{"capabilities":{"alwaysMatch":{' + '"browserName":"chrome","browserVersion":"126.0.6478.126-6",' + '"acceptInsecureCerts":true}}}' # Навигация на целевой URL curl -sk --max-time 30 -X POST \ "http://moon.test-cluster.internal.corp/wd/hub/session/{SESSION_ID}/url" \ -d '{"url":"https://app-int.moscow.internal.corp/"}'
Процесс 5. Kafka в автотестах
Задача: Проверить, что сервис отправил сообщение в Kafka-топик после выполнения бизнес-операции.
Что делает ИИ:
Подключается к Kafka-топику с SASL/SCRAM-аутентификацией
Читает сообщения с нужным offset
Проверяет содержимое сообщения
Пример кода — Cucumber-шаги для Kafka:
@Дано("подключение к Kafka топику {string}") public void subscribeToTopic(String topic) { KafkaConsumerClient consumer = getConsumer(); consumer.subscribe(topic); } @Когда("прочитаны сообщения из Kafka топика в течение {int} секунд") public void pollMessages(int timeoutSeconds) { List<ConsumerRecord<String, String>> messages = consumer.pollMessages(timeoutSeconds); ScenarioContext.getInstance().setData("kafkaMessages", messages); } @Тогда("сообщение из Kafka топика содержит текст {string}") public void verifyMessageContainsText(String expectedText) { List<ConsumerRecord<String, String>> messages = ScenarioContext.getInstance().getData("kafkaMessages"); boolean found = messages.stream() .anyMatch(record -> record.value().contains(expectedText)); assertTrue(found, "Сообщение с текстом '" + expectedText + "' не найдено"); }
Два режима: subscribe (consumer group) и assign (seekToBeginning, без влияния на offset).
Процесс 6. Выявление инфраструктурных проблем
Задача: Тесты падают массово, но код правильный. Нужно понять, что сломалось в инфраструктуре.
Что делает ИИI:
Проверяет доступность Moon-кластера
Проверяет создание сессии и навигацию
Если навигация не работает — проверяет DNS
Если DNS не резолвится — проверяет CoreDNS в K8s
Формирует диагноз и передаёт инфраструктурной команде
Типичные инфраструктурные проблемы:
Moon недоступен — все тесты падают с Connection Refused
DNS не резолвится — браузер открывается, но навигация зависает, ERR_NAME_NOT_RESOLVED
Egress заблокирован — сетевые политики K8s блокируют исходящий трафик, страница без стилей
Сессии зависают — поды не освобождаются, новые тесты встают в очередь и падают по таймауту
БД недоступна — API-тесты падают с 500, в логах ConnectionPoolTimeoutException
Пример кода — цепочка диагностики:
# Шаг 1: Moon жив? curl -sk http://moon.test-cluster.internal.corp/wd/hub/status # → 200 OK # Шаг 2: Можем создать сессию? curl -sk -X POST http://moon.test-cluster.internal.corp/wd/hub/session ... # → Сессия создана # Шаг 3: Навигация работает? curl -sk --max-time 30 -X POST ".../url" -d '{"url":"https://app-int.moscow.internal.corp/"}' # → Timeout! # Шаг 4: DNS резолвится? nslookup app-int.moscow.internal.corp # → NXDOMAIN # Шаг 5: CoreDNS kubectl get pods -n kube-system -l k8s-app=kube-dns # → CoreDNS pod CrashLoopBackOff
Весь диагноз за 3 минуты вместо 20–30 минут ручного разбора. ИИ не может починить CoreDNS — только поставить диагноз.
Система Skills: как мы управляем знаниями
Skills — это это конкретные протоколы под конкретные задачи:
Skill | Когда применять |
|---|---|
quickfix | Упал UI-тест — починить |
new-test | Написать новый тест |
dom-tree-analysis | Исследовать DOM через playwright-cli |
cucumber-rerun | Реализовать умный реран |
moon-cluster-analysis | Диагностика Moon |
kafka-consumer | Работа с Kafka в тестах |
infra-diagnostics | Диагностика инфраструктурных проблем |
no-analysis-scripts | НЕ создавать мусорные файлы в проекте |
Правило маршрутизации: читать минимум Skills, достаточный для задачи. Не читать всё подряд.
Ограничения и проблемы
Галлюцинации

Проблема: ИИ генерирует несуществующие Cucumber-шаги и data-test-id.
Пример 1 — несуществующие шаги. ИИ «придумывает» шаг, которого нет в коде. Feature-файл выглядит логично, синтаксис Gherkin правильный — но при запуске UndefinedStepException. Какой именно шаг не существует — выясняется только после компиляции.
Пример 2 — придуманные data-test-id. ИИ анализирует паттерны именования в проекте и генерирует правдоподобный идентификатор. Выглядит как настоящий, но при запуске — NoSuchElementException.
Пример 3 — NBSP в тексте. ИИ видит в DOM <span>Руководители</span> и предлагает:
$(byXpath("//span[text()='Руководители']"))
Но реальный текст содержит NBSP: "Руководители\xa0". В 9 из 10 случаев работает, на конкретном стенде — нет.
Пример 4 — CSS-хеш-суффиксы. ИИ видит <div class="modal_header__a3b2c1"> и предлагает $(byClassName("modal_header__a3b2c1")). Завтра фронтенд пересоберётся, хеш изменится — тест падает. Правильный вариант:
$(byXpath("//div[contains(@class, 'modal_header')]"))
Последствие: UndefinedStepException или NoSuchElementException при запуске. Инженер тратит время на поиск несуществующего шага в кодовой базе.
Решение:
Перед добавлением нового Cucumber-шага ИИ обязан найти все существующие шаги через поиск по репозиторию. Если подходящий шаг существует — использовать его, а не создавать новый.
Перед предложением локатора ИИ обязан проверить реальный DOM через playwright-cli. Придуманные data-test-id запрещены.
Зацикливание
Проблема: ИИ зацикливается на исправлении, меняя локатор туда-сюда без результата.
Пример — перекрытый элемент:
Тест падает (NoSuchElementException) → AI меняет локатор: //span[text()='X'] → Тест падает (элемент перекрыт) → AI меняет локатор: //span[contains(text(),'X')] → Тест падает (та же причина) → AI возвращает оригинальный локатор → Цикл повторяется
AI пробует стратегии клика:
click() → падает, элемент перекрыт
click(ClickOptions.usingJavaScript()) → падает, клик уходит не туда
scrollIntoView(true).click() → падает, элемент за пределами viewport
hover().click() → падает, hover не помогает
Меняет локатор на другой → падает, потому что проблема не в локаторе
5 итераций без результата.
Другой сценарий: тест падает из-за таймаута Moon. ИИ думает, что проблема в локаторе, и начинает его менять. //span[text()='X'] → //span[contains(text(),'X')] → //div//span[text()='X'] → добавляет shouldBe(visible) вместо shouldHave(text(...)) — не помогает. Проблема в том, что Moon-под завис и браузер не отвечает. ИИ видит только код, а не метрики кластера.
Последствие: 3–5 итераций без результата, потерянное время на запуск тестов.
Решение: лимит 3 итерации, после — эскалация на инженера с описанием всех попыток.
Невозможность определить причину

Проблема: NoSuchElementException имеет 5+ разных причин, ИИ не может отличить их без анализа DOM.
Пример: тест падает на шаге «отображается блок кнопка Пользователи и роли». ИИ видит NoSuchElementException и начинает менять локатор. Но проблема в том, что пользователь авторизовался под ролью, у которой нет доступа к Подсистеме А — и плитка просто не рендерится. ИИ не знает бизнес-логику, он видит только «элемент не найден».
Возможные причины NoSuchElementException:
Элемент убрали из интерфейса → переписать тест
Элемент переехал в другой контейнер → обновить локатор
Элемент не загрузился из-за таймаута → добавить wait
Элемент скрыт из-за прав пользователя → проверить роль
Элемент рендерится в iframe → переключить контекст
Последствие: ИИ тратит 3–5 минут на анализ DOM, инженер с опытом видит паттерн за 30 секунд.
Решение: если после анализа DOM причина неясна — ИИ честно пишет «не могу определить причину, требуется инженер» вместо угадывания.
Частичные решения
Проблема: ИИ может починить 8 из 10 упавших тестов, а 2 останутся — и для них нужен инженер. Эти 2 — обычно самые сложные: race condition, неочевидная зависимость между шагами, баг в самом приложении.
ИИ может написать 90% нового теста, но последние 10% — только человек. Сложная бизнес-логика: почему при роли Role1 видна плитка «Пользователи и роли», а при роли Role2 — нет. ИИ видит DOM, но не знает почему.
Хитрые wait-стратегии — тоже проблема. Нужно дождаться не просто появления элемента, а завершения асинхронной операции. ИИ ставит shouldBe(visible) — элемент появился, но данные ещё пустые. Тест проходит иногда, иногда нет. Флак.
Потеря контекста
Проблема: Workers не видят историю чата. Если Supervisor не передал критическую деталь — воркер работает без контекста.
Пример: инженер сказал «не меняй локатор для модалки, он правильный, проблема в таймауте». Supervisor не передал это воркеру — воркер поменял локатор. По протоколу: элемент не найден → проверить DOM → обновить локатор. Воркер действовал по протоколу, но без контекста — сломал то, что не нужно было трогать.
Чем длиннее сессия — тем больше контекста теряется. Supervisor пытается сжимать историю, но иногда выкидывает критические детали.
Последствие: воркер выполняет правильный протокол, но без нужных инструкций — ломает то, что не нужно было трогать.
Решение: протокол инъекции — Supervisor обязан передавать содержимое релевантных Skills в каждом вызове воркеру. Пропущенный пункт — воркер работает без нужных инструкций.
Сколько времени экономит ИИ-ассистент

Ассистент не заменяет инженера, но оптимизирует рутину: диагностика, локаторы, реран, инфраструктурные проверки. Ниже сводная таблица, которая поможет наглядно оценить экономию времени и понять ограничения:
Процесс / Задача | Вручную (Было) | С ИИ-ассистентом (Стало) | Ограничения / Нужен человек |
|---|---|---|---|
Диагностика UI-теста | 30–60 минут | 5–10 минут | В 20% случаев (сложная бизнес-логика) |
Написание нового теста | 2–3 часа | 30–40 минут | ИИ пишет ~90% кода, финал за человеком |
Актуализация при релизе | Несколько дней | Максимум 1 день | Требуется контроль контекста |
Инфраструктурный анализ | 20–30 минут | 3–5 минут | ИИ только диагностирует, но не чинит K8s |
Реран упавших тестов | Ручной разбор Allure | Автоматический анализ + перезапуск | Ложные срабатывания ~5% |
Отладка сложных флоу (iframe, перекрытия, race conditions) | 1–2 часа | 15–30 минут | ИИ не понимает бизнес-контекст, только DOM |
Ускорение производительности инженера | 30–40% времени на рутину | Рутина автоматизирована, фокус на архитектуре | ИИ не принимает архитектурные решения |
Актуализация DOM-карты при изменении фронтенда | Полный аудит вручную 4–8 часов | Автоматический обход через playwright-cli за 30–60 минут | Требуется верификация человеком |
Поиск корневой причины массовых падений | 1–3 часа | 5–15 минут | ИИ ставит диагноз, но не чинит инфраструктуру |
Сколько это стоит: считаем ресурсы

Возьмём команду из 8 человек. Средний запрос к ассистенту около 2 000 токенов (вход плюс выход). За рабочий день набегает примерно 200 запросов, это около 400 000 токенов. За месяц (22 рабочих дня) — порядка 8,8 млн токенов.
При цене ~$5 за миллион токенов это выходит около $44 в месяц. Немного. Но это уже оптимизированный режим — экономию потребления мы выстраивали семь месяцев. Без оптимизации каждый запрос использует полный контекст и требует около 15 000 токенов. Тот же месяц превращается в ~66 млн токенов, а счёт — примерно в $330 в месяц. Разница почти в 7 раз.
Когда ассистентом пользуется весь отдел тестирования, цифры растут:
Параметр | 1 команда | 10 команд | 50 команд |
Токенов/мес | ~8,8 млн | ~88 млн | ~440 млн |
Стоимость/мес | ~$44 | ~$440 | ~$2 200 |
Стоимость/год | ~$528 | ~$5 280 | ~$26 400 |
Без оптимизации/год | ~$3 960 | ~$39 600 | ~$198 000 |
И это ещё оптимистичная картина. В неё не заложены пиковые нагрузки при релизах (потребление ×3–5 к базовому), стоимость GPU-инфраструктуры под LLM, затраты на адаптацию под каждый проект и лицензии на IDE-плагины.
Почему одних денег может не хватить

Даже с бюджетом упереться можно в физику инфраструктуры:
— GPU в дефиците. LLM требуют A100/H100, очередь на такие карты — месяцы. Закупишь под пик — переплата, под среднее — не хватит в часы релизов.
— Пропускная способность ограничена. При 200+ одновременных сессиях сервис начинает троттлить: время ответа растёт с 5 секунд до 30+, и автотесты сыплются по таймаутам.
— Контекстное окно — бутылочное горлышко. У каждого проекта свой контекст, а 2 000+ тестов — это огромная кодовая база. Втиснуть её в окно целиком нельзя, резать — терять качество, а больше контекста означает дороже запрос.
— Оптимизация не бесплатна. Те самые семь месяцев и 80% потребления придётся повторять для каждого нового проекта. Без этого — потребление и стоимость в 5 раз выше.
— Скрытые расходы. Дообучение, промпт-инженерия, поддержка Skills, мониторинг качества ответов — это не разовые затраты, а постоянный FTE.
Вывод
ИИ-ассистент в тестировании — это инфраструктурный проект со своей стоимостью владения. Для одной команды это $528–3 960 в год, в зависимости от того, оптимизировали вы потребление или нет, — терпимо в обоих случаях. Для 50 команд вилка уже $26K–198K, и это предмет для разговора.
Экономия времени реальна. Но вопрос в том, кто за неё платит и сколько. На масштабе банка ИИ в тестировании — это больше про перераспределение затрат: вместо часов инженеров — часы GPU-кластеров.
P.S. А вы считали полную стоимость владения ИИ на своём проекте — не только токены, но и GPU-инфраструктуру, адаптацию и поддержку? Или пока платите только за токены?