HTML

C#
using Nullean.Snapshots.Html;

page.Should().MatchHtmlSnapshot();
page.Should().BeHtml("<p class=\"lead\">Welcome</p>");
page.Should().ContainHtml("<strong>Welcome</strong>");
page.Should().NotContainHtml("<script>");

HTML is parsed with AngleSharp, pretty-printed (one element per line, tab indented) and compared structurally with AngleSharp.Diffing: attribute order and whitespace do not matter (except inside <pre> and <textarea>, see below). A failure names each difference and shows a line diff of the pretty-printed HTML:

Text
HTML did not match snapshot: tests/Docs.Tests/.snapshots/Rendering/TableTests/Renders.verified.html
2 difference(s):
The text in table(0) > tbody(1) > tr(1) > td(1) > #text(0) is different.
  expected: {kibana}
  actual:   {Kibana}
The attribute at table(0) > tbody(1) > tr(1) > td(1)[class] was not expected.
  expected: missing
  actual:   class="x"
Line diff:
@@ line 1 @@
 <table>
 	<tbody>
 		<tr>
-			<td>{kibana}</td>
+			<td class="x">{Kibana}</td>

Whitespace is significant inside <pre>, <textarea> and <listing>, so the snapshot keeps it exactly, at any depth: a code block's lines, blank lines and indentation, with the markup of a highlighted block (<pre><code><span>…) printed as it is, on the lines it has. A change that only moves a newline or a space there fails the comparison, and the message shows the lines. Everywhere else whitespace still does not matter.

HtmlSnapshotOptions select, remove and transform before printing, applied to both sides:

C#
var html = new HtmlSnapshotOptions()
	.Select("main#content")
	.Remove("script")
	.RemoveAttributes("hx-*", "preload")
	.RemoveAttributesFrom("a", "data-*")
	.Transform(root => root.FirstElementChild?.Remove());

page.Should().MatchHtmlSnapshot(html);
  1. print the content of every match instead of the body (SelectFirst: only the first)
  2. drop matching elements
  3. drop attributes everywhere; * is a wildcard
  4. or only from matching elements

Select prints every element the selector matches, one after the other in document order, so .Select(".card") covers all the cards; an element inside another match is not printed twice. SelectFirst keeps one. Removals, attribute removals and transforms run on each match.

ContainHtml ignores indentation, so a fragment matches wherever it is nested.

For a short value there is MatchInlineHtmlSnapshot, which keeps the expected value in the test as a literal: see inline snapshots.