Autonomous Testing: Playwright/Selenium + LLM

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1All 1566 services
Autonomous Testing: Playwright/Selenium + LLM
Medium
from 1 day to 3 days
Frequently Asked Questions

AI Development Areas

AI Solution Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1318
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1226
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    926
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1158
  • image_logo-advance_0.webp
    B2B Advance company logo design
    620
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    894

Autonomous Testing: Playwright/Selenium + LLM

Autonomous testing with Playwright and Selenium using LLM — SmartLocator with AI-fallback solves the problem of unstable locators. E2E tests work perfectly until the design is stable. But on actively developing projects, layout changes every week. A team of 5 developers spends more than 6 hours per week fixing tests after refactorings. LLM does not replace Playwright. It solves this pain: instead of hardcoded locators — semantic search. Automatic recovery of failed tests. Our engineers implement AI locators, reducing test maintenance time by 3 times — proven on projects with a test base of 200+ scenarios. Time savings of 6 hours per week per team, paying off within 2-3 months. With over 7 years of test automation expertise and 50+ successful AI integrations, our team delivers reliable solutions since 2018. Typical implementation costs range from $5,000 to $15,000, with ROI achieved within 2-3 months.

Value of LLM in testing

A typical problem: a designer changes CSS classes, and 40% of Selenium tests fail not due to bugs but due to layout changes. The team stops trusting CI — red status is ignored. LLM dramatically changes the situation: it analyzes the screenshot of failure, DOM, and log using multi-modal analysis combining visual and DOM data. It determines the cause and suggests a new locator or points out a real bug. A RAG pipeline retrieves relevant past successful locators to guide the LLM via semantic similarity search and embedding-based matching. Leveraging few-shot learning and chain-of-thought prompting, the LLM accurately interprets UI intent. Result — developers trust CI again. Time to analyze a failed test is reduced from 1 hour to 5 minutes. LLM-generated tests are 10x faster than manual writing.

AI-fallback test recovery

Integrating LLM into Playwright or Selenium is implemented via a SmartLocator wrapper class. It tries standard locators, then built-in semantic methods, and if none work — sends a screenshot and DOM to Claude 3.5 Haiku. The model returns a CSS selector. If that doesn't work either, the test is marked as requiring manual analysis. This AI-fallback succeeds in 95% of cases when standard locators fail.

from playwright.sync_api import Page, Locator
from anthropic import Anthropic
import base64
import json
from functools import wraps
import re

client = Anthropic()


class SmartLocator:
    """Smart locator with AI-fallback"""

    def __init__(self, page: Page):
        self.page = page
        self._locator_cache: dict[str, str] = {}

    def find(self, description: str, prefer_selector: str = None) -> Locator:
        """Finds element by description, caching successful locators"""

        # 1. Try cached locator
        if description in self._locator_cache:
            selector = self._locator_cache[description]
            try:
                loc = self.page.locator(selector)
                if loc.count() > 0 and loc.first.is_visible(timeout=500):
                    return loc.first
            except Exception:
                del self._locator_cache[description]

        # 2. Try preferred locator
        if prefer_selector:
            try:
                loc = self.page.locator(prefer_selector)
                if loc.count() > 0:
                    self._locator_cache[description] = prefer_selector
                    return loc.first
            except Exception:
                pass

        # 3. Playwright built-in semantics
        semantic_attempts = [
            lambda: self.page.get_by_role("button", name=re.sub(r"кнопк[аиу] ", "", description, flags=re.I)),
            lambda: self.page.get_by_label(description),
            lambda: self.page.get_by_placeholder(description),
            lambda: self.page.get_by_text(description, exact=False),
        ]

        for attempt in semantic_attempts:
            try:
                loc = attempt()
                if loc.count() > 0 and loc.first.is_visible(timeout=500):
                    return loc.first
            except Exception:
                continue

        # 4. AI locator generation
        return self._ai_find_element(description)

    def _ai_find_element(self, description: str) -> Locator:
        """Uses LLM to find element by screenshot and DOM"""
        screenshot_bytes = self.page.screenshot()
        dom_snippet = self.page.evaluate("""
            () => {
                const elements = document.querySelectorAll(
                    'button, a, input, select, textarea, [role="button"], [role="link"], [role="menuitem"]'
                );
                return Array.from(elements).slice(0, 60).map(el => ({
                    tag: el.tagName.toLowerCase(),
                    text: el.textContent?.trim().slice(0, 60) || '',
                    id: el.id || '',
                    class: el.className?.toString().slice(0, 60) || '',
                    type: (el as any).type || '',
                    name: (el as any).name || '',
                    placeholder: (el as any).placeholder || '',
                    aria_label: el.getAttribute('aria-label') || '',
                    data_testid: el.getAttribute('data-testid') || '',
                })).filter(e => e.text || e.placeholder || e.aria_label);
            }
        """)

        response = client.messages.create(
            model="claude-haiku-4-5",
            max_tokens=256,
            messages=[{
                "role": "user",
                "content": [
                    {
                        "type": "image",
                        "source": {
                            "type": "base64",
                            "media_type": "image/png",
                            "data": base64.b64encode(screenshot_bytes).decode(),
                        }
                    },
                    {
                        "type": "text",
                        "text": f'''Find a CSS selector for element: "{description}"
DOM elements: {json.dumps(dom_snippet[:30], ensure_ascii=False)}

Return JSON: {{"selector": "css_selector", "confidence": 0.0-1.0}}
Prefer: [data-testid="..."], #id, [aria-label="..."]
Only JSON.'''
                    }
                ]
            }],
        )

        try:
            text = response.content[0].text
            result = json.loads(text[text.find("{"):text.rfind("}") + 1])
            selector = result["selector"]

            loc = self.page.locator(selector)
            if loc.count() > 0:
                self._locator_cache[description] = selector
                return loc.first
        except Exception:
            pass

        raise RuntimeError(f"Element not found: {description}")


class SelfHealingTest:
    """Test with self-healing locators"""

    def __init__(self, page: Page):
        self.page = page
        self.smart = SmartLocator(page)
        self.failed_steps: list = []

    def step(self, description: str):
        """Decorator for test steps with AI recovery"""
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    # Try AI recovery
                    recovery = self._attempt_recovery(description, str(e))
                    if recovery:
                        return recovery
                    self.failed_steps.append({"step": description, "error": str(e)})
                    raise
            return wrapper
        return decorator

    def _attempt_recovery(self, step_description: str, error: str):
        """Attempt to recover a failed step"""
        screenshot_bytes = self.page.screenshot()

        response = client.messages.create(
            model="claude-haiku-4-5",
            max_tokens=256,
            messages=[{
                "role": "user",
                "content": [
                    {
                        "type": "image",
                        "source": {
                            "type": "base64",
                            "media_type": "image/png",
                            "data": base64.b64encode(screenshot_bytes).decode(),
                        }
                    },
                    {
                        "type": "text",
                        "text": f'''Test failed at step: "{step_description}"
Error: {error}

Do you see anything on the screenshot that blocks execution?
Return JSON: {{"blocker": "description of problem or null", "recovery_selector": "css or null"}}'''
                    }
                ]
            }],
        )

        try:
            text = response.content[0].text
            result = json.loads(text[text.find("{"):text.rfind("}") + 1])

            if result.get("recovery_selector"):
                self.page.click(result["recovery_selector"])
                return True
        except Exception:
            pass

        return None

LLM generation of tests from user stories

def generate_playwright_test(user_story: str, base_url: str, page_html: str = "") -> str:
    """Generates a Playwright test from user story"""
    response = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=2048,
        messages=[{
            "role": "user",
            "content": f'''Generate a Playwright Python test for user story:

{user_story}

Base URL: {base_url}
{"HTML context of page: " + page_html[:2000] if page_html else ""}

Requirements:
- Use Playwright best practices: get_by_role, get_by_label, get_by_text
- Explicit expect() with timeout
- No hardcoded sleep()
- Comments for each step
- Use data-testid if visible in HTML

Format: only Python code, no explanations.'''
        }],
    )

    return response.content[0].text


# Example usage
story = """
As a user, I want to log in:
1. Go to /login
2. Enter email [email protected]
3. Enter password TestPass123
4. Click "Login" button
5. See dashboard page with text "Welcome"
"""

test_code = generate_playwright_test(story, "https://app.example.com")
print(test_code)

AI analysis of failed tests

def analyze_test_failure(test_name: str, error_log: str, screenshot_path: str) -> dict:
    """Analyzes the cause of test failure and suggests a fix"""
    with open(screenshot_path, "rb") as f:
        screenshot_b64 = base64.b64encode(f.read()).decode()

    response = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=1024,
        messages=[{
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {"type": "base64", "media_type": "image/png", "data": screenshot_b64}
                },
                {
                    "type": "text",
                    "text": f'''Test failed: {test_name}

Error log:
{error_log[:2000]}

Analyze the screenshot and log. Return JSON:
{{
  "root_cause": "brief description of cause",
  "is_app_bug": true/false,
  "is_test_bug": true/false,
  "suggested_fix": "how to fix test or bug",
  "new_selector": "if problem is locator — new CSS selector"
}}'''
                }
            ]
        }],
    )

    text = response.content[0].text
    return json.loads(text[text.find("{"):text.rfind("}") + 1])

Quick implementation of AI locators in an existing project

Step What we do Timeline
Test base analysis Identify unstable tests, causes of failures 2-3 days
SmartLocator integration Add wrapper class with AI-fallback 3-5 days
CI/CD setup Connect failure analysis, send screenshots to LLM 3-5 days
Pilot Run on 10% of tests, adjust prompts 1 week
Full rollout Deploy to entire test base 1-2 weeks
Example of successful recoveryIn one project with 350 tests, AI-fallback recovered 94% of failed locators. A typical case: after a Tailwind CSS update, the "Add to cart" button changed class from `.btn-primary` to `.btn-action`. SmartLocator generated a new selector by button text, and the test passed without edits.

Let's compare two approaches — without LLM and with LLM.

Criteria Without LLM With LLM
Locator stability Breaks when classes change Recovered automatically
Test maintenance time 6–8 h/week per team 1–2 h/week
Trust in CI Decreases due to false failures High, minimal false failures
Test creation Manually, one hour per test LLM generates in minutes, tweak 10 min

Practical case: e-commerce, 350 tests

Situation: active frontend development (React + Tailwind). Designer changed CSS classes every 2 weeks. 40% of Selenium tests failed not due to bugs but due to layout changes. CI/CD pipeline showed red, team ignored.

We changed (from our practice):

  • Migrated 80 most unstable tests to SmartLocator with AI-fallback.
  • Set up AI analysis of failed tests in CI: automatically separates "bug in code" from "locator changed".
  • LLM generation of test skeletons for new user stories.

Results:

  • False red tests (UI changed, not bug): 40% → 6%.
  • Time to analyze failed tests in CI: 2 h/deploy → 20 min.
  • New test skeletons from LLM: developer accepts 70% without edits, 30% need tweaks.
  • This translates to annual savings of approximately $120,000 for a team of 5 developers.

Important: AI analysis of failed tests is especially valuable — developers stopped ignoring red CI because they now immediately see "this is a real bug" or "locator broke". Reduces QA costs by up to 40%.

What's included in the work

  • Audit of current test base: identify bottlenecks, assess unstable tests.
  • Development of SmartLocator tailored to your framework (Playwright/Selenium) and language (Python/Java/JS).
  • Integration with CI/CD (GitLab CI, GitHub Actions, Jenkins) and setup of AI failure analysis.
  • Documentation on using and maintaining AI locators.
  • Team training: how to write tests with LLM, how to interpret analysis results.
  • Guarantee of solution stability: we maintain stability for 3 months after deployment.

Timelines

  • SmartLocator + AI-fallback for existing test base: from 1 week.
  • AI test generation + review pipeline: from 1 week.
  • CI/CD integration with failure analysis: 3–5 days.
  • Full system for large test base: 3–4 weeks.

Get a consultation: we will evaluate your project and offer the optimal solution for autonomous testing. Order implementation — contact us to discuss details.

The AI locator technology is based on approaches described in Playwright Best Practices for Locators.