TestNexus.LocatAI
1.0.1
dotnet add package TestNexus.LocatAI --version 1.0.1
NuGet\Install-Package TestNexus.LocatAI -Version 1.0.1
<PackageReference Include="TestNexus.LocatAI" Version="1.0.1" />
<PackageVersion Include="TestNexus.LocatAI" Version="1.0.1" />
<PackageReference Include="TestNexus.LocatAI" />
paket add TestNexus.LocatAI --version 1.0.1
#r "nuget: TestNexus.LocatAI, 1.0.1"
#:package TestNexus.LocatAI@1.0.1
#addin nuget:?package=TestNexus.LocatAI&version=1.0.1
#tool nuget:?package=TestNexus.LocatAI&version=1.0.1
<div align="center">
TestNexus.LocatAI
Stop fixing broken selectors. Let AI do it.
TestNexus.LocatAI is a self-healing locator layer for Playwright (.NET). When a selector fails, it reads the page, figures out what you meant, and drives the correct element — automatically caching the fix so the next run is instant.
</div>
The 10-second pitch
// Classic Playwright — breaks the moment a class name changes
await page.Locator("[data-testid='submit-btn']").ClickAsync();
// LocatAI — heals itself when the selector breaks
var ai = page.LocatAI();
await ai.ClickAsync(
page.Locator("[data-testid='submit-btn']"),
"Submit button on the checkout form"
);
// Or skip selectors entirely — describe it, LocatAI finds it
await ai.ClickAsync("", "Place order button");
Give it a selector and a plain-English description. The selector is the fast path; the description is the safety net. When the UI refactors and your selector disappears, LocatAI falls back to the description, locates the element with AI, and caches the healed selector for next time — no re-runs, no manual patching.
Why it exists
UI tests rot. A button becomes a <div role="button">. A data-testid gets renamed. A framework upgrade reshuffles the DOM. Suddenly half your suite is red and your afternoon is gone.
Most teams deal with this two ways: they either over-invest in brittle selector hygiene, or they write loose selectors that miss real bugs. LocatAI gives you a third option — ship your normal selectors, and have AI repair them in place when reality drifts from your test code.
Feature highlights
| Self-healing | Failed locators are re-resolved by AI against a live DOM snapshot. |
| Selector-free mode | Pass "" and let the description alone locate the element. |
| Four AI providers | OpenAI, Anthropic, Google, or fully local via Ollama. |
| Zero-cloud option | Run healing entirely offline with a local LLM. No API keys needed. |
| Persistent cache | Healed selectors saved to disk — successful heals cost nothing on repeat runs. |
| Auto .env loading | Drop a .env next to your .csproj — no boilerplate needed. |
| Token-efficient | DOM is compacted, pre-ranked, and capped to keep bills low. |
| Framework-agnostic | Works with NUnit, xUnit, MSTest — any test runner. |
Install
dotnet add package TestNexus.LocatAI
NUnit users: also install
TestNexus.LocatAI.NUnitfor a zero-boilerplate base class —PageandLocatAIwired automatically.
Requires Microsoft.Playwright >= 1.40.0 and .NET Standard 2.0 (compatible with .NET 6, 7, 8, 9).
60-second setup
1. Create a .env file next to your .csproj
SELF_HEAL=1
AI_PROVIDER=google # openai | anthropic | google | local
AI_API_KEY=your-key-here
The package loads this file automatically before the first test. CI environment variables are never overwritten — they always win.
2. Call page.LocatAI() in your tests
using TestNexus.LocatAI;
// page is a standard IPage from Playwright
var ai = page.LocatAI();
await page.GotoAsync("https://example.com/login");
await ai.FillAsync(page.Locator("#email"), "Email input on login form", "jane@example.com");
await ai.FillAsync("", "Password input", "hunter2");
await ai.ClickAsync("", "Sign in button");
SELF_HEAL=1 turns healing on. Leave it unset in local dev for plain Playwright with zero overhead.
Three modes
Self-healing — broken selector, AI fallback
The locator is tried first. If it fails, the description drives AI to find the correct element and cache the fix.
// #old-email-input no longer exists in the DOM — AI heals automatically
await ai.FillAsync(
page.Locator("#old-email-input"),
"Email input on the login form",
"jane@example.com"
);
AI-only — no selector at all
Pass "" as the first argument. LocatAI locates the element purely from the description on every run until the result is cached.
await ai.ClickAsync("", "Add to cart button for the Backpack");
await ai.FillAsync("", "First name input field", "John");
await ai.ClickAsync("", "Finish button to complete the order");
Chainable locator — drop-in ILocator replacement
ai.Locator() returns an ILocatAILocator you can chain like a normal Playwright locator, but with the description bound for automatic healing.
var cartBtn = ai.Locator(".wrong-cart-selector", "Shopping cart icon");
await cartBtn.ClickAsync();
var newTodo = ai.Locator(".new-todo", "New todo input");
await newTodo.FillAsync("Buy milk");
API surface
page.LocatAI() returns an ILocatAI. All action methods accept either an ILocator, a selector string, or "" for AI-only mode.
| Method | Signature | Use for |
|---|---|---|
ClickAsync |
(locator, desc, options?) |
Clicking any element |
FillAsync |
(locator, desc, value) |
Typing into inputs and textareas |
SelectOptionAsync |
(locator, desc, value) |
Native <select> dropdowns |
CheckAsync |
(locator, desc) |
Checkboxes and radio buttons |
UncheckAsync |
(locator, desc) |
Unchecking checkboxes |
DblclickAsync |
(locator, desc) |
Double-click |
HoverAsync |
(locator, desc) |
Hover to reveal hidden elements |
FocusAsync |
(locator, desc) |
Focus without clicking |
Locator |
(selector, desc) |
Chainable self-healing ILocatAILocator |
SetTestName |
(name) |
Tag cache and log entries with the test name |
Per-test options
var ai = page.LocatAI(new LocatAIOptions
{
Provider = "anthropic",
Model = "claude-opus-4-5",
ApiKey = "sk-ant-...",
MaxCandidates = 40, // DOM elements sent to AI (default 80)
MaxAiTries = 4, // Strategies validated per heal (default 4)
Timeout = 5000, // Selector timeout ms before healing kicks in (default 5000)
CacheFile = "custom/path/healed_locators.json",
});
LocatAIOptions always overrides env vars. The same ILocatAI instance is returned for the same IPage within a process, so cache state is shared across all actions in a test.
Providers
| Provider | AI_PROVIDER values |
Default model | API key required |
|---|---|---|---|
| OpenAI | openai, gpt |
gpt-5-nano |
Yes |
| Anthropic | anthropic, claude |
claude-sonnet-4-20250514 |
Yes |
google, gemini |
gemini-2.5-flash |
Yes | |
| Local (Ollama) | local, ollama |
gemma3:4b |
No — fully offline |
Override the model with AI_MODEL or LocatAIOptions.Model:
AI_PROVIDER=anthropic
AI_MODEL=claude-opus-4-5
AI_API_KEY=sk-ant-...
Going fully local with Ollama
# 1. Install Ollama: https://ollama.com
ollama pull llama3.2:3b
# 2. Configure .env
AI_PROVIDER=local
AI_MODEL=llama3.2:3b
SELF_HEAL=1
Custom Ollama host:
OLLAMA_HOST=http://192.168.1.100:11434
Configuration reference
Environment variables
| Variable | Default | Purpose |
|---|---|---|
SELF_HEAL |
off | Master switch. Set to 1 to enable healing. |
AI_SELF_HEAL |
off | Alias — true or 1 also enables healing. |
AI_PROVIDER |
openai |
Backend provider. |
AI_API_KEY |
— | API key for cloud providers. |
AI_MODEL |
provider default | Override the model for the chosen provider. |
OLLAMA_HOST |
http://127.0.0.1:11434 |
Custom Ollama endpoint. |
LocatAIOptions properties
| Property | Type | Default |
|---|---|---|
Enabled |
bool? |
from SELF_HEAL env var |
Provider |
string? |
from AI_PROVIDER env var |
Model |
string? |
from AI_MODEL env var |
ApiKey |
string? |
from AI_API_KEY env var |
CacheFile |
string? |
.self-heal/healed_locators.json next to .csproj |
ReportFile |
string? |
.self-heal/heal_events.jsonl next to .csproj |
MaxAiTries |
int? |
4 |
MaxCandidates |
int? |
80 |
Timeout |
int? |
5000 ms |
How healing actually works
Every healed call follows the same four-step path:
- Try the selector. If it resolves within
Timeoutms, LocatAI gets out of the way — zero AI calls, zero cost. - Check the cache.
.self-heal/healed_locators.jsonstores previous heals keyed by action + description + URL. A cache hit → zero AI cost. - Ask the AI. LocatAI snapshots candidate DOM elements, pre-ranks by keyword/role/tag/test-id relevance, and sends the top candidates to the AI. The AI returns up to 3 locator strategies.
- Validate and cache. Each strategy is tried against the live page. The first that works is written to disk so step 2 hits next time.
Every heal event is also appended to .self-heal/heal_events.jsonl — one JSON line per event with token counts, provider, model, strategy, and test name. Parse it for cost dashboards or CI analytics.
The .self-heal/ directory
Both files are anchored to the directory containing your .csproj — they survive dotnet clean.
| File | Purpose | Commit to git? |
|---|---|---|
.self-heal/healed_locators.json |
Persistent heal cache. | Yes — lets CI reuse heals and skip AI calls on repeat runs. |
.self-heal/heal_events.jsonl |
Append-only event log. | No — add to .gitignore. |
Minimum .gitignore addition:
.self-heal/heal_events.jsonl
Token & cost optimization
- Visibility filter — invisible elements are never included in the DOM snapshot.
- Semantic pre-ranking — candidates scored on keyword match, ARIA role, tag inference, test-id presence. Bottom ~20% dropped before sending.
- Attribute compaction — keys shortened, null fields stripped.
- Capped output — AI returns max 3 strategies per heal.
- Cache-first — a successful heal writes to disk; all subsequent runs cost nothing.
Console output on every heal:
↑ 1350 input · 180 output · 1530 total tokens
Privacy notes
When the AI path fires, LocatAI sends a structured list of candidate DOM elements — tag, role, ARIA attributes, visible text, test IDs — to the configured provider. It does not send screenshots, full HTML, or cookies.
Options if your DOM contains sensitive data:
- Go local.
AI_PROVIDER=local+ Ollama keeps everything on-machine. - Review the log.
.self-heal/heal_events.jsonlshows exactly what went over the wire. - Gate by environment. Set
SELF_HEAL=1only in CI against staging fixtures, not production.
Full example (framework-agnostic)
using Microsoft.Playwright;
using TestNexus.LocatAI;
using NUnit.Framework;
[TestFixture]
public class CheckoutTests
{
private IPlaywright _playwright = null!;
private IBrowser _browser = null!;
private IPage _page = null!;
[OneTimeSetUp]
public async Task SetUp()
{
_playwright = await Playwright.CreateAsync();
_browser = await _playwright.Chromium.LaunchAsync(new() { Headless = true });
_page = await _browser.NewPageAsync(new() { BaseURL = "https://example.com" });
}
[Test]
public async Task Checkout_SingleItem_Succeeds()
{
var ai = _page.LocatAI();
await _page.GotoAsync("/shop");
// Broken selector — AI finds the real product card
await ai.ClickAsync(_page.Locator(".product-card-old"), "First product card");
// AI-only — no selector needed
await ai.ClickAsync("", "Add to cart button");
await ai.ClickAsync("", "Shopping cart icon in header");
// Mix freely — plain Playwright for stable selectors
await _page.Locator("[data-testid='email']").FillAsync("customer@example.com");
// AI for third-party / flaky widgets
await ai.SelectOptionAsync("", "Country dropdown", "US");
await ai.CheckAsync("", "Agree to terms checkbox");
await ai.ClickAsync("", "Place order button");
await Assertions.Expect(_page).ToHaveURLAsync(new System.Text.RegularExpressions.Regex("/confirmation"));
}
[OneTimeTearDown]
public async Task TearDown()
{
await _browser.CloseAsync();
_playwright.Dispose();
}
}
FAQ
Does it work without SELF_HEAL=1?
Yes — LocatAI becomes a zero-overhead pass-through and your tests run as plain Playwright.
What happens when the AI can't find the element?
LocatAI throws a LocatAIError with a clear message including the description. Your test fails loudly with context.
Does healing work across iframes?
Not yet. Healing resolves against the main frame. Use standard Playwright FrameLocator for iframe content.
Is the cache safe to commit?
Commit healed_locators.json, ignore heal_events.jsonl. The cache is a net positive for CI — one AI call per heal, not one per run.
Can I use this with xUnit or MSTest?
Yes — page.LocatAI() is framework-agnostic. TestNexus.LocatAI.NUnit is an optional convenience layer exclusively for NUnit.
Does the .env file override CI secrets?
No. Existing process environment variables always win. .env is only applied when the variable is not already set.
License
MIT © Divyarajsinh Dodia
If LocatAI saves your Friday afternoon, a ⭐ on GitHub is a very nice thank-you.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Playwright (>= 1.47.0)
- System.Text.Json (>= 8.0.5)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on TestNexus.LocatAI:
| Package | Downloads |
|---|---|
|
TestNexus.LocatAI.NUnit
NUnit base class for TestNexus.LocatAI — wires up self-healing locators on top of Microsoft.Playwright.NUnit. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.1 | 133 | 4/24/2026 |