Может ли ИИ писать автотесты за QA-инженера? Проверили на реальных задачах

Привет, Хабр! Меня зовут Евгения Мясникова, я QA-инженер SpaceWeb. Передавать искусственному интеллекту рутинные задачи тестирования — подготовку черновиков тест-кейсов, первичное покрытие, оценку трудозатрат и другие — уже стало привычной практикой. Однако при таком разнообразии инструментов важно понять, как эффективно встроить ИИ-агентов в рабочие процессы и распределить задачи между специалистом и нейросетью.
В этой статье расскажу, как мы совместно с руководителем отдела исследований и разработки SpaceWeb Виталием Киреевым разработали первоначальную модель взаимодействия QA-специалиста с LLM. Для работы с чувствительными данными мы интегрировали агента с локальной моделью Qwen3.8-27B. На реальных примерах покажу, в каких задачах ИИ действительно нам помог, а где его применение пока вызывает вопросы.
Навигация по тексту:
Какие задачи тестирования мы решили передать ИИ
Тестировщики в современной ИТ-компании работают с огромной кодовой базой, десятками API-методов и постоянным потоком новых фич. Запросы от клиентов не заканчиваются, разработка идет полным ходом, а QA-инженерам приходится искать компромисс между качеством тестирования и скоростью релизов.
Мы решили выяснить, в каких задачах LLM действительно экономит время, а где генерирует красивую воду, которую потом всё равно приходится переписывать вручную.
Для этого проанализировали текущие процессы и выявили две основные проблемы: большие затраты времени на рутинные операции и недостаточное покрытие отдельных сценариев тестами.
Сократить трудозатраты хотелось прежде всего на генерацию тестовых данных, написание однотипных тест-кейсов и создание скриптов для автоматизации. А расширить покрытие — при тестировании безопасности и проверке API-методов.
Как организовали работу QA-инженеров с LLM
После анализа задач и выявления узких мест мы начали внедрять ИИ в процесс тестирования. Автоматизировать всё сразу невозможно, поэтому для начала выбрали три направления:
создание тестовой документации и генерация данных. ИИ составляет базовый набор позитивных, негативных и граничных проверок, тест-планы и чек-листы. Затем тестировщик изучает результат и дополняет его необходимыми сценариями;
написание скриптов для автоматизации. LLM генерирует код по промпту, а QA-инженер отлаживает его и интегрирует в проект. Готовые скрипты можно повторно использовать для других подходящих задач;
расширение сценариев тестирования безопасности и API. ИИ помогает генерировать дополнительные проверки и тестовые комбинации, анализировать исходный код и JSON-схемы.
Тестовая документация, автотесты и проверка API
Для генерации тестовой документации, кода и скриптов мы использовали внутреннего LLM-ассистента. Непосредственно для тестирования API и запуска UI-автотестов применяли Postman, Python, Pytest и Selenium.
Кейс 1. Генерируем тест-кейсы и данные для проверки Serverless и VPS
Задача: проверить форму создания приложения облачного сервиса Serverless.
Загружаем предусловия, требования к обязательности полей и валидации, а также основные сценарии работы с формой. Просим ИИ составить список позитивных проверок и оформить его в виде таблицы.
Получаем базовый список проверок по позитивному сценарию:

Затем просим ИИ дополнить список негативными сценариями:

Отдельного внимания заслуживает интеграция с GitHub/GitLab. Просим ИИ-ассистента сгенерировать проверки для этого функционала и выделить наиболее критичные. Получаем довольно хороший список:

Создаем тестовые данные для проверки API
По заданному промпту агент генерирует необходимое количество синтетических, но реалистичных данных: банковские счета, имена, логины, пароли, домены и т. д.
Мы попробовали использовать его для создания JSON-объектов при тестировании создания VPS.
Передаем агенту пример валидного JSON и просим подготовить данные для разных проверок:
корректные запросы — минимальные и эталонные;
граничные значения для проверки лимитов;
поддержку кириллических доменов и специальных символов.
Получаем десяток объектов для разных сценариев. Например, для проверки того, как API обрабатывает предельные значения полей:
{
"hostname": "a-really-long-hostname-that-is-exactly-sixty-three-characters-long-to-test-dns-limits-12345",
"region": "us-west",
"plan": "baremetal-ultra",
"image": "windows-server-2022",
"ssh_keys": [],
"user_data": "%0A%0A%0A%0A" (пустые строки, закодированные для проверки обрезки),
"tags": [
"x" * 50,
"y" * 50,
"z" * 50
],
"ipv6": "2001:0000 0000:0000:0000:0000:0000:7334"
}Отдельно просим подготовить данные для тестирования безопасности: проверки валидации, нарушений бизнес-логики и попыток внедрения вредоносного кода.
Например, можно проверить попытку создания сервера в несуществующем или закрытом регионе:
{
"hostname": "geo-test",
"region": "atlantis",
"plan": "cloud-1",
"image": "ubuntu-22.04"
}Кейс 2. Создаем UI-автотест с помощью LLM меньше чем за час
Если генерация документации экономит минуты, то написание кода автотестов экономит часы, а иногда и дни работы. Мы решили проверить, сможет ли ИИ заменить рутинную часть написания «скелета» теста.
Задача: автоматизировать создание почтового ящика в панели управления виртуальным хостингом и проверить позитивный сценарий.
Промпт:
Напиши код на Python с использованием библиотек Pytest, Selenium и драйвером Сhrome и учетом того, что авторизация подхватывается из файла conftest.py, который выполнит следующие действия по шагам.
Перейти на страницу добавления почтового ящика.
Найти input с атрибутом name="mbox" и заполнить его именем ящика. Допустимые символы: строчные латинские буквы, цифры, +, -, ., _. Максимальная длина — 64 символа.
Найти элемент span с классом password-generator__text и кликнуть на него.
Проверить, что после этого в input с id="passwordWithGenerator" появился пароль длиной от 8 до 20 символов, содержащий буквы, цифры и специальные символы -!_@#$%^&*.
Найти кнопку button с текстом «Создать» и атрибутом type="submit", затем кликнуть на нее.
После выполнения этих шагов необходимо проверить, что произошел редирект на страницу /mail_add/review, а созданный почтовый ящик появился в списке.
Нейросеть сгенерировала готовый скрипт test_mail_add.py с подробными комментариями и необходимыми проверками по заданному промпту.
import os
import string
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Замените на реальный URL тестового стенда через переменную окружения или конфиг
BASE_URL = os.environ.get("BASE_URL", "https://YOUR_TEST_ENV_HOST:PORT")
def _generate_mbox_name() -> str:
"""Генерирует случайное имя почтового ящика (12 символов)."""
allowed = string.ascii_lowercase + string.digits + "+-._"
length = 12
return "".join(allowed[b % len(allowed)] for b in os.urandom(length))
class TestMailAdd:
"""Тест создания почтового ящика через панель управления SpaceWeb."""
def test_create_mail_box(self, login_user):
driver = login_user
wait = WebDriverWait(driver, 15)
# Шаг 1: Переход на страницу добавления почты
driver.get(f"{BASE_URL}/hosting/mail/mail_add/")
# Шаг 2: Заполнение поля mbox допустимым именем ящика
mbox_value = _generate_mbox_name()
mbox_input = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, 'input[name="mbox"]'))
)
mbox_input.clear()
mbox_input.send_keys(mbox_value)
# Шаг 3: Клик по генератору пароля
generator_span = wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "span.password-generator__text"))
)
generator_span.click()
# Шаг 4: Проверка сгенерированного пароля
password_input = wait.until(
EC.presence_of_element_located((By.ID, "passwordWithGenerator"))
)
wait.until(lambda d: len(password_input.get_attribute("value") or "") >= 8)
password = password_input.get_attribute("value")
assert 8 <= len(password) <= 20, (
f"Длина пароля {len(password)} вне диапазона 8–20"
)
allowed_chars = set(string.ascii_letters + string.digits + "-!_@#$%^&*")
invalid = [ch for ch in password if ch not in allowed_chars]
assert not invalid, f"Недопустимые символы в пароле: {invalid}"
# Шаг 5: Нажатие кнопки "Создать"
submit_btn = wait.until(
EC.element_to_be_clickable(
(By.XPATH, '//button[text()="Создать" and @type="submit"]')
)
)
submit_btn.click()
# Финальная проверка: редирект на страницу review
wait.until(EC.url_contains("/hosting/mail/mail_add/review"))
expected_url = f"{BASE_URL}/hosting/mail/mail_add/review"
assert driver.current_url == expected_url, (
f"Ожидалось {expected_url}, фактически {driver.current_url}"
)
# Проверка отображения созданного ящика на странице review
mailbox_displayed = wait.until(
EC.presence_of_element_located(
(By.XPATH, f"//*[contains(text(), '{mbox_value}')]")
)
)
assert mailbox_displayed.is_displayed(), (
f"Ящик {mbox_value} не отображается на странице review"
)Теперь усложним задачу. Спрашиваем агента: «Можно ли параметризовать тест, чтобы он проверял создание ящиков с разными допустимыми и недопустимыми именами?»
Получаем код с параметризацией test_mail_add_parametrs.py. Позитивный и негативный сценарии разделены на два теста и выполняются в разных методах: test_create_valid_mail_box и test_reject_invalid_mail_box.
import os
import string
import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
BASE_URL = os.environ.get("BASE_URL", "https://YOUR_TEST_ENV_HOST:PORT")
def _generate_mbox_name(length=12) -> str:
"""Генерирует случайное имя ящика из разрешённых символов."""
allowed = string.ascii_lowercase + string.digits + "+-._"
return "".join(allowed[b % len(allowed)] for b in os.urandom(length))
# Позитивные кейсы: имя должно быть принято
POSITIVE_CASES = [
("test.user+mail-2026", "базовое имя со спецсимволами"),
("a", "минимальная длина 1 символ"),
("a" * 64, "максимальная длина 64 символа"),
("user.name_99", "цифры, точка и подчёркивание"),
]
# Негативные кейсы: имя должно быть отклонено
NEGATIVE_CASES = [
("UPPER.case", "заглавные буквы не допускаются"),
("name with spaces", "пробелы не допускаются"),
("a" * 65, "длина больше 64 символов"),
("bad/name", "символ '/' не входит в допустимый набор"),
("emoji", "не-латинские символы"),
]
class TestMailAdd:
@pytest.mark.parametrize("mbox_name, description", POSITIVE_CASES,
ids=[d for _, d in POSITIVE_CASES])
def test_create_valid_mail_box(self, login_user, mbox_name, description):
"""Позитивный сценарий: ящик создаётся успешно."""
driver = login_user
wait = WebDriverWait(driver, 15)
# Если имя не уникальное (фиксированные строки), добавляем суффикс
if not any(c.isdigit() for c in mbox_name[-8:]):
mbox_name = f"{mbox_name}.{_generate_mbox_name(6)}"
driver.get(f"{BASE_URL}/hosting/mail/mail_add/")
mbox_input = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, 'input[name="mbox"]'))
)
mbox_input.clear()
mbox_input.send_keys(mbox_name)
generator_span = wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "span.password-generator__text"))
)
generator_span.click()
password_input = wait.until(
EC.presence_of_element_located((By.ID, "passwordWithGenerator"))
)
wait.until(lambda d: len(password_input.get_attribute("value") or "") >= 8)
password = password_input.get_attribute("value")
assert 8 <= len(password) <= 20, (
f"[{description}] Длина пароля {len(password)} вне диапазона 8–20"
)
allowed_chars = set(string.ascii_letters + string.digits + "-!_@#$%^&*")
invalid = [ch for ch in password if ch not in allowed_chars]
assert not invalid, f"[{description}] Недопустимые символы в пароле: {invalid}"
submit_btn = wait.until(
EC.element_to_be_clickable(
(By.XPATH, '//button[text()="Создать" and @type="submit"]')
)
)
submit_btn.click()
wait.until(EC.url_contains("/hosting/mail/mail_add/review"))
expected_url = f"{BASE_URL}/hosting/mail/mail_add/review"
assert driver.current_url == expected_url, (
f"[{description}] Ожидалось {expected_url}, фактически {driver.current_url}"
)
mailbox_displayed = wait.until(
EC.presence_of_element_located(
(By.XPATH, f"//*[contains(text(), '{mbox_name}')]")
)
)
assert mailbox_displayed.is_displayed(), (
f"[{description}] Ящик {mbox_name} не отображается на странице review"
)
@pytest.mark.parametrize("mbox_name, description", NEGATIVE_CASES,
ids=[d for _, d in NEGATIVE_CASES])
def test_reject_invalid_mail_box(self, login_user, mbox_name, description):
"""Негативный сценарий: ящик НЕ создаётся, показывается ошибка."""
driver = login_user
wait = WebDriverWait(driver, 15)
driver.get(f"{BASE_URL}/hosting/mail/mail_add/")
mbox_input = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, 'input[name="mbox"]'))
)
mbox_input.clear()
mbox_input.send_keys(mbox_name)
# Вариант А: фронтенд блокирует отправку (кнопка неактивна или появляется инлайн-ошибка)
error_visible = wait.until(
EC.presence_of_element_located(
(By.CSS_SELECTOR, ".error-message, .form-error, [role='alert']")
)
)
assert error_visible.is_displayed(), (
f"[{description}] Не отображена ошибка для недопустимого имени '{mbox_name}'"
)
# Вариант Б: если форма всё же отправляется — проверяем, что редиректа на review нет
# и URL остался на mail_add/
# assert "/mail_add/review" not in driver.current_urlЕсли один из тестов падает, сразу видно, какой сценарий сломался. Тестировщику остается подправить некоторые локаторы и явно указать ожидаемое поведение системы при ошибках валидации.
На написание промпта и поиск нужных селекторов ушло около 30 минут, на отладку и уточнения — еще 20. Так менее чем за час мы получили готовый автотест.
Кейс 3. Проверяем безопасность API и ищем IDOR-уязвимости
При тестировании API-методов мы пробовали использовать ИИ для генерации нескольких типов проверок:
контрактные проверки — коды ответов, структура тела ответа, бизнес-логика метода;
проверки производительности — например, как метод ведет себя при передаче объемного JSON в теле запроса;
проверки безопасности — возможные утечки данных и различные уязвимости.
Задача: проверить IDOR-уязвимость в методе редактирования услуги.
Поле service_ref — прямой идентификатор объекта. Нам нужно убедиться, что сервер проверяет права текущего пользователя на управление конкретным объектом.
JSON-схема метода edit:
{
"jsonrpc": "2.0",
"method": "edit",
"params": {
"service_ref": "new_balancer_7",
"scheduling_strategy": "weighted",
"backend_endpoints": "ip: 10.0.0.11",
"traffic_profile": "protocolBalancerV2",
"active_probe": "true",
"client_ip_passthrough": "true",
"connection_reuse": "false",
"sticky_sessions": "true",
"display_name": "Новый балансировщик 7"
}
}Первое, что предлагает агент, — отправить тот же запрос, но с service_ref другого пользователя. Ок, базовая проверка есть. Идем дальше и пробуем автоматизировать перебор значений service_ref.
Просим ассистента написать код на Python с использованием библиотеки Requests. Предварительно создаем TXT-файл со списком реальных service_ref, к которым у текущего пользователя нет доступа.
Получаем следующий код:
import requests
URL = "https://api.example.com/jsonrpc"
TOKEN = "YOUR_BEARER_TOKEN"
FILE = "service_refs.txt"
def load_service_refs(path):
with open(path, encoding="utf-8") as f:
text = f.read()
# Поддерживает значения по строкам или через запятую
return [
item.strip()
for item in text.replace(",", "\n").splitlines()
if item.strip()
]
service_refs = load_service_refs(FILE)
session = requests.Session()
session.headers.update({
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json",
})
for service_ref in service_refs:
payload = {
"jsonrpc": "2.0",
"method": "edit",
"params": {
"params": {
"service_ref": "new_balancer_7",
"scheduling_strategy": "weighted",
"backend_endpoints": "ip: 10.0.0.11",
"traffic_profile": "protocolBalancerV2",
"active_probe": "true",
"client_ip_passthrough": "true",
"connection_reuse": "false",
"sticky_sessions": "true",
"display_name": "Новый балансировщик 7"
},
} }
response = session.post(URL, json=payload, timeout=10)
try:
result = response.json()
except ValueError:
result = response.text
print(service_ref, response.status_code, result)Остается изменить URL запроса, доработать авторизацию и получение токена под свой проект. После этого тест с перебором необходимых значений готов.
Как внедрение LLM повлияло на количество тестов и ошибок
Мы собрали статистику и оценили эффективность применения ИИ по ключевым направлениям.
Скорость подготовки тестовых данных и документации выросла в среднем на 70%. ИИ взял на себя значительную часть рутинной и шаблонной работы.
Время подготовки автотестов сократилось на 30%. ИИ оказался полезным помощником в автоматизации, однако весь сгенерированный код по-прежнему требует тщательного ревью специалистом.
Количество проверок API-методов на безопасность и валидацию увеличилось на 40%. С помощью ИИ мы расширили набор сценариев за пределы стандартных проверок по документации.
Количество багов, с которыми пользователи столкнулись на продакшене, уменьшилось на 70% (по метрике bugs per KLOC).
При этом общее количество тест-кейсов выросло на 35%, а время, которое QA-специалисты затрачивают на тестирование, сократилось на 20% (по метрикам времени, затраченного на фиксированный объем тестов).
По результатам эксперимента мы считаем внедрение ИИ в процессы тестирования успешным. В дальнейшем планируем масштабировать этот подход и расширять применение нейросетей в работе QA-инженеров.
Тестировщик в связке с искусственным интеллектом не просто ищет баги. У него появляется больше времени и возможностей для детального проектирования сложных сценариев, поиска нестандартных решений и их применения в работе.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.