41Allure Reports (deep)
What you will master here
- What Allure is, what it gives you over plain reports
- Installing for Playwright, Java (TestNG/JUnit), Pytest
- Annotations: @Feature, @Story, @Severity, @Step, @Description, @Issue, @TmsLink
- Attachments: screenshots, logs, traces
- History, trends, categories of defects
- Hosting reports (S3, Allure TestOps SaaS)
- CI integration
41.1 Why Allure
- Rich, stakeholder-friendly HTML report
- Feature/Story/Test hierarchy maps to BAU language
- Trends across runs (pass-rate over time, retry rate)
- Defect categorisation (product defect vs test defect)
- Step-level visibility — see exactly what failed inside a test
41.2 Setup — Playwright
npm i -D allure-playwright allure-commandline
// playwright.config.ts
reporter: [
['line'],
['allure-playwright', {
detail: true,
outputFolder: 'allure-results',
suiteTitle: false,
environmentInfo: {
env: process.env.ENV ?? 'staging',
browser: 'chromium',
node: process.version,
},
}],
],
npx playwright test npx allure generate ./allure-results -o ./allure-report --clean npx allure open ./allure-report
41.3 Annotations (Playwright)
import { test } from '@playwright/test';
import { allure } from 'allure-playwright';
test('login - happy path', async ({ page }) => {
await allure.epic('Authentication');
await allure.feature('Login');
await allure.story('Happy path with valid credentials');
await allure.severity('critical');
await allure.tag('smoke');
await allure.issue('JIRA-1234', 'https://acme.atlassian.net/browse/JIRA-1234');
await allure.tms('TC-5678');
await allure.step('Open login page', async () => {
await page.goto('/login');
});
await allure.step('Submit credentials', async () => {
await page.getByLabel('Email').fill('alice@test.com');
await page.getByLabel('Password').fill('p4ssw0rd');
await page.getByRole('button', { name: 'Sign in' }).click();
});
await allure.step('Verify dashboard loaded', async () => {
await expect(page.getByText('Dashboard')).toBeVisible();
});
});
41.4 Java (TestNG) version
@Epic("Authentication")
@Feature("Login")
public class LoginTest {
@Test(description = "Happy path")
@Story("Happy path with valid credentials")
@Severity(SeverityLevel.CRITICAL)
@TmsLink("TC-5678")
@Issue("JIRA-1234")
public void happyLogin() {
Allure.step("Open login page", () -> driver.get("/login"));
Allure.step("Submit credentials", () -> { /* ... */ });
Allure.step("Verify dashboard", () -> { /* ... */ });
}
}
41.5 Attachments
// Screenshot
const buf = await page.screenshot();
await allure.attach('failure-screenshot', buf, 'image/png');
// HTML
await allure.attach('response', JSON.stringify(body, null, 2), 'application/json');
// Trace
await allure.attach('trace.zip', fs.readFileSync('trace.zip'), 'application/zip');
41.6 History & trends
Allure keeps a history/ folder. To get trends across runs, copy the previous report's history/ into the next run's allure-results/ before generating.
# In CI
- name: Download previous history
uses: actions/download-artifact@v4
with: { name: allure-history }
- run: cp -r allure-history/* allure-results/history/ || true
- run: npx allure generate allure-results -o allure-report --clean
- name: Upload new history
uses: actions/upload-artifact@v4
with: { name: allure-history, path: allure-report/history }
41.7 Categories (defect classification)
// allure-results/categories.json
[
{ "name": "Product defects", "matchedStatuses": ["failed"] },
{ "name": "Test defects", "matchedStatuses": ["broken"] },
{ "name": "Outdated tests", "matchedStatuses": ["skipped"] },
{ "name": "Infrastructure", "matchedStatuses": ["failed","broken"],
"messageRegex": ".*ECONNREFUSED.*|.*ETIMEDOUT.*" }
]
41.8 Hosting
- Push the static
allure-report/folder to GitHub Pages / S3 / nginx - Allure TestOps — SaaS that ingests results from many runs, central dashboard
- Jenkins Allure plugin — one-click report per build
Module 64 — Allure Q&A
Why Allure over the default reporter?
Stakeholder-readable hierarchy (Epic/Feature/Story/Test), step-level visibility, trends across runs, defect categorisation, attachments inline. Plain reports are pass/fail; Allure is "what happened and why".
What's allure.step for?
Wraps an action so the report shows it as a named step with its own status, duration, and attachments. Makes long tests readable. Failures point at the exact step.
How do you get history/trends?
Copy the previous run's
history/ folder into the current allure-results/ before generating. CI workflow saves history as an artifact and restores on next run.What are Categories?
Rules in
categories.json that group failures by message regex/status. Lets you classify "product defect" vs "test defect" vs "infra flake" — actionable triage.How do you attach a Playwright trace to Allure?
await allure.attach('trace.zip', fs.readFileSync(tracePath), 'application/zip'). Open in Allure → download → npx playwright show-trace locally. Some integrations link the trace viewer inline.Severity field values?
blocker, critical, normal, minor, trivial. Use blocker for fundamental functionality, critical for major flows, normal for typical, minor for cosmetic.
What's Allure TestOps?
Paid SaaS / on-prem product from the Allure team. Aggregates results across many projects, supports manual test cases, links to Jira, history, defect tracking. Useful at scale.
Allure with Playwright Python — Full Setup
pip install allure-pytest pytest-playwright # Run tests and generate Allure results pytest tests/ --alluredir=allure-results -v # Generate HTML report (requires Allure CLI) allure generate allure-results --clean -o allure-report # Serve report locally allure serve allure-results # Install Allure CLI (macOS) brew install allure
import allure
import pytest
from playwright.sync_api import Page
@allure.epic("E-Commerce")
@allure.feature("Checkout")
@allure.story("Payment Processing")
class TestCheckout:
@allure.title("User can complete checkout with valid card")
@allure.severity(allure.severity_level.CRITICAL)
@allure.description("Verify complete checkout flow from cart to order confirmation")
def test_checkout_success(self, page: Page):
with allure.step("Navigate to product and add to cart"):
page.goto("https://shop.example.com/product/123")
page.click('[data-testid="add-to-cart"]')
allure.attach(page.screenshot(), "product-page.png", allure.attachment_type.PNG)
with allure.step("Proceed to checkout"):
page.click('[data-testid="checkout-btn"]')
assert page.url.contains("/checkout")
with allure.step("Fill payment details"):
page.fill('[name="cardNumber"]', "4242424242424242")
page.fill('[name="expiry"]', "12/28")
page.fill('[name="cvv"]', "123")
allure.attach(page.screenshot(), "payment-form.png", allure.attachment_type.PNG)
with allure.step("Submit order and verify confirmation"):
page.click('[type="submit"]')
page.wait_for_selector('[data-testid="order-confirmation"]')
order_id = page.text_content('[data-testid="order-id"]')
allure.attach(
f"Order ID: {order_id}".encode(),
"order-details.txt",
allure.attachment_type.TEXT
)
assert order_id.startswith("ORD-")
@allure.title("Payment fails with expired card")
@allure.severity(allure.severity_level.NORMAL)
@pytest.mark.xfail(reason="Known issue: error message text changes monthly")
def test_checkout_expired_card(self, page: Page):
page.goto("https://shop.example.com/checkout")
page.fill('[name="cardNumber"]', "4000000000000002")
page.fill('[name="expiry"]', "01/20") # expired
page.click('[type="submit"]')
with allure.step("Verify error message"):
error = page.locator('[data-testid="payment-error"]')
allure.attach(page.screenshot(), "error-state.png", allure.attachment_type.PNG)
assert error.is_visible()
assert "card" in error.text_content().lower()
Allure Custom Categories & Environment Info
// allure-results/categories.json — classify test failures
[
{
"name": "Product defects",
"matchedStatuses": ["failed"],
"messageRegex": ".*AssertionError.*expected.*"
},
{
"name": "Test infrastructure problems",
"matchedStatuses": ["broken"],
"messageRegex": ".*TimeoutError.*|.*ConnectionError.*"
},
{
"name": "Known flaky tests",
"matchedStatuses": ["failed", "broken"],
"traceRegex": ".*flaky.*"
}
]
// allure-results/environment.properties
{
"Browser": "Chromium 124",
"Environment": "Staging",
"Base URL": "https://staging.example.com",
"Build": "2.14.0",
"Python": "3.11.4",
"Playwright": "1.44.0"
}