---
name: Allure Report Generator
slug: allure-report-generator
category: Quality
description: Allure Report Generator configures Allure test reports with categories, annotations, attachments, and environment metadata. Use it when you need richer CI test visibility and historical failure analysis.
github: "https://github.com/PramodDutta/qaskills/tree/main/seed-skills/allure-report-generator"
language: TypeScript
stars: 216
forks: 23
install: "npx degit https://github.com/PramodDutta/qaskills/tree/main/seed-skills/allure-report-generator ~/.claude/skills/allure-report-generator"
installs_to: ~/.claude/skills/allure-report-generator
source_path: seed-skills/allure-report-generator/SKILL.md
collection_size: 25
category_size: 1557
collection_url: "https://dirskills.com/collections/PramodDutta/qaskills"
added: 2026-09-04T05:25:35.779Z
last_synced: 2026-09-04T05:25:35.779Z
canonical_url: "https://dirskills.com/skills/allure-report-generator"
---

# Allure Report Generator

Allure Report Generator configures Allure test reports with categories, annotations, attachments, and environment metadata. Use it when you need richer CI test visibility and historical failure analysis.

**Install:**

```bash
npx degit https://github.com/PramodDutta/qaskills/tree/main/seed-skills/allure-report-generator ~/.claude/skills/allure-report-generator
```

## README

# Allure Report Generator

Allure is an open-source test reporting framework that produces rich, interactive HTML reports from test execution results. Unlike basic test reporters that show pass/fail summaries, Allure provides detailed test categorization, step-by-step execution breakdowns, attachment support for screenshots, logs, and videos, historical trend tracking across builds, and environment metadata. This skill guides AI coding agents through configuring Allure reporters for popular testing frameworks, annotating tests with meaningful metadata, integrating with CI/CD pipelines, and establishing report hosting strategies that give teams comprehensive test visibility.

## Core Principles

1. **Reports Serve Multiple Audiences**: A good test report provides quick pass/fail summaries for managers, detailed failure analysis for developers, trend data for QA leads, and categorized views for test strategists. Allure's multi-view design supports all these personas from a single report.

2. **Annotations Are Documentation**: Test step annotations, severity labels, and feature/story categorization serve as living documentation of test intent. Well-annotated tests in Allure reports communicate what is being tested and why without requiring code access.

3. **Attachments Accelerate Debugging**: Screenshots, DOM snapshots, network logs, and video recordings attached to test steps eliminate the need to reproduce failures locally. Every failure should carry sufficient attachments for diagnosis from the report alone.

4. **History Reveals Patterns**: A single test run is a snapshot. Historical trend data across builds reveals flaky tests that oscillate between pass and fail, degrading tests with gradually increasing failures, and regression patterns that correlate with specific changes.

5. **Categories Group Failures by Root Cause**: Allure categories classify failures by type (product defect, test defect, infrastructure issue) rather than by test name. This grouping accelerates triage by surfacing the most common failure modes across the entire suite.

6. **Environment Context Is Non-Negotiable**: Test results without environment information (browser version, OS, API version, deployment target) are incomplete. The same test can produce different results across environments, and the report must capture this context.

7. **Reports Must Be Accessible**: Test reports that exist only on a developer's local machine provide no team value. Reports must be published to a shared location where all stakeholders can access them without technical setup.

## Project Structure

```
project-root/
├── tests/
│   ├── e2e/
│   │   ├── checkout.spec.ts
│   │   ├── search.spec.ts
│   │   └── user-management.spec.ts
│   ├── integration/
│   │   ├── api-orders.test.ts
│   │   └── api-users.test.ts
│   └── fixtures/
│       └── allure-fixture.ts
├── allure-results/                    # Raw test results (JSON + attachments)
│   ├── *-result.json
│   ├── *-container.json
│   └── *-attachment.*
├── allure-report/                     # Generated HTML report
│   ├── index.html
│   ├── data/
│   └── widgets/
├── config/
│   ├── playwright.config.ts
│   ├── jest.config.ts
│   ├── allure-categories.json
│   └── allure-environment.properties
├── scripts/
│   ├── generate-report.sh
│   ├── publish-report.ts
│   └── setup-history.sh
├── .github/
│   └── workflows/
│       └── test-and-report.yml
└── package.json
```

## Allure Reporter Setup for Playwright

### Installation and Configuration

```bash
npm install --save-dev allure-playwright allure-commandline
```

```typescript
// config/playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/e2e',
  timeout: 30000,
  retries: 1,

  reporter: [
    // Console output for local development
    ['list'],

    // Allure reporter for rich reports
    [
      'allure-playwright',
      {
        detail: true,
        outputFolder: 'allure-results',
        suiteTitle: true,

        // Attach environment info
        environmentInfo: {
          'Node Version': process.version,
          OS: process.platform,
          'Test Environment': process.env.TEST_ENV || 'local',
          'Base URL': process.env.BASE_URL || 'http://localhost:3000',
          Browser: 'Chromium',
          'Playwright Version': require('@playwright/test/package.json').version,
        },

        // Categories for failure classification
        categories: [
          {
            name: 'Product Defects',
            matchedStatuses: ['failed'],
            messageRegex: '.*Expected.*to (be|have|contain).*',
          },
          {
            name: 'Test Infrastructure',
            matchedStatuses: ['broken'],
            messageRegex: '.*timeout|ECONNREFUSED|net::ERR.*',
          },
          {
            name: 'Flaky Tests',
            matchedStatuses: ['failed'],
            messageRegex: '.*flaky|intermittent.*',
            traceRegex: '.*retry.*',
          },
        ],
      },
    ],
  ],

  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'retain-on-failure',
  },

  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
  ],
});
```

### Custom Step Annotations in Playwright

```typescript
// tests/e2e/checkout.spec.ts
import { test, expect } from '@playwright/test';
import { allure } from 'allure-playwright';

test.describe('Checkout Flow', () => {
  test.beforeEach(async ({ page }) => {
    await allure.epic('E-Commerce');
    await allure.feature('Checkout');
    await allure.owner('team-payments');
  });

  test('should complete purchase with credit card', async ({ page }) => {
    await allure.severity('critical');
    await allure.story('Credit Card Payment');
    await allure.tag('smoke');
    await allure.tag('payments');

    // Add link to test case management system
    await allure.link('https://jira.example.com/browse/QA-100', 'Test Case', 'tms');
    await allure.issue('BUG-456', 'https://jira.example.com/browse/BUG-456');

    // Step 1: Add item to cart
    await allure.step('Add product to cart', async () => {
      await page.goto('/products/1');
      await page.click('[data-testid="add-to-cart"]');
      await expect(page.locator('[data-testid="cart-count"]')).toHaveText('1');

      // Attach product page screenshot
      const screenshot = await page.screenshot();
      await allure.attachment('Product Page', screenshot, 'image/png');
    });

    // Step 2: Navigate to checkout
    await allure.step('Navigate to checkout page', async () => {
      await page.click('[data-testid="cart-icon"]');
      await page.click('[data-testid="proceed-to-checkout"]');
      await expect(page).toHaveURL('/checkout');
    });

    // Step 3: Fill payment details
    await allure.step('Enter payment information', async () => {
      await allure.step('Fill card number', async () => {
        await page.fill('[data-testid="card-number"]', '4242424242424242');
      });
      await allure.step('Fill expiry date', async () => {
        await page.fill('[data-testid="card-expiry"]', '12/28');
      });
      await allure.step('Fill CVV', async () => {
        await page.fill('[data-testid="card-cvv"]', '123');
      });
    });

    // Step 4: Submit order
    await allure.step('Place order', async () => {
      await page.click('[data-testid="place-order"]');
      await expect(page.locator('[data-testid="order-confirmation"]')).toBeVisible({
        timeout: 15000,
      });

      // Capture confirmation details
      const confirmationText = await page
        .locator('[data-testid="order-confirmation"]')
        .textContent();
      await allure.attachment('Order Confirmation', confirmationText || '', 'text/plain');
    });

    // Step 5: Verify order details
    await allure.step('Verify order details', async () => {
      const orderId = await page.locator('[data-testid="order-id"]').textContent();
      expect(orderId).toBeTruthy();

      // Attach full page screenshot
      const fullPage = await page.screenshot({ fullPage: true });
      await allure.attachment('Confirmation Page', fullPage, 'image/png');

      // Attach API response for debugging
      const orderDetails = await page.evaluate(async (id) => {
        const res = await fetch(`/api/orders/${id}`);
        return res.json();
      }, orderId);
      await allure.attachment(
        'Order API Response',
        JSON.stringify(orderDetails, null, 2),
        'application/json'
      );
    });
  });

  test('should show validation errors for invalid card', async ({ page }) => {
    await allure.severity('normal');
    await allure.story('Payment Validation');

    await page.goto('/checkout');

    await allure.step('Submit empty payment form', async () => {
      await page.click('[data-testid="place-order"]');
    });

    await allure.step('Verify validation errors displayed', async () => {
      await expect(page.locator('[data-testid="card-error"]')).toHaveText(
        'Card number is required'
      );
      await expect(page.locator('[data-testid="expiry-error"]')).toHaveText(
        'Expiry date is required'
      );
    });
  });
});
```

### Custom Allure Fixture for Reusable Annotations

```typescript
// tests/fixtures/allure-fixture.ts
import { test as base } from '@playwright/test';
import { allure } from 'allure-playwright';

interface AllureOptions {
  epic: string;
  feature: string;
  severity: 'blocker' | 'critical' | 'normal' | 'minor' | 'trivial';
}

export const test = base.extend<{ allureConfig: AllureOptions }>({
  allureConfig: [
    async ({}, use, testInfo) => {
      // Auto-derive categorization from test file path
      const filePath = testInfo.file;
      let epic = 'General';
      let feature = 'Uncategorized';

      if (filePath.includes('checkout')) {
        epic = 'E-Commerce';
        feature = 'Checkout';
      } else if (filePath.includes('search')) {
        epic = 'Discovery';
        feature = 'Search';
      } else if (filePath.includes('user')) {
        epic = 'Account';
        feature = 'User Management';
      }

      await allure.epic(epic);
      await allure.feature(feature);

      // Attach test metadata
      await allure.parameter('Browser', testInfo.project.name);
      await allure.parameter('Retry Attempt', String(testInfo.retry));

      const options: AllureOptions = {
        epic,
        feature,
        severity: 'normal',
      };

      await use(options);

      // After test: attach trace on failure
      if (testInfo.status !== 'passed') {
        const tracePath = testInfo.outputPath('trace.zip');
        try {
          const traceBuffer = require('fs').readFileSync(tracePath);
          await allure.attachment('Playwright Trace', traceBuffer, 'application/zip');
        } catch {
          // Trace not available
        }
      }
    },
    { auto: true },
  ],
});

export { expect } from '@playwright/test';
```

## Allure Reporter Setup for Jest

```bash
npm install --save-dev allure-jest allure-js-commons allure-commandline
```

```typescript
// config/jest.config.ts
import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'allure-jest/node',
  testEnvironmentOptions: {
    resultsDir: 'allure-results',
    environmentInfo: {
      'Node Version': process.version,
      OS: process.platform,
      'Test Environment': process.env.TEST_ENV || 'local',
    },
  },
  reporters: ['default'],
};

export default config;
```

### Jest Test with Allure Annotations

```typescript
// tests/integration/api-orders.test.ts
import { allure } from 'allure-js-commons';

describe('Orders API', () => {
  beforeAll(async () => {
    await allure.epic('API');
    await allure.feature('Orders');
  });

  it('should create a new order', async () => {
    await allure.severity('critical');
    await allure.story('Order Creation');
    await allure.owner('team-orders');
    await allure.tag('api');
    await allure.tag('smoke');

    await allure.step('Prepare order payload', async () => {
      const payload = {
        customerId: 42,
        items: [{ productId: 'prod-001', quantity: 2 }],
        shippingAddress: {
          street: '123 Main St',
          city: 'Portland',
          state: 'OR',
          zip: '97201',
        },
      };
      await allure.attachment(
        'Request Payload',
        JSON.stringify(payload, null, 2),
        'application/json'
      );

      await allure.step('Send POST request', async () => {
        const response = await fetch('http://localhost:3000/api/orders', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(payload),
        });

        expect(response.status).toBe(201);

        const body = await response.json();
        await allure.attachment(
          'Response Body',
          JSON.stringify(body, null, 2),
          'application/json'
        );

        await allure.step('Verify order ID assigned', async () => {
          expect(body.id).toBeDefined();
          expect(typeof body.id).toBe('string');
        });

        await allure.step('Verify order total calculated', async () => {
          expect(body.total).toBeGreaterThan(0);
        });
      });
    });
  });

  it('should return 400 for invalid order', async () => {
    await allure.severity('normal');
    await allure.story('Order Validation');

    await allure.step('Send order without required fields', async () => {
      const response = await fetch('http://localhost:3000/api/orders', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({}),
      });

      expect(response.status).toBe(400);

      const body = await response.json();
      await allure.attachment(
        'Error Response',
        JSON.stringify(body, null, 2),
        'application/json'
      );

      await allure.step('Verify error details', async () => {
        expect(body.errors).toBeDefined();
        expect(body.errors.length).toBeGreaterThan(0);
      });
    });
  });
});
```

## Allure Reporter Setup for pytest

```bash
pip install allure-pytest
```

```ini
# pytest.ini
[pytest]
addopts = --alluredir=allure-results --clean-alluredir
```

### Python Test with Allure Annotations

```python
# tests/test_user_api.py
import allure
import pytest
import requests
import json

BASE_URL = "http://localhost:8000"


@allure.epic("API")
@allure.feature("User Management")
class TestUserAPI:

    @allure.severity(allure.severity_level.CRITICAL)
    @allure.story("User Registration")
    @allure.title("Register a new user with valid data")
    @allure.tag("api", "smoke", "registration")
    @allure.link("https://jira.example.com/browse/QA-200", name="Test Case")
    def test_register_user(self):
        payload = {
            "name": "Jane Doe",
            "email": "jane@example.com",
            "password": "SecureP@ss123",
        }

        with allure.step("Prepare registration payload"):
            allure.attach(
                json.dumps(payload, indent=2),
                name="Request Payload",
                attachment_type=allure.attachment_type.JSON,
            )

        with allure.step("Send POST /api/users/register"):
            response = requests.post(
                f"{BASE_URL}/api/users/register", json=payload
            )

            allure.attach(
                json.dumps(response.json(), indent=2),
                name="Response Body",
                attachment_type=allure.attachment_type.JSON,
            )

            allure.attach(
                str(response.status_code),
                name="Status Code",
                attachment_type=allure.attachment_type.TEXT,
            )

        with allure.step("Verify registration succeeded"):
            assert response.status_code == 201
            body = response.json()
            assert "id" in body
            assert body["email"] == "jane@example.com"

        with allure.step("Verify user can authenticate"):
            login_response = requests.post(
                f"{BASE_URL}/api/auth/login",
                json={
                    "email": "jane@example.com",
                    "password": "SecureP@ss123",
                },
            )
            assert login_response.status_code == 200
            assert "token" in login_response.json()

    @allure.severity(allure.severity_level.NORMAL)
    @allure.story("User Registration")
    @allure.title("Reject registration with duplicate email")
    def test_register_duplicate_email(self):
        payload = {
            "name": "Duplicate",
            "email": "existing@example.com",
            "password": "Pass123!",
        }

        with allure.step("Register user with existing email"):
            response = requests.post(
                f"{BASE_URL}/api/users/register", json=payload
            )

        with allure.step("Verify conflict error returned"):
            assert response.status_code == 409
            assert "already exists" in response.json()["message"]

    @allure.severity(allure.severity_level.CRITICAL)
    @allure.story("User Profile")
    @allure.title("Retrieve user profile with valid token")
    def test_get_user_profile(self, auth_token):
        with allure.step("Send GET /api/users/me with auth token"):
            response = requests.get(
                f"{BASE_URL}/api/users/me",
                headers={"Authorization": f"Bearer {auth_token}"},
            )

            allure.attach(
                json.dumps(response.json(), indent=2),
                name="Profile Response",
                attachment_type=allure.attachment_type.JSON,
            )

        with allure.step("Verify profile data"):
            assert response.status_code == 200
            profile = response.json()
            assert "id" in profile
            assert "name" in profile
            assert "email" in profile
            assert "password" not in profile  # Verify password not exposed


@pytest.fixture
def auth_token():
    """Fixture that provides an authenticated user token."""
    with allure.step("Authenticate test user"):
        response = requests.post(
            f"{BASE_URL}/api/auth/login",
            json={
                "email": "test@example.com",
                "password": "TestPass123!",
            },
        )
        return response.json()["token"]
```

## Environment Info Configuration

```properties
# config/allure-environment.properties
# This file is copied to allure-results/ before report generation
Browser=Chromium 120
OS=Ubuntu 22.04
Node.Version=20.10.0
Test.Environment=staging
API.Base.URL=https://staging-api.example.com
App.Version=2.5.0
Build.Number=${BUILD_NUMBER}
Git.Commit=${GIT_COMMIT}
Git.Branch=${GIT_BRANCH}
Deployment.Region=us-east-1
Database=PostgreSQL 16
```

### Dynamic Environment Properties Script

```typescript
// scripts/generate-environment.ts
import * as fs from 'fs';
import { execSync } from 'child_process';

const gitCommit = execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim();
const gitBranch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();

const properties = [
  `Browser=${process.env.BROWSER || 'Chromium'}`,
  `OS=${process.platform} ${process.arch}`,
  `Node.Version=${process.version}`,
  `Test.Environment=${process.env.TEST_ENV || 'local'}`,
  `Base.URL=${process.env.BASE_URL || 'http://localhost:3000'}`,
  `Git.Commit=${gitCommit}`,
  `Git.Branch=${gitBranch}`,
  `Build.Number=${process.env.BUILD_NUMBER || 'local'}`,
  `Timestamp=${new Date().toISOString()}`,
].join('\n');

fs.writeFileSync('allure-results/environment.properties', properties);
console.log('Environmen
