Extends Verify to allow verification of Word, Excel, and PowerPoint documents via OpenXML.
- Converts each worksheet to CSV, in a file named for the worksheet
- Extracts formulas and displays them alongside cell values
- Captures document properties (title, subject, keywords, description, category, status, company, manager)
- Captures custom document properties
- Supports date scrubbing and GUID scrubbing for deterministic tests
- Generates deterministic XLSX output using DeterministicIoPackaging
- Optionally renders each page to PNG via Morph (opt-in)
- Extracts document text content from paragraphs and tables
- Captures document properties (title, subject, keywords, description, category, status, revision)
- Captures custom document properties
- Extracts font information
- Generates deterministic DOCX output using DeterministicIoPackaging
- Optionally renders each page to PNG via Morph (opt-in)
- Extracts the text of each slide, as the text of its page
- Captures document properties (title, subject, keywords, description, category, status, revision)
- Reports the slide count, as the page count
- Generates deterministic PPTX output using DeterministicIoPackaging
- Optionally renders each slide to PNG via Morph (opt-in)
How the files of a document are named, where its text goes, and which of them are verified, is decided by Verify's paged documents support, which every Verify plugin that splits a document into pages shares. See The files and Choosing what is verified.
See Milestones for release notes.
Entity Framework Extensions is a major sponsor and is proud to contribute to the development this project.
[ModuleInitializer]
public static void Initialize() =>
VerifyOpenXml.Initialize();For a test Samples.VerifyWord verifying a docx:
| File | Holds |
|---|---|
Samples.VerifyWord.verified.docx |
The document, made deterministic |
Samples.VerifyWord.verified.txt |
The info file: the properties and fonts of the document, its page count, and its text |
Samples.VerifyWord#page_0001.verified.png |
The first page, drawn. Requires a rendering backend |
A pptx is verified the same way, with a page for each slide. An xlsx has its sheets in place of text: each is a csv named for the sheet, Samples.VerifyExcel#Sheet1.verified.csv, and its info file holds the properties of the workbook, its sheets and their columns.
The info file has the shape every paged document has, with what is read from the document under Document. Where a page of a Word document ends is only known once it is laid out. So with a rendering backend its text is under the page it is on, as that of a presentation is, and without one it is read as one text:
{
Document: {
Properties: {
Subject: Test Subject,
Title: Sample Document
}
},
Text:
Hello World! This is a sample Word document.
This is the second paragraph with some more text.
}A presentation is read slide by slide, so the text of a slide is under its page:
{
Document: {
Properties: {
Title: Sample Presentation
}
},
PageCount: 1,
Pages: [
{
Number: 1,
Text: Hello, PowerPoint!
}
]
}PageCount is the number of slides of a presentation. The pages of a Word document and of a workbook only exist once they are laid out, so their PageCount is written when the pages are rendered.
What a document is split into is controlled by Verify's settings for paged documents.
ExcludeDerivedTargets("png") leaves out the rendered pages, keeping the document and its text:
[Test]
public Task ExcludeRenderedPages() =>
VerifyFile("sample.docx")
.ExcludeDerivedTargets("png");ExcludeDerivedTargets("csv") leaves out the sheets of a workbook the same way, and ExcludeTargets the document itself.
The text is in the info file by default. PageText moves it to a file of its own, or leaves it out with PageTextPlacement.None. The file is #page_0001.verified.txt for each slide of a presentation and each page of a Word document, or a single #text.verified.txt for a Word document that is read as one text. Here with the rendered pages left out as well:
[Test]
public Task PageTextPerPage() =>
VerifyFile("sample.pptx")
.PageText(PageTextPlacement.PerPage)
.ExcludeDerivedTargets("png");What these leave out is not produced at all (pages are not rendered, text is not read, sheets are not converted), so they also save work.
PagesToInclude limits the pages that are verified, to the first pages of a document or to those a delegate accepts. The document itself is still verified whole, and PageCount is still the number of pages it has:
[Test]
public async Task PagesToInclude()
{
using var presentation = ThreeSlides();
await Verify(presentation)
.PagesToInclude(2);
}It applies to pages: their images, their text, and for a workbook the csv of the sheet. A page of a workbook is a sheet, numbered in tab order, hidden or not, so the csv of a sheet is left out with its page whether or not there is a rendering backend. The info file of a workbook still lists every sheet. The text of a Word document that is read as one text, for want of a rendering backend, belongs to no page, so it is verified whole. A paragraph or a row of a table that runs over the end of a page is divided where the page ends. The text by page is that of the body as it is laid out, so a list paragraph starts with its marker. A document is also rendered whole, so the pages left out are still drawn before they are dropped.
Each can also be set for every test, on VerifierSettings:
[ModuleInitializer]
public static void Initialize()
{
VerifyOpenXml.Initialize();
// For every test: no text and no rendered pages
VerifierSettings.PageText(PageTextPlacement.None);
VerifierSettings.ExcludeDerivedTargets("png");
}[Test]
public Task VerifyExcel() =>
VerifyFile("sample.xlsx");[Test]
public Task VerifyExcelStream()
{
var stream = new MemoryStream(File.ReadAllBytes("sample.xlsx"));
return Verify(stream, "xlsx");
}[Test]
public async Task VerifySpreadsheetDocument()
{
await using var stream = File.OpenRead("sample.xlsx");
using var reader = SpreadsheetDocument.Open(stream, false);
await Verify(reader);
}The sheet Sheet1, as Samples.VerifyExcel#Sheet1.verified.csv:
0,First Name,Last Name,Gender,Country,Date,Age,Id,Formula
1,Dulce,Abril,Female,United States,2017-10-15,32,1562,G2+H21594 (G2+H2)
2,Mara,Hashimoto,Female,Great Britain,2016-08-16,25,1582,1607
3,Philip,Gent,Male,France,2015-05-21,36,2587,2623
4,Kathleen,Hanner,Female,United States,2017-10-15,25,3549,3574
5,Nereida,Magwood,Female,United States,2016-08-16,58,2468,2526
6,Gaston,Brumm,Male,United States,2015-05-21,24,2554,2578[Test]
public Task VerifyWord() =>
VerifyFile("sample.docx");[Test]
public Task VerifyWordStream()
{
var stream = new MemoryStream(File.ReadAllBytes("sample.docx"));
return Verify(stream, "docx");
}When verifying binary package output (xlsx, docx, nupkg, etc.) across multiple target frameworks (e.g. net48 and net10.0), the binary output may differ due to Deflate compression implementation differences. The XML content within entries is identical — only the compressed bytes differ. Use UniqueForRuntime to generate framework-specific verified files:
await Verify(stream, extension: "xlsx")
.UniqueForRuntime();See Verify Naming docs for more details.
[Test]
public async Task VerifyWordprocessingDocument()
{
await using var stream = File.OpenRead("sample.docx");
using var reader = WordprocessingDocument.Open(stream, false);
await Verify(reader);
}[Test]
public Task VerifyPowerpoint() =>
VerifyFile("sample.pptx");[Test]
public Task VerifyPowerpointStream()
{
var stream = new MemoryStream(File.ReadAllBytes("sample.pptx"));
return Verify(stream, "pptx");
}[Test]
public async Task VerifyPresentationDocument()
{
await using var stream = File.OpenRead("sample.pptx");
using var reader = PresentationDocument.Open(stream, false);
await Verify(reader);
}Verify.OpenXml can additionally snapshot a rendered PNG of every page of a .docx, .xlsx, or .pptx using the Morph renderer. This catches visual regressions (layout, fonts, images, tables) that the text-based snapshot would miss.
The base Morph package is referenced automatically by Verify.OpenXml on net10.0. To turn on rendering, add exactly one backend package to the test project:
Morph.Skia — uses SkiaSharp:
<PackageReference Include="Morph.Skia" />or Morph.ImageSharp — uses ImageSharp, fully managed:
<PackageReference Include="Morph.ImageSharp" />The backend is detected at runtime by probing for the assembly. No code changes are needed in ModuleInitializer.cs — the existing VerifyOpenXml.Initialize() call picks it up automatically.
When a backend is present, every verification (file, stream, or document object) produces additional PNG targets — one per rendered page — alongside the existing binary and text targets. What counts as a page differs per document type:
- Word - one page per laid-out page of the document.
- PowerPoint - one page per slide, in
p:sldIdLstorder. - Excel - one page per sheet, drawn whole as the one image, however long the sheet is and whatever paper its page setup names. A hidden sheet is verified as any other: it has a page and a csv, and is named under
HiddenSheetsin the info file.
A page is named for its number, counted from 1, whether the document has one page or several:
Samples.VerifyWord.verified.docx
Samples.VerifyWord.verified.txt
Samples.VerifyWord#page_0001.verified.png
For example a two-sheet workbook, where each sheet is a page:
Samples.MultipleSheets.verified.xlsx
Samples.MultipleSheets.verified.txt
Samples.MultipleSheets#Sheet1.verified.csv
Samples.MultipleSheets#Sheet2.verified.csv
Samples.MultipleSheets#page_0001.verified.png
Samples.MultipleSheets#page_0002.verified.png
Rendering also puts the PageCount of a Word document or a workbook in its info file.
ExcludeDerivedTargets("png") leaves the pages out, for one verification or for every test, and nothing is rendered.
- Neither backend referenced — rendering is silently skipped. The text and binary targets are still produced. This is the default for consumers who do not opt in.
- One backend referenced — that backend is used for all verifications.
- Both backends referenced — an exception is thrown on the first verification with a clear message. Pick one.
Rendering is only available on net10.0 because Morph targets net10.0 only. On net472, net48, net8.0, and net9.0, verification continues to produce only the existing text and binary targets — the rendering code is conditionally compiled out.
PNG output from Skia and ImageSharp depends on installed fonts and platform-specific rasterization. A .verified.png generated on one machine may not be byte-identical on another OS or with different fonts installed. Recommendations:
- Generate and commit
.verified.pngfiles from a single canonical machine (often a CI agent). - For cross-platform CI, combine with
UniqueForOSPlatform()so each OS gets its own.verified.png:
await Verify(stream, "docx")
.UniqueForOSPlatform();- Consider PNG SSIM comparer for tolerance-based image diffing.
See Verify Naming docs for the full list of UniqueFor* modifiers.
For a worked example of running the same test suite against both backends side-by-side, see the Tests.Skia and Tests.ImageSharp projects in this repository. Both projects link the source files from Tests and use DerivePathInfo in their ModuleInitializer to redirect snapshots into the per-backend project directory:
[ModuleInitializer]
public static void Initialize()
{
VerifyOpenXml.Initialize();
var projectDir = ProjectDir();
Verifier.DerivePathInfo(
(sourceFile, projectDirectory, type, method) =>
new(directory: projectDir, typeName: type.Name, methodName: method.Name));
}
static string ProjectDir([CallerFilePath] string here = "") =>
Path.GetDirectoryName(here)!;This pattern lets a single set of tests produce two parallel sets of .verified.* snapshots — one per rendering backend.
The source document is included in the snapshot as a .verified.xlsx, .verified.docx, or .verified.pptx. Building the deterministic package is expensive, and committing it is not always wanted. ExcludeTargets drops it from a verification and skips the build, while the info, text, csv, and rendered pages still verify:
// Skips the .verified.xlsx (and building it), keeping the info and csv sheets.
[Test]
public Task ExcludeExcel() =>
VerifyFile("sample.xlsx")
.ExcludeTargets("xlsx");The same applies to docx and pptx. To exclude for every test, call VerifierSettings.ExcludeTargets("xlsx") at initialization.
A change to a document is a change to several files: the document, its info file, and every page and sheet. Verify tells the diff tool that the pages, the sheets and the info file were derived from the document, and DiffEngineViewer, which draws the pages of a Word, Excel or PowerPoint document itself, shows them as one row and accepts them together. Other diff tools are given each file, as before.
When the document has changed, its pages, sheets and info file are compared exactly, skipping any comparer registered for them.
Version 2 moves to the paged document support in Verify 33.3. Pages and single sheets are renamed, the text moves into the info file, and OpenXmlOutputs gives way to Verify's own settings.
Initialize no longer takes an OpenXmlOutputs. Each output that could be left out of it is now left out by a setting of Verify, for every test on VerifierSettings or for a single verification:
1.x: not in OpenXmlOutputs |
2.x |
|---|---|
Png |
ExcludeDerivedTargets("png") |
Text |
PageText(PageTextPlacement.None) |
Csv |
ExcludeDerivedTargets("csv") |
So Initialize(OpenXmlOutputs.Csv) becomes:
VerifyOpenXml.Initialize();
VerifierSettings.PageText(PageTextPlacement.None);
VerifierSettings.ExcludeDerivedTargets("png");For a test Tests.Report:
| 1.x | 2.x |
|---|---|
Tests.Report.verified.png, the page of a document with one |
Tests.Report#page_0001.verified.png |
Tests.Report#00.verified.png, #01, the pages of a document with several |
Tests.Report#page_0001.verified.png, #page_0002 |
Tests.Report.verified.csv, the sheet of a workbook with one |
Tests.Report#Sheet1.verified.csv, by the name of the sheet |
Tests.Report#Sheet1.verified.csv, a sheet of a workbook with several |
The same |
Tests.Report#00.verified.txt, the info of a docx or pptx |
Tests.Report.verified.txt |
Tests.Report#01.verified.txt, the text of a docx or pptx |
In the info file, or with PageText(PageTextPlacement.PerPage) in #page_0001.verified.txt for each slide of a pptx and each page of a docx, or #text.verified.txt for a docx read without a rendering backend |
Tests.Report.verified.txt, the info of an xlsx |
The same |
Tests.Report.verified.docx, .xlsx, .pptx |
The same |
A renamed snapshot shows as a new file and a pending delete. Accepting both, or running once with AutoVerify, moves a test over. The content of a page and of a sheet is unchanged, so source control shows each as a rename.
What was at the top of the info file is now under Document, with the page count and the text beside it. For a docx:
{ {
Properties: { Document: {
Title: Sample Document Properties: {
}, Title: Sample Document
Fonts: [ },
Aptos Fonts: [
] Aptos
} ]
},
PageCount: 1,
Text: The text of the document
}
For an xlsx the properties of the workbook, with its Sheets, move under Document the same way.
For a pptx SlideCount is now PageCount, and the text is the Text of each page, where it was one text with --- between the slides. Slides are read in the order they are shown in, that of p:sldIdLst, so that the text of a slide is on the page it is drawn on. They were read in the order of the slide parts, which is not the order of a deck whose slides have been moved.

