Подпишитесь на наш ТЕЛЕГРАМ КАНАЛ ПО АВТОМАТИЗАЦИИ ТЕСТИРОВАНИЯ
Перевод статьи «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. При использовании больших наборов данных и более продвинутых методов этот подход может показать значительно лучшие результаты в зрелых проектах автоматизации.
Пингбэк: 20 лучших инструментов для автоматизации тестирования - QaRocks