TestNexus.LocatAI 1.0.1

dotnet add package TestNexus.LocatAI --version 1.0.1
                    
NuGet\Install-Package TestNexus.LocatAI -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TestNexus.LocatAI" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TestNexus.LocatAI" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="TestNexus.LocatAI" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TestNexus.LocatAI --version 1.0.1
                    
#r "nuget: TestNexus.LocatAI, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TestNexus.LocatAI@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TestNexus.LocatAI&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=TestNexus.LocatAI&version=1.0.1
                    
Install as a Cake Tool

<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.

NuGet License: MIT .NET Standard 2.0 Playwright

</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.NUnit for a zero-boilerplate base class — Page and LocatAI wired 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 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:

  1. Try the selector. If it resolves within Timeout ms, LocatAI gets out of the way — zero AI calls, zero cost.
  2. Check the cache. .self-heal/healed_locators.json stores previous heals keyed by action + description + URL. A cache hit → zero AI cost.
  3. 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.
  4. 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.jsonl shows exactly what went over the wire.
  • Gate by environment. Set SELF_HEAL=1 only 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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