Allure on Windows and Linux: Install It, Then Make the Report Worth Reading

SarbatDaBhalla  · August 29, 2026

Most people meet Allure through one of these two lines:

allure: command not found                                    # Linux
'allure' is not recognized as the name of a cmdlet ...       # Windows

Same cause, two dialects. This post fixes both, then goes past the install — because a working allure command still gives you a report nobody wants to read until you feed it a few extra things.

Versions below were checked on 2026-08-29. Check the releases page for today’s number.

Contents

  1. The part everyone skips: what Allure actually is
  2. Install on Linux
  3. Install on Windows
  4. Check it worked
  5. Your first report from pytest
  6. serve vs generate vs open
  7. Make the report worth reading
  8. Trends and history
  9. Troubleshooting table
  10. Cheat sheet

1. The part everyone skips: what Allure actually is

Allure is not a test runner and not a pytest plugin. It is a Java program that reads a folder of JSON files and renders a static HTML site.

There are two separate pieces, installed in two different places:

Piece Job Installed with Lives in Current
Adapter Writes allure-results/*.json while tests run pip install allure-pytest your virtualenv, per project 2.16.0
Command line Turns allure-results/ into HTML apt / dpkg / scoop your machine, once 2.46.0

Three consequences worth internalising:

  • Their versions are unrelated. Adapter 2.16.0 with command line 2.46.0 is normal, not a bug.
  • pip install allure-pytest does not give you the allure command. This is the single most common misunderstanding. The pip package writes results; it does not render them.
  • You need a JRE. Allure 2 compiles to Java 8 bytecode, so Java 8 or newer — anything current is fine.

There are adapters for most ecosystems (allure-pytest, allure-behave, JUnit, TestNG, Jest, Cypress, NUnit, RSpec). The command line is the same binary for all of them, and everything below section 6 applies whichever adapter you use.

2. Install on Linux

Debian and Ubuntu, three commands:

sudo apt-get install -y default-jre
wget https://github.com/allure-framework/allure2/releases/download/2.46.0/allure_2.46.0-1_all.deb
sudo dpkg -i allure_2.46.0-1_all.deb
  • _all.deb means architecture-independent. The same file works on x86_64 and ARM — it is Java.
  • Red Hat, Fedora, SUSE: the same release ships allure_2.46.0-1.noarch.rpm.
  • If dpkg -i stops on unmet dependencies, run sudo apt-get -f install and it will finish.

No root, or a distro with neither deb nor rpm? Use the tarball:

wget https://github.com/allure-framework/allure2/releases/download/2.46.0/allure-2.46.0.tgz
tar -zxvf allure-2.46.0.tgz -C ~/.local/share/
echo 'export PATH="$HOME/.local/share/allure-2.46.0/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

If your shell is zsh — the default on macOS and on a growing number of distros — write that export line into ~/.zshrc instead. Appending it to ~/.bashrc on a zsh machine leaves PATH unchanged, with nothing on screen to say why.

On WSL: treat it as Linux and use the commands above. Do not try to call a Windows-installed allure.bat from inside WSL — the path translation will bite you. Install it twice if you work on both sides.

3. Install on Windows

Use Scoop. Manual unzip-and-edit-PATH is precisely what produces the is not recognized error.

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
scoop install allure

Why this works where the manual route does not: the Scoop manifest extracts the release zip, sets ALLURE_HOME, and creates a shim for bin\allure.bat. Nothing left for you to wire up. Upgrades are scoop update allure.

Set-ExecutionPolicy RemoteSigned -Scope CurrentUser is needed because the Scoop installer is a downloaded script. It applies to your user account only, not the machine.

One thing catches nearly everyone: a new PATH only exists in a new shell. If allure --version fails immediately after install, close the terminal, open a fresh one, and try again before debugging anything else. This also applies to the VS Code integrated terminal, which inherits its environment from whenever VS Code was started — restart VS Code, not just the panel.

If that combination of PATH and execution policy sounds familiar, it is the same pair of problems that makes a fresh Python install on Windows so unpleasant. I wrote those up separately in setting up Python on Windows 11.

If corporate policy blocks Scoop, there is no Chocolatey package for Allure either. Fall back to the zip:

  1. Download allure-2.46.0.zip from the releases page.
  2. Extract to a path with no spaces, e.g. C:\tools\allure-2.46.0.
  3. Add C:\tools\allure-2.46.0\bin to your user PATH (System Properties → Environment Variables → user Path → New).
  4. Open a new terminal.

4. Check it worked

java -version      # must print something; Allure is a Java program
allure --version   # should print 2.46.0

Run both. If allure --version fails but java -version works, it is a PATH problem. If java -version fails, install the JRE first — the error Allure gives you for a missing Java is not a helpful one.

5. Your first report from pytest

--alluredir is not a pytest flag. It appears only once the adapter is installed:

pip install allure-pytest
pytest tests/ --alluredir=allure-results --clean-alluredir
allure serve allure-results

--clean-alluredir wipes the results directory first. Get in the habit now: Allure merges whatever it finds in that folder, so without it a test you deleted last week still shows up in today’s report, passing.

Your browser opens on a report. Nothing was uploaded anywhere — allure serve starts a local web server and stops when you press Ctrl+C.

6. serve vs generate vs open

Three commands, three different jobs:

# Throwaway. Generate to a temp dir, start a local server, open the browser.
allure serve allure-results
allure serve allure-results --port 8080        # if the default port clashes

# Persistent. Write a real directory you can archive or publish.
allure generate allure-results --clean --output allure-report

# View a directory produced by 'generate'.
allure open allure-report

# One self-contained HTML file — for a ticket attachment, an email, a CI artifact.
allure generate allure-results --clean --single-file --output allure-report

Windows is identical; quote paths that contain spaces:

allure serve "C:\projects\my-tests\allure-results"
allure generate "C:\projects\my-tests\allure-results" --clean --single-file --output allure-report

Why allure open and not just double-clicking index.html: the report fetches its data over XHR, and browsers block that on the file:// protocol. Double-clicking gives you a rendered page with no test results in it, which looks like a generation failure and is not one. Use allure open for local viewing, or --single-file when the report has to travel as one attachment.

One caveat on --single-file: the report data is inlined into that one HTML file rather than sitting in folders beside it, and attachments are part of that data. The file therefore grows with everything your suite attaches, so a run that screenshots every failure produces a much larger file than the pass/fail data alone would suggest. Check the size against your mail provider’s attachment limit before you rely on emailing it. It is the right format for a ticket or a CI artifact; for a large suite, publish the directory and send a link.

7. Make the report worth reading

Out of the box you get pass/fail and a stack trace. Four additions turn it into something a non-author can actually diagnose from.

Steps and attachments

import allure

@allure.step("Log in as {username}")
def login(page, username, password):
    ...

def test_checkout(page):
    login(page, "demo", "secret")
    allure.attach(
        page.screenshot(),
        name="cart page",
        attachment_type=allure.attachment_type.PNG,
    )
    allure.attach.file("app.log", name="server log",
                       attachment_type=allure.attachment_type.TEXT)

@allure.step gives a failing test a readable timeline instead of one opaque block. The step title is formatted with the call’s arguments, so {username} is filled in per call — and it works whether the argument was passed positionally or by keyword. (page.screenshot() above is Playwright; swap in whatever your driver returns bytes from.)

allure.attach takes bytes or a string; allure.attach.file takes a path. The supported attachment_type values are PNG, JPG, SVG, GIF, BMP, TIFF, TEXT, HTML, JSON, XML, YAML, CSV, TSV, URI_LIST, PDF, ZIP, PCAP, MP4, OGG and WEBM.

The type you pass decides whether the report can show the evidence or only offer it for download:

You attach The report gives you
PNG, JPG, GIF, BMP, TIFF, SVG The image, inline
MP4, OGG, WEBM A real video player
CSV, TSV A rendered table, not raw text
JSON, XML, YAML Syntax-highlighted code
HTML, TEXT, URI_LIST Rendered inline
PDF, ZIP, PCAP A download link only

Attach a CSV as TEXT and you get a wall of commas; attach it as CSV and you get a table. Same file, one-word change.

Two size limits apply to previews. Above 10 MiB the preview is dropped and the attachment degrades to a plain download link; above 2 MiB syntax highlighting is dropped. Photos and video are exempt from the 10 MiB rule, so a large screenshot still displays — but SVG is not exempt, because it is text.

The high-value habit: attach the evidence on failure, automatically, from a fixture or pytest_runtest_makereport hook, so nobody has to reproduce a failure to see what the screen looked like.

environment.properties — what was this run against?

Drop a plain properties file into the results directory and it becomes an Environment widget on the report’s front page:

# allure-results/environment.properties
Browser=Chrome 141
OS=Ubuntu 24.04
Build=2026.08.28-1044
Target.URL=https://staging.example.com

Generate it from your test session rather than committing it — the point is recording what this run used. Without it, a report three weeks old tells you nothing about what it was testing.

categories.json — stop triaging the same failure twice

By default every failure is “failed”. categories.json sorts them into buckets by regex, so infrastructure noise stops looking like product defects:

[
  {
    "name": "Infrastructure problem",
    "matchedStatuses": ["broken"],
    "messageRegex": ".*ConnectionError.*|.*Timeout.*"
  },
  {
    "name": "Outdated test",
    "matchedStatuses": ["broken"],
    "traceRegex": ".*NoSuchElementException.*"
  },
  {
    "name": "Product defect",
    "matchedStatuses": ["failed"]
  }
]

Two rules decide whether this file does anything, and neither is obvious:

The patterns have to match the whole message, not a fragment. That is why every regex above is wrapped in .*. Write "messageRegex": "ConnectionError" and you get no matches and no error — the category simply renders empty, which looks exactly like Allure ignoring the file. (Allure compiles these with DOTALL, so . spans newlines and .* does reach into a multi-line stack trace.)

Order matters, because a test lands in the first category it matches and no others. That is why the catch-all Product defect sits last. Move it to the top and it swallows every failure before the specific rules get a look, leaving you exactly where you started. Put your narrow rules first and the broad ones last. Anything matching nothing falls into Allure’s built-in “Product defects” or “Test defects” bucket.

Copy it into allure-results/ before generating. On a suite with a hundred failures this is the difference between an afternoon of triage and five minutes.

{
  "name": "Jenkins",
  "type": "jenkins",
  "buildOrder": 412,
  "buildName": "nightly-regression #412",
  "buildUrl": "https://ci.example.com/job/nightly/412/",
  "reportUrl": "https://ci.example.com/job/nightly/412/allure/"
}

Also goes in allure-results/. It puts a clickable build reference on the report, so a report someone forwarded you six weeks later is still traceable to a pipeline run.

buildOrder is optional but worth setting: it is what labels each point on the trend graph. Allure renders it as #412, so when you spot a dip in the pass rate you can tell which build it was. Without it the points are still plotted in the right order — ordering comes from the chain of reports, not from this field — but they carry no label.

type only selects the icon shown next to the build link. Allure ships icons for jenkins, github, gitlab, bitbucket, teamcity, bamboo, circleci and azure; anything else falls back to a generic one.

Trend graphs are the first thing people ask for and the first thing that silently does not work.

Allure has no database. History lives in a history/ folder inside the generated report, and it is only carried forward if you carry it forward — copy it from the previous report into the new results directory before generating:

# after the previous run's report exists
cp -r allure-report/history allure-results/ 2>/dev/null || true
allure generate allure-results --clean --output allure-report

On CI this means archiving the previous allure-report/ as a build artifact and restoring it at the start of the next run. Skip that and every report shows a single data point and looks like the first run you have ever done.

Copying the folder is necessary but not sufficient. History is keyed on each test’s historyId, which is derived from its full name and its parameters. Rename a test, move it to another module, or change what you parametrise it with, and that test starts a fresh history line with one point in it while everything around it keeps its trend. That is expected behaviour, not a failed copy.

Pin the command line version in CI. A build image that resolves allure to whatever is newest generates each run’s report with a potentially different binary — an extra moving part in the one artefact you are using to diagnose everything else. Use the exact .deb URL:

RUN apt-get update && apt-get install -y default-jre wget \
 && wget -q https://github.com/allure-framework/allure2/releases/download/2.46.0/allure_2.46.0-1_all.deb \
 && dpkg -i allure_2.46.0-1_all.deb \
 && rm allure_2.46.0-1_all.deb

Package managers do not all track the release at the same pace. Checked on 2026-08-29: GitHub 2.46.0, Scoop 2.46.0, Homebrew 2.46.0, npm allure-commandline 2.43.0 — so Scoop and Homebrew are currently level with the release, and npm is three minor versions behind. For a workstation, take whatever your package manager gives you; for CI, pin.

9. Troubleshooting table

Symptom Cause Fix
allure: command not found / is not recognized Not on PATH, or PATH changed in an already-open shell Open a new terminal; then scoop install allure / dpkg -i
allure --version fails, java -version also fails No JRE sudo apt-get install default-jre
unrecognized arguments: --alluredir allure-pytest not installed in the active virtualenv pip install allure-pytest
Report opens but is empty Opened index.html over file:// allure open allure-report, or generate --single-file
Deleted tests still in the report Stale results merged Add --clean-alluredir to the pytest run
Report shows “Unknown” / no trend history/ not carried forward Copy allure-report/history into allure-results/ before generating
Trend points have no build labels No buildOrder in executor.json Add your CI build number as buildOrder
Trend never shows more than 20 points Allure caps trend history at 20 Expected, not a bug
One test lost its history, the rest kept theirs Test renamed or reparametrised, so its historyId changed Expected — it rebuilds from this run onwards
A category renders empty Regex matched a fragment, not the whole message Wrap the pattern in .*
Every failure is one undifferentiated pile No categories.json Add one to allure-results/
dpkg -i fails on dependencies Missing transitive packages sudo apt-get -f install
Scoop install blocked by policy Execution policy or IT restriction Zip + manual PATH; there is no Chocolatey package

10. Cheat sheet

# Linux install
sudo apt-get install -y default-jre
wget https://github.com/allure-framework/allure2/releases/download/2.46.0/allure_2.46.0-1_all.deb
sudo dpkg -i allure_2.46.0-1_all.deb
# Windows install
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
scoop install allure
# Everyday use
pip install allure-pytest
pytest tests/ --alluredir=allure-results --clean-alluredir
allure serve allure-results

# Persistent report with history
cp -r allure-report/history allure-results/ 2>/dev/null || true
allure generate allure-results --clean --output allure-report
allure open allure-report

Files Allure picks up from allure-results/: environment.properties, categories.json, executor.json, history/.

References


If one of those two error messages is what brought you here, which one was it? They need different fixes, and I would like to know which half of this post is doing the work. And if you got the install sorted but your trend graph still shows a single lonely point, leave a comment describing what your CI does between runs — that one is nearly always the history/ folder not surviving the build, and it is far easier to spot with the pipeline in front of us.


Questions or comments?

Ask a question, share your experience, or add a correction below.

Loading comments...