---
title: Dart and Flutter
description: Learn how to integrate Allure with Dart and Flutter tests by using the official allure_dart_test and allure_flutter_test packages to generate rich, interactive test reports.
---

# Getting started with Dart and Flutter

[![allure_dart_test pub.dev latest version](https://img.shields.io/pub/v/allure_dart_test?style=flat&label=allure_dart_test "allure_dart_test pub.dev latest version")](https://pub.dev/packages/allure_dart_test)

[![allure_flutter_test pub.dev latest version](https://img.shields.io/pub/v/allure_flutter_test?style=flat&label=allure_flutter_test "allure_flutter_test pub.dev latest version")](https://pub.dev/packages/allure_flutter_test)

Generate beautiful HTML reports using [Allure Report](https://allurereport.org/docs/) and your Dart and Flutter tests.

Info:
The main user-facing packages are [`allure_dart_test`](https://pub.dev/packages/allure_dart_test) for `package:test` suites and [`allure_flutter_test`](https://pub.dev/packages/allure_flutter_test) for Flutter widget tests and host-run `integration_test` suites. If you are building your own integration for a custom runner or framework, use [`allure_dart_commons`](https://pub.dev/packages/allure_dart_commons) instead. All three packages live in the official [`allure-dart`](https://github.com/allure-framework/allure-dart) repository.

## Setting up

### 1. Prepare your project

1. Make sure a recent Dart or Flutter toolchain is installed.

   The official `allure-dart` packages require Dart 3.8 or newer. The Flutter adapter additionally requires Flutter 3.24 or newer.

1. Open a terminal and go to the project directory. For example:

   ```bash
   cd /home/user/myproject
   ```

1. Install Allure Report, following the [installation guide](/docs/v3/install/).

1. Add the integration as a dev dependency:

   **Dart:**
   ```bash
   dart pub add --dev allure_dart_test
   ```

   **Flutter:**
   ```bash
   flutter pub add --dev allure_flutter_test
   ```

1. Enable the integration in your test files. The simplest way is the drop-in import: replace the test framework import with the matching Allure import, and keep the rest of the file unchanged.

   | Original import                                  | Allure drop-in import                               |
   | ------------------------------------------------ | --------------------------------------------------- |
   | `package:test/test.dart`                         | `package:allure_dart_test/test.dart`                |
   | `package:flutter_test/flutter_test.dart`         | `package:allure_flutter_test/flutter_test.dart`     |
   | `package:integration_test/integration_test.dart` | `package:allure_flutter_test/integration_test.dart` |

   The drop-in libraries re-export the original framework, so `expect`, matchers, and the other APIs keep working. Add a second, aliased import for Allure's runtime API:

   **Dart:**
   ```dart
   import 'package:allure_dart_test/test.dart';
   import 'package:allure_dart_test/allure_dart_test.dart' as allure;

   void main() {
     group('authentication', () {
       test('login works', () async {
         await allure.feature('Authentication');
         await allure.parameter('browser', 'chromium');

         await allure.step('submit credentials', (step) async {
           await step.parameter('user', 'alice');
           await allure.attachment(
             'request',
             '{"user":"alice"}',
             contentType: 'application/json',
             fileExtension: 'json',
           );
         });

         expect(2 + 2, equals(4));
       });
     });
   }
   ```

   **Flutter:**
   ```dart
   import 'package:allure_flutter_test/flutter_test.dart';
   import 'package:allure_flutter_test/allure_flutter_test.dart' as allure;

   void main() {
     testWidgets('renders empty state', (tester) async {
       await allure.feature('Home screen');

       await allure.step('pump widget', (_) async {
         await tester.pumpWidget(const MyApp());
       });

       expect(find.text('No items'), findsOneWidget);
     });
   }
   ```

   **Flutter integration test:**
   ```dart
   import 'package:allure_flutter_test/integration_test.dart';
   import 'package:allure_flutter_test/allure_flutter_test.dart' as allure;

   void main() {
     IntegrationTestWidgetsFlutterBinding.ensureInitialized();

     testWidgets('signs in', (tester) async {
       await allure.story('Sign in');
       expect(find.text('Sign in'), findsOneWidget);
     });
   }
   ```

   Alternatively, if you prefer to keep the original framework imports, install the runtime plugin once with `installAllure()`. In Dart tests, call it at the start of `main()`; in Flutter tests, call it from `test/flutter_test_config.dart` so it applies to the whole suite:

   **Dart:**
   ```dart
   import 'package:allure_dart_test/allure_dart_test.dart';
   import 'package:test/test.dart';

   void main() {
     installAllure();

     test('login works', () async {
       await step('submit credentials', (_) async {
         expect(2 + 2, equals(4));
       });
     });
   }
   ```

   **Flutter — test/flutter_test_config.dart:**
   ```dart
   import 'dart:async';

   import 'package:allure_flutter_test/allure_flutter_test.dart';

   Future<void> testExecutable(FutureOr<void> Function() testMain) async {
     installAllure();
     await testMain();
   }
   ```

   Both styles produce Allure results and support the same runtime API. The drop-in style additionally reports `setUp`/`tearDown` callbacks as Allure fixtures, converts a declared `skip:` into a runtime self-skip so it's still reported (see [Skip tests](#skip-tests)), and can skip tests excluded by a [test plan](#select-tests-via-a-test-plan-file).

### 2. Run tests

Run your tests the same way as usual:

**Dart:**
```bash
dart test
```

**Flutter:**
```bash
flutter test
```

By default, the integration writes the results to an `allure-results` directory in the current working directory.

Warning:
These adapters only work on the Dart VM — the default platform for `dart test` and `flutter test`. On browsers or Node.js (`dart test -p chrome`, `dart test -p node`, `flutter test --platform chrome`), every test fails with a message `Allure cannot write test results on this platform (no filesystem)`, even ones that never call the Allure API, because the adapters always try to write a result file with no filesystem to write it to.

To use a different directory, set `ALLURE_RESULTS_DIR` before the test run:

```bash
ALLURE_RESULTS_DIR=build/allure-results dart test
```

If the results directory already exists, the new files are added to the existing ones, so that a future report will be based on all of them.

### 3. Generate a report

After the test run, generate and open the report with the Allure CLI:

```bash
allure generate ./allure-results --output ./allure-report --clean
allure open ./allure-report
```

If you changed the results directory, use that path in the `allure generate` command.

## Writing tests

The Allure Dart adapters extend plain `dart test` and `flutter test` output with richer reporting features. You can use them to:

- add [descriptions, owners, links, and other metadata](#add-metadata),
- organize tests into [behavior-based and suite-based hierarchies](#organize-tests),
- split the execution into [nested steps](#divide-a-test-into-steps),
- describe [parameters and attachments](#add-parameters-and-attachments),
- report [setup and teardown fixtures](#report-fixtures),
- [skip tests](#skip-tests) without losing them from the report,
- run only selected tests through a [test plan file](#select-tests-via-a-test-plan-file).

### Add metadata

Inside a test body, use the top-level runtime functions to enrich the test result:

```dart
import 'package:allure_dart_test/test.dart';
import 'package:allure_dart_test/allure_dart_test.dart' as allure;

void main() {
  test('login works', () async {
    await allure.displayName('Login works');
    await allure.description('This test verifies login with a username and a password.');
    await allure.owner('John Doe');
    await allure.tag('smoke');
    await allure.severity('critical');
    await allure.allureId('AUTH-1');
    await allure.issue('https://jira.example.com/browse/AUTH-123', name: 'AUTH-123');
    await allure.tms('https://tms.example.com/cases/TMS-456', name: 'TMS-456');
  });
}
```

You can also declare metadata statically, without touching the test body. Inline markers in the test name are parsed and removed from the displayed name:

```dart
test('checkout works @allure.id:123 @allure.label.owner:payments', () async {
  // ...
});
```

Supported markers are `@allure.id:<value>`, `@allure.label.<name>:<value>`, `@allure.link.<type>:<url>`, and `@allure.name:<display name>` (`=` works in place of `:`).

Ordinary `package:test` tags are reported as Allure `tag` labels. Reserved `@allure.*` tags are treated as inline metadata instead and do not become `tag` labels:

```dart
test(
  'checkout works',
  () async {
    // ...
  },
  tags: ['smoke'],
);
```

### Organize tests

Allure supports both behavior-based and suite-based hierarchies. For example:

```dart
test('login works', () async {
  await allure.epic('Web interface');
  await allure.feature('Authentication');
  await allure.story('Login with username and password');

  await allure.parentSuite('UI tests');
  await allure.suite('Authentication');
  await allure.subSuite('Positive scenarios');
});
```

The adapters also derive suite labels from the `group()` hierarchy automatically. Explicit calls to `allure.parentSuite(...)`, `allure.suite(...)`, or `allure.subSuite(...)` override the automatically derived labels. See [Configuration](/docs/dart-configuration/#automatic-labels-and-identifiers) for the full list of automatic labels.

### Divide a test into steps

Wrap parts of the test into named steps with `allure.step()`. Steps nest, record their own timing and status, and can carry parameters:

```dart
test('login works', () async {
  await allure.step('open login page', (_) async {
    // ...
  });

  await allure.step('submit credentials', (step) async {
    await step.parameter('user', 'alice');
    await allure.step('fill the form', (_) async {
      // ...
    });
  });
});
```

If a step body throws, the step is reported as failed or broken, and the error is rethrown, so the test itself fails as usual.

To record an already-completed action without wrapping code, use `allure.logStep()`:

```dart
await allure.logStep('verify audit log');
```

### Add parameters and attachments

Parameters and attachments are stored in the generated Allure results and rendered in the report:

```dart
test('login works', () async {
  await allure.parameter('browser', 'firefox');
  await allure.parameter('password', 'qwerty', mode: allure.AllureParameterMode.masked);

  await allure.attachment(
    'request.json',
    '{"username":"demo","rememberMe":true}',
    contentType: 'application/json',
    fileExtension: 'json',
  );

  await allure.attachmentPath(
    'screenshot',
    'screenshots/failure.png',
    contentType: 'image/png',
  );
});
```

Attachment content can be a `String`, a list of bytes, or any JSON-encodable object. By default, attachments added from inside a test are wrapped in an attachment step, so they keep their logical position between the surrounding steps; pass `wrapInStep: false` to attach them directly to the test or current step instead.

Info:
`testWidgets` bodies run on a fake-async zone, so an attachment call that does real I/O (`attachmentPath`, or `attachment` with stream content) must run inside `tester.runAsync(...)`. Outside `runAsync`, the fake zone never pumps the real I/O callback and the test hangs.

### Capture Flutter failures automatically

`allure_flutter_test`'s `installAllure()` accepts two Flutter-only opt-in flags that install a process-wide hook the first time either is passed as `true`:

```dart
installAllure(
  autoScreenshotOnFailure: true,
  autoAttachGoldenDiff: true,
);
```

- `autoScreenshotOnFailure` captures a screenshot of the widget tree when a test fails and attaches it as `screenshot-on-failure`.
- `autoAttachGoldenDiff` attaches the actual rendered image as `golden-actual` (plus, best-effort, the master and test images) whenever a `matchesGoldenFile` comparison fails.

Both hooks are best-effort — a capture failure never masks the original test failure — and monotonic: once enabled by any `installAllure(...)` call in the process, a later bare `installAllure()` call does not disable them. Enable them once, for example in `test/flutter_test_config.dart`. See [Reference](/docs/dart-reference/#auto-screenshot-and-golden-diff-attachments) for details.

### Report fixtures

When you use the drop-in imports, `setUp`, `tearDown`, `setUpAll`, and `tearDownAll` callbacks are reported as Allure fixtures, shown in the report alongside the tests they prepare:

```dart
import 'package:allure_dart_test/test.dart';
import 'package:allure_dart_test/allure_dart_test.dart' as allure;

void main() {
  group('authentication', () {
    setUp(() async {
      await allure.step('reset database', (_) async {
        // ...
      });
    });

    test('login works', () async {
      // ...
    });
  });
}
```

Steps and attachments created inside a fixture are attached to the fixture result. Labels and links declared inside a `setUp` or `setUpAll` fixture apply to the tests in its scope.

### Skip tests

On the drop-in wrappers, a declaration-time `skip: true` (or a skip message) is converted into a runtime self-skip, so the test is still reported as **skipped**, not left out of the report:

```dart
test('checkout applies a discount', () {
  // ...
}, skip: true);
```

With `installAllure()` and the original framework imports, the same declaration produces **no Allure result at all**, because the framework never runs `setUp` for a declaration-skipped test, and that's where the adapter schedules the result. Call `markTestSkipped(...)` from inside the test body instead if you need a skipped result without switching to the drop-in import:

```dart
test('checkout applies a discount', () {
  markTestSkipped('temporarily disabled');
});
```

Warning:
A drop-in `group(..., skip: true)` is not forwarded to the underlying framework's own group skip — each nested test still runs far enough to self-skip individually. This means `setUp`, `setUpAll`, and `tearDown` fixtures inside a skipped group **may still run**, unlike a stock declaration-time group skip, which never runs the group's fixtures at all. Skip individual tests instead of the group if fixtures must not run.

### Select tests via a test plan file

The adapters support the standard Allure test plan mechanism via the `ALLURE_TESTPLAN_PATH` environment variable.

Create a JSON file such as:

```json
{
  "version": "1.0",
  "tests": [{ "id": "AUTH-1" }, { "selector": "test/auth_test.dart#authentication#login works" }]
}
```

Then run the tests with:

```bash
ALLURE_TESTPLAN_PATH=./testplan.json dart test
```

Entries with `id` match tests that declare an Allure ID, for example via the `@allure.id:AUTH-1` marker. Entries with `selector` match the test's full name: the test file path, the group names, and the test name, joined with `#`.

When the tests are declared through the drop-in `test`, `group`, or `testWidgets` wrappers, tests not included in the plan are declaration-skipped: the body does not run, and no Allure result is written. In suites that use `installAllure()` with the original framework imports, excluded tests still run, but their results are left out of `allure-results`.

## Building a custom integration

If you need to integrate Allure with a custom Dart test runner or framework, use `allure_dart_commons`:

```bash
dart pub add allure_dart_commons
```

At that level, you create a lifecycle, start a test case, and stop and write it when execution ends:

```dart
import 'package:allure_dart_commons/allure_dart_commons.dart';

Future<void> main() async {
  final lifecycle = AllureLifecycle(
    writer: AllureResultsWriter(outputDirectory: 'allure-results'),
  );

  final testUuid = lifecycle.startTest(
    name: 'login works',
    fullName: 'auth/login_test.dart#login works',
  );
  // ... update metadata, add steps, and add attachments ...
  await lifecycle.stopTest(testUuid, status: AllureStatus.passed);
  await lifecycle.writeTest(testUuid);
}
```

See [Reference](/docs/dart-reference/) for the runtime and lifecycle APIs, or [Configuration](/docs/dart-configuration/) for the supported environment variables and the `allure-dart.yaml` config file.
