This document describes the architecture and design principles of the AI-powered self-healing test automation framework.
graph TB
subgraph "Test Layer"
T1[Test Classes]
T2[BaseSeleniumTest]
end
subgraph "AI Framework Layer"
AH[AIElementHealer]
APM[AIProviderManager]
end
subgraph "AI Providers"
O[OllamaClient]
LMS[LLMStudioClient]
SA[SimpleAIClient]
end
subgraph "WebDriver Layer"
WD[WebDriver]
SE[Selenium]
end
subgraph "External AI Services"
OL[Ollama Service]
LM[LM Studio]
end
T1 --> T2
T2 --> AH
AH --> APM
APM --> O
APM --> LMS
APM --> SA
AH --> WD
WD --> SE
O --> OL
LMS --> LM
Purpose: Main interface for AI-powered element location
Responsibilities:
- Accept natural language element descriptions
- Generate multiple locator strategies using AI
- Try strategies until element is found
- Cache successful strategies for performance
- Provide diagnostics and error analysis
Key Methods:
public WebElement findElement(String description)
public List<WebElement> findElements(String description)
public String getDiagnostics(String description)Purpose: Manages multiple AI providers with automatic failover
Responsibilities:
- Discover available AI providers
- Implement provider priority and fallback logic
- Health check and monitoring
- Resource management and cleanup
Provider Priority:
- Ollama (Primary - Best performance)
- LM Studio (Secondary - Good performance)
- Simple AI (Fallback - Basic functionality)
Purpose: Abstract interface for AI providers
Contract:
public interface LLMInterface {
String generateResponse(String prompt);
String generateResponse(String prompt, float temperature, int maxTokens);
boolean isAvailable();
String getModelInfo();
void close();
}sequenceDiagram
participant T as Test
participant AH as AIElementHealer
participant AI as AIProvider
participant WD as WebDriver
T->>AH: findElement("submit button")
AH->>AI: Generate locator strategies
AI-->>AH: ["#submit", ".btn-submit", "[type='submit']"]
loop Try each strategy
AH->>WD: findElement(strategy)
alt Element found
WD-->>AH: WebElement
AH-->>T: WebElement (Success!)
else Element not found
WD-->>AH: NoSuchElementException
Note over AH: Try next strategy
end
end
alt All strategies failed
AH->>AI: Analyze page for diagnostics
AI-->>AH: Diagnostic suggestions
AH-->>T: null (with diagnostics)
end
public class OllamaClient implements LLMInterface {
private final String baseUrl; // http://localhost:11434
private final String model; // llama3
private final OkHttpClient client;
private final ObjectMapper mapper;
// Connection management
// Request/response handling
// Error handling and retries
}Features:
- Local model execution
- No API costs
- High performance
- Privacy-focused
public class LLMStudioClient implements LLMInterface {
private final String baseUrl; // http://localhost:1234
private final OkHttpClient client;
private final ObjectMapper mapper;
// OpenAI-compatible API
// Model management
// Performance monitoring
}Features:
- GUI model management
- OpenAI API compatibility
- Easy model switching
- Good performance
public class SimpleAIClient implements LLMInterface {
private final Map<String, String> responses;
// Predefined response patterns
// Keyword matching
// Fallback functionality
}Features:
- No external dependencies
- Instant responses
- Basic locator suggestions
- Always available
String prompt = String.format(
"You are a Selenium WebDriver expert. Generate CSS selectors and XPath expressions " +
"to find this element: '%s' on a web page.\n\n" +
"Provide 5 different locator strategies in this exact format:\n" +
"1. By.id(\"element-id\")\n" +
"2. By.className(\"class-name\")\n" +
"3. By.name(\"element-name\")\n" +
"4. By.cssSelector(\"css-selector\")\n" +
"5. By.xpath(\"//xpath-expression\")",
elementDescription
);public List<By> parseAILocators(String aiResponse) {
List<By> locators = new ArrayList<>();
String[] lines = aiResponse.split("\n");
for (String line : lines) {
if (line.contains("By.id")) {
String id = extractValue(line, "By.id");
locators.add(By.id(id));
}
// Parse other locator types...
}
return locators;
}- ID-based: Highest priority (most stable)
- Name-based: High priority (form elements)
- Class-based: Medium priority (can change)
- CSS Selector: Medium priority (flexible)
- XPath: Lowest priority (most fragile)
public class LocatorCache {
private final Map<String, List<By>> cache = new ConcurrentHashMap<>();
private final long TTL = 300_000; // 5 minutes
public List<By> getStrategies(String description) {
CacheEntry entry = cache.get(description);
if (entry != null && !entry.isExpired()) {
return entry.getStrategies();
}
return null;
}
public void putStrategies(String description, List<By> strategies) {
cache.put(description, new CacheEntry(strategies, System.currentTimeMillis()));
}
}- Strategy Caching: Cache successful locator strategies
- Connection Pooling: Reuse HTTP connections to AI services
- Async Processing: Generate multiple strategies in parallel
- Circuit Breaker: Fail fast when AI provider is down
- Timeout Management: Prevent hanging on slow AI responses
public class AIFrameworkException extends RuntimeException {
// Base exception for all AI framework errors
}
public class AIProviderException extends AIFrameworkException {
// AI provider connection or response errors
}
public class HealingException extends AIFrameworkException {
// Element healing specific errors
}
public class ConfigurationException extends AIFrameworkException {
// Configuration and setup errors
}public WebElement findElementWithRetry(String description, int maxRetries) {
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return findElement(description);
} catch (Exception e) {
if (attempt == maxRetries) {
throw new HealingException(
String.format("Failed to find element after %d attempts: %s",
maxRetries, description), e);
}
// Exponential backoff
try {
Thread.sleep(1000 * attempt);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new HealingException("Interrupted during retry", ie);
}
}
}
return null;
}public class AIProviderCircuitBreaker {
private enum State { CLOSED, OPEN, HALF_OPEN }
private State state = State.CLOSED;
private int failureCount = 0;
private long lastFailureTime = 0;
private final int failureThreshold = 5;
private final long timeout = 60_000; // 1 minute
public String callAI(String prompt) throws AIProviderException {
if (state == State.OPEN) {
if (System.currentTimeMillis() - lastFailureTime > timeout) {
state = State.HALF_OPEN;
} else {
throw new AIProviderException("Circuit breaker is OPEN");
}
}
try {
String response = aiProvider.generateResponse(prompt);
onSuccess();
return response;
} catch (Exception e) {
onFailure();
throw e;
}
}
private void onSuccess() {
failureCount = 0;
state = State.CLOSED;
}
private void onFailure() {
failureCount++;
lastFailureTime = System.currentTimeMillis();
if (failureCount >= failureThreshold) {
state = State.OPEN;
}
}
}public class AIFrameworkMetrics {
private final AtomicLong totalRequests = new AtomicLong();
private final AtomicLong successfulRequests = new AtomicLong();
private final AtomicLong failedRequests = new AtomicLong();
private final AtomicLong totalResponseTime = new AtomicLong();
public void recordRequest(long responseTime, boolean success) {
totalRequests.incrementAndGet();
totalResponseTime.addAndGet(responseTime);
if (success) {
successfulRequests.incrementAndGet();
} else {
failedRequests.incrementAndGet();
}
}
public double getSuccessRate() {
long total = totalRequests.get();
return total > 0 ? (double) successfulRequests.get() / total : 0.0;
}
public double getAverageResponseTime() {
long total = totalRequests.get();
return total > 0 ? (double) totalResponseTime.get() / total : 0.0;
}
}public class AIProviderHealthCheck {
public HealthStatus checkHealth() {
try {
// Test simple AI request
String response = aiProvider.generateResponse("Test prompt");
if (response != null && !response.trim().isEmpty()) {
return HealthStatus.HEALTHY;
} else {
return HealthStatus.UNHEALTHY;
}
} catch (Exception e) {
return HealthStatus.UNHEALTHY;
}
}
}- Properties Files:
ollama-config.properties,llmstudio-config.properties - System Properties:
-Dai.provider.timeout=30 - Environment Variables:
AI_PROVIDER_URL=http://localhost:11434 - Programmatic:
config.setProperty("timeout", "30")
public class AIConfiguration {
private final Properties config = new Properties();
public AIConfiguration() {
loadDefaults();
loadFromPropertiesFile();
loadFromSystemProperties();
loadFromEnvironmentVariables();
}
private void loadDefaults() {
config.setProperty("ai.timeout", "30");
config.setProperty("ai.retries", "3");
config.setProperty("ai.cache.ttl", "300");
}
}- Element descriptions may contain sensitive information
- AI providers should be local (Ollama, LM Studio)
- No data sent to external services without explicit consent
- AI services run locally by default
- HTTP connections use localhost only
- No authentication tokens stored in code
- Sanitize element descriptions
- Validate AI responses before parsing
- Prevent injection attacks through prompts
- Multiple AI provider instances
- Load balancing between providers
- Distributed caching
- Connection pooling
- Async request processing
- Efficient memory management
- Optimize AI prompt templates
- Tune cache TTL values
- Monitor and adjust timeouts
This architecture supports a robust, scalable, and maintainable AI-powered test automation framework.