Самовосстановление в веб-автоматизации

Подпишитесь на наш ТЕЛЕГРАМ КАНАЛ ПО АВТОМАТИЗАЦИИ ТЕСТИРОВАНИЯ

Перевод статьи «Self-Healing Web Test Automation».

В этой статье я хочу поделиться своим опытом работы над одной из самых болезненных проблем в автоматизации веб-тестирования. Этот путь оказался непростым, но, возможно, выводы, к которым я пришёл, откликнутся и у вас.

Проблема и мотивация

В классической автоматизации, если элемент на странице не найден, тест сразу падает. Отсюда и появился общий вопрос: могут ли тесты восстанавливаться сами?

Что такое «самовосстановление»?

Самовосстановление в веб-автоматизации — это способность автотестов автоматически обнаруживать и устранять сбои, вызванные изменениями в пользовательском интерфейсе приложения.

Например, если локатор элемента (ID или CSS-селектор) изменился после обновления страницы, обычный тест сломается и потребует ручной правки. Самовосстанавливающиеся тесты используют такие техники, как машинное обучение и умные эвристики, чтобы находить альтернативные локаторы или динамически адаптироваться. Благодаря этому тест продолжает выполняться без участия человека.

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

<!-- Old -->
<button id="submit-btn" class="btn-primary">Submit</button>

<!-- New -->
<button id="submit-button" class="btn btn-primary btn-large">Submit</button>
# This code had stopped working
submit_button = driver.find_element(By.ID, "submit-btn")

Это всего лишь небольшое изменение в ID, но некоторые из наших тестов всё равно упали.

В нашем подходе к самовосстановлению тестов всё работает примерно так, как показано в коде ниже:

# Self-healing approach  
def get_element_with_healing(self, selector_dict, timeout=5000):
    # First try the primary selector
    element = self.page.locator(selector_dict["primary"])
    if element.is_visible():
        return element
    
    # Predict alternatives using ML model
    alternatives = self.ml_predictor.predict(
        self.page.content(), 
        selector_dict.get("type"), 
        selector_dict.get("hints")
    )
    
    for alt in alternatives:
        try:
            element = self.page.locator(alt['selector'])
            if element.is_visible():
                self.logger.info(f"Self-healing successful: {alt['selector']}")
                return element
        except:
            continue
    
    return None

Основная логика ML подхода

Сбор обучающих данных

В проекте мы собираем данные с реальных веб-сайтов для обучения ML-модели.

# From MLController class - src/ml/ml_controller.py
TRAINING_SITES = [
    {"name": "Facebook", "url": "https://www.facebook.com"},
    {"name": "Google", "url": "https://www.google.com"},
    {"name": "TGO Yemek", "url": "https://tgoyemek.com/restoranlar"},
    {"name": "Sahibinden", "url": "https://www.sahibinden.com"},
    {"name": "Hepsiburada", "url": "https://www.hepsiburada.com"},
    # ... total 30+ sites
]

COMMON_ELEMENTS = [
    {"locator_id": "search_input", "hints": ["ara", "search", "query", "search box"]},
    {"locator_id": "login_button", "hints": ["giriş", "login", "signin", "user login"]},
    {"locator_id": "main_navigation", "hints": ["menü", "menu", "navigation", "navbar"]},
    # ... 20+ element types
]

Процесс сбора данных

Главная проблема заключается в том, что объем данных ограничен. В среднем с одного сайта удаётся извлечь только 5–10 полезных элементов, что даёт суммарно 150–300 обучающих примеров. По современным меркам машинного обучения это крайне мало.

def collect_training_data(self):
    training_data = []
    
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        
        for site in self.TRAINING_SITES:
            page = browser.new_page()
            page.goto(site["url"])
            html_content = page.content()
            
            # Extract selectors for each element type
            for element_info in self.COMMON_ELEMENTS:
                extracted = self._extract_common_elements(
                    BeautifulSoup(html_content), site["url"], element_info
                )
                
                for element in extracted:
                    element["full_html"] = html_content
                    training_data.append(element)

Чтобы подготовить качественные обучающие данные для системы самовосстанавливающейся автоматизации тестов, мы используем многошаговый пайплайн:

Запускаем headless-браузер Chrome через Playwright, чтобы симулировать реальные пользовательские действия на живых сайтах. Система обходит специально подобранный список из 30+ веб-платформ, включая такие популярные, как Facebook и Google.

Для каждого сайта сохраняем полную структуру DOM, что позволяет анализировать весь контекст страницы. Далее ищем общие UI элементы (поиск, кнопки входа, навигационные меню) — именно они критически важны для типичных сценариев автотестирования.

Используем BeautifulSoup для парсинга HTML и извлечения ключевых характеристик найденных элементов: ID, классы, атрибуты и текстовое содержимое.

Чтобы сохранить более широкий контекст страницы, мы добавляем к каждому элементу полный HTML-контент. Это позволяет ML модели учитывать окружение и делать более точные предсказания, восстанавливая сломанные локаторы.

Далее извлекаем признаки элементов для нашей ML модели следующим образом:

def extract_element_features(element):
    features = {
        # Basic HTML properties
        'tag_name': element.name,
        'has_id': bool(element.get('id')),
        'has_class': bool(element.get('class')),
        'class_count': len(element.get('class', [])),
        
        # Text content
        'text_length': len(element.get_text(strip=True)),
        'has_text': bool(element.get_text(strip=True)),
        
        # Structural properties
        'parent_tag': element.parent.name if element.parent else '',
        'sibling_count': len(element.parent.children) if element.parent else 0,
        
        # Turkish/English hint matching
        'matches_search_hints': any(hint in str(element).lower() 
                                   for hint in ['ara', 'search', 'query']),
        'matches_login_hints': any(hint in str(element).lower() 
                                 for hint in ['giriş', 'login', 'signin'])
    }
    return features

Чтобы точно предсказывать и восстанавливать сломанные локаторы, мы формируем компактный, но разнообразный набор признаков для каждого веб-элемента. Все признаки делятся на четыре категории:

  • Структурные признаки. HTML-теги и CSS-свойства, которые помогают определить тип элемента (например, кнопка или поле ввода) и особенности его оформления.
  • Контентные признаки. Анализируем видимый текст, чтобы отличить декоративные элементы от функциональных (например, кнопки «Submit» или «Login»).
  • Контекстные признаки. Исследуем связи родитель–потомок в DOM-дереве, чтобы понять, какое место элемент занимает в структуре страницы.
  • Семантические признаки. Выполняем поиск ключевых слов на турецком и английском языках, чтобы лучше интерпретировать роль элемента. Это особенно полезно для мультиязычных интерфейсов.

Такой набор признаков был выбран исходя из практичности и эффективности:

  • Кросс-фреймворковая совместимость — признаки одинаково хорошо работают с популярными CSS-фреймворками (Bootstrap, Material UI) и кастомными компонентами.
  • Языковая независимость — система обрабатывает сайты как на английском, так и на турецком языках, что расширяет применимость в разных продуктах и регионах.
  • Лёгкость — всего 10–12 признаков на элемент позволяют модели работать быстро и эффективно даже на машинах с ограниченными ресурсами.
  • Интерпретируемость — все признаки читаемы человеком, что упрощает отладку модели и повышает доверие к её предсказаниям.
# For a search input like: <input class="form-control search-input" placeholder="Ara..." id="search">
features = {
    'tag_name': 'input',
    'has_id': True,
    'has_class': True, 
    'class_count': 2,
    'text_length': 0,
    'has_text': False,
    'parent_tag': 'form',
    'sibling_count': 3,
    'matches_search_hints': True,  # "search" found in class name
    'matches_login_hints': False
}

В проекте мы используем следующий подход:

# src/core/ml_locator_predictor.py
class MLLocatorPredictor(LocatorPredictor):
    def __init__(self, model_path=None, vector_size=128):
        self.vector_size = vector_size
        self.embedder = ElementEmbedder(vector_size=vector_size)
        
        # Using simple Random Forest
        self.classifier = RandomForestClassifier(n_estimators=50, random_state=42)
        
    def predict_from_html(self, html_content, element_type, hints):
        soup = dom_utils.parse_html(html_content)
        candidates = self._find_candidate_elements(soup, element_type, hints)
        
        # Feature extraction
        candidate_features = []
        for element in candidates:
            features = dom_utils.extract_element_features(element)
            candidate_features.append(features)
        
        # Vectorization and prediction
        feature_vectors = self._vectorize_features(candidate_features)
        predictions = self._make_predictions(feature_vectors)
        
        return self._convert_predictions_to_locators(predictions, candidates)

Архитектурные решения

1. Мы остановились на Random Forest вместо глубоких нейросетей, потому что он:

  • хорошо работает на небольших датасетах (150–300 обучающих примеров)
  • обеспечивает быструю инференс-скорость (~50 мс на одно предсказание)
  • даёт интерпретируемую важность признаков
  • не требует GPU для развёртывания

2. 128-мерные эмбеддинги: оптимальный баланс между выразительностью и вычислительной эффективностью при ограниченных данных.

3. Многоступенчатый пайплайн: разделяет задачи на этапы, что облегчает отладку и оптимизацию.

Этап 1: Фильтрация кандидатов
Вместо анализа всех DOM-элементов страницы (а их могут быть тысячи), мы сначала сокращаем круг поиска до примерно 5–15 наиболее вероятных кандидатов.
Фильтрация основана на:

  • типе элемента (например, input, button, a),
  • наличии ключевых слов в атрибутах (например, placeholder="search"),
  • базовых структурных требованиях (элемент должен быть видимым и кликабельным).

Этап 2: Извлечение признаков
Каждый отфильтрованный кандидат преобразуется в компактный вектор признаков размерностью 10–12.

Например, такой элемент:

<input class="search-box" placeholder="Search...">

Может быть представлен, например, так:

[1, 1, 1, 2, 0, 0, 'form', 3, 1, 0, ...]
Эти признаки кодируют структурную, текстовую, контекстную и семантическую информацию.

Этап 3: ML-оценка
Мы используем модель Random Forest, чтобы присвоить каждому кандидату оценку уверенности (от 0.0 до 1.0).
Чем выше значение, тем больше вероятность, что найденный элемент соответствует исходному локатору, который «сломался» в тесте.

Этап 4: Генерация селектора
Кандидаты с наивысшими оценками преобразуются обратно в рабочие CSS-селекторы.
Система возвращает ранжированный список, например:

[
  {"selector": ".search-box", "confidence": 0.87},
  {"selector": "#main-search-input", "confidence": 0.74}
]

Эти селекторы затем могут быть использованы для автоматического восстановления сломанного теста или предложены разработчику в качестве вариантов исправления.

Результаты оценки

Мы провели комплексное тестирование нашего проекта на 20 различных веб-сайтах и 5 различных типах элементов.

Общая статистика тестов

  • Общее количество тестов: 100 (20 сайтов × 5 типов элементов)
  • Успешные восстановления: 53
  • Процент успешных тестов: 53%
  • Средняя продолжительность теста: ~30–35 секунд
                    | Element Type    | Success Rate |
                    | --------------- | ------------ |
                    | Search Input    | 65%          |
                    | Login Button    | 55%          |
                    | Navigation Menu | 50%          |
                    | Signup Form     | 40%          |
                    | Filter Dropdown | 55%          |
  • Процент успешности по категории сайтов
                    | Site Category | Success Rate |
                    | ------------- | ------------ |
                    | Social Media  | 60%          |
                    | E-commerce    | 50%          |
                    | News & Media  | 55%          |
                    | Food Delivery | 45%          |
                    | Classifieds   | 52%          |
  • Лучшие по результатам тестирования сайты
              | Site          | Successful Tests | Success Rate |
              | ------------- | ---------------- | ------------ |
              | Facebook      | 5/5              | 100%         |
              | Instagram     | 5/5              | 100%         |
              | LinkedIn      | 5/5              | 100%         |
              | Trendyol      | 5/5              | 100%         |
              | Apple         | 5/5              | 100%         |
              | Wikipedia     | 5/5              | 100%         |
              | StackOverflow | 5/5              | 100%         |
  • Худшие по результатам тестирования сайты
                | Site        | Successful Tests | Success Rate |
                | ----------- | ---------------- | ------------ |
                | Google      | 0/5              | 0%           |
                | Hepsiburada | 0/5              | 0%           |
                | Amazon      | 0/5              | 0%           |
                | Ekşi Sözlük | 0/5              | 0%           |
                | Hürriyet    | 0/5              | 0%           |
                | Milliyet    | 0/5              | 0%           |

В этом исследовании мы изучали подход самовосстанавливающейся автоматизации веб-тестирования, который использует машинное обучение для восстановления сломанных локаторов, вызванных динамическими изменениями веб-элементов. Собрав реальные обучающие данные с более чем 30 разнообразных сайтов и выделив значимые структурные, контентные, контекстуальные и семантические признаки, мы разработали лёгкую, но эффективную модель на основе Random Forest.

Это была моя первая попытка применения машинного обучения. Из-за ограниченного объёма обучающих данных процент успешности не достиг желаемого уровня, но это оказалось ценным опытом. Наше комплексное тестирование показало процент успешности 53%, что демонстрирует потенциал самовосстанавливающейся автоматизации для значительного снижения ручного сопровождения тестов.

Хотя лучшие сайты, такие как Facebook и LinkedIn, показали идеальные показатели восстановления, остаются проблемы для более сложных или динамичных сайтов, таких как Google и Amazon. При использовании больших наборов данных и более продвинутых методов этот подход может показать значительно лучшие результаты в зрелых проектах автоматизации.

🔥 Какой была ваша первая зарплата в QA и как вы искали первую работу? 

Мега обсуждение в нашем телеграм-канале о поиске первой работы. Обмен опытом и мнения.

Читать в телеграм

1 комментарий к “Самовосстановление в веб-автоматизации”

  1. Пингбэк: 20 лучших инструментов для автоматизации тестирования - QaRocks

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *