TestNexus.LocatAI.NUnit 1.0.1

dotnet add package TestNexus.LocatAI.NUnit --version 1.0.1
                    
NuGet\Install-Package TestNexus.LocatAI.NUnit -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.NUnit" 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.NUnit" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="TestNexus.LocatAI.NUnit" />
                    
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.NUnit --version 1.0.1
                    
#r "nuget: TestNexus.LocatAI.NUnit, 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.NUnit@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.NUnit&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=TestNexus.LocatAI.NUnit&version=1.0.1
                    
Install as a Cake Tool

<div align="center">

TestNexus.LocatAI.NUnit

Zero-boilerplate self-healing locators for NUnit + Playwright.

TestNexus.LocatAI.NUnit wraps the core TestNexus.LocatAI package in a single NUnit base class. Inherit instead of PageTest and a healing-ready LocatAI facade is wired to the test's Page automatically — no setup code needed.

NuGet License: MIT .NET 6 / 8 Playwright

</div>


What this package adds

Without this package With this package
Install core + NUnit packages separately Single install
Call page.LocatAI() manually in every test LocatAI property ready from the first line
Wire SetTestName in [SetUp] Done automatically — test name in every log line
Manage ILocatAI lifetime yourself Shared per-test, disposed with the page

This is a thin convenience layer. Every heal, cache, and AI call runs in TestNexus.LocatAI — this package just removes the boilerplate of wiring it to NUnit's PageTest.


Install

dotnet add package TestNexus.LocatAI.NUnit

This pulls in TestNexus.LocatAI and Microsoft.Playwright.NUnit automatically. No other installs needed.

Requires .NET 6 or .NET 8. For other frameworks use the core package directly.


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 your first test. CI environment variables are never overwritten — they always win.

2. Inherit LocatAITest instead of PageTest

using TestNexus.LocatAI.NUnit;
using NUnit.Framework;

[TestFixture]
public class LoginTests : LocatAITest
{
    [Test]
    public async Task Login_WithBrokenSelector_HealsAndSucceeds()
    {
        await Page.GotoAsync("https://example.com/login");

        // Broken selector — AI finds the real element and caches the fix
        await LocatAI.FillAsync(
            Page.Locator("#old-email-id"),
            "Email input on login form",
            "jane@example.com"
        );

        // No selector at all — AI locates it from the description
        await LocatAI.FillAsync("", "Password input", "hunter2");
        await LocatAI.ClickAsync("", "Sign in button");

        await Assertions.Expect(Page).ToHaveURLAsync("/dashboard");
    }
}

Page, Browser, Context, and Playwright all come from the PageTest base — exactly as in standard Playwright NUnit tests. LocatAI is added on top.


The LocatAITest base class

public abstract class LocatAITest : PageTest
{
    protected ILocatAI LocatAI { get; }
    protected virtual LocatAIOptions? LocatAIOptions => null;
}

LocatAI property

Returns an ILocatAI bound to the current test's Page. The same instance is reused across all actions in a test so the heal cache is shared within a test run.

LocatAIOptions property

Override to configure the AI provider, model, or cache path per fixture — without touching env vars.

[TestFixture]
public class StagingCheckoutTests : LocatAITest
{
    protected override LocatAIOptions? LocatAIOptions => new LocatAIOptions
    {
        Provider      = "anthropic",
        Model         = "claude-opus-4-5",
        ApiKey        = Environment.GetEnvironmentVariable("ANTHROPIC_KEY"),
        MaxCandidates = 40,
        Timeout       = 8000,
    };

    [Test]
    public async Task Checkout_Succeeds()
    {
        await Page.GotoAsync("https://staging.example.com/shop");
        await LocatAI.ClickAsync("", "Add to cart button");
        // ...
    }
}

The default (null) means env vars (SELF_HEAL, AI_PROVIDER, AI_API_KEY, AI_MODEL) drive everything — same as the core package.


API surface

LocatAI exposes the full ILocatAI interface from TestNexus.LocatAI. All action methods accept 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 (called automatically by base class)

Three modes in NUnit

Self-healing — broken selector, AI fallback

// #old-email-input no longer exists — AI heals automatically
await LocatAI.FillAsync(
    Page.Locator("#old-email-input"),
    "Email input on the login form",
    "jane@example.com"
);

AI-only — no selector at all

await LocatAI.ClickAsync("", "Add to cart button for the Backpack");
await LocatAI.FillAsync("", "First name input field", "John");
await LocatAI.ClickAsync("", "Finish button to complete the order");

Chainable locator

LocatAI.Locator() returns an ILocatAILocator that you can chain exactly like a normal Playwright locator, but with the description bound for automatic healing.

var cartBtn = LocatAI.Locator(".wrong-cart-selector", "Shopping cart icon");
await cartBtn.ClickAsync();

var newTodo = LocatAI.Locator(".new-todo", "New todo input");
await newTodo.FillAsync("Buy milk");

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 via AI_MODEL env var or LocatAIOptions.Model:

AI_PROVIDER=anthropic
AI_MODEL=claude-opus-4-5
AI_API_KEY=sk-ant-...

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

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.
.self-heal/heal_events.jsonl Append-only event log. No — add to .gitignore.

Minimum .gitignore addition:

.self-heal/heal_events.jsonl

Full example

using Microsoft.Playwright;
using TestNexus.LocatAI;
using TestNexus.LocatAI.NUnit;
using NUnit.Framework;

[TestFixture]
public class CheckoutTests : LocatAITest
{
    [OneTimeSetUp]
    public async Task SuiteSetUp()
    {
        await Page.GotoAsync("https://example.com");
    }

    [Test]
    public async Task Checkout_SingleItem_Succeeds()
    {
        await Page.GotoAsync("/shop");

        // Broken selector — AI finds the real product card
        await LocatAI.ClickAsync(Page.Locator(".product-card-old"), "First product card");

        // AI-only — no selector needed
        await LocatAI.ClickAsync("", "Add to cart button");
        await LocatAI.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 LocatAI.SelectOptionAsync("", "Country dropdown", "US");
        await LocatAI.CheckAsync("", "Agree to terms checkbox");
        await LocatAI.ClickAsync("", "Place order button");

        await Assertions.Expect(Page).ToHaveURLAsync(new System.Text.RegularExpressions.Regex("/confirmation"));
    }

    [Test]
    public async Task Checkout_ChainableLocator_Works()
    {
        await Page.GotoAsync("/shop");

        // ILocatAILocator — chainable, description-bound
        var addToCartBtn = LocatAI.Locator(".outdated-btn-class", "Add to cart button");
        await addToCartBtn.ClickAsync();

        var qtyInput = LocatAI.Locator("", "Quantity input field");
        await qtyInput.FillAsync("3");
    }
}

Running NUnit tests in parallel

When using healing + an AI provider with rate limits, run tests sequentially or use a low worker count. Add a locatai.runsettings file next to your .csproj:

<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
  <NUnit>
    <NumberOfTestWorkers>1</NumberOfTestWorkers>
  </NUnit>
  <Playwright>
    <BrowserName>chromium</BrowserName>
    <Headless>true</Headless>
  </Playwright>
</RunSettings>

Run with:

dotnet test --settings locatai.runsettings

FAQ

Does it work without SELF_HEAL=1? Yes — LocatAI becomes a zero-overhead pass-through. Your tests run as plain Playwright with no AI calls.

What happens when the AI can't find the element? A LocatAIError is thrown with a clear message including the description. Your test fails loudly with context.

Can I still use Page.Locator() directly? Absolutely. Page is the standard IPage from PageTest. Mix and match — use LocatAI for flaky or complex selectors, plain Playwright for anything stable.

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 .self-heal/healed_locators.json, ignore .self-heal/heal_events.jsonl. The cache is a net positive for CI — one AI call per heal, not one per run.

What if I'm not using NUnit? Use TestNexus.LocatAI directly. It works with xUnit, MSTest, and any other test runner via page.LocatAI().


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 net6.0 is compatible.  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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 108 4/24/2026