E2E 0.1.2
See the version list below for details.
dotnet add package E2E --version 0.1.2
NuGet\Install-Package E2E -Version 0.1.2
<PackageReference Include="E2E" Version="0.1.2" />
<PackageVersion Include="E2E" Version="0.1.2" />
<PackageReference Include="E2E" />
paket add E2E --version 0.1.2
#r "nuget: E2E, 0.1.2"
#:package E2E@0.1.2
#addin nuget:?package=E2E&version=0.1.2
#tool nuget:?package=E2E&version=0.1.2
E2E for .NET
Community .NET port of e2e, the agentic end-to-end testing framework. Describe a goal in natural language, let an agent drive the app, then check the result with locators.
This is not an official TesterArmy product and is not endorsed by TesterArmy. It reimplements the public testing flow. It does not copy the TypeScript sources. Behavior that is intentionally different is listed in COMPATIBILITY.md.
License: Apache License 2.0. Copyright 2026 TesterArmy.
Docs and API reference: https://hardkoded.github.io/e2e-dotnet/
Install
dotnet add package E2E
dotnet add package E2E.NUnit
The library targets net10.0 and includes WebEngine, which drives Chromium through Microsoft.Playwright. Firefox and WebKit are not exposed yet. Install a browser once, from the build output of the project that references E2E:
pwsh bin/Debug/net10.0/playwright.ps1 install chromium
A test
using E2E;
using E2E.NUnit;
using NUnit.Framework;
public sealed class BillingTests : E2ETest
{
protected override IAgentModel? CreateModel() =>
new OpenAiCompatibleModel(new OpenAiCompatibleModelOptions { Model = "gpt-4.1-mini" });
[Test]
public async Task Member_upgrades_to_Pro()
{
await App.OpenAsync("/settings/billing");
await Agent.ActAsync("upgrade the workspace to the Pro plan");
await Agent.AssertAsync("the invoice preview shows a prorated amount");
await Expect.That(Screen.GetByRole("status")).ToContainTextAsync("Pro");
}
}
E2ETest starts a WebEngine session for each [Test]. Override CreateEngine with a DocumentEngine when the test should not open a browser. An act that a later assert, waitFor, or locator Expect verifies is recorded. The next run replays those actions with no model calls until the screen no longer matches. When the test ends, verified acts are written and unverified acts that recorded or replayed are evicted, whether it passed, failed, or was skipped. [Retry] runs the later attempts live, and they still record. Tests that never call the agent need no model.
Config
E2ETest reads the nearest e2e.config.json above the test assembly directory, then above the working directory. The shape follows upstream e2e.config.ts. Every key is optional, and an unknown key fails with INVALID_CONFIG.
{
"targets": [{ "platform": "web", "app": { "url": "http://127.0.0.1:4173" } }],
"timeout": 120000,
"launchTimeout": 60000,
"actionTimeout": 30000,
"assertionTimeout": 5000,
"cleanupTimeout": 30000,
"agents": {
"default": {
"model": "gpt-4.1-mini",
"baseUrl": "https://api.openai.com/v1",
"apiKeyEnv": "OPENAI_API_KEY"
}
},
"cache": { "mode": "read-write", "dir": ".e2e/cache", "strict": false },
"secrets": { "stripe-key": null }
}
There is no default model and no shared API key. baseUrl can point at any OpenAI-compatible server, including a local one. Tests without agent steps ignore it.
provider picks who serves model and judge. Unset, it is openai (chat completions). baseUrl and apiKeyEnv default to the provider's own:
provider |
Key | Notes |
|---|---|---|
openai |
OPENAI_API_KEY |
Chat completions |
openai-responses |
OPENAI_API_KEY |
The Responses API, as the AI SDK's openai(id) |
azure |
AZURE_API_KEY |
Responses API at https://$AZURE_RESOURCE_NAME.openai.azure.com/openai/v1, or baseUrl. model is the deployment |
anthropic |
ANTHROPIC_API_KEY |
Messages API, with prompt-cache breakpoints |
google |
GOOGLE_GENERATIVE_AI_API_KEY |
Gemini API |
bedrock |
AWS credentials, or AWS_BEARER_TOKEN_BEDROCK |
Converse API in AWS_REGION |
xai |
XAI_API_KEY |
SpaceXAI API |
openrouter |
OPENROUTER_API_KEY |
Model ids such as openai/gpt-6-luna-fast |
gateway |
AI_GATEWAY_API_KEY, else VERCEL_OIDC_TOKEN |
Vercel AI Gateway |
openai-compatible |
LLM_API_KEY, optional |
Any /v1/chat/completions server; baseUrl is required |
chatgpt, copilot, grok, opencode-console |
The stored login | Subscriptions, below |
An apiKeyEnv you set wins over the provider's default, so remove it when you switch providers. Otherwise the client reads the old variable, sends no key, and the provider answers 401.
anthropic needs a key scoped to a workspace (sk-ant-api03-...). A user key (sk-ant-usr-...) fails with a 400 that asks for an anthropic-workspace-id header, and the config cannot send one.
In code, ModelProviders builds the same clients (ModelProviders.Anthropic("claude-sonnet-5")), and AnthropicModel, GoogleModel, BedrockModel, OpenAiResponsesModel, and OpenAiCompatibleModel take full options.
Subscriptions
A ChatGPT Plus/Pro, GitHub Copilot, OpenCode Console (Zen and Go), or SuperGrok/X Premium+ plan can serve the agent instead of an API key. Sign in once with the e2e tool:
dotnet tool install --global E2E.Cli
e2e login openai # or github-copilot, opencode-console, spacexai
e2e models openai # the model ids the plan serves
Then set "provider": "chatgpt" (or copilot, opencode-console, grok) with one of those ids, or use Subscriptions.ChatGpt("gpt-6-luna") in code. e2e login openai --device works without a browser, and e2e login github-copilot reuses gh auth token (or takes --client-id of your own OAuth App, and --enterprise-url for GitHub Enterprise). The login is stored in ~/.config/e2e/oauth.json, the file upstream's npx e2e login writes, so a login made with either tool serves both. E2E_OAUTH_CREDENTIALS holding that JSON stands in for the file, and OPENCODE_API_KEY replaces an OpenCode Console login. Use API keys in CI.
A Claude Pro or Max plan cannot serve the agent. Anthropic allows that login only in its own apps. To run Claude on a subscription, use GitHub Copilot. Copilot writes model ids with a dot (claude-sonnet-5.5), where the Anthropic API uses a dash (claude-sonnet-5-5), so take the id from e2e models github-copilot:
"agents": { "default": { "provider": "copilot", "model": "claude-sonnet-5.5" } }
From a clone of this repo, run the tool with dotnet run --project src/E2E.Cli -- login github-copilot.
An agent entry also takes judge (the model id for assert, waitFor, and extract), system, context, maxSteps, maxModelCalls, judgmentTimeout, and providerOptions. agents can name more agents than default; a call picks one with its Agent option, such as new ActOptions { Agent = "careful" }.
cache.mode is off, read-only, or read-write. Unset, it is read-write locally and read-only when CI is set. cache.strict fails a recording that no longer matches with REPLAY_STALE instead of running the step live. cache.dir resolves against the config file's directory.
Each secret reads E2E_SECRET_<NAME> first, then the config value. null means the variable is required. A test gets one with Secrets.Get("stripe-key").
A fixture overrides any value with the matching property, such as BaseUrl, CacheMode, or ActionTimeout, or replaces the whole config by overriding Config.
Sample
The sample is a real end-to-end test of Dariten, a public personal finance demo. It drives Chromium through WebEngine and installs the browser on its first run. The demo data is shared and anyone can edit it, so the tests only read the app and never check specific balances.
The_dashboard_links_to_the_transaction_registeruses locators only. It needs no model.An_agent_filters_the_register_by_categoryasks the agent to filter the register, then judges the result. It runsclaude-sonnet-5.5on a GitHub Copilot plan and is in theRealModelcategory, which CI skips. A second passing run replays the filter without asking the model to act.
Sign in once with the GitHub CLI already logged in, then run the sample:
dotnet run --project src/E2E.Cli -- login github-copilot
dotnet test --project samples/E2E.Sample
To use an API key instead, change agents.default in samples/E2E.Sample/e2e.config.json, for example to "provider": "openai", "model": "gpt-4.1-mini" with OPENAI_API_KEY set.
Tests
dotnet test E2E.slnx -c Release
Most unit tests use DocumentEngine and a scripted model. No test needs an API key. The Playwright tests install Chromium once per run, before the first of them starts, so the first run needs network access.
What is in this port
| JavaScript | .NET |
|---|---|
e2e test, expect, agent, cache, and @e2e-dev/web |
E2E (WebEngine) |
| NUnit | E2E.NUnit (E2ETest) |
e2e login, e2e logout, e2e models |
E2E.Cli (the e2e .NET tool) |
@e2e-dev/mobile, @e2e-dev/github, @e2e-dev/kernel, and @e2e-dev/eas are not ported. Details are in COMPATIBILITY.md.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- Microsoft.Playwright (>= 1.63.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on E2E:
| Package | Downloads |
|---|---|
|
E2E.NUnit
NUnit fixture for the E2E .NET port. Each test gets an engine session, and the replay cache commits from the NUnit result. |
|
|
E2E.XUnit.V3
xUnit v3 fixture for the E2E .NET port. Each test gets an engine session, and the replay cache commits from the xUnit result. |
GitHub repositories
This package is not used by any popular GitHub repositories.