Verify Headless Browsers

August 2, 2026 ยท View on GitHub

Discussions Build status NuGet Status NuGet Status NuGet Status

Extends Verify to allow verification of Web UIs using headless browsers.

See Milestones for release notes.

Sponsors

Entity Framework Extensions

Entity Framework Extensions is a major sponsor and is proud to contribute to the development this project.

Entity Framework Extensions

Developed using JetBrains IDEs

JetBrains logo.

NuGet

Playwright Usage

Verification of Web UIs via Playwright.

Enable

Enable VerifyPlaywright once at assembly load time:

[ModuleInitializer]
public static void InitPlaywright() =>
    VerifyPlaywright.Initialize(installPlaywright: true);

snippet source | anchor

Instantiate browser

// wait for target server to start
await SocketWaiter.Wait(port: 5000);

playwright = await Playwright.CreateAsync();
browser = await playwright.Chromium.LaunchAsync();

snippet source | anchor

Page test

The current page state can be verified as follows:

var page = await browser.NewPageAsync();
await page.GotoAsync("http://localhost:5000");
await Verify(page);

snippet source | anchor

With the state of the element being rendered as a verified files:

<!DOCTYPE html><html lang="en"><head>
  <meta charset="utf-8">
  <title>The Title</title>
  <link href="https://getbootstrap.com/docs/4.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="jumbotron">
    <h1 class="display-4">The Awareness Of Relative Idealism</h1>
    <p class="lead">
      One hears it stated that a factor within the logical radical priority embodies the
      key principles behind the best practice marginalised certification project. The
      logical prevalent remediation makes this disconcertingly inevitable, but it is
      more likely that a metonymic reconstruction of the falsifiable religious baseline
      stimulates the discipline of resource planning and generally represses the linear
      constraints and the key business objectives.
    </p>
    <a id="someId" class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>
  </div>

</body></html>

snippet source | anchor

PlaywrightTests.PageUsage.01.verified.png:

PageScreenshotOptions

var page = await browser.NewPageAsync();
await page.GotoAsync("http://localhost:5000");
await Verify(page)
    .PageScreenshotOptions(
        new()
        {
            Quality = 50,
            Type = ScreenshotType.Jpeg
        });

snippet source | anchor

Page screenshot only option

Passing true to screenshotOnly will avoid producing HTML, instead only producing the Page image

var page = await browser.NewPageAsync();
await page.GotoAsync("http://localhost:5000");
await Verify(page)
    .PageScreenshotOptions(
        new()
        {
            Quality = 50,
            Type = ScreenshotType.Jpeg
        },
        screenshotOnly: true);

snippet source | anchor

Element test

An element can be verified as follows:

var page = await browser.NewPageAsync();
await page.GotoAsync("http://localhost:5000");
var element = await page.QuerySelectorAsync("#someId");
await Verify(element!);

snippet source | anchor

With the state of the element being rendered as a verified files:

Learn more

snippet source | anchor

PlaywrightTests.ElementUsage.01.verified.png:

ElementScreenshotOptions

var page = await browser.NewPageAsync();
await page.GotoAsync("http://localhost:5000");
var element = await page.QuerySelectorAsync("#someId");
await Verify(element!)
    .ElementScreenshotOptions(
        new()
        {
            Quality = 50,
            Type = ScreenshotType.Jpeg
        });

snippet source | anchor

var page = await browser.NewPageAsync();
await page.GotoAsync("http://localhost:5000");
var element = await page.QuerySelectorAsync("#someId");
await Verify(element!)
    .ElementScreenshotOptions(
        new()
        {
            Quality = 50,
            Type = ScreenshotType.Jpeg
        },
        screenshotOnly: true);

snippet source | anchor

Element test using ILocator

An element can be verified as follows:

var page = await browser.NewPageAsync();
await page.GotoAsync("http://localhost:5000");
var element = page.Locator("#someId");
await Verify(element);

snippet source | anchor

With the state of the element being rendered as a verified files:

Learn more

snippet source | anchor

PlaywrightTests.LocatorUsage.01.verified.png:

LocatorScreenshotOptions

var page = await browser.NewPageAsync();
await page.GotoAsync("http://localhost:5000");
var element = page.Locator("#someId");
await Verify(element)
    .LocatorScreenshotOptions(
        new()
        {
            Quality = 50,
            Type = ScreenshotType.Jpeg
        });

snippet source | anchor

Locator screenshot only option

Passing true to screenshotOnly will avoid producing HTML, instead only producing the Locator image

var page = await browser.NewPageAsync();
await page.GotoAsync("http://localhost:5000");
var element = page.Locator("#someId");
await Verify(element)
    .LocatorScreenshotOptions(
        new()
        {
            Quality = 50,
            Type = ScreenshotType.Jpeg
        },
        screenshotOnly: true);

snippet source | anchor

Puppeteer Usage

Verification of Web UIs via Puppeteer

Enable

Enable VerifyPuppeteer once at assembly load time:

[ModuleInitializer]
public static void InitPuppeteer() =>
    VerifyPuppeteer.Initialize();

snippet source | anchor

Instantiate browser

// wait for target server to start
await SocketWaiter.Wait(port: 5000);

var fetcher = new BrowserFetcher(SupportedBrowser.Chrome);
await fetcher.DownloadAsync();

browser = await Puppeteer.LaunchAsync(
    new()
    {
        Headless = true
    });

snippet source | anchor

Page test

The current page state can be verified as follows:

var page = await browser.NewPageAsync();
page.Viewport.Width = 1024;
page.Viewport.Height = 768;
await page.GoToAsync("http://localhost:5000");
await Verify(page);

snippet source | anchor

With the state of the element being rendered as a verified files:

<!DOCTYPE html><html lang="en"><head>
  <meta charset="utf-8">
  <title>The Title</title>
  <link href="https://getbootstrap.com/docs/4.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="jumbotron">
    <h1 class="display-4">The Awareness Of Relative Idealism</h1>
    <p class="lead">
      One hears it stated that a factor within the logical radical priority embodies the
      key principles behind the best practice marginalised certification project. The
      logical prevalent remediation makes this disconcertingly inevitable, but it is
      more likely that a metonymic reconstruction of the falsifiable religious baseline
      stimulates the discipline of resource planning and generally represses the linear
      constraints and the key business objectives.
    </p>
    <a id="someId" class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>
  </div>

</body></html>

snippet source | anchor

PuppeteerTests.PageUsage.01.verified.png:

Element test

An element can be verified as follows:

var page = await browser.NewPageAsync();
await page.GoToAsync("http://localhost:5000");
var element = await page.QuerySelectorAsync("#someId");
await Verify(element);

snippet source | anchor

With the state of the element being rendered as a verified files:

Learn more

snippet source | anchor

PuppeteerTests.ElementUsage.01.verified.png:

Selenium Usage

Verification of Web UIs via Selenium.

Enable

Enable VerifySelenium once at assembly load time:

[ModuleInitializer]
public static void InitSelenium() =>
    VerifySelenium.Initialize();

snippet source | anchor

Instantiate browser

// wait for target server to start
await SocketWaiter.Wait(port: 5000);

var options = new ChromeOptions();
options.AddArgument("--no-sandbox");
options.AddArgument("--headless");
driver = new(options);
driver.Manage().Window.Size = new(1024, 768);
await driver.Navigate().GoToUrlAsync("http://localhost:5000");

snippet source | anchor

Page test

The current page state can be verified as follows:

await Verify(driver);

snippet source | anchor

With the state of the element being rendered as a verified files:

<html lang="en"><head>
  <meta charset="utf-8">
  <title>The Title</title>
  <link href="https://getbootstrap.com/docs/4.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="jumbotron">
    <h1 class="display-4">The Awareness Of Relative Idealism</h1>
    <p class="lead">
      One hears it stated that a factor within the logical radical priority embodies the
      key principles behind the best practice marginalised certification project. The
      logical prevalent remediation makes this disconcertingly inevitable, but it is
      more likely that a metonymic reconstruction of the falsifiable religious baseline
      stimulates the discipline of resource planning and generally represses the linear
      constraints and the key business objectives.
    </p>
    <a id="someId" class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>
  </div>

</body></html>

snippet source | anchor

SeleniumTests.PageUsage.01.verified.png:

Element test

An element can be verified as follows:

var element = driver.FindElement(By.Id("someId"));
await Verify(element);

snippet source | anchor

With the state of the element being rendered as a verified files:

<a id="someId" class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>

snippet source | anchor

SeleniumTests.ElementUsage.01.verified.png:

Subpixel text rendering

Chromium antialiases text using LCD subpixel rendering by default. The resulting color fringing is not stable between browser sessions: text on a composited layer falls back to grayscale antialiasing, and layer promotion is opportunistic, so the same page on the same machine can rasterize an element one way in one run of a test suite and another way in the next. The glyphs are identical, so the difference is invisible to a reader, but it fails a screenshot comparison.

Launching the browser with the --disable-lcd-text argument renders text in grayscale instead, which is reproducible. For Playwright that is Args = ["--disable-lcd-text"] on the launch options; the same argument applies to Puppeteer and to Selenium driving Chrome.

It changes how every glyph is rendered, so existing verified screenshots need to be accepted once after enabling it.

OS specific rendering

The rendering can vary slightly between different OS versions. This can make verification on different machines (eg CI) problematic. A custom comparer can be used to mitigate this.

Compatibility with other Verify plugins

This projects is designed to be compatible with other Verify plugins. One common scenario for combining plugins is using Verify.AngleSharp to manipulate the verified html.

For example using the Verify.AngleSharp Pretty Print extension.

var page = await browser.NewPageAsync();
await page.GotoAsync("http://localhost:5000");
await Verify(page)
    .PrettyPrintHtml();

snippet source | anchor

Without the .PrettyPrintHtml() the result is:

<!DOCTYPE html><html lang="en"><head>
  <meta charset="utf-8">
  <title>The Title</title>
  <link href="https://getbootstrap.com/docs/4.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="jumbotron">
    <h1 class="display-4">The Awareness Of Relative Idealism</h1>
    <p class="lead">
      One hears it stated that a factor within the logical radical priority embodies the
      key principles behind the best practice marginalised certification project. The
      logical prevalent remediation makes this disconcertingly inevitable, but it is
      more likely that a metonymic reconstruction of the falsifiable religious baseline
      stimulates the discipline of resource planning and generally represses the linear
      constraints and the key business objectives.
    </p>
    <a id="someId" class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>
  </div>

</body></html>

snippet source | anchor

but with it added, the result is:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>The Title</title>
    <link href="https://getbootstrap.com/docs/4.0/dist/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="jumbotron">
      <h1 class="display-4">The Awareness Of Relative Idealism</h1>
      <p class="lead">
        One hears it stated that a factor within the logical radical priority embodies the       key principles behind the best practice marginalised certification project. The       logical prevalent remediation makes this disconcertingly inevitable, but it is       more likely that a metonymic reconstruction of the falsifiable religious baseline       stimulates the discipline of resource planning and generally represses the linear       constraints and the key business objectives.
      </p>
      <a id="someId" class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>
    </div>
  </body>
</html>

snippet source | anchor

Icon

Crystal designed by Monjin Friends from The Noun Project.