Modern QA2026Основной цикл ReAct для тестирования
Join

Course03 Agentic Testing Architectures

Cutting-edge · Chapter 03

Основной цикл ReAct для тестирования

Updated Jul 2026

От скриптов к агентам: фундаментальный сдвиг

Традиционная автоматизация тестирования является императивной: вы пишете пошаговые скрипты, которые выполняются детерминированно. Агентное тестирование принципиально отличается. Агент наблюдает состояние системы, рассуждает о следующем действии, действует и оценивает результат -- всё в рамках цикла, который адаптируется к неожиданным ситуациям.

ReAct (Reason + Act) -- это фундаментальный паттерн для агентных систем. Первоначально описанный для LLM-агентов общего назначения, он напрямую отображается на рабочие процессы тестирования.

Четыре фазы

+---------------------------------------------------+
|                                                   |
|  OBSERVE --> THINK --> ACT --> EVALUATE --> LOOP  |
|     |           |         |          |            |
|  Read page   Decide    Execute    Check if        |
|  state,      what to   the test   assertion       |
|  logs,       test      action     passed or       |
|  errors      next                 failed          |
|                                                   |
+---------------------------------------------------+

Фаза 1: НАБЛЮДЕНИЕ (OBSERVE)

Агент собирает информацию о текущем состоянии тестируемой системы. В браузерном тестировании это означает чтение содержимого страницы, URL, ошибок консоли и сетевой активности. В API-тестировании -- чтение кодов статуса ответов, заголовков и тел ответов.

class Observation:
    url: str                    # Current page URL
    text: str                   # Visible text content (truncated)
    errors: list[str]           # Console errors
    network_requests: list[dict]  # Recent HTTP requests
    screenshot_path: str | None   # Path to current screenshot
    page_title: str              # Document title

Ключевой инсайт: Качество наблюдения определяет качество рассуждений агента. Богатое наблюдение (текст + ошибки + сеть) даёт лучшие решения, чем скудное (только URL).

Фаза 2: РАЗМЫШЛЕНИЕ (THINK)

Агент анализирует наблюдение и решает, что делать дальше. Именно здесь способность LLM к рассуждению незаменима. Он учитывает:

  • Цель теста (что мы пытаемся проверить?)
  • Текущее состояние (где мы сейчас?)
  • Историю (что мы уже пробовали?)
  • Ограничения (сколько шагов осталось? какие действия разрешены?)
prompt = f"""
Objective: {test_objective}
Current URL: {observation.url}
Page content (truncated): {observation.text[:2000]}
Console errors: {observation.errors}
History: {self.history[-5:]}  # Last 5 actions

What should I do next? Choose one:
- NAVIGATE <url>
- CLICK <selector>
- TYPE <selector> <text>
- ASSERT <condition>
- DONE <pass|fail> <reason>
"""
decision = self.llm.generate(prompt)

Фаза 3: ДЕЙСТВИЕ (ACT)

Агент выполняет принятое решение на тестируемой системе. Это единственная фаза, которая изменяет состояние.

def execute(self, decision: str) -> ActionResult:
    if decision.startswith("NAVIGATE"):
        url = decision.split(" ", 1)[1]
        self.browser.navigate(url)
    elif decision.startswith("CLICK"):
        selector = decision.split(" ", 1)[1]
        self.browser.click(selector)
    elif decision.startswith("TYPE"):
        parts = decision.split(" ", 2)
        self.browser.type(parts[1], parts[2])
    elif decision.startswith("ASSERT"):
        condition = decision.split(" ", 1)[1]
        return self.evaluate_assertion(condition)
    elif decision.startswith("DONE"):
        return self.parse_final_result(decision)

Фаза 4: ОЦЕНКА (EVALUATE)

После действия агент оценивает, удалось ли действие и достигнута ли цель теста. Если цель ещё не достигнута, цикл продолжается.

if result.is_terminal:
    return TestResult(
        status=result.status,
        reason=result.reason,
        steps_taken=self.step_count,
        history=self.history
    )
# Otherwise, loop back to OBSERVE

Полная реализация агента ReAct

class ReActTestAgent:
    def __init__(self, llm, browser, max_steps=20):
        self.llm = llm
        self.browser = browser
        self.max_steps = max_steps
        self.history = []

    def run(self, test_objective: str) -> TestResult:
        """Execute a test using the ReAct loop."""
        for step in range(self.max_steps):
            # OBSERVE
            observation = self.browser.get_state()

            # THINK
            prompt = f"""
            Objective: {test_objective}
            Current URL: {observation.url}
            Page content (truncated): {observation.text[:2000]}
            Console errors: {observation.errors}
            History: {self.history[-5:]}

            What should I do next? Choose one:
            - NAVIGATE <url>
            - CLICK <selector>
            - TYPE <selector> <text>
            - ASSERT <condition>
            - DONE <pass|fail> <reason>
            """
            decision = self.llm.generate(prompt)

            # ACT
            action_result = self.execute(decision)
            self.history.append({
                "step": step,
                "observation": observation.summary,
                "decision": decision,
                "result": action_result
            })

            # EVALUATE
            if decision.startswith("DONE"):
                return self.parse_result(decision)

        return TestResult(
            status="TIMEOUT",
            reason=f"Exceeded {self.max_steps} steps"
        )

Почему ReAct важен для тестирования

Ключевой инсайт: агент не следует фиксированному скрипту. Он адаптирует следующее действие на основе того, что наблюдает. Это создаёт возможности, недоступные детерминированным скриптам:

Возможность Скриптовое тестирование Агентное тестирование ReAct
Отсутствующий элемент Тест сразу падает Агент ищет альтернативный селектор
Неожиданное всплывающее окно Тест падает или блокируется Агент закрывает окно и продолжает
Медленная загрузка страницы Таймаут (фиксированное ожидание) Агент наблюдает состояние загрузки, ждёт интеллектуально
Изменённый макет Тест падает (сломанный селектор) Агент рассуждает о новом макете
Состояние ошибки Тест идёт только по happy path Агент исследует ошибку
Новая функциональность Тест игнорирует её Агент замечает и может исследовать

Пример: адаптивное исследование ошибки

Step 1: OBSERVE — Login page loaded, email and password fields visible
Step 2: THINK  — "I should fill in valid credentials and click login"
Step 3: ACT    — Type email, type password, click submit
Step 4: OBSERVE — Error banner appeared: "Account locked after 5 failed attempts"
Step 5: THINK  — "Unexpected error. This is not a test failure, it is test
                  environment state. The account was locked by a previous test run.
                  I should report this as an environment issue."
Step 6: ACT    — DONE fail "Account locked - environment state issue, not a bug"

Детерминированный скрипт просто упал бы с ошибкой «Ожидался URL дашборда, получен URL логина». Агент предоставляет диагностическую информацию.

Буфер истории

Буфер истории критически важен для предотвращения бесконечных циклов и обеспечения обучения в рамках одного тестового прогона.

# Keep the last N steps to fit in context window
self.history = self.history[-5:]

# Each history entry includes:
{
    "step": 3,
    "observation": "Page shows login form with email/password fields",
    "decision": "TYPE input[name=email] test@example.com",
    "result": "success"
}

Без истории: Агент может кликать одну и ту же кнопку многократно, не помня, что уже пробовал это.

С историей: Агент рассуждает: «Я уже пробовал нажать кнопку отправки, и это не привело к навигации. Дайте-ка проверю, не пропустил ли я ошибку валидации.»

Ограничения ReAct для тестирования

  1. Недетерминированность. Одна и та же цель теста может привести к разным путям в разных запусках. Это преимущество для исследования, но проблема для регрессионного тестирования.

  2. Дороговизна. Каждый шаг требует вызова LLM. 20-шаговый тест стоит в 20 раз больше токенов, чем скрипт.

  3. Медлительность. Задержка LLM (100-500 мс на вызов) накапливается. 20-шаговому агенту требуется 2-10 секунд только на рассуждения, плюс время выполнения действий.

  4. Риск галлюцинаций. Агент может «видеть» несуществующие элементы или неверно интерпретировать содержимое страницы.

  5. Сложность отладки. Когда агент ReAct падает, приходится прослеживать всю историю его рассуждений, чтобы понять причину. Это сложнее, чем отладка линейного скрипта.

Когда использовать ReAct, а когда скрипты

Используйте ReAct Используйте скрипты
Исследовательское тестирование Регрессионное тестирование
Обнаружение новых функций Проверка известных потоков
Самовосстанавливающиеся наборы тестов Тесты-шлюзы CI/CD
Сложные многошаговые рабочие процессы Простые CRUD-операции
Окружения с частыми изменениями UI Стабильные окружения

Ключевой вывод

Цикл ReAct -- это строительный блок всего агентного тестирования. Он превращает тестирование из «следуй этим точным шагам» в «достигни этой цели, адаптируясь к тому, что наблюдаешь». Освойте этот паттерн, и вы поймёте основу каждого инструмента агентного тестирования на рынке.