C# scripting reference for Trowser β write and run test scripts interactively or from the command line. For full reference, see trowserkit/scripting-reference.md.
| Ctrl + Enter | Run/Stop current script |
| Ctrl + K | Toggle line comments (//) on selected lines |
| Ctrl + 0 | Show/hide the script console panel |
| Ctrl + L | Show/hide AI Assistant |
| Ctrl + D | Copy random test data to clipboard (see GetRandomTestData) |
| Ctrl + I | Get a testing inspiration message |
| Ctrl + B | Toggle screen curtain to simulate blind user |
| Ctrl + Shift + T | Write a Tester's note |
| Ctrl + Shift + S | Take a screenshot (if Session Log is active) |
| Ctrl + T | Open a new browser tab |
| Ctrl + W | Close current browser tab |
| Ctrl + Tab | Switch to next tab |
| Ctrl + Shift + Tab | Switch to previous tab |
| Ctrl + 1...9 | Switch to tab by number |
| Alt + Home | Return to welcome page, clear captured traffic |
| F12 | Developer tools |
Click βΆ Run to start a script. While running, you can stop the script or pause it if you need to check or change something before resuming. The script output shows [Cancelled] and reports elapsed time.
Cancellation is cooperative: the script stops at the next async checkpoint (Sleep, Navigate, Click, WaitFor*, etc.). Pure CPU loops without async calls cannot be interrupted.
Click π Watch in the Script Console toolbar to activate watch mode (button turns orange). While active, the current script runs automatically on every page navigation β perfect for continuous smoke testing as you browse. Quicktest SurpriseMe can have serendipitous effects.
Click π Watch again to deactivate.
Scripts can also call await SurpriseMe() directly to run a random eligible quicktest β handy in watch mode for varied checks on each navigation.
In watch mode, full-history APIs like GetConsoleErrors() accumulate across pages β an error from page 1 would fail on every subsequent page. Use the page-scoped variants instead, which only check errors from the current navigation:
// Watch mode script β checks current page only
await WaitForIdle(300, 5000);
Assert.Equal(0, GetPageConsoleErrors().Count, "No JS errors on this page");
Assert.Equal(0, Network.FindPageErrors().Count, "No HTTP errors on this page");
| Full Session | Current Page Only | Description |
|---|---|---|
GetConsoleErrors() | GetPageConsoleErrors() | JS console errors |
GetConsoleLog() | GetPageConsoleLog() | All console messages |
Network.FindErrors() | Network.FindPageErrors() | HTTP 4xx/5xx errors |
GetNetworkLog() | GetPageNetworkLog() | All network requests |
Console.Entries | Console.PageEntries | Console entry list |
Network.Entries | Network.PageEntries | Network entry list |
The Quicktests βΎ dropdown in the toolbar provides ready-made test scripts, grouped by folder. Select one to load it into the editor, then run it.
| Name | Description |
|---|---|
SiteHealth | Comprehensive health check: all audits on the current page |
SurpriseMe | Run a random non-destructive quicktest (great with Watch mode) |
| Name | Description |
|---|---|
BackAndForward | Navigate Back 10 times, Forward 10 times, verify the page is unchanged |
LinkCheck | Check all links on the page for broken URLs |
RequiredForbiddenStrings | Assert required strings appear and forbidden strings do not |
| Name | Description |
|---|---|
BrowserCompatibilityScan | Scan JavaScript for features that may break older browsers |
HtmlValidation | Check markup for errors, accessibility issues, and deprecated elements |
ThirdPartyAudit | Third-party resource inventory (domains, sizes, SRI coverage) |
| Name | Description |
|---|---|
BiggestFiles | List the 10 largest files from captured network traffic |
PerformanceCheck | Web Vitals performance check on the current page |
ResourceLeakProbe | Compare memory and DOM resources before and after user actions |
| Name | Description |
|---|---|
ConsoleAndNetworkErrors | Report console errors/warnings and failed HTTP requests from the session |
FaultInjection | Test how the page handles API failures (500, 404, invalid JSON, tampering) |
MonkeyTest | Gremlins.js monkey test plus smart random interaction stress test |
RapidFire | Many fast clicks on the right-clicked element (via Run Script β uses RunScriptTarget / This) |
RequestFuzzer | Fuzz query params, JSON bodies, and headers on endpoints from the network log |
TotalRefresh | Refresh the page 100 times and check for errors |
| Name | Description |
|---|---|
1 AttackSurfaceFromNetwork | Attack surface map from captured network traffic |
2 JavaScriptVulnerabilities | Retire.js scan for known vulnerable JavaScript libraries |
2 JsAudit | Static scan of external JS for credentials, internal URLs, debug markers |
2 JwtAnalysis | Decode JWTs from cookies/storage and flag hygiene issues |
2 OidcFromNetwork | Analyze OIDC provider from captured IdP traffic |
2 SecretsHarvest | Unified secret scan across JS, cookies, storage, network, and page text |
2 SecurityScan | Full security scan: JS CVEs, headers, CORS, XSS, mixed content, endpoints |
2 ThirdPartySecurity | Third-party resource security: domains, sizes, SRI coverage |
3 AutoScan | Discover endpoints from traffic and run targeted injection scanners |
3 FirewallTest | Verify CSP, SRI, and sandbox defenses actually block bypass attempts |
3 GraphqlAnalysis | GraphQL introspection, batching, depth, and limit probes |
4 CstiScan | Client-side template injection probe (AngularJS, Vue, Handlebars) |
4 SamlFromNetwork | Decode SAMLRequest/Response from the network log |
5 HttpSmuggling | HTTP request smuggling timing probes against the page origin |
6 WafBypass | Detect WAF blocks and test encoding/header/path bypass transforms |
BolaAcrossSessions | Compare API responses across peer sessions for BOLA/authorization flaws |
| Name | Description |
|---|---|
A00 Full Site Security | Run all Trowser Twelve playbooks and correlate attack chains |
A00SpecificScan | Active scan on one URL+method from the network log |
A01 Access Control | IDOR, CSRF, mass assignment, BFLA, privilege escalation |
A02 Misconfiguration | Headers, cookies, CORS, endpoints, clickjacking, subdomain takeover |
A03 Supply Chain | JS CVEs, SRI inventory, manifest/OSV, CI/CD exposure, file upload |
A04 Crypto and Secrets | TLS/HSTS, secrets harvest, token entropy, cookie audit |
A05 Injection | SQLi, NoSQL, XSS, SSTI, prototype pollution, and related injection sinks |
A06 Insecure Design | Workflow skip, race conditions, business-logic tampering |
A07 Authentication | JWT/OAuth, session fixation, password reset, account enumeration |
A09 Operational Security | Error disclosure, fail-open behavior, log-leak probes |
A11 SSRF | Internal IP, cloud metadata, webhook, and URL-parameter SSRF probes |
A12 Availability | Rate limits, body-size limits, ReDoS, GraphQL cost, load testing |
A13 AI and LLM | LLM endpoint discovery, prompt injection, output sink trace |
A14 Other | Cache poisoning, HTTP smuggling, host header, open redirect, WebSocket injection |
| Name | Description |
|---|---|
ChecksAfterSession | Basic smoke check: structure, headings, images, console and network errors |
TrowserSmokePass | Full Trowser regression suite (navigation, scripting APIs, CLI smoke) |
| Name | Description |
|---|---|
GUIAudit | GUI visual consistency + image optimization |
GetAllPageStrings | Dump every text node on the page (including hidden) for proof-reading |
ResponsiveAccessibility | WCAG AA check across all viewport presets |
ScreenReaderTexts | List alt text, ARIA labels, titles, and SVG descriptions |
TabOrderCheck | Walk focus order with Tab/Shift+Tab; detects modal scope leaks |
ToggleScreenCurtain | Toggle blind-testing screen curtain (same as Ctrl+B) |
.csx files in the right folder, and rebuilding Trowser.Trowser can record your interactions and generate a .csx script automatically β useful for creating regression tests and providing LLMs with concrete interaction sequences.
Click β Record in the toolbar (turns red while recording). Interact with the page normally β click buttons, type in forms, select dropdowns, navigate. Click β Record again to stop. The generated script appears in the Script Console.
The recorder uses semantic locators (ByRole, ByLabel, ByText) whenever possible. Dynamic IDs are automatically avoided. Dropdown menus are detected and recorded as Hover + Click pairs.
Right-clicking any element always offers Run Script: the clicked element becomes the script target (This and the RunScriptTarget shorthand β¦), and the Script Console runs the script currently in the editor. Trowser resolves that target when the context menu opens β nothing is injected on every navigation.
While recording or session logging, additional items appear:
| Menu Item | Effect |
|---|---|
| Run Script | Sets This to the elementβs CSS selector and runs the console script (see RapidFire quicktest). |
| Assert text | await AssertPageContains("visible text"); in the recording |
| Element of interest | // Element of interest: button "Submit" comment in the recording |
Assert text adds a verification step β assert that the element's text is visible on the page.
Element of interest adds a comment marking this element as noteworthy for LLM-driven testing.
Click βΆ Play to open a file picker, select a .csx file, and execute it immediately β no need to paste the script into the editor first.
Scripts are C# with full async/await support. All automation methods are available as top-level calls β no object prefix needed. Common namespaces are auto-imported.
// Navigate and inspect
await Navigate("https://example.com");
var title = await GetTitle();
Log($"Title: {title}");
// Interact with elements
await Type("#email", "test@example.com");
await Click("#submit");
await WaitForNavigation();
// Assert results (non-throwing, summary auto-printed)
Assert.Contains("success", await GetCurrentUrl(), "Navigated to success page");
await AssertPageContains("Thank you");
When you right-click an element and choose Run Script, that element's CSS selector is available as This. Use the shorthand RunScriptTarget (β¦) in any action method:
// Right-click a button, then run this script
await Click(RunScriptTarget);
// equivalent to: await Click(This);
| Method | Description |
|---|---|
Navigate(url) | Navigate and wait for page load |
GoBack() | Navigate back in history |
GoForward() | Navigate forward in history |
GetCurrentUrl() | Current page URL |
GetTitle() | Current page title |
GetPageText() | All visible text on the page |
GetAllPageText() | All text nodes including hidden/off-screen; one line per segment (shadow DOM) |
GetScreenReaderTexts() | Alt, aria-label, aria-labelledby/describedby, title, SVG title/desc; one line each with [kind] prefix |
GetPageSource() | Full HTML source |
Search visible page text with context and a nearest CSS selector β useful for status messages, table cells, and other non-interactive content. Complements AssertPageContains.
| Method | Description |
|---|---|
FindText(query, ...) | Search page text; returns matchCount and matches[] with context, selector, and rect. Supports regex, scroll-to, and highlight. |
ClearFindHighlights() | Remove highlights added by FindText(..., highlight: true) |
var result = await FindText("Order confirmed");
Log($"Found {result["matchCount"]} matches");
All methods auto-wait for elements to be ready (visible + enabled, default 5s) and pierce shadow DOM automatically. For Web Components, use shadow-piercing selectors: host >>> inner (e.g. my-widget >>> button).
| Method | Description |
|---|---|
Click(selector) | Click an element. Auto-waits for navigation on links. |
Type(selector, text, clearFirst?) | Clear field, then type text (clearFirst: false to append) |
Select(selector, value) | Select dropdown option by value |
SetFileInput(selector, paths) | Assign file(s) to a <input type="file"> (absolute host paths; hidden inputs OK) |
Hover(selector) | Hover over element (CDP mouse β triggers CSS :hover, tooltips, and menus) |
PressKey(key, selector?, modifiers?) | Send keyboard key (Tab, Enter, Escape, etc.) |
GetText(selector) | Get element inner text |
GetAttribute(selector, attr) | Get HTML attribute value |
GetProperty(selector, prop) | Get live DOM property (value, checked, selectedIndex, etc.) β works with Angular/React/Vue |
IsChecked(selector) | Check if checkbox/toggle is checked (reads DOM property) |
Exists(selector) | Check if element exists (returns bool, never throws, no auto-wait) |
Count(selector) | Count matching elements (returns 0 if none) |
GetSelectState(selector) | Read actual DOM state of a <select>: value, selectedIndex, selectedText |
Extract(selector, fields...) | Extract structured data from repeating elements (table rows, card lists, etc.) |
Extracts data from repeating page elements into a list of dictionaries. Tables auto-detect columns from <th> headers. Use field tuples to map sub-selectors. Append @attr to extract an attribute instead of text.
// Auto-extract table (column headers become field names)
var rows = await Extract("table.products");
// Field mapping from repeating containers
var products = await Extract(".product-card", ("name", "h3"), ("price", ".price"), ("url", "a@href"));
// Simple text list
var items = await Extract("ul.results li");
// With limit
var top5 = await Extract("table.data", null, limit: 5);
Find elements by meaning instead of CSS selectors β resilient to DOM changes.
await Click(ByRole("button", "Submit"));
await Type(ByLabel("Email"), "user@example.com");
await Click(ByText("Sign in"));
await Type(ByPlaceholder("Search..."), "query");
await Click(ByTestId("submit-btn"));
| Locator | Finds by |
|---|---|
ByRole(role, name?) | ARIA role + accessible name |
ByLabel(text) | Associated label text |
ByText(text) | Visible text content |
ByPlaceholder(text) | Input placeholder |
ByTestId(id) | data-testid attribute |
| Method | Description |
|---|---|
WaitForElement(selector, timeoutMs?) | Wait for element to appear (throws on timeout) |
WaitForNavigation(timeoutMs?) | Wait for a single page navigation (returns false on timeout) |
WaitForFinalNavigation(quietMs?, timeoutMs?) | Wait for redirect chain to settle; returns nav count, 0 on timeout |
WaitForRequest(urlPart, timeoutMs?) | Wait for matching network request (throws on timeout) |
WaitForRequest(urlPart, method?, status?, timeoutMs?) | Wait with method/status filters (throws on timeout) |
WaitForIdle(quietMs?, timeoutMs?) | Wait for network quiet + no CSS animations (returns false on timeout) |
Sleep(ms) | Pause execution |
Click and Type already auto-wait. You only need explicit waits for things like page loads, AJAX responses, or dynamic content appearing.Methods behave differently when elements or timeouts are missing:
| Method | Not found / timeout |
|---|---|
Click, Type, Select, Hover, GetText, β¦ | Throws InvalidOperationException |
Exists | Returns false (no auto-wait β use WaitForElement first after navigation) |
Count | Returns 0 |
WaitForElement, WaitForRequest | Throws TimeoutException |
WaitForNavigation, WaitForIdle | Returns false (does not throw) |
await Scroll(direction: "down"); // Scroll one viewport
await Scroll(direction: "top"); // Jump to top
await Scroll("#footer", scrollIntoView: true); // Scroll element into view
All assertions log pass/fail and continue β they never throw. Use Assert.Section() to group them.
Assert.True(cond, msg?) |
Assert.False(cond, msg?) |
Assert.Equal(expected, actual, msg?) |
Assert.NotEqual(expected, actual, msg?) |
Assert.Null(value, msg?) |
Assert.NotNull(value, msg?) |
Assert.NotEmpty(value, msg?) |
Assert.GreaterThan(expected, actual, msg?) |
Assert.LessThan(expected, actual, msg?) |
Assert.Contains(sub, actual, msg?) |
Assert.NotContains(sub, actual, msg?) |
Assert.Matches(pattern, actual, msg?) |
Assert.CountEquals(n, collection, msg?) |
Assert.Pass(msg) |
Assert.Fail(msg) |
Assert.Section(name) |
Assert.Section("Login Flow");
Assert.True(await Exists("#login-form"), "Login form is present");
await AssertPageContains("Sign in");
await AssertPageNotContains("Error");
await AssertAccessible("AA"); // Fail if any WCAG AA violations
await PrintAccessibilityReport("AA"); // Print full report
var results = await CheckAccessibility("AA");
foreach (var v in results.Violations)
Log($"[{v.Impact}] {v.RuleId}: {v.Help}");
await AssertPerformance(); // Fail if any Core Web Vital is not Good
await PrintPerformanceReport(); // Print full report
await AssertPerformanceMetric("LCP", 2500); // Assert LCP <= 2500ms
await AssertPerformanceMetric("TTFB", 800); // Assert TTFB <= 800ms
var perf = await CheckPerformance();
foreach (var m in perf.Metrics)
Log($"[{m.Rating}] {m.Name}: {m.Value}{m.Unit}");
await AssertSecure(); // Fail if any known CVEs
await PrintSecurityReport(); // Print full report
await AssertSecure(minSeverity: Severity.High); // Only high/critical
var scan = await CheckSecurity();
foreach (var lib in scan.Libraries)
Log($"{lib.Name} {lib.Version} -- {(lib.IsVulnerable ? "VULNERABLE" : "OK")}");
await AssertSecurityHeaders(); // Fail if any issues
await PrintSecurityHeadersReport(); // Print full report
await AssertSecurityHeaders(minSeverity: Severity.High); // Only high/critical
var result = await CheckSecurityHeaders();
foreach (var f in result.Findings)
Log($"[{f.Severity}] {f.Header}: {f.Description}");
Audits CORS by probing the page URL with various Origin headers β detects reflected origins, wildcard policies, null origin acceptance, and subdomain/scheme attacks.
await AssertCors(); // Fail if any CORS issues
await PrintCorsReport(); // Print full report
await AssertCors(minSeverity: Severity.High); // Only high/critical
var result = await CheckCors();
var credLeaks = result.Issues.Where(f => f.AllowCredentials).ToList();
Assert.Equal(0, credLeaks.Count, "No credential exposure via CORS");
Scans for hidden endpoints, admin panels, config files, and API docs by probing common paths. Wordlists: "common" (~50), "full" (~150), "api" (API-focused). Use delayMs to avoid rate limiting.
await AssertDiscover(); // Assert no sensitive endpoints
await PrintDiscoverReport(wordlist: "full"); // Detailed scan
var result = await DiscoverEndpoints(wordlist: "api", basePath: "/api/");
foreach (var d in result.Discovered)
Log($" {d.StatusCode} {d.Path} [{d.ContentType}]");
// Slow scan to avoid 429 rate limiting
await PrintDiscoverReport(wordlist: "common", delayMs: 200);
await AssertHtmlValid(); // Fail if any errors
await PrintHtmlReport(); // Print full report
var result = await CheckHtml();
foreach (var msg in result.GetErrors())
Log($"line {msg.Line}: {msg.Message} ({msg.RuleId})");
var a11y = result.GetByCategory("Accessibility"); // Filter by category
Checks all links on the page for broken URLs, redirects, and connection errors. Sends HEAD requests from the server side (no CORS issues). Redirects are observations, not errors.
await AssertLinksValid(); // Fail if any broken links
await PrintLinkCheckReport(); // Print full report
await AssertLinksValid(scope: "all"); // Check all resources too
await AssertLinksValid(external: false); // Internal links only
await AssertLinksValid(delayMs: 200); // Slower, avoids 429 rate limits
var result = await CheckLinks();
Log($"Checked {result.CheckedCount}, Broken: {result.BrokenCount}");
foreach (var link in result.Broken)
Log($" [{link.StatusCode}] {link.Url}");
// Redirects are separate β not treated as errors
var redirects = result.Links.Where(l => l.Status == LinkStatus.Redirect);
foreach (var r in redirects)
Log($" Redirect: {r.Url} β {r.RedirectUrl}");
Scope: "anchors" (default, <a> tags only), "resources" (images, scripts, stylesheets), "all" (everything).
Audits the DOM for XSS-prone patterns: inline event handlers, javascript: URLs, dangerous sinks, cross-domain scripts without SRI, data URIs, external form actions, and base tag hijacking. Inspired by OWASP ZAP passive scan rules.
await AssertXssClean(); // Fail if any XSS findings
await PrintXssReport(); // Print full report
await AssertXssClean(minSeverity: XssSeverity.Medium); // Medium+ only
var result = await CheckXss();
Log($"Found {result.FindingCount} XSS-related findings");
var high = result.GetByMinSeverity(XssSeverity.High);
Assert.Equal(0, high.Count, "No high-severity XSS patterns");
Extracts all visible text in a structured format β headings, paragraphs, links, buttons, labels, images. Designed for proofreading and text analysis.
var text = await GetStructuredText();
Log($"{text.WordCount} words, {text.Headings.Count} headings, {text.Links.Count} links");
// Heading hierarchy
foreach (var h in text.Headings)
Log($"{"".PadLeft(h.Level * 2)}H{h.Level}: {h.Text}");
// Full text for proofreading
Log(text.AllText);
await PrintStructuredText();
Audits visual consistency (fonts, colors, sizing, spacing) and checks images for optimization issues (oversized, missing dimensions, missing lazy loading).
await AssertConsistent(); // Fail if warnings found
await PrintConsistencyReport(); // Print full report
var result = await CheckConsistency();
Log($"Fonts: {result.Stats.FontFamilies.Count}, Colors: {result.Stats.TextColors.Count}");
Log($"Image issues: {result.Stats.ImageIssues.Count}");
// Check image optimization specifically
foreach (var img in result.Stats.ImageIssues)
Log($" [{img.IssueType}] {img.Description}");
// Custom thresholds
Assert.True(result.Stats.FontFamilies.Count <= 3, "At most 3 font families");
Inventories all third-party resources (scripts, stylesheets, iframes, fonts). Reports domains, transfer sizes, and Subresource Integrity (SRI) coverage.
await AssertThirdPartySecure(); // Fail if SRI issues found
await PrintThirdPartyReport(); // Print full report
var result = await CheckThirdParty();
Log($"Third-party: {result.TotalCount} resources from {result.DomainCount} domains");
Log($"SRI coverage: {result.SriCount} / {result.TotalCount}");
foreach (var r in result.Resources.Where(r => !r.HasSri && r.Type == "script"))
Log($" Script without SRI: {r.Url}");
Detects HTTP resources loaded on HTTPS pages. Active mixed content (scripts, iframes) is blocked by modern browsers. Part of the SecurityScan quicktest.
await AssertNoMixedContent(); // Fail if any mixed content
await AssertNoMixedContent(minSeverity: MixedContentSeverity.High); // Only fail on active
await PrintMixedContentReport(); // Print full report
var result = await CheckMixedContent();
if (result.IsHttps)
Log($"Mixed content: {result.FindingCount} issues ({result.HighCount} high)");
await AssertCompatible(); // Default: iOS 15, Android 10
await AssertCompatible(minIos: 16, minAndroid: 10); // Custom targets
await PrintCompatibilityReport(minIos: 15, minAndroid: 10); // Print full report
var result = await CheckCompatibility(minIos: 15); // iOS only
foreach (var f in result.Features)
Log($"{f.Name}: Chrome {f.ChromeMin}+, Safari {f.SafariMin}+");
var risky = result.Targets.Where(t => t.UnsupportedFeatures.Count > 0);
foreach (var t in risky)
Log($"{t.Name}: {t.UnsupportedFeatures.Count} unsupported feature(s)");
await AssertRobust(); // Fail if errors found
await PrintMonkeyTestReport(); // Print full report
var result = await RunMonkeyTest(actions: 200, seed: 42);
Log($"Survived: {result.Survived}, Errors: {result.ErrorCount}");
await RunMonkeyTest(new MonkeyTestOptions {{ // Targeted test
Species = MonkeySpecies.Clicker | MonkeySpecies.Typer,
Actions = 300
}});
await AssertSmartMonkey(); // Fail if issues found (requires scope)
await PrintSmartMonkeyReport(); // Print detailed report
var result = await RunSmartMonkey(maxActions: 50, followLinks: true, maxPages: 20);
Log($"Passed: {result.Passed}, Pages: {result.PagesVisited}, Issues: {result.IssueCount}");
GetConsoleErrors() |
GetPageConsoleErrors() |
GetConsoleLog() |
GetPageConsoleLog() |
Console.Find(text) |
Console.HasErrors |
PrintConsoleLog() |
ClearConsoleLog() |
FindRequests(urlPart) |
Network.FindErrors() |
Network.FindPageErrors() |
Network.FindByUrl(urlPart) |
Network.FindByMethod(m) |
Network.FindByStatus(code) |
Network.FindByType(type) |
PrintNetworkLog() |
ClearNetworkLog() |
// Check for errors after testing
Assert.Equal(0, GetConsoleErrors().Count, "No JS errors");
Assert.Equal(0, Network.FindErrors().Count, "No HTTP errors");
// Inspect a specific API call (bodies are captured for Fetch/JSON/XML requests)
var req = await WaitForRequest("/api/users");
Assert.Equal(200, req.StatusCode);
if (req.ResponseBody != null)
Assert.Contains("\"id\":", req.ResponseBody, "Response includes ID");
NetworkEntry has RequestBody and ResponseBody properties for API-like requests (capped at 100KB). Use them to verify payloads.Send HTTP requests from the browser context using fetch() β includes the page's cookies, auth tokens, and session automatically. Relative URLs resolve against the current page origin. Returns a JSON string with ok, status, headers, body, etc.
| Method | Description |
|---|---|
Fetch(url, method?, headers?, body?, timeoutMs?, redirect?) | Single request; set redirect: "manual" to capture 3xx without following |
FetchBatchAsync(requests, concurrent?) | Multiple requests; set concurrent: true for parallel/race testing |
var raw = await Fetch("/api/users", "POST",
headers: new Dictionary<string, string> { ["Content-Type"] = "application/json" },
body: "{\"name\": \"Alice\"}");
var doc = System.Text.Json.JsonDocument.Parse(raw);
Assert.Equal(201, doc.RootElement.GetProperty("status").GetInt32(), "User created");
var cookies = await GetCookies();
var session = await GetCookie("sessionId");
if (session != null)
Assert.True((bool)session["httpOnly"], "Session is HttpOnly");
await SetCookie("test", "true", secure: true);
await DeleteCookie("sessionId");
await ClearCookies();
// Read all localStorage
var storage = await GetAllStorage("local");
Log($"localStorage: {storage.Count} entries");
// Get/set/delete individual keys
var theme = await GetStorageItem("local", "theme");
await SetStorageItem("local", "theme", "dark");
await DeleteStorageItem("local", "authToken");
await ClearStorage("local");
// sessionStorage works the same way
await SetStorageItem("session", "tempData", "test");
// Simulate slow network
await SetNetworkThrottle("3G");
await Navigate("https://example.com");
await WaitForIdle();
// Presets: 2G, Slow3G, 3G, 4G, Offline
await SetNetworkThrottle("Offline");
await Click("#refresh");
await AssertPageContains("No connection");
// Custom values (KB/s down, KB/s up, latency ms)
await SetNetworkThrottle(100, 50, 1000);
// Restore full speed
await ClearNetworkThrottle();
// Simulate a server error
MockResponse("/api/users", statusCode: 500,
body: "{\"error\": \"Internal Server Error\"}");
await Click("#load-users");
await WaitForIdle();
await AssertPageContains("Something went wrong");
ClearMocks();
// Simulate slow response
MockResponse("/api/data", statusCode: 200,
body: "{\"ok\": true}", delayMs: 5000);
Intercept requests matching a URL regex and hold them until you decide: continue, fulfill with a custom response, or abort. Auto-continues after 120 seconds.
| Method | Description |
|---|---|
AddInterceptRule(urlPattern, method?) | Add breakpoint (regex match on URL) |
RemoveInterceptRule(urlPattern) | Remove a breakpoint rule |
ClearInterceptRules() | Remove all breakpoints, release pending requests |
GetInterceptRules() | List active breakpoint rules |
GetPendingRequests() | List currently held requests |
ResolvePendingRequest(id, decision) | Continue, fulfill, or abort a pending request |
// Set a breakpoint, trigger it, then fulfill with custom response
AddInterceptRule("/api/users");
await Click("#load-users");
await Sleep(1000);
var pending = GetPendingRequests();
ResolvePendingRequest(pending[0].Id, new InterceptDecision {
Action = InterceptAction.Fulfill,
StatusCode = 500,
Body = "{\"error\": \"fail\"}"
});
ClearInterceptRules();
MockResponse is usually simpler.var tab2 = await NewTab("https://example.com");
SwitchTab(0); // Back to first tab
var tabs = GetTabs(); // List all tabs
Log($"Tab count: {GetTabCount()}");
await CloseTab(tab2); // Close by index
Exploratory methods that return List<Dictionary<string, string>> for quick page analysis.
| Method | Returns |
|---|---|
GetLinks() | All links: href, text, target |
GetHeadings() | All h1βh6: level, text, visible |
GetImages() | All images: src, alt, hasAlt, width, height |
GetFormFields() | All inputs: tag, type, name, id, value, label, required |
GetMetaTags() | All meta tags: name, content, charset |
var headings = await GetHeadings();
foreach (var h in headings.Where(h => h["visible"] == "True"))
Log($"{h["level"]}: {h["text"]}");
var images = await GetImages();
var missingAlt = images.Where(i => !bool.TryParse(i.GetValueOrDefault("hasAlt"), out var hasAlt) || !hasAlt).ToList();
Assert.Equal(0, missingAlt.Count, "All images have alt text");
| Method | Description |
|---|---|
GetSnapshot(fullText?, textSelector?) | Full page state (same as REST GET /api/state) |
GetInteractiveElements() | All interactive elements with ref numbers and visibility |
GetSelectorByRef(ref) | Resolve a ref number to a CSS selector for use with Click, Type, etc. |
GetRefsInContainer(selector, elements) | Ref numbers of elements inside a CSS container |
// Act on an element by ref (same refs as GET /api/state)
var elements = await GetInteractiveElements();
var submit = elements.First(e => e["label"]?.ToString() == "Submit");
var refNum = ((System.Text.Json.JsonElement)submit["ref"]).GetInt32();
var selector = GetSelectorByRef(refNum);
if (selector != null) await Click(selector);
Trowser detects tools registered by web pages via navigator.modelContext (W3C Web Model Context API). Registered tools are available for discovery and invocation.
| Method | Description |
|---|---|
GetWebMcpTools() | List all WebMCP tools on the current page (name, description, inputSchema) |
CallWebMcpTool(name, args?) | Call a WebMCP tool and return the result |
var tools = await GetWebMcpTools();
foreach (var tool in tools)
Log($" {tool["name"]}: {tool["description"]}");
var result = await CallWebMcpTool("add_to_cart", new Dictionary<string, object> {
["productId"] = "abc123", ["quantity"] = "2"
});
Test responsive layouts by changing the viewport size. Uses device emulation β only the page rendering changes, the Trowser window stays the same. Media queries, CSS breakpoints, and layout all respond.
| Method | Description |
|---|---|
SetViewport(width, height) | Set exact viewport in pixels (200β7680 Γ 200β4320) |
SetViewport(preset) | Set viewport to a named preset (see below) |
ResetViewport() | Reset to full screen (same as SetViewport("Desktop")) |
GetViewportSize() | Returns (int Width, int Height) of the current viewport |
| Preset | Size | Use Case |
|---|---|---|
"SmallPhone" | 320 Γ 568 | iPhone SE / small Android |
"BigPhone" | 412 Γ 915 | iPhone 15 / modern Android |
"LandscapePhone" | 844 Γ 390 | iPhone 14 / modern Android (landscape) |
"SmallPad" | 768 Γ 1024 | iPad / small tablet |
"BigPad" | 1024 Γ 1366 | iPad Pro / large tablet |
"Desktop" | (reset) | Full screen β clears the override |
"BigDesktop" | 1920 Γ 1080 | Full HD monitor |
// Test all responsive breakpoints
var presets = new[] { "SmallPhone", "BigPhone", "LandscapePhone", "SmallPad", "BigPad", "Desktop", "BigDesktop" };
foreach (var preset in presets)
{
Assert.Section(preset);
await SetViewport(preset);
await Sleep(500);
await AssertPageContains("Welcome");
await AssertAccessible("AA");
}
// Exact dimensions
await SetViewport(1440, 900);
var (w, h) = await GetViewportSize();
Log($"Viewport: {w}Γ{h}");
// Reset when done
await ResetViewport();
ClearAll() resets console, network, mocks, intercepts, assertions, cookies, storage, throttle, and viewport in one call. ClearSession() is the lighter variant β same reset but preserves cookies and storage (keeps you logged in). Also available via the toolbar Clear All button.
await ClearSession(); // Reset test artifacts, keep login
await ClearAll(); // Full reset including cookies/storage
| Method | Description |
|---|---|
Screenshot(filePath) | Save screenshot as PNG |
Log(message) / Log(message, color) | Print to script output; optional hex color (e.g. "#F44747") |
GetRandomTestData(kind?) | Random test string or security payload ("strings", "payloads", or active tab from Test Data dialog β Ctrl+D) |
ExecuteJs(script) | Run JavaScript, returns string result |
CollectGarbage() | Force GC to free memory (auto-runs after script) |
var payload = await GetRandomTestData("payloads");
if (payload != null) await Type("#comment", payload);
Log("Failed!", "#F44747");
Save scripts as .csx files and run from the command line:
Trowser.exe --run test.csx --url https://example.com
Trowser.exe --run test.csx --visible --timeout 300 --output report.txt
Exit codes: 0 = all passed, 1 = assertion failed, 2 = script error
Safe mode: --safe blocks scripting, fetch, mocks, fuzzing, and most active security probes. Scripts that use Fetch, MockResponse, or injection scanners need safe mode off.
| Object | Type | Description |
|---|---|---|
Assert | TestAssert | Assertion library (non-throwing, summary auto-printed) |
Console | ConsoleLog | Browser console messages for the active tab |
Network | NetworkLog | Network traffic log for the active tab |
Trowser scripts are powered by Roslyn (Microsoft.CodeAnalysis.CSharp.Scripting), the same compiler platform behind Visual Studio and dotnet-script. Scripts use the .csx format and support full C# 12 syntax β pattern matching, records, LINQ, string interpolation, tuples, and more.
For deeper C# language and scripting reference:
| C# Language Documentation | Official C# reference from Microsoft |
| What's New in C# 12 | Latest language features available in scripts |
| Roslyn on GitHub | The open-source compiler platform powering Trowser scripts |