
Привет, Хабр! Меня зовут Евгения Мясникова, я QA-инженер SpaceWeb. Передавать искусственному интеллекту рутинные задачи тестирования — подготовку черновиков тест-кейсов, первичное покрытие, оценку трудозатрат и другие — уже стало привычной практикой. Однако при таком разнообразии инструментов важно понять, как эффективно встроить ИИ-агентов в рабочие процессы и распределить задачи между специалистом и нейросетью.
В этой статье расскажу, как мы совместно с руководителем отдела исследований и разработки SpaceWeb Виталием Киреевым разработали первоначальную модель взаимодействия QA-специалиста с LLM. Для работы с чувствительными данными мы интегрировали агента с локальной моделью Qwen3.8-27B. На реальных примерах покажу, в каких задачах ИИ действительно нам помог, а где его применение пока вызывает вопросы.
Навигация по тексту:
-
Какие задачи тестирования мы решили передать ИИ
-
Как организовали работу QA-инженеров с LLM
-
Практические кейсы: тестовая документация, автотесты и проверка API
-
Как ИИ повлиял на скорость и результаты тестирования
Какие задачи тестирования мы решили передать ИИ
Тестировщики в современной ИТ-компании работают с огромной кодовой базой, десятками 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 osimport stringfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom 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 osimport stringimport pytestfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECBASE_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 requestsURL = "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-инженеров.
Тестировщик в связке с искусственным интеллектом не просто ищет баги. У него появляется больше времени и возможностей для детального проектирования сложных сценариев, поиска нестандартных решений и их применения в работе.
А какие задачи тестирования вы уже доверяете ИИ?
Источник: habr.com