comparison_report library
What a comparison wrote down, typed — the reader for index.json.
Every fw compare writes its whole verdict to index.json: every preview
entry and every scenario step, each with what the four channels found —
pixels, widget tree, visible texts, app events — and where its two frames
are. This library reads that back so a project's tool/ script — a
pull-request gate, a screenshot uploader, a custom comment — is a few lines
over typed classes rather than a map walk written once per project:
- ComparisonReport.read takes an exported page's directory and hands back the ComparisonIndex with a way to open the frames it names.
- ComparisonIndex.fromJson takes the file, or the object
fw compare --jsonprints, when only the verdict is wanted. - ComparisonIndex.findings is the verdict in the order a reader wants it:
worst first, both halves merged, the rows that are neither
samenorskipped. ComparisonIndex.ok is the gate. - ComparisonIndex.verdictGap says when there is no verdict to gate on —
a harness that would not build, a half of nothing but both-sides
failures — which
okalone cannot tell apart from real regressions.fw compareexits 1 on exactly this sentence.
var report = await ComparisonReport.read('build/comparison/report/web');
if (report.index.ok) return;
for (var finding in report.index.findings) {
print('${finding.state.name} ${finding.half.name} ${finding.id}');
if (finding.preview?.shots case var shots?) {
if (report.frame(shots.head) case var png?) {
await service.upload(png, finding.id);
}
}
}
exitCode = 1;
This is published API. A field renamed here breaks somebody's script, which is why comparisonReportVersion exists and why ComparisonIndex.fromJson refuses a major it does not know rather than handing back a half-decoded object. Added fields do not bump the version — an older reader ignoring a new key is the behaviour that makes adding one cheap.
One vocabulary, several careers, exactly as scenarios_report.dart
argues: fw compare builds these classes, writes them, the studio's own
panel renders them, the exported page parses them in a browser, and a
consumer's script reads them here. What is not here is what never reaches
the file — the runner's live frames and the step aligner — because a shape
that cannot be written cannot be read, and publishing it would freeze a
runner internal.
Plain Dart on purpose — nothing in the model may import package:flutter,
and nothing may import dart:io: a consumer's script runs under a bare
dart run, and the exported comparison page parses this very model in a
browser. The disk-facing reader lives in report_io.dart, and
test/comparison/purity_test.dart fails the build if either rule is
broken.
Classes
- BranchDelta
- A whole branch that exists on one side only.
- ChannelDelta
- One difference, on whichever channel found it.
- ComparedFinding
- One row worth attention, with the half it came from.
- ComparedHalf
- What one half of a comparison did.
- ComparedItem
- One thing compared, on every channel that had something to say.
- ComparisonHost
- The machine a comparison ran on — what a reader needs before deciding a run that took three times its usual length, or a picture drawn by a software rasterizer, is about the branch.
- ComparisonIndex
-
A whole
index.json, read back. - ComparisonPhase
- One timed phase of a comparison.
- ComparisonReport
- A written comparison, read back.
- ComparisonTimings
-
Where one comparison's time went —
index.json'stimings. - DiffRect
- One region that changed.
- EventChannel
- What the app did on the way to a step, compared.
- EventDelta
- One field of one event that moved.
- FoldedDelta
- One shape of difference, and how much of a comparison wore it.
- FrameRef
- Where a frame is, and how to read it.
- PixelChannel
- PixelDiff
- Two frames compared as pixels.
- ScenarioComparison
- One scenario's two runs, compared.
- StepIds
- Hands out a flow's step ids, each of them once.
- TextChannel
- The visible text, which diffs exactly and costs nothing.
- TreeChannel
- TreeDelta
- One thing that is not the same about the two trees.
- TreeDiff
- Why the pixels differ, in words.
Enums
- ComparedHalfKind
- Which half a row came from.
- ComparedState
- Declared in the order a report ranks them: the top row should be the thing most likely to be a mistake.
- ComparisonFrames
- Where the frames a report names actually are.
- EventDeltaKind
- What kind of disagreement one delta is.
- ExportedFrames
- Which of a report's frames were written beside it.
- TreeDeltaKind
- Declared in the order a report ranks them.
Constants
- comparisonReportFile → const String
- What a comparison writes its whole verdict to, in the cache and inside an export alike.
- comparisonReportVersion → const int
-
The format
index.jsonis written in. - maxEventDeltas → const int
- How many field deltas one item's events channel keeps.
Functions
-
comparedIdIn(
String package, String id) → String - How a row is addressed when a comparison spans several packages.
-
foldChannelDeltas(
Iterable< List< perItem) → List<ChannelDelta> >FoldedDelta> - Groups deltas by their shape, keeping the order they were built in.
-
isComparedFinding(
ComparedState state) → bool -
Whether
stateis worth a reader's attention. -
rankComparedFindings(
{List< ComparedItem> previews = const [], List<ScenarioComparison> scenarios = const []}) → List<ComparedFinding> - Every row worth attention, worst first, both halves merged.
-
verdictGapOf(
{String? scenariosNote, String? previewsNote, Iterable< ComparedState> scenarioStates = const [], Iterable<ComparedState> previewStates = const [], int inconclusiveScenarios = 0, bool narrowed = false}) → String? - Why a comparison's verdict is incomplete, or null when it is whole.