⛵ gleon is a high-performance, developer-first, framework-agnostic visual regression testing CLI built in Rust. It isolates screenshot baselines by platform and Git branch and uses a content-addressed storage (CAS) model for baseline artifacts, minimizing bandwidth and storage overhead in CI pipelines.
Follow these 5 steps to add visual regression testing to any codebase (Flutter, Web, iOS, Android, etc.):
Download the binary of your platform from the Releases page: gleon-linux-x86_64 (Ubuntu 26.04 or newer), gleon-macos-aarch64 (Apple silicon) or gleon-windows-x86_64.exe. SHA256SUMS.txt lists the checksums of every asset, and each binary carries a build provenance attestation:
gh release download --repo gleon-rs/gleon --pattern gleon-macos-aarch64 --pattern SHA256SUMS.txt
shasum -a 256 --check --ignore-missing SHA256SUMS.txt
gh attestation verify gleon-macos-aarch64 --repo gleon-rs/gleon
mkdir -p ~/.local/bin && install -m 755 gleon-macos-aarch64 ~/.local/bin/gleonWithout gh, curl -fLO https://github.com/gleon-rs/gleon/releases/latest/download/gleon-macos-aarch64 (and the same for SHA256SUMS.txt) downloads them. ~/.local/bin must be on your PATH. The macOS binary is not notarized: gh release download and curl leave no quarantine attribute, while a copy downloaded with a browser runs only after xattr -d com.apple.quarantine gleon.
To build from source (contributors), install rustup: the toolchain is pinned to Rust 1.100 nightly (nightly-2026-09-25, Edition 2024) in rust-toolchain.toml, and rustup installs it automatically when you build from the repository root. Then install gleon:
cargo install --path gleon --bin gleon --forceIn the root of your repository, run:
gleon initThis creates the .gleon/ workspace scaffold:
.gleon/gleon.yaml: Workspace configuration file (always inside.gleon/, not the repository root). Next to apubspec.yaml(a Flutter package or app, or the root of a pub workspace) it is the Flutter template: the goldens under everytest/compared exactly (include: "**/test/**/*.png",mode: exact), their text on the platform of the run (fallback_platform, the OS and architecture of the host or of--os/--arch), the images of failures excluded (exclude: "**/failures/**");gleon init --genericwrites the generic template instead (every.png,mode: pixelwith itsthresholdspelled out),--flutterthe Flutter one anywhere. An existinggleon.yamlis never replaced. Likegit init,gleon initinitializes the directory it runs in: inside another workspace (a package of a monorepo) it starts a nested one, whose config applies to the goldens below it, and says so..gleon/.gitignore: Automatically ignores large binary blobs (blobs/), run outputs (runs/), the.envfiles,credentials,dashboard.htmlandhistory.json..gleon/.env.template: Storage credentials template..gleon/manifests/: Directory where lightweight, deterministic JSON baseline manifests will be stored in Git.
Edit .gleon/gleon.yaml to point to where your test framework outputs golden screenshots. For example:
required_version: ">=0.1.0"
screenshots:
- include: "test/**/goldens/**/*.png"
mode: pixel
diff:
threshold: 0.01
exclude:
- "**/build/**"
- "**/target/**"
- "**/node_modules/**"Generate golden screenshots using your existing test suite (e.g. flutter test or npm test), then record them as your baseline:
gleon stagegleon stage computes cryptographic hashes, copies the image files into local content-addressable storage, and writes deterministic JSON manifests into .gleon/manifests/<platform>/ (<os>-<arch>, e.g. macos-aarch64, then +<renderer> and +<key>=<value> per label when set).
Commit these manifest files to Git:
git add .gleon/gleon.yaml .gleon/.gitignore .gleon/manifests/
git commit -m "chore: record initial visual regression baselines"When you or your team make changes and re-run your visual tests:
# Check test status (Clean, Added, Modified, Deleted)
gleon status
# Run pixel/SSIM comparison against committed baselines (one case report per screenshot)
gleon diff
# View visual diff report in browser
gleon report html --out report.htmlIntegrations such as the gleon Flutter package compare in their own test runner and write the same
case reports; run the tests through gleon test so they form one run, then use the same commands:
gleon test -- flutter test
gleon report markdown
gleon dashboardThe gleon Flutter package compares goldens inside
flutter test and keeps them where Flutter does: PNG files next to the tests, committed to Git.
The CLI adds what one test process cannot do: a summary of the whole run, an HTML report with
each golden, candidate and diff, a PR comment, history, and /gleon approve, which commits the
new goldens for you. Requires the Flutter package 0.6.0 or newer (older ones reject the
baselines key below).
gleon init # next to pubspec.yaml: the Flutter template
gleon test -- flutter test # one run with metrics, summed up at the end
gleon report html --out .gleon/runs/latest/report.html
gleon approve # writes the failed candidates over their goldensgleon init next to a pubspec.yaml writes baselines: goldens into .gleon/gleon.yaml: the
goldens are the baselines, so the workspace gets no manifests, blobs or storage settings. Its rule
compares the goldens under every test/ exactly; the package reads the same file (tolerances,
masks, the fallback_platform whose goldens compare text).
gleon test ends with the totals of the run and one line per failure with its case report:
Run run-20261020T101500Z-1a2b3c4d: 128 case(s): 125 passed, 3 failed (2 mismatch, 1 missing)
test/goldens/counter_3: Mismatch: 0.0167% (1 of 6000px) differ (.gleon/runs/latest/cases/macos-aarch64/test/goldens/counter_3.json)
Next: `gleon report html --out .gleon/runs/latest/report.html` shows each failure with its golden, candidate and diff; `gleon approve` accepts the candidates
Its exit code is always that of the tests; an interrupted run (Ctrl-C, SIGTERM, SIGHUP, all passed on to the tests) is not summed up. A package with a workspace of its own (gleon init in packages/app/ of a monorepo) records its goldens there: gleon test at the root records the run in every workspace below it and sums them all up, and its hint names the package's config (gleon --config packages/app/.gleon/gleon.yaml report html …). gleon dashboard keeps the history of the runs.
In a goldens workspace the commands that work with manifests do not apply:
| Command | In a goldens workspace |
|---|---|
gleon status, stage, diff, clean --screenshots |
Refused: they would take every golden for an unrecorded screenshot. |
gleon pull, push, gc, lint, resolve |
Nothing to do (success), so shared CI steps pass. |
gleon clean |
Removes .gleon/runs/ as usual. |
In CI, the Action runs the tests with test-command, posts
the failures as a PR comment (each golden linked at the PR's head commit) and uploads the run for
/gleon approve (its workflow), which needs no storage
secrets in a goldens workspace. The comment needs pull-requests: write:
name: Goldens
on:
pull_request:
jobs:
goldens:
runs-on: macos-26 # the `fallback_platform` of .gleon/gleon.yaml compares text
permissions:
contents: read
pull-requests: write # the PR comment
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Set up Flutter here (e.g. subosito/flutter-action, pinned to a commit SHA).
- uses: gleon-rs/gleon@main
with:
command: verify
test-command: flutter test
artifact-name: gleon-artifacts-${{ github.event.pull_request.number }}-${{ runner.os }}The Action reports and uploads the workspace of its working-directory; for packages with
workspaces of their own, use one step (and one artifact-name) per package.
Public repositories and local runs need no license; private repositories need one in CI (see License).
| Command | Description | Example |
|---|---|---|
gleon init |
Scaffolds the .gleon/ directory tree and a .gleon/gleon.yaml (--flutter/--generic). |
gleon initgleon init --generic |
gleon stage [PATHS...] |
Records matching screenshots as official baseline manifests for the current platform. | gleon stagegleon stage test/goldens/login.png |
gleon status |
Reports the status (Clean, Added, Modified, Deleted) of all discovered screenshots. |
gleon statusgleon status --json |
gleon diff |
Runs visual comparison between actual screenshots and committed baselines. | gleon diffgleon diff --artifacts .gleon/runs/ci |
gleon test -- <COMMAND> |
Runs a test command (e.g. flutter test) as one run with metrics on, then sums it up. |
gleon test -- flutter testgleon test -- npm test |
gleon report <FORMAT> |
Renders the case reports of the latest run (html, markdown, junit, json). |
gleon report html --out report.htmlgleon report markdown --pr-number 42 |
gleon dashboard |
Adds the latest run to history.json and compiles the static history dashboard. |
gleon dashboardgleon dashboard --push |
gleon approve [NAMES...] |
Accepts the candidates of failed cases as new baselines. | gleon approvegleon approve auth/login |
gleon pull |
Downloads missing baseline blobs from remote object storage (--require-storage or GLEON_REQUIRE_STORAGE=1: fail without one). |
gleon pullgleon pull --all |
gleon push |
Uploads locally staged baseline blobs to remote object storage (--require-storage likewise). |
gleon push |
gleon clean |
Removes .gleon/runs/; --screenshots also deletes, untracks and ignores screenshots. |
gleon cleangleon clean --screenshots --dry-run |
gleon lint |
Verifies integrity and schema compliance of all manifests and configs. | gleon lint |
gleon resolve |
Interactively or automatically resolves Git merge conflicts in baseline manifests. | gleon resolve |
All commands support the following global options:
--config <PATH>: Specify an explicit path to the configuration file (default:.gleon/gleon.yaml, searched upwards from the current directory, no further than the repository root (the first directory with.git,.hg,.jjor.sl) and never in the home directory or above unless that is a repository root itself: a~/.gleon/gleon.yamlis no workspace, and neither is its.env). Integrations search upwards from each golden the same way.--target-branch <BRANCH>: Target branch for baseline comparison (defaults tomain, orGLEON_TARGET_BRANCH).--platform <STRING>: Override platform context with an opaque string (e.g.--platform my-custom-env).--os <OS>/--arch <ARCH>/--renderer <RENDERER>: Override individual platform context dimensions.--label <KEY=VALUE>: Add custom isolation labels (e.g.--label theme=dark).--verbose: Enable debug logging output (routed tostderr).--quiet: Suppress informational output (only display warnings and errors).
Below is a complete, annotated .gleon/gleon.yaml reference. Unknown keys are rejected. A JSON Schema is committed at gleon-model/schema/config.v1.json.
# Optional: enforce a minimum CLI version across the team and CI (`gleon init` writes its own).
# A workspace of an integration alone needs none.
required_version: ">=0.1.0"
# Optional: where the baselines live. `manifests` (the default): `.gleon/manifests/` and blobs,
# recorded by `gleon stage` and compared by `gleon diff`. `goldens`: the golden files an
# integration (the Flutter package) commits next to its tests and compares itself; the CLI then
# reads their case reports (`gleon test`, `report`, `approve`) and refuses `status`, `stage`,
# `diff` and `clean --screenshots`. `gleon init` writes `goldens` next to a `pubspec.yaml`.
baselines: manifests
# Rules for discovering and comparing screenshots (optional: a workspace may name only its
# `fallback_platform`). A file is excluded if it matches `exclude`;
# otherwise the FIRST rule whose `include` matches applies (paths are matched case-insensitively,
# relative to the workspace root, with `/` separators). Globs: `*` and `?` stay within a path
# segment, `**` (a whole segment) crosses segments, `[...]` is a character class; `{a,b}`
# alternatives and a trailing `/` are config errors.
screenshots:
- include: "test/**/goldens/**/*.png" # Single pattern or list of glob patterns
# Required: 'exact' (every pixel identical, no `diff` keys), 'pixel' (the share of the pixels
# in `diff.threshold` may differ) or 'ssim' (tolerates rendering noise, see below). There is
# no default: a rule added for its masks alone would otherwise tolerate pixels silently (rules
# without a mode meant 'pixel' with `threshold: 0.01` before gleon 0.4.0).
mode: pixel
diff: # each key belongs to one mode: a key of another mode is a config error, even at its default
threshold: 0.01 # allowed fraction of differing pixels [0.0 - 1.0], required for 'pixel' (0: none)
# All off by default, never inside text: differing pixels that count as equal (rendering
# noise of shapes; not a substitute for 'ssim' or per-platform goldens). Case reports count
# them as `tolerated_pixels` and `edge_pixels`.
channel_tolerance: 0 # no RGBA byte differs by more than this [0 - 254]: GPU and color drift
anti_alias: false # the pixel looks anti-aliased in either image (the detection of pixelmatch)
# The Sobel gradient of the golden's luma there exceeds this [0 - 254] (Skia Gold's edge
# mask, unnormalized: a sharp step of 16 gives 64). Every change on edges passes too: a
# missing glyph or small icon, a 1px move (see gleon-engine/tests/ssim_corpus.rs); the lower
# the value, the more pixels are edges.
edge_threshold: 0
# Optional: integrations that report the text of a screenshot (the Flutter package) compare it
# under this tolerance (in 'ssim' mode left out of both of its gates), the rest under the
# rule's mode: the text passes while every 16x16 square of it has at most this share of
# differing pixels [0.0 - 1.0]. Unset, the default
# depends on the golden: 0.05 on the platform it was recorded on (see `fallback_platform`),
# else 1 (text never fails, because operating systems rasterize glyphs differently, about
# 40% of a square, more than a changed character does). A value set here always applies: 1
# turns text comparison off everywhere. `gleon diff` sees no text.
text_tolerance: 1.0
masks:
# Optional: Ignore dynamic regions (clocks, avatars, blinking cursors)
- path: "**/dashboard.png"
zones:
- x: 10
y: 20
width: 150 # Absolute pixels (150) or relative percentage ("25%")
height: 40
- include: "test/**/charts/**/*.png"
mode: ssim # the keys of 'ssim' only
diff:
min_similarity: 0.8 # minimum local SSIM of every neighborhood [0.0 - 1.0] (default: 0.8)
color_tolerance: 8 # tolerated deviation beyond the local 3x3 envelope, 8-bit units (default: 8)
# Global directory exclusion patterns
exclude:
- "**/build/**"
- "**/target/**"
- "**/node_modules/**"
# Optional: Fallback platform for Sparse Multi-Platform Baselines
# When secondary platforms render identically to the fallback, no duplicate manifests are stored.
# For integrations with golden files (Flutter) it is the canonical platform (OS and architecture,
# with the names a process reports: `macos-aarch64`, `linux-x86_64`; `arm64`, `x64`, `amd64` and
# `darwin` are read as them): goldens without a gleon stamp count as recorded there, it records
# again what other platforms recorded, and only with it a golden recorded elsewhere is kept by
# update mode while only its text differs.
fallback_platform:
os: macos
arch: aarch64
# Optional: per-golden comparison metrics, recorded by integrations such as the gleon Flutter
# package into `.gleon/runs/latest/cases/<platform>/<test name>.json` (git-ignored with `runs/`).
# The GLEON_METRICS environment variable (1/true or 0/false) overrides `enabled`.
metrics:
enabled: false # default: false
console: true # also print one line per golden (default: true)
# Optional: where the images of failed cases go (`<dir>/<platform>/<test name>/{golden,candidate,diff}.png`),
# relative to the workspace root: this default or a directory under `.gleon/runs/` outside `latest/`.
# GLEON_ARTIFACTS_DIR and `gleon diff --artifacts` override it.
artifacts: .gleon/runs/latest/artifactsRemote blob storage (AWS S3, Cloudflare R2, Google Cloud Storage) is configured through environment variables, not in gleon.yaml, so credentials never end up in Git: copy .gleon/.env.template to .gleon/.env.local and set GLEON_STORAGE_URL (e.g. s3://my-visual-baselines-bucket/blobs) plus the provider credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, ...).
Case reports are the one result format of gleon (schema: gleon-model/schema/case.v5.json): gleon diff writes one per screenshot, integrations one per golden with metrics enabled. A report holds the golden and candidate SHA-256 and size, the effective tolerance and masks, the outcome (identical, match, mismatch, dimension_mismatch, error with its kind, updated, missing), the metrics including their headroom to the thresholds (for example min_ssim - min_similarity and color_tolerance - peak_excess in SSIM mode) and the paths of the images a failure keeps in the artifacts directory. Passing goldens report their margin too, so thresholds can be tuned from measurements instead of guesses.
Reports and images are kept per platform: .gleon/runs/latest/cases/<platform>/<test name>.json and <artifacts dir>/<platform>/<test name>/{golden,candidate,diff}.png, where <platform> is the platform key (macos-aarch64, linux-x86_64, os=ios-sim+arch=arm when the OS or architecture contains -, then +<renderer> and +<key>=<value> per label) and <test name> the same on every platform. A report lists only images of its own platform and name, and readers skip a report anywhere else (the flat cases/<test name>.json of an older gleon, a copy) with a warning; gleon clean removes them. Several platforms in one workspace therefore never overwrite each other: a macOS host and a Linux container on a bind-mounted checkout, or several gleon diff --platform runs; gleon diff clears only its own platform's previous reports and images. To read them as one joint run, give the processes of every platform the same GLEON_RUN_ID (gleon test keeps a preset one); the HTML and Markdown reports then name the platform next to each test (test/goldens/a (linux-x86_64)), while junit.xml always names the platform as the classname and the test as the name, so CI tools track a test case the same way in every run. gleon diff exits with the result of its own cases, but renders report.md, junit.xml and report.html from the cases of gleon diff in its run on every platform: the joint run when the platforms share GLEON_RUN_ID, its own cases otherwise. Downloaded runs of several CI jobs can also be copied into one latest/ without clashes.
gleon report, gleon dashboard and gleon approve read the reports of one run: the run of GLEON_RUN_ID on every platform (set it in CI, e.g. ${{ github.run_id }}-${{ github.run_attempt }}); without it, the run of each platform on its own: on the platform where gleon test ran, the run it recorded in .gleon/runs/latest/run.json unless a newer report of that platform belongs to another run; else, and on every other platform, the run of the platform's newest report (gleon diff names its own run when GLEON_RUN_ID is not set). Platforms that ran apart (two CI jobs copied into one latest/, or a container's tests run apart) are thus both read, with a warning that they come from different runs. Reports without a run id (tests run without gleon test) are read together, without those whose golden no longer exists, and with a warning that they may mix runs; without metrics integrations record only failures, so such a run counts no passes: run the tests through gleon test for complete totals. Only the process environment sets GLEON_RUN_ID, never .gleon/.env. Invalid reports are skipped with a warning. --from <dir> reads a downloaded copy of .gleon/runs/latest (relative to the working directory, with its cases/) as it is, ignoring GLEON_RUN_ID; gleon approve takes several (--from linux/latest --from macos/latest), and its filters also take <platform>/<test name> (as under cases/) to approve one platform's case, or a bare platform key (gleon approve linux-x86_64) to approve every case of that platform; filters match whole names, by test name and by <platform>/<test name> (so linux-x86_64/foo also matches a test named linux-x86_64/foo on any platform). The approved cases are listed as <platform>/<test name>, sorted. golden.path is the golden file of an integration, and for gleon diff the screenshot whose baseline lives in the manifests. Approving turns cases of gleon diff into manifests and blobs of the platform the case ran on, and overwrites the golden PNG of an integration (only .png files inside the workspace, outside hidden directories and not through symlinks); every candidate is checked before anything is written, and two different candidates for one golden are refused, naming both candidate files and how to pick one (a <platform>/<test name> filter when they come from two platforms, else one run at a time), unless the fallback_platform reported that golden of an integration: then the other platforms' cases for it are skipped (see "Goldens of integrations on several platforms"). The manifests of the fallback_platform are written first, so another platform's candidate equal to its new baseline keeps no override. .gleon/history.json keeps the failures of each run and counts the passes; an entry names the platforms of its run (a joint run all of them, replacing the entry of its first platform when it is recorded again), and the dashboard's platform filter shows a run under each of them.
Tests of an integration write their case reports themselves, so there is no gleon diff step: the Action's verify with test-command does it all (see Flutter: Goldens in Git), or by hand: give the job a run id, run the tests through gleon test, then report that run and upload .gleon/runs/ for /gleon approve (the artifact name must start with gleon-artifacts-<PR number>-):
env:
GLEON_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}
steps:
- run: gleon test -- flutter test
- if: failure() && github.event_name == 'pull_request'
run: gleon report markdown --pr-number ${{ github.event.pull_request.number }} > gleon-report.md
- if: failure() && github.event_name == 'pull_request'
uses: actions/upload-artifact@v7
with:
name: gleon-artifacts-${{ github.event.pull_request.number }}-${{ runner.os }}
path: .gleon/runs/On Windows gleon test -- flutter test finds flutter.bat like a shell does.
Integrations commit goldens as PNG files, one per test, recorded on any platform: each golden
gleon writes names the platform it was recorded on in a tEXt chunk of its PNG
(gleon:platform, e.g. macos-aarch64; decoders ignore it), with or without a workspace or a
rule, except a golden the workspace excludes (written and compared like Flutter does: its text
is pixels like the rest, unless the call sets a text tolerance). Its text is
compared there (text_tolerance 0.05: a pixel of font-engine noise per 16x16 square passes, a
changed digit fails) and ignored elsewhere (1, layout exact). A golden without the stamp
(recorded by another tool, or by the Flutter package before 0.4.0) counts as recorded on
fallback_platform, with the names a process reports (std::env::consts: macos-aarch64,
linux-x86_64, windows-x86_64; macos-arm64, linux-x64 and darwin are read as them, in
the config, the flags and the variables alike, while a custom key such as custom-arm64 keeps
its spelling),
else nowhere known: its text is ignored everywhere, and a pass whose text differed says so once
per test process.
Update mode (--update-goldens) |
|
|---|---|
| A missing golden, or one recorded here or nowhere known | written, stamped with this platform |
| The same image, stamped | left as it is |
| The same image, unstamped (recorded here or nowhere known) | written once more, stamped: --update-goldens on the recording platform is how an older golden gets its stamp |
A golden recorded on another platform, with a fallback_platform |
left as it is while the candidate passes against it (only its text differs), with a warning naming the fallback_platform that records it again, so goldens never flip between platforms over their glyphs; written over when it would change |
A golden recorded on another platform, without a fallback_platform |
written over (nothing could record it again) |
On the fallback_platform, a golden recorded elsewhere |
recorded again, even when only its text differs |
The fallback_platform warns about each golden recorded elsewhere it compares (its text is not
compared there); with GLEON_REQUIRE_CANONICAL=1 it compares them as its own instead, so their
glyphs fail until they are recorded there. A text_tolerance set in the rule or the call
applies on every platform, so 1 turns text comparison off everywhere. golden.platform of a
case report names the stamp. A failure of a golden a rule covers names its case report (by
its absolute path) and gleon report html, which renders the reports of the run.
gleon approve writes the candidates of integrations stamped with the platform of their case.
When the fallback_platform reported a golden in the approved runs, its result holds: passed, the
golden stays; failed, its candidate is written; the other platforms' cases for that golden are
skipped with a warning (a fallback_platform that names only an OS makes every architecture of it
canonical, so their differing candidates conflict). PNG optimizers that strip text chunks drop the
stamp. A text_tolerance set below 1 compares text on every platform, so update mode on another
platform than the golden's rewrites it whenever the glyphs differ: the golden then moves between
the platforms that update it.
gleon approve --from metrics-macos-arm64
gleon approve --from metrics-macos-arm64 --from metrics-linux-x64The platform is the test process's: an x86_64 Flutter under Rosetta on Apple silicon runs as
macos-x86_64. The platform of .gleon/gleon.yaml, GLEON_PLATFORM and the platform flags
set the CLI's platform only. The case name and rule matching are the same on every platform.
gleon provides a composite GitHub Action (gleon-rs/gleon) for turnkey CI verification.
Important
gleon computes baseline manifests by resolving the merge-base commit between the pull request branch and the target branch (main).
Default CI checkout actions (actions/checkout) perform a shallow clone (fetch-depth: 1), which lacks commit ancestry.
You must configure actions/checkout with fetch-depth: 0:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0 # Required for gleon merge-base resolutionAdd .github/workflows/visual-tests.yml to your repository:
name: Visual Regression Tests
on:
pull_request:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write # Required to post diff reports as PR comments
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Run Test Suite (generate actual screenshots)
run: npm test # or flutter test, cargo test, etc.
- name: Run gleon Visual Regression Verify
uses: gleon-rs/gleon@main
with:
command: "verify"
github-token: ${{ secrets.GITHUB_TOKEN }}| Input | Description | Default |
|---|---|---|
version |
Release version tag to download (e.g. 'v0.3.0' or 'latest') |
'latest' |
command |
Execution mode: 'verify' (pull + diff + report) or single command ('diff', 'pull', 'approve') |
'diff' |
github-token |
Token (${{ secrets.GITHUB_TOKEN }}) for release downloads and PR comments |
${{ github.token }} |
target-branch |
Target branch for baseline comparison | PR base ref or default branch |
working-directory |
Working directory to run gleon from (useful for monorepos) | '.' |
args |
Additional flags for the selected command | '' |
test-command |
For verify: the tests of an integration (flutter test), run as gleon test -- <command> instead of pull + diff |
'' |
license-key |
Commercial BSL license key for private enterprise repositories | '' |
strict |
Fail build immediately on license violation ('true' / 'false') |
'false' |
When gleon detects visual regressions during a PR CI run, it automatically posts a detailed Markdown report with diff previews in the PR comment section.
To accept the new visual changes as the updated baseline:
-
Approve All Changed Screenshots: Comment directly on the PR:
/gleon approve -
Approve Specific Tests Only:
/gleon approve auth/login
To enable comment-based approvals, add .github/workflows/gleon-approve.yml referencing the reusable workflow. It approves the cases of the PR head's latest runs and commits the new baselines to the PR branch: golden PNGs in a goldens workspace (no storage secrets needed), manifests otherwise (their blobs need the GLEON_STORAGE_URL secret and the provider credentials; without them it fails). It runs gleon from the default branch with the PR's own .gleon/gleon.yaml and manifests (never the PR's code), so a PR that adds .gleon/ or switches to baselines: goldens is approved as it will be merged. A CI job that failed before comparing any golden uploads an empty run, which is read as one:
name: Gleon Approve
on:
issue_comment:
types: [created]
jobs:
approve:
permissions:
actions: write
contents: write
pull-requests: read
uses: gleon-rs/gleon/.github/workflows/approve.yml@main
with:
trigger-workflow: "visual-tests.yml" # Optional: auto-rerun CI after baseline approval
secrets: inheritgleon separates the control plane (manifests) from the data plane (images) using a Content-Addressable Storage (CAS) architecture.
Committing binary blobs directly to Git causes repository bloat, slow clone times, and unmanageable PR diffs. gleon solves this:
- Manifests in Git: Tiny, deterministic JSON files (
.gleon/manifests/**/*.json) containing cryptographic digests (SHA-256) and spatial dimensions. - Blobs in Object Storage: Actual PNG images (
.gleon/blobs/) are ignored by Git. They are uploaded to S3-compatible object storage viagleon pushand downloaded on demand viagleon pull.
Different operating systems (macOS vs Ubuntu CI) render fonts and anti-aliasing differently. Never inflate global error thresholds to mask these differences!
Instead, use Sparse Multi-Platform Baselines with Fallback:
- Configure
fallback_platformin.gleon/gleon.yaml(e.g.os: macos,arch: aarch64). - Tests that render identically across platforms dynamically inherit the fallback baseline in memory.
- Only genuine platform-specific differences generate override manifests when approved (
/gleon approve). - If an override later becomes byte-identical to the fallback,
gleon approveautomatically prunes the redundant manifest.
When a golden test is removed from the codebase:
gleon statusdetects the missing image file and reports it asDeleted.- Running
gleon stageon the fallback platform (macOS) removes the manifest from Git. - Once the fallback manifest is removed, all secondary platforms automatically stop tracking the deleted test.
- Rust 1.100 nightly (Edition 2024), pinned in
rust-toolchain.tomland installed automatically by rustup
# Build binary in release mode
cargo build --release --workspace
# Install binary into local cargo bin (~/.cargo/bin)
cargo install --path gleon --bin gleon --force
# Run full test suite
cargo test --workspace
# Run clippy lints
cargo clippy --workspace --all-targets --all-features -- -D warnings
# Format code
cargo fmt --allThe crates shared with other integrations — gleon-engine (comparison engine), gleon-model (configuration, naming, platform keys and case reports) and gleon-ffi (C ABI for the Flutter package) — are licensed MIT OR Apache-2.0.
The rest of Gleon — the gleon CLI and gleon-core — is licensed under the Business Source License 1.1 (BUSL-1.1), converting to Apache 2.0 after 4 years. Free for non-commercial use, open-source projects, and evaluation.