ZplCore.Testing
1.0.0
dotnet add package ZplCore.Testing --version 1.0.0
NuGet\Install-Package ZplCore.Testing -Version 1.0.0
<PackageReference Include="ZplCore.Testing" Version="1.0.0" />
<PackageVersion Include="ZplCore.Testing" Version="1.0.0" />
<PackageReference Include="ZplCore.Testing" />
paket add ZplCore.Testing --version 1.0.0
#r "nuget: ZplCore.Testing, 1.0.0"
#:package ZplCore.Testing@1.0.0
#addin nuget:?package=ZplCore.Testing&version=1.0.0
#tool nuget:?package=ZplCore.Testing&version=1.0.0
ZplCore.Testing+ – semantic label tests for .NET
Labels are code. Test them like code. ZplCore.Testing asserts what a ZPL label prints – not only how
it looks. It renders the label with the zplCore.com engine, reads every barcode back with two independent decoders
and measures every element. When something breaks, the message says which element, where (dots, mm, ZPL line)
and links an image with the element outlined.
Label(sscc.zpl).Barcode("SSCC").Decodes("(00)376104250021234560") failed: the decoders read "(00)376104250021234569", expected "(00)376104250021234560".
label sscc.zpl: label 1 of 1 · 812 × 1218 dots (101.6 × 152.4 mm) @ 203 dpi
element ^BC Code128 "(00)376104250021234569" at x=94 y=800 (11.8 / 100.1 mm), 624 × 260 dots (78.1 × 32.5 mm) · line 17, col 1
source ^FO94,800^BCN,260,Y,N,N,D^FD(00)376104250021234569^FS
scan-proof Match · the field encodes "(00)376104250021234569"
decoders zxing-cpp: "(00)376104250021234569" · ZXing.Net: "(00)376104250021234569"
image bin/Debug/net10.0/zplcore-failures/Sscc_label-80bca2c9b5.png
Works with xUnit, NUnit, MSTest and any other runner. The package does not reference a test framework.
Quick start in 3 steps
1. Add the package to your test project
dotnet add package ZplCore.Testing
2. Write a test
using ZplCore.Compliance;
using ZplCore.Testing;
public class ShippingLabelTests
{
[Fact]
public void Pallet_label_is_scannable_and_compliant()
{
string zpl = File.ReadAllText("labels/pallet.zpl");
ZplAssert.Label(zpl)
.HasLabels(1)
.NoOverlaps()
.NoClipping()
.Complies(CompliancePacks.Gs1LogisticLabel)
.Barcode("SSCC").Decodes("(00)376104250021234569").HasGrade(atLeast: 'B').HasQuietZones()
.And.Text("RETAIL DC").IsWithinLabel().DoesNotOverlap("SSCC")
.And.MatchesSnapshot("pallet");
}
}
3. Run it and approve the snapshot once
dotnet test # first run: fails, writes __snapshots__/pallet.received.png
ZPLCORE_ACCEPT_SNAPSHOTS=1 dotnet test # or: zplcore snapshot accept tests/__snapshots__
git add tests/__snapshots__/pallet.approved.* # commit approved PNG + semantic JSON
Add *.received.* and __snapshots__/*.diff.png to .gitignore.
What you can assert
ZplAssert.Label(zpl), ZplAssert.Label(labelModel) and ZplAssert.LabelFile("label.zpl" | "label.json") return
a fluent LabelAssertions. The job is rendered once; all assertions share the result.
| Assertion | Checks |
|---|---|
HasLabels(n) |
the ZPL prints n formats (^PQ copies count once) |
Renders(dpi?, failOn) |
at least one label, no render diagnostics (invalid data, missing font/graphic) – optionally at another resolution |
NoOverlaps(toleranceDots) |
no two elements print on the same dots. Each candidate element is rendered on its own, so font line spacing, frames and ^FR fields are not overlaps |
NoClipping() |
every element lies on the label (^PW/^LL) |
Complies(CompliancePacks…) / Complies(pack) |
compliance rule packs (GS1 logistic, Sunrise 2027, Amazon FBA, UPS, UDI, FedEx, DHL Paket, custom packs) |
AllBarcodesDecode() / AllBarcodesHaveGrade('C') |
scan-proof for every barcode |
FitsPrinter(IZplPrinterFit) |
resolution, print width, label length (PrinterSpec, or any implementation) |
FitsPrinter(PrinterProfile) |
the Printer Twin of a printer model: printhead width and clipped fields, length, resolution, firmware, options, fonts, memory |
FieldEquals("FN1", "…") |
the ^FN field prints this value (also in ^XF recalled formats and with ZplTestOptions.FieldValues) |
HasText / DoesNotHaveText / HasBarcodes(n) |
content checks |
MatchesSnapshot(name, tolerance) |
pixel snapshot + semantic snapshot with an approval workflow |
Satisfies(predicate, because) |
your own rule on the measured ComplianceLabel |
OnLabel(i) |
switch to label i (0-based) of a multi-label job |
Barcodes: Barcode(selector) selects one barcode. A selector can be "#0" (index), "^BC" (command),
"QR", "DataMatrix" or "GS1-128" (symbology), "SSCC", "GTIN" or "(01)" (GS1 key or AI), or any part of
the data. It fails when nothing or more than one barcode matches. To pick one of several matches, pass an index:
Barcode("Code128", 1).
Decodes(), Decodes("(00)…") (GS1 compared element by element), HasGrade(atLeast: 'B'), HasQuietZones(),
HasXDimension(minMm, maxMm), HasBarHeight(mm), IsSymbology(…), IsGs1("00", "…").
Texts: Text("Empfänger") matches the exact text first, then a substring, then a substring ignoring case.
HasContent, HasCharHeight(mm).
Layout (texts and barcodes): IsWithinLabel(marginMm), DoesNotOverlap("other"), DoesNotOverlapAnything(),
IsAt(xMm, yMm, toleranceMm), IsInside(x, y, w, h), IsAbove("other"), IsBelow("other").
Use .And to go back to the label.
The grade is a digital pre-check modelled on ISO/IEC 15416/15415. It is not an ISO verification. Compliance packs are pre-checks against public specifications, not certifications.
Failure messages and images
Every failure throws ZplAssertionException with:
Failures: structured data withAssertion,Summary,Details,LabelIndex,Line/Columnand the image paths- a message with the label, the element position in dots and mm, the ZPL source excerpt and the measured values
ImagePath: the label rendered with the failing element in red and related elements in orange. It is written tozplcore-failuresin the test output folder. Change the folder withZPLCORE_FAILURE_DIRorZplAssert.Settings.FailureImageDirectory.
Use ZplAssertionScope to collect all failures of a test and throw them together:
using (new ZplAssertionScope())
{
ZplAssert.Label(zpl).NoOverlaps().NoClipping().AllBarcodesDecode();
}
Snapshots: pixels plus semantics
MatchesSnapshot("name") compares the 1-bit render with __snapshots__/name.approved.png. The folder sits next
to the test source file and is also found for deterministic CI builds with mapped /_/ paths.
name.approved.json stores every element (kind, command, text/data, position). A failing snapshot therefore says
what changed, not only how many pixels:
Label(zpl).MatchesSnapshot("pallet") failed: the render differs from the approved snapshot "pallet" and elements changed.
pixels 2782 changed (0.281 % of 989016), tolerance 0 (exact)
regions x=315 y=180 111 × 25 dots (39.4 / 22.5 mm, 1342 px); …
changes text "TO" moved by 0 / +12 dots (0.0 / +1.5 mm): x=40 y=140 → x=40 y=152
text ^A0 at x=40 y=180: text "RETAIL DC SOUTH, MUNICH" → "RETAIL DC NORTH, BERLIN"
approved tests/__snapshots__/pallet.approved.png
received tests/__snapshots__/pallet.received.png
diff tests/__snapshots__/pallet.diff.png
- Tolerance:
MatchesSnapshot("x", 0.5)(percent),SnapshotTolerance.Pixels(50),.WithShift(1)(ignores ink moved by 1 dot, e.g. font rasterising on other operating systems). - An added, removed or changed text or barcode always fails, even when the pixel tolerance would allow it. Turn
this off with
IgnoreContentChanges. - To accept, set
ZPLCORE_ACCEPT_SNAPSHOTS=1, setZplTestOptions.AcceptSnapshots, runzplcore snapshot accept <dir>, or rename.received.to.approved.. - The diff image shows unchanged ink in grey, removed ink in red, added ink in green and changed regions in blue boxes.
Options
ZplAssert.Label(zpl, new ZplTestOptions
{
Dpi = 300, WidthMm = 100, HeightMm = 150, // used when the ZPL has no ^PW / ^LL
FieldValues = new Dictionary<int, string> { [1] = "ACME GmbH" },
Robustness = true, // scan-proof robustness simulations
SnapshotDirectory = "label-snapshots",
});
Set process-wide defaults in ZplAssert.Settings: Dpi, SnapshotDirectory, SnapshotTolerance,
WriteFailureImages, FailureImageDirectory and ExceptionFactory.
Test framework adapters (optional)
Any exception fails a test. By default (ZplTestFrameworks.AutoDetect), NUnit gets an AssertionException, so it
reports a failure and not an error, and MSTest gets an AssertFailedException. Every other runner, xUnit
included, gets a ZplAssertionException. The original exception is always the InnerException. To choose
explicitly:
ZplAssert.Settings.ExceptionFactory = ZplTestFrameworks.NUnit; // or MSTest, None, or your own Func<ZplAssertionException, Exception>
Printer fit and the Printer Twin
FitsPrinter takes any IZplPrinterFit. PrinterSpec checks data-sheet limits:
ZplAssert.Label(zpl).FitsPrinter(new PrinterSpec("ZD421", dpi: 203, printWidthMm: 104, maxLabelLengthMm: 991));
Printer models with firmware, fonts, memory and options (the zplCore Printer Twin) plug in through the same
interface. Pass a profile directly, by id or alias (zplcore twin list):
using ZplCore.Twin; // PrinterProfiles, PrinterOption
ZplAssert.Label(zpl).FitsPrinter(PrinterProfiles.Get("ZD421-203"));
ZplAssert.Label(zpl).FitsPrinter(PrinterProfiles.Get("ZT411-300").WithOptions(PrinterOption.Cutter).AsPrinterFit());
PrinterProfileFit runs ZplPrinterTwin.Check and reports its ZPL5xxx diagnostics: errors (a field clipped by the
printhead, a missing option the label needs …) fail the assertion, warnings (e.g. ^PW wider than the printhead)
only with failOnWarnings: true, and unknown profile facts (ZPL5001) are infos that never fail. The resolution the test
renders at counts as the design resolution, so a 300 dpi profile reports a label rendered at 203 dpi (ZPL5104); field
values and the default label length come from the test options. You can also wrap your own rules with
ZplPrinterFit.From(name, context => issues).
CI: zplcore check
The same checks run without a test project. This is the zplcore CLI and the GitHub Action
zplcore/label-check (tools/github-action in the zplCore repository; not yet published on the GitHub Marketplace, planned):
dotnet tool install -g ZplCore.Cli
zplcore check "labels/**/*.zpl" --pack gs1-logistic --verify --min-grade C --layout \
--snapshots tests/label-snapshots --junit out/junit.xml --sarif out/labels.sarif
| Option | |
|---|---|
--pack ids\|all |
compliance packs (zplcore comply --list-rules) |
--verify, --min-grade C |
scan-proof, fail below a grade |
--layout |
overlaps (ZT1101) and clipping (ZT1102) as warnings |
--printer 203:104[:991] |
data-sheet printer limits: dpi, print width, max length in mm (ZT1201–ZT1203) |
--printer ZD421d-203 |
a Printer Twin profile by id or alias (ZPL5xxx; zplcore twin list) |
--snapshots dir, --update-snapshots, --tolerance 0.1%\|25px, --shift n |
snapshots per file (name = relative path) |
--baseline dir --report-dir dir |
compare with the base version; write before/after/diff PNGs |
--format text\|junit\|sarif\|json\|markdown\|github, -o |
output (plus --junit, --sarif, --json, --markdown files) |
--fail-on error\|warning\|note\|none |
exit code 1 at this severity (default error) |
Exit codes: 0 passed, 1 failed, 2 usage or input error. JUnit XML is valid against the Ant and Surefire
schemas. SARIF 2.1.0 contains the compliance (ZPL3xxx), scan-proof (SPxxxx) and check rules (ZTxxxx). With a local
tool manifest the command is dotnet zplcore check (alias dotnet zplcore test).
Licence
ZplCore.Testing is part of the commercial zplCore.com SDK. The licence terms of the ZplCore package apply.
See zplcore.com or write to support@zplcore.com. Third-party notices: THIRD-PARTY-NOTICES.md.
Trademarks
Zebra, ZPL, ZPL II, EPL and Link-OS are trademarks or registered trademarks of Zebra Technologies Corp. QR Code is a registered trademark of DENSO WAVE INCORPORATED. GS1, GS1 DataBar, GS1 Digital Link, EPC and EPCglobal are trademarks of GS1 AISBL. ESC/POS is a registered trademark of Seiko Epson Corporation, Intelligent Mail a registered trademark of the United States Postal Service and Mailmark a registered trademark of Royal Mail Group Ltd. Other product and company names and logos mentioned are trademarks or registered trademarks of their respective owners. We use these names only to describe compatibility with file formats, printer languages and standards. zplCore.com is an independent product and is not endorsed, sponsored or certified by these companies; compliance packs are pre-checks, not certifications.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.FileSystemGlobbing (>= 10.0.13)
- ZplCore (>= 1.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 38 | 10/27/2026 |