Skip to content

Commit e9bc29a

Browse files
authored
test: add local E2E scenario runner (#155)
1 parent ce004ea commit e9bc29a

7 files changed

Lines changed: 839 additions & 3 deletions

File tree

AGENTS.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,27 @@ dart analyze --fatal-infos # Optional stricter local check for info
1818
dart test # Run all platform-compatible tests (VM or browser)
1919
dart test -p vm # Run only VM (native) tests
2020
dart test -p chrome # Run only Chrome (web) tests
21-
dart test --run-skipped -t local-only # Run local-only E2E scenarios
21+
dart run tool/testing/run_local_e2e.dart --list # Discover local-only E2E scenarios
22+
dart test --run-skipped -t local-only # Run root local-only Dart E2E scenarios
2223
dart test test/path/to/test_file.dart # Run a single test file
2324
dart test -p vm --coverage=coverage # Run VM tests and collect coverage
2425
dart pub global run coverage:format_coverage --lcov --in=coverage/test --out=coverage/lcov.info --report-on=lib --check-ignore
2526
dart run tool/testing/check_lcov_threshold.dart coverage/lcov.info 70
2627
```
2728

29+
### Local-Only E2E Runner
30+
Use the scenario runner as the discovery entry point for heavyweight manual checks:
31+
32+
```bash
33+
dart run tool/testing/run_local_e2e.dart --list
34+
dart run tool/testing/run_local_e2e.dart --scenario <name> --dry-run
35+
dart run tool/testing/run_local_e2e.dart --scenario chat-app-model-cache --device macos
36+
```
37+
38+
Heavy scenarios remain skipped by default and out of CI unless explicitly requested.
39+
Use `--dry-run` before Web smoke scenarios to see the required build/server/
40+
Playwright steps and model URL defaults.
41+
2842
### Local Chat App Web E2E
2943
Use the real chat app path for WebGPU bridge validation after bridge/runtime
3044
updates. This catches issues that direct bridge probes miss.

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## Unreleased
2+
3+
* **Testing**:
4+
* Added `tool/testing/run_local_e2e.dart` as a discovery and orchestration
5+
entry point for heavyweight local-only Dart E2E, Flutter device, and
6+
Web/Playwright smoke scenarios.
7+
* Documented that real-model/device/WebGPU scenarios remain skipped from
8+
default CI and should be opted into explicitly with `--list` and
9+
`--dry-run` first.
10+
111
## 0.6.14
212

313
* **WebGPU bridge assets**:

CONTRIBUTING.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,13 @@ Running `dart test` will run VM and Chrome-compatible tests. Tests tagged
125125
# Run default suite (VM + Chrome-compatible tests)
126126
dart test
127127
128-
# Run local-only E2E tests
128+
# Discover heavyweight local-only E2E scenarios
129+
dart run tool/testing/run_local_e2e.dart --list
130+
131+
# Preview the commands for one scenario without running it
132+
dart run tool/testing/run_local_e2e.dart --scenario chat-app-model-cache --device macos --dry-run
133+
134+
# Run root local-only Dart E2E tests directly
129135
dart test --run-skipped -t local-only
130136
```
131137

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,13 +728,16 @@ Check out our [LoRA Training Notebook](https://github.com/leehack/llamadart/blob
728728
This project maintains a high standard of quality with **>=70% line coverage on maintainable `lib/` code** (auto-generated files marked with `// coverage:ignore-file` are excluded).
729729

730730
- **Multi-Platform Testing**: `dart test` runs VM and Chrome-compatible suites automatically.
731-
- **Local-Only Scenarios**: Slow E2E tests are tagged `local-only` and skipped by default.
731+
- **Local-Only Scenarios**: Slow E2E tests are tagged `local-only` and skipped by default; use `tool/testing/run_local_e2e.dart` to discover the root Dart, Flutter device, and Web smoke commands.
732732
- **CI/CD**: Automatic analysis, linting, and cross-platform test execution on every PR.
733733

734734
```bash
735735
# Run default test suite (VM + Chrome-compatible tests)
736736
dart test
737737
738+
# Discover local-only E2E scenarios
739+
dart run tool/testing/run_local_e2e.dart --list
740+
738741
# Run local-only E2E scenarios
739742
dart test --run-skipped -t local-only
740743
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
@TestOn('vm')
2+
library;
3+
4+
import 'dart:io';
5+
6+
import 'package:test/test.dart';
7+
8+
import '../../../tool/testing/run_local_e2e.dart';
9+
10+
void main() {
11+
group('run_local_e2e', () {
12+
test('lists local-only Dart, Flutter, and Web smoke scenarios', () async {
13+
final result = await runLocalE2e(const ['--list'], projectRoot: '/repo');
14+
15+
expect(result.exitCode, 0);
16+
expect(result.stdout, contains('root-template-e2e'));
17+
expect(result.stdout, contains('root-native-tool-e2e'));
18+
expect(result.stdout, contains('qwen35-multimodal-macos-repro'));
19+
expect(result.stdout, contains('webgpu-multimodal-regression'));
20+
expect(result.stdout, contains('chat-app-model-cache'));
21+
expect(result.stdout, contains('chat-app-web-real-model-smoke'));
22+
expect(result.stdout, contains('bridge-smoke'));
23+
expect(result.stdout, contains('Dart local-only'));
24+
expect(result.stdout, contains('Flutter device'));
25+
expect(result.stdout, contains('Web smoke'));
26+
});
27+
28+
test(
29+
'dry-runs a Flutter device scenario with the requested device',
30+
() async {
31+
final result = await runLocalE2e(const [
32+
'--scenario',
33+
'chat-app-model-cache',
34+
'--device',
35+
'macos',
36+
'--dry-run',
37+
], projectRoot: '/repo');
38+
39+
expect(result.exitCode, 0);
40+
expect(result.stdout, contains('chat-app-model-cache'));
41+
expect(
42+
result.stdout,
43+
contains(
44+
'cd /repo/example/chat_app && flutter test --run-skipped '
45+
'-t local-only integration_test/model_cache_mmproj_e2e_test.dart '
46+
'-d macos',
47+
),
48+
);
49+
},
50+
);
51+
52+
test(
53+
'dry-runs Web real-model smoke with build, serve, and Playwright steps',
54+
() async {
55+
final result = await runLocalE2e(const [
56+
'--scenario',
57+
'chat-app-web-real-model-smoke',
58+
'--model-url',
59+
'http://127.0.0.1:7358/models/tiny.gguf',
60+
'--expect',
61+
'ok',
62+
'--python',
63+
'/custom/python',
64+
'--dry-run',
65+
], projectRoot: '/repo');
66+
67+
expect(result.exitCode, 0);
68+
expect(result.stdout, contains('flutter build web'));
69+
expect(result.stdout, contains('serve_static_with_headers.py'));
70+
expect(
71+
result.stdout,
72+
contains('playwright_chat_app_real_model_smoke.py'),
73+
);
74+
expect(
75+
result.stdout,
76+
contains(
77+
'/custom/python tool/testing/playwright_chat_app_real_model_smoke.py',
78+
),
79+
);
80+
expect(
81+
result.stdout,
82+
contains('--model-url http://127.0.0.1:7358/models/tiny.gguf'),
83+
);
84+
expect(result.stdout, contains('--expect ok'));
85+
},
86+
);
87+
88+
test('dry-runs WebGPU regression with forwarded runner options', () async {
89+
final result = await runLocalE2e(const [
90+
'--scenario',
91+
'webgpu-multimodal-regression',
92+
'--port',
93+
'9123',
94+
'--python',
95+
'/custom/python',
96+
'--skip-build',
97+
'--dry-run',
98+
], projectRoot: '/repo');
99+
100+
expect(result.exitCode, 0);
101+
expect(result.stdout, contains('PLAYWRIGHT_GATE_PORT=9123'));
102+
expect(result.stdout, contains('PLAYWRIGHT_PYTHON=/custom/python'));
103+
expect(result.stdout, contains('LLAMADART_SKIP_WEB_BUILD=1'));
104+
expect(
105+
result.stdout,
106+
contains('bash tool/testing/run_webgpu_multimodal_regression_gate.sh'),
107+
);
108+
});
109+
110+
test('reports unknown scenarios without executing anything', () async {
111+
final result = await runLocalE2e(const [
112+
'--scenario',
113+
'does-not-exist',
114+
'--dry-run',
115+
], projectRoot: '/repo');
116+
117+
expect(result.exitCode, isNot(0));
118+
expect(
119+
result.stderr,
120+
contains('Unknown local E2E scenario: does-not-exist'),
121+
);
122+
expect(result.stderr, contains('Use --list'));
123+
});
124+
125+
test('reports port conflicts before starting Web smoke servers', () async {
126+
final socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
127+
addTearDown(socket.close);
128+
129+
final result = await runLocalE2e([
130+
'--scenario',
131+
'bridge-smoke',
132+
'--port',
133+
'${socket.port}',
134+
], projectRoot: '/repo');
135+
136+
expect(result.exitCode, isNot(0));
137+
expect(
138+
result.stdout,
139+
contains('Running local E2E scenario: bridge-smoke'),
140+
);
141+
expect(result.stderr, contains('Port ${socket.port} is already in use'));
142+
});
143+
144+
test('reports background server startup failures', () async {
145+
final socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
146+
final port = socket.port;
147+
await socket.close();
148+
149+
final tempDir = await Directory.systemTemp.createTemp(
150+
'run_local_e2e_test_',
151+
);
152+
addTearDown(() => tempDir.delete(recursive: true));
153+
await Directory(
154+
'${tempDir.path}/example/chat_app/web',
155+
).create(recursive: true);
156+
157+
final result = await runLocalE2e([
158+
'--scenario',
159+
'bridge-smoke',
160+
'--python',
161+
'dart',
162+
'--port',
163+
'$port',
164+
], projectRoot: tempDir.path);
165+
166+
expect(result.exitCode, isNot(0));
167+
expect(result.stderr, contains('Background server exited'));
168+
});
169+
});
170+
}

0 commit comments

Comments
 (0)