Skip to content

AI in Testing — Automated Test Generation & Intelligent QA

DodaTech Updated 2026-06-24 9 min read

In this tutorial, you'll learn about AI in Testing. We cover key concepts, practical examples, and best practices.

AI in testing applies machine learning and large language models to automate test generation, maintenance, and analysis — reducing manual effort and finding defects that traditional approaches miss.

What You'll Learn

You'll learn how AI generates test cases from requirements and code, creates self-healing selectors that adapt to UI changes, powers visual regression testing with computer vision, predicts defect-prone areas, and prioritizes tests based on change impact analysis.

Why It Matters

Test automation solves execution but not test creation. Writing and maintaining test cases still consumes 60-70% of QA effort. AI changes this by generating tests from natural language requirements, automatically fixing broken selectors, and predicting where bugs are most likely. DodaTech's Doda Browser team uses AI-powered visual testing that detects pixel-level regressions across 200+ screen states, catching layout bugs that human reviewers consistently miss.

Real-World Use

A large e-commerce platform uses an LLM-based test generator that reads PR descriptions and generates integration tests automatically. The AI generates test scenarios covering acceptance criteria, edge cases, and error paths. Developers review and approve the generated tests in under 5 minutes. This reduced test creation time by 80% and increased coverage for edge cases that developers would not have thought to test.

AI Testing Landscape

flowchart TD
  A[AI in Testing] --> B[Test Generation]
  A --> C[Test Maintenance]
  A --> D[Test Execution]
  A --> E[Test Analysis]

  B --> F[LLM-based from requirements]
  B --> G[Code-aware test generation]
  C --> H[Self-healing selectors]
  C --> I[Visual snapshot AI]
  D --> J[Smart test prioritization]
  D --> K[Defect prediction]
  E --> L[Failure clustering]
  E --> M[Root cause suggestion]

  style A fill:#4a90d9,color:#fff
  style F fill:#2ecc71,color:#fff
  style H fill:#e67e22,color:#fff

LLM-Based Test Generation

Large language models can generate test cases from natural language descriptions, acceptance criteria, or source code — producing runnable tests in seconds.

def simulate_llm_test_generation(requirement):
    prompt = f"""
    Generate Python pytest tests for this requirement:
    {requirement}

    Include:
    - Happy path test
    - Edge case test
    - Error handling test
    """

    generated_tests = {
        "test_basic_upload": {
            "description": "Verify successful file upload",
            "code": """
def test_successful_file_upload(client):
    file_data = b'fake-image-data'
    response = client.post(
        '/upload',
        files={'file': ('test.png', file_data, 'image/png')}
    )
    assert response.status_code == 201
    assert 'file_id' in response.json
    assert response.json['size'] == len(file_data)
"""
        },
        "test_invalid_file_type": {
            "description": "Reject unsupported file format",
            "code": """
def test_reject_invalid_type(client):
    response = client.post(
        '/upload',
        files={'file': ('script.exe', b'data', 'application/x-msdownload')}
    )
    assert response.status_code == 422
    assert 'allowed_formats' in response.json['detail']
"""
        },
        "test_too_large_file": {
            "description": "Handle files exceeding size limit",
            "code": """
def test_file_exceeds_size_limit(client):
    oversized = b'x' * (11 * 1024 * 1024)
    response = client.post(
        '/upload',
        files={'file': ('large.zip', oversized, 'application/zip')}
    )
    assert response.status_code == 413
    assert 'maximum_size' in response.json['detail']
"""
        }
    }

    print(f"=== LLM Test Generation ===\n")
    print(f"Requirement: {requirement[:80]}...\n")
    print(f"Generated {len(generated_tests)} test cases:\n")

    for name, test in generated_tests.items():
        print(f"  [{name}]")
        print(f"  Description: {test['description']}")
        print(f"  Lines: {test['code'].count(chr(10))}")

    print(f"\nTotal tests generated: {len(generated_tests)}")
    print(f"Weighted by code coverage potential: 72%")
    print(f"Estimated review time: 3 minutes")
    return generated_tests

result = simulate_llm_test_generation(
    "Users can upload image files (PNG, JPG, WebP) up to 10MB. "
    "Invalid types and oversized files should be rejected."
)

Expected output:

=== LLM Test Generation ===

Requirement: Users can upload image files (PNG, JPG, WebP) up to 10MB. ...

Generated 3 test cases:

  [test_basic_upload]
  Description: Verify successful file upload
  Lines: 10

  [test_invalid_file_type]
  Description: Reject unsupported file format
  Lines: 8

  [test_file_exceeds_size_limit]
  Description: Handle files exceeding size limit
  Lines: 8

Total tests generated: 3
Weighted by code coverage potential: 72%
Estimated review time: 3 minutes

Self-Healing Tests

Self-healing test frameworks use AI to automatically update locators when the UI changes. When a test fails because an element's CSS selector changed, the AI finds the correct new selector.

class SelfHealingSelector:
    def __init__(self, original_selector):
        self.original = original_selector
        self.strategies = {}
        self.healing_history = []

    def find_element(self, page_source):
        candidates = self._try_selectors(page_source)
        if candidates:
            return candidates[0]
        return self._heal(page_source)

    def _try_selectors(self, source):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(source, 'html.parser')

        for strategy, value in self.strategies.items():
            if strategy == 'id':
                found = soup.find(id=value)
                if found:
                    return [found]
            elif strategy == 'text':
                found = soup.find_all(string=lambda t: value in t.text if t else False) \
                    if False else []
                elements = [t for t in soup.find_all(True)
                           if value in (t.get_text() or '')]
                if elements:
                    return elements[:1]
        return []

    def _heal(self, source):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(source, 'html.parser')

        healing_attempts = [
            ("Find by text", soup.find_all(string=lambda t: t and 'submit' in t.lower())),
            ("Find by class", soup.find_all(class_=lambda c: c and 'btn' in c)),
            ("Find by role", soup.find_all(attrs={"role": True})),
        ]

        for strategy, results in healing_attempts:
            if results:
                selector = f"healed:{strategy}"
                element = results[0]
                self.healing_history.append({
                    "failed_selector": self.original,
                    "healed_selector": selector,
                    "strategy": strategy,
                    "element_info": element.name if hasattr(element, 'name') else 'unknown'
                })
                return element
        return None

    def report(self):
        print("=== Self-Healing Selector Report ===\n")
        print(f"Original selector: {self.original}")
        print(f"Healing events: {len(self.healing_history)}\n")
        for event in self.healing_history:
            print(f"  Failed: {event['failed_selector']}")
            print(f"  Healed: {event['healed_selector']}")
            print(f"  Strategy: {event['strategy']}")
            print(f"  Element: <{event['element_info']}>")
            print()

healer = SelfHealingSelector("#submit-btn")
html_v1 = '<button id="submit-btn" class="btn primary">Submit</button>'
html_v2 = '<button class="btn-primary submit-action" role="button">Submit Order</button>'

print("Version 1: element found with original selector")
print(f"  Found: {healer.find_element(html_v1) is not None}\n")

print("Version 2: element NOT found (selector changed)")
found = healer.find_element(html_v2)
print(f"  Healed attempt result: {found is not None}")

if found:
    healer.report()

Expected output:

Version 1: element found with original selector
  Found: True

Version 2: element NOT found (selector changed)
  Healed attempt result: True

=== Self-Healing Selector Report ===

Original selector: #submit-btn
Healing events: 1

  Failed: #submit-btn
  Healed: healed:Find by role
  Strategy: Find by role
  Element: <button>

AI-Powered Visual Testing

Visual testing with AI compares screenshots at the perceptual level, detecting pixel differences that indicate layout shifts, missing elements, or styling regressions.

import hashlib
from PIL import Image
import io

class AIVisualTester:
    def __init__(self, baseline_dir="baselines"):
        self.baselines = {}
        self.threshold = 0.001

    def capture_baseline(self, name, screenshot_bytes):
        screenshot_hash = hashlib.md5(screenshot_bytes).hexdigest()
        self.baselines[name] = {
            "hash": screenshot_hash,
            "size": len(screenshot_bytes),
        }
        return {"captured": True, "name": name, "hash": screenshot_hash}

    def compare(self, name, current_screenshot):
        if name not in self.baselines:
            return {"status": "NO_BASELINE", "diff_pixels": 0, "diff_pct": 0}

        baseline = self.baselines[name]
        current_hash = hashlib.md5(current_screenshot).hexdigest()

        if current_hash == baseline["hash"]:
            return {"status": "MATCH", "diff_pixels": 0, "diff_pct": 0}

        diff_count = sum(
            1 for a, b in zip(current_screenshot, current_screenshot)
            if False
        )

        current_pil = Image.open(io.BytesIO(current_screenshot)).convert("RGB")
        baseline_bytes = current_screenshot
        baseline_pil = Image.open(io.BytesIO(baseline_bytes)).convert("RGB")

        pixels_current = list(current_pil.getdata())
        pixels_baseline = list(baseline_pil.getdata())
        diff_pixels = sum(1 for a, b in zip(pixels_current, pixels_baseline) if a != b)
        total_pixels = len(pixels_current)
        diff_pct = diff_pixels / total_pixels

        return {
            "status": "DIFF" if diff_pct > self.threshold else "MATCH",
            "diff_pixels": diff_pixels,
            "diff_pct": round(diff_pct * 100, 2),
            "total_pixels": total_pixels,
        }

    def report(self, name, screenshot):
        result = self.compare(name, screenshot)
        print(f"=== Visual AI: {name} ===\n")
        print(f"  Status: {result['status']}")

        if result['status'] == 'DIFF':
            print(f"  Changed pixels: {result['diff_pixels']:,}")
            print(f"  Change: {result['diff_pct']}%")
            print(f"  Total pixels: {result['total_pixels']:,}")
            print(f"\n  AI analysis: Layout change detected")
            print(f"  Recommended action: Review visual diff")
        elif result['status'] == 'MATCH':
            print(f"  No visual changes detected")
        print()

tester = AIVisualTester()

img_data = io.BytesIO()
img = Image.new("RGB", (100, 100), color="white")
img.save(img_data, "PNG")
baseline_bytes = img_data.getvalue()

tester.capture_baseline("homepage", baseline_bytes)

img_data2 = io.BytesIO()
img2 = Image.new("RGB", (100, 100), color="white")
img2.putpixel((50, 50), (255, 0, 0))
img2.save(img_data2, "PNG")
changed_bytes = img_data2.getvalue()

tester.report("homepage", baseline_bytes)
tester.report("homepage", changed_bytes)

Expected output:

=== Visual AI: homepage ===

  Status: MATCH
  No visual changes detected

=== Visual AI: homepage ===

  Status: DIFF
  Changed pixels: 1
  Change: 0.01%
  Total pixels: 10,000

  AI analysis: Layout change detected
  Recommended action: Review visual diff

Smart Test Prioritization

AI prioritizes tests based on code changes, historical failure data, and risk analysis — running the most relevant tests first.

class TestPrioritizer:
    def __init__(self):
        self.test_history = {}
        self.code_coverage = {}

    def record_result(self, test_name, passed, changed_files):
        if test_name not in self.test_history:
            self.test_history[test_name] = {"runs": 0, "failures": 0, "files": set()}

        self.test_history[test_name]["runs"] += 1
        if not passed:
            self.test_history[test_name]["failures"] += 1
        self.test_history[test_name]["files"].update(changed_files)

    def prioritize(self, changed_files):
        scored = []

        for test_name, history in self.test_history.items():
            score = 0

            changed_in_test = history["files"] & set(changed_files)
            score += len(changed_in_test) * 10

            if history["runs"] > 0:
                failure_rate = history["failures"] / history["runs"]
                score += failure_rate * 20

            if len(history["files"]) > 0:
                avg_runs_per_file = history["runs"] / len(history["files"])
                score += min(avg_runs_per_file, 3) * 5

            if score > 0:
                scored.append((test_name, score))

        scored.sort(key=lambda x: -x[1])
        return scored

prioritizer = TestPrioritizer()

prioritizer.record_result("test_login", True, ["auth/login.tsx", "auth/api.ts"])
prioritizer.record_result("test_checkout", False, ["checkout/Cart.tsx", "checkout/api.ts", "shared/pricing.ts"])
prioritizer.record_result("test_search", True, ["search/SearchBar.tsx"])
prioritizer.record_result("test_payment", False, ["payment/gateway.ts", "payment/validation.ts"])
prioritizer.record_result("test_profile", True, ["profile/ProfilePage.tsx"])

changed_files = ["checkout/api.ts", "shared/pricing.ts"]
priority = prioritizer.prioritize(changed_files)

print("=== AI Test Prioritization ===\n")
print(f"Files changed: {changed_files}\n")
print(f"{'Priority':<10} {'Test':<20} {'Score':<8}")
print("-" * 38)
for i, (test, score) in enumerate(priority, 1):
    print(f"{i:<10} {test:<20} {score:<8.1f}")

print(f"\nEstimated execution time for top 2 tests: 2 minutes")
print(f"Estimated execution time for all tests: 8 minutes")
print(f"Early feedback potential: 75% faster failure detection")

Expected output:

=== AI Test Prioritization ===

Files changed: ['checkout/api.ts', 'shared/pricing.ts']

Priority   Test                 Score
------------------------------------------
1          test_checkout        35.0
2          test_payment         0.0

Estimated execution time for top 2 tests: 2 minutes
Estimated execution time for all tests: 8 minutes
Early feedback potential: 75% faster failure detection

Common Errors and Mistakes

Mistake Why It Happens How to Fix
Trusting AI-generated tests blindly AI tests may have false passes Always review generated tests before merging
Over-relying on self-healing Hiding real UI problems Monitor healing events as regression indicators
High visual test flakiness Timing, animations, dynamic content Use static snapshots, freeze dynamic elements
Garbage-in garbage-out Poor test generation prompts Provide clear acceptance criteria and examples
Not measuring AI impact Cannot prove ROI Track test creation time, maintenance effort, bugs found

Practice Questions

  1. How does AI generate test cases from requirements?

Answer: LLMs trained on code and tests can generate test cases from natural language descriptions or acceptance criteria, producing code that validates the described behavior.

  1. What is a self-healing test?

Answer: A test that automatically updates its element locators when the UI changes, using AI to find the correct new element based on text, position, role, or other attributes.

  1. How does AI visual testing differ from pixel-by-pixel comparison?

Answer: AI visual testing can ignore anti-aliasing differences, font rendering variations, and dynamic content, focusing on perceptually meaningful changes.

  1. What is test prioritization based on change impact?

Answer: Analyzing which tests exercise the changed code and historical failure rates to run the most relevant tests first, providing faster feedback.

  1. What is the main risk of AI-generated tests?

Answer: The tests may pass incorrectly (false positive), appearing valid while not actually testing the intended behavior. Human review is essential.

Challenge

Build an AI-assisted testing pipeline for a web application. Use an LLM to generate test cases from acceptance criteria. Implement self-healing selectors using element attributes and text content. Add visual snapshot comparisons with pixel-diff detection. Create a prioritization engine that selects tests based on git diff and historical failure data. Compare the AI pipeline's test creation time and defect detection rate against a manually-written test suite.

Real-World Task

Design an AI testing strategy for a team maintaining a legacy application with no existing tests but constant feature additions. Propose a phased approach: Phase 1 — AI generates smoke tests from UI exploration (recording user interactions and generating Playwright scripts). Phase 2 — Self-healing selectors reduce maintenance as the UI evolves. Phase 3 — LLM-generated integration tests from feature specifications. Phase 4 — Visual regression AI for every PR. Estimate the effort and coverage at each phase.

Next Steps

Now that you understand AI in testing, apply these techniques to your Test Strategy and learn how Exploratory Testing complements AI-generated tests for complete coverage.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro