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
<PackageReference Include="TestNexus.LocatAI.NUnit" Version="1.0.1" />
<PackageVersion Include="TestNexus.LocatAI.NUnit" Version="1.0.1" />
<PackageReference Include="TestNexus.LocatAI.NUnit" />
paket add TestNexus.LocatAI.NUnit --version 1.0.1
#r "nuget: TestNexus.LocatAI.NUnit, 1.0.1"
#:package TestNexus.LocatAI.NUnit@1.0.1
#addin nuget:?package=TestNexus.LocatAI.NUnit&version=1.0.1
#tool nuget:?package=TestNexus.LocatAI.NUnit&version=1.0.1
<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.
</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, 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 | Versions 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. |
-
net6.0
- Microsoft.Playwright.NUnit (>= 1.47.0)
- TestNexus.LocatAI (>= 1.0.1)
-
net8.0
- Microsoft.Playwright.NUnit (>= 1.47.0)
- TestNexus.LocatAI (>= 1.0.1)
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 |