Getting started with Dart and Flutter
Generate beautiful HTML reports using Allure Report and your Dart and Flutter tests.
INFO
The main user-facing packages are allure_dart_test for package:test suites and 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 instead. All three packages live in the official allure-dart repository.
Setting up
1. Prepare your project
Make sure a recent Dart or Flutter toolchain is installed.
The official
allure-dartpackages require Dart 3.8 or newer. The Flutter adapter additionally requires Flutter 3.24 or newer.Open a terminal and go to the project directory. For example:
bashcd /home/user/myprojectInstall Allure Report, following the installation guide.
Add the integration as a dev dependency:
bashdart pub add --dev allure_dart_testbashflutter pub add --dev allure_flutter_testEnable 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.dartpackage:allure_dart_test/test.dartpackage:flutter_test/flutter_test.dartpackage:allure_flutter_test/flutter_test.dartpackage:integration_test/integration_test.dartpackage:allure_flutter_test/integration_test.dartThe 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:dartimport '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)); }); }); }dartimport '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); }); }dartimport '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 ofmain(); in Flutter tests, call it fromtest/flutter_test_config.dartso it applies to the whole suite:dartimport '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)); }); }); }dartimport '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/tearDowncallbacks as Allure fixtures, converts a declaredskip:into a runtime self-skip so it's still reported (see Skip tests), and can skip tests excluded by a test plan.
2. Run tests
Run your tests the same way as usual:
dart testflutter testBy 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:
ALLURE_RESULTS_DIR=build/allure-results dart testIf 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:
allure generate ./allure-results --output ./allure-report --clean
allure open ./allure-reportIf 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,
- organize tests into behavior-based and suite-based hierarchies,
- split the execution into nested steps,
- describe parameters and attachments,
- report setup and teardown fixtures,
- skip tests without losing them from the report,
- run only selected tests through a test plan file.
Add metadata
Inside a test body, use the top-level runtime functions to enrich the test result:
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:
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:
test(
'checkout works',
() async {
// ...
},
tags: ['smoke'],
);Organize tests
Allure supports both behavior-based and suite-based hierarchies. For example:
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 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:
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():
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:
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:
installAllure(
autoScreenshotOnFailure: true,
autoAttachGoldenDiff: true,
);autoScreenshotOnFailurecaptures a screenshot of the widget tree when a test fails and attaches it asscreenshot-on-failure.autoAttachGoldenDiffattaches the actual rendered image asgolden-actual(plus, best-effort, the master and test images) whenever amatchesGoldenFilecomparison 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 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:
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:
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:
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:
{
"version": "1.0",
"tests": [{ "id": "AUTH-1" }, { "selector": "test/auth_test.dart#authentication#login works" }]
}Then run the tests with:
ALLURE_TESTPLAN_PATH=./testplan.json dart testEntries 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:
dart pub add allure_dart_commonsAt that level, you create a lifecycle, start a test case, and stop and write it when execution ends:
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 for the runtime and lifecycle APIs, or Configuration for the supported environment variables and the allure-dart.yaml config file.