An expressive, YAML-driven scripting layer over Selenium WebDriver — a modern .NET 8 / C# 12 port of the original Java SeleniumScripter (Spicule / Kythera Labs).
Write browser automation as a numbered list of steps in YAML — no C# required to author a test — and run it on Selenium 4. The port preserves the original YAML contract: existing scripts run unchanged.
This is the canonical example from the Java repo's SCRIPT_EXAMPLES.md. It runs as-is on the .NET port — same operation names, selector keys, parameter names, interpolation syntax, and subscript semantics:
# Populate the selects, enter a token, wait for the autocomplete, then loop the results.
0: { operation: wait, selector: id, name: mcoveragetype, timeout: 30 }
1: { operation: select, selector: id, name: mcoveragetype, selectBy: value, value: 6 }
2: { operation: select, selector: id, name: benefitYear, selectBy: value, value: 2021 }
3: { operation: keys, selector: id, name: medicationname, value: Zyvox }
4: { operation: wait, selector: class, name: ui-autocomplete, timeout: 30 }
# Capture the autocomplete labels into a list...
6:
operation: captureList
selector: class
name: ui-menu-item
variable: autocompletelabels
# ...then loop a subscript over each captured label.
7:
operation: loop
variable: autocompletelabels
subscript: searchSubScript
subscripts:
searchSubScript:
1: { operation: keys, selector: id, name: medicationname, value: "{autocompletelabels}" }
2: { operation: snapshot }Run it from C#:
using OpenQA.Selenium;
using SeleniumScripter.Drivers;
using SeleniumScripter.Loaders;
using SeleniumScripter.Model;
using SeleniumScripter.Operations;
// Selenium Manager resolves the browser binary automatically (Firefox by default,
// matching the Java side; Chrome and Edge are also supported).
IWebDriver driver = new DriverFactory().CreateFirefox();
driver.Navigate().GoToUrl("https://example.com/search");
ScriptDocument script = ScriptLoader.LoadFile("samples/myprime.yaml");
var runner = new ScriptRunner(OperationRegistry.CreateDefault(), driver, initialUrl: driver.Url);
await runner.RunAsync(script);
// Captured lists and snapshots are available after the run.
IReadOnlyList<object?>? labels = runner.CaptureLists.Get("autocompletelabels");
IReadOnlyList<Snapshot> pages = (IReadOnlyList<Snapshot>)runner.Snapshots;YAML and JSON are both accepted; ScriptLoader.LoadFile picks the parser by extension.
No code needed — run a script straight from the CLI (src/SeleniumScripter.Cli):
dotnet run --project src/SeleniumScripter.Cli -- run samples/forward.yaml --browser chrome --url https://example.comOptions:
-b, --browser <chrome|firefox|edge> default: chrome
-u, --url <url> navigate here before running
-o, --output <dir> base dir for screenshot/dumpstack output
--headless run the browser headless
--dev enable dev-only operations (pause, dumpstack)
--arg <browser-arg> extra browser argument (repeatable)
It is also packaged as a .NET global tool (ToolCommandName: seleniumscripter), so after dotnet pack + dotnet tool install it runs as seleniumscripter run script.yaml.
YAML / JSON script
│ ScriptLoader → ScriptDocument (numbered steps + subscripts)
▼
OperationRegistry → one IOperationHandler per operation (dispatch table, not a switch)
│
├─ control flow try · do_while · if · for · loop · break · noop · set
├─ interaction wait · click · clickListItem · select · keys · jsclick · …
├─ navigation / utility loadpage · window · restore · alert · token · execute_js · recaptcha
└─ capture / snapshot captureList · snapshot · screenshot · pushsnapshot · dumpstack
│
▼
StepContext → IWebDriver · VariableStore ({…} interpolation) · CaptureListStore · snapshots stack
DriverFactory (Chrome/Firefox/Edge) · Conditions (owned, not DotNetSeleniumExtras)
Design highlights:
- Page-Object-friendly, YAML-on-top. The DSL sits over Selenium 4 so non-engineers can author tests in YAML while engineers extend the operation set in C#.
- One handler per operation, registered by the literal YAML name — adding an operation never means editing a monolithic dispatch
switch. - Owned wait conditions (
Conditions) rather than the unofficialDotNetSeleniumExtras.WaitHelpers. - Parity-first port. Every original golden script parses and runs; behavioral deviations from the Java source (and the bugs deliberately not reproduced) are catalogued in
PORTING_NOTES.md. - Full-page screenshots without AShot —
FullPageScreenshotterreproduces AShot's scroll-and-stitch using ImageSharp (cross-platform).
These are stable strings the port must never rename:
| Concept | Examples |
|---|---|
| Operations | wait, select, keys, click, clickListItem, captureList, loop, for, if, try, do_while, snapshot, screenshot, … |
| Selector strategies | id, class, css / cssSelector, xpath, name, linkText, partialLinkText, tagName |
| Interpolation | {variable} — substituted from the flat variable store (note: ${x} keeps the literal $; see PORTING_NOTES.md) |
dotnet build
dotnet test # unit + parity tests; browser tests skip automaticallyBrowser-launching tests are gated — they run only when SELENIUMSCRIPTER_RUN_BROWSER_TESTS=1 is set (locally or on a runner with browsers installed). The default lane is fast and browser-free.
CI runs on Azure DevOps Pipelines (azure-pipelines.yml) and is mirrored on GitHub Actions (.github/workflows/ci.yml, Ubuntu + Windows).
Beyond unit + parity tests, the repo demonstrates a full QA toolbox:
- Parity tests — every original golden script parses and its operations resolve.
- UI tests — gated headless-browser runs driving real scripts end to end.
- API tests —
SeleniumScripter.Api.Tests(RestSharp + FluentAssertions): data-driven cases asserting status, body shape, and timing. Gated bySELENIUMSCRIPTER_RUN_API_TESTS=1so the default lane is offline. - Performance smoke — a JMeter plan (
perf/posts-smoke.jmx) with a latency-ceiling assertion; seeperf/README.md. - Defect artifacts — on a failed UI test, a triable bundle (screenshot, page source, console log, structured
defect.jsonwith environment + expected-vs-actual) is written for attachment to a build or filing into Azure DevOps Boards / Jira.
Every operation from the Java source is ported except filter (intentionally not carried over — it was Groovy-backed). See PORTING_NOTES.md for the full deviation log.
- Web UI. The original Java repo ships a zero-framework web GUI (build/paste a YAML
script, pick a browser + URL, run it, and view logs + snapshots in the browser). A
SeleniumScripter.Webport is planned: an ASP.NET Core minimal API mirroring the same/api/operations,/api/yaml,/api/run, and/api/run/{id}/…endpoints, reusing the original's staticindex.html/app.js/styles.cssfrontend verbatim (it only talks to those endpoints). The runner, loader, and driver factory it needs already exist. - NuGet polish. Symbol packages (
.snupkg), SourceLink, deterministic build, and a final version/package-id decision (currently2.0.0-alpha.1). - Optional reporting. ExtentReports / Allure HTML output attached to CI artifacts (the dependency-free defect-artifact bundle already covers per-failure evidence).
MIT (preserved from the original).