Table of Contents

Getting Started

Dramaturge automates Chrome, Firefox, and Edge from .NET. It launches the browser, finds elements, acts on them the way a user would, and checks the page, waiting at each step until the page is ready.

Pre-release: Dramaturge is at version 0.0.x, and its API may change in any release.

Installation

dotnet add package Dramaturge

Dramaturge runs on .NET Standard 2.0 (so on .NET Framework 4.6.2 and later) and .NET 10, and supports native AOT on .NET 10. The Dramaturge.Browsers package, which downloads and launches browsers, comes with it.

A First Program

await using BrowserGroup group = await BrowserGroup.LaunchAsync(BrowserLauncher.Configure(BrowserKind.Chrome));
Page page = await group.DefaultBrowser.NewPageAsync();
await page.NavigateAsync("https://example.com");

ElementLocator heading = page.GetByRole("heading");
Console.WriteLine(await heading.TextContentAsync());

await page.GetByRole("link").ClickAsync();
await Expect(page).Not.ToHaveUrlAsync("https://example.com/");

The first launch downloads Chrome for Testing into a cache in your user profile, so it takes a little while; later launches use the cached copy. Expect is a static method of Assertions, brought in with using static Dramaturge.Assertions;.

The Objects

  • A BrowserGroup is one browser process and the WebDriver BiDi session that drives it. BrowserGroup.LaunchAsync starts both from a configured launcher; disposing the group ends the session and closes the browser.
  • A Browser is a set of pages that share cookies, storage, and cache: a WebDriver BiDi user context. DefaultBrowser is the one every browser process has; CreateBrowserAsync adds another, isolated from the rest, with its own options. A launched browser may start with no page: headless Chrome and Edge launched without a driver start without a window; otherwise a browser starts with one blank tab, and Firefox always keeps one. Open the pages you need with NewPageAsync.
  • A Page is a tab or window, and its Frames are its main document and the iframes within it. A page's navigation, script, and locator methods act on its main frame.
  • An ElementLocator describes how to find elements. It finds nothing when it is created; each action or read looks the element up again, so a locator stays correct when the page changes underneath it.
// A browser of its own: cookies, storage, and cache that no other browser in the group shares.
Browser browser = await group.CreateBrowserAsync();
Page page = await browser.NewPageAsync();

// Frames are found from the elements that hold them.
Frame frame = await page.Locate(new WebDriverBiDi.BrowsingContext.CssLocator("iframe#editor")).ContentFrameAsync();
await frame.GetByRole("textbox").FillAsync("Hello");

// Closing the browser closes its pages and discards its cookies and storage.
await browser.CloseAsync();

Waiting

Dramaturge waits so that your code does not have to:

  • Actions wait for their element. A click waits until exactly one element matches, and until it is visible, stable (not moving), enabled, and not covered by another element, scrolling it into view if needed. Typing waits until the element can be edited. An element that is removed or replaced while an action waits is found again.
  • Reads wait for their element. TextContentAsync, InputValueAsync, GetAttributeAsync, and the other reads wait until exactly one element matches. CountAsync and IsVisibleAsync answer at once.
  • Navigation waits for the page to load. NavigateAsync and the other navigation methods wait until the document is loaded, or until the state you ask for.
  • Expectations retry. Expect(...) checks its condition again until it holds, and fails after its timeout saying what it last saw. Use it, not a read and a .NET assertion, for anything the page may still be changing.

Nothing waits for a fixed time. The limits are 30 seconds for actions and navigation, and 5 seconds for expectations; Configuration changes them.

When Something Fails

try
{
    await page.GetByRole("button", "Save").ClickAsync();
}
catch (WebDriverBiDiTimeoutException ex)
{
    // Names the locator and what the last check saw, such as "the element was disabled".
    Console.WriteLine(ex.Message);
}
catch (AmbiguousElementException ex)
{
    // More than one element matched; a locator for an action must match exactly one.
    Console.WriteLine(ex.Message);
}
  • WebDriverBiDiTimeoutException: an action, read, or wait ran out of time. The message names the locator and what the last check saw.
  • ExpectationFailedException: an expectation was not met in time. Its Expected and Actual properties say what was required and what was seen.
  • AmbiguousElementException: a locator for one element matched several. Narrow it, or choose one with First(), Last(), or Nth(index).
  • InvalidOperationException: the element cannot do what was asked, such as filling a <div>. This fails at once, because waiting would not help.

None of these depend on a test framework, so Dramaturge works with any, or none.

In a Test Project

The Dramaturge.Xunit, Dramaturge.NUnit, Dramaturge.MSTest, and Dramaturge.TUnit packages give test classes a page each, in a browser of its own, launch the browser once for the whole run, and save screenshots of a failed test's pages; Test Frameworks describes them. The examples project is a complete xUnit project, built on Dramaturge.Xunit, that tests a small shop, with routes that serve its pages and answer its API, so that the tests need no server.

With a Driver You Connected Yourself

Dramaturge is built on WebDriverBiDi.NET, and can be added to a BiDiDriver that is already connected, to use both:

// Disposing the group removes what it added, and leaves the driver, its session, and the browser running.
await using BrowserGroup group = await BrowserGroup.ConnectAsync(driver);
Page page = await group.DefaultBrowser.NewPageAsync();

Next Steps