Skip to content

Commit 7ee8b52

Browse files
lexe-agentMaxFangX
authored andcommitted
app+just+: Automate documentation screenshots
Implements automated screenshot generation from the Flutter app's design mode using integration tests. The system navigates directly to design mode components and captures screenshots. - Added Component.screenshot field to embed paths in component definitions - Integration test filters components with non-null screenshot paths - flutter drive saves screenshots to lexe-docs/docs.lexe.app./images/ - Supports iOS Simulator (macOS) and Android Emulator (Linux) - Uses oxipng --opt 2 for fast PNG optimization (25-43% compression) Usage: just docs-update-screenshots
1 parent cec9143 commit 7ee8b52

File tree

6 files changed

+1251
-1037
lines changed

6 files changed

+1251
-1037
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// ignore_for_file: avoid_print
2+
3+
import 'dart:async' show unawaited;
4+
5+
import 'package:app_rs_dart/app_rs_dart.dart' as app_rs_dart;
6+
import 'package:flutter/material.dart';
7+
import 'package:flutter_test/flutter_test.dart';
8+
import 'package:integration_test/integration_test.dart';
9+
import 'package:lexeapp/cfg.dart' as cfg;
10+
import 'package:lexeapp/date_format.dart' as date_format;
11+
import 'package:lexeapp/design_mode/main.dart' show LexeDesignPage;
12+
import 'package:lexeapp/logger.dart';
13+
import 'package:lexeapp/style.dart' show LxColors, LxTheme;
14+
import 'package:lexeapp/uri_events.dart';
15+
16+
void main() {
17+
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
18+
19+
setUpAll(() async {
20+
// Initialize Rust FFI
21+
await app_rs_dart.init();
22+
23+
// Initialize date formatting
24+
await date_format.initializeDateLocaleData();
25+
26+
// Initialize logger
27+
Logger.init();
28+
});
29+
30+
testWidgets('Take screenshots for documentation', (
31+
WidgetTester tester,
32+
) async {
33+
// Build config for design mode
34+
final userAgent = await cfg.UserAgent.fromPlatform();
35+
final config = await cfg.buildTest(userAgent: userAgent);
36+
37+
final uriEvents = await UriEvents.prod();
38+
39+
// Build the design mode app
40+
await tester.pumpWidget(
41+
MaterialApp(
42+
title: "Lexe App - Design Mode",
43+
color: LxColors.background,
44+
themeMode: ThemeMode.light,
45+
theme: LxTheme.light(),
46+
darkTheme: null,
47+
debugShowCheckedModeBanner: false,
48+
home: LexeDesignPage(config: config, uriEvents: uriEvents),
49+
),
50+
);
51+
await tester.pumpAndSettle();
52+
53+
// Wait for all widgets to render
54+
await tester.pump(const Duration(seconds: 2));
55+
await tester.pumpAndSettle();
56+
57+
// Convert the Flutter surface to an image (required on Android)
58+
await binding.convertFlutterSurfaceToImage();
59+
await tester.pump();
60+
61+
// Get the design page state to access components directly
62+
final designPageFinder = find.byType(LexeDesignPage);
63+
if (designPageFinder.evaluate().isEmpty) {
64+
throw Exception('LexeDesignPage not found in widget tree');
65+
}
66+
67+
final designPageElement = designPageFinder.evaluate().first;
68+
final designPageState = designPageElement as StatefulElement;
69+
final state = designPageState.state as dynamic;
70+
71+
// Build the components list
72+
final components = state.buildComponentsList(designPageElement) as List;
73+
74+
// Filter to only components with a screenshot path
75+
final screenshotComponents = components.where((component) {
76+
final comp = component as dynamic;
77+
return comp.screenshot != null;
78+
}).toList();
79+
80+
print('Found ${screenshotComponents.length} components to screenshot');
81+
82+
for (final component in screenshotComponents) {
83+
final comp = component as dynamic;
84+
final componentName = comp.title as String;
85+
final outputPath = comp.screenshot as String;
86+
final builder = comp.builder as Widget Function(BuildContext);
87+
88+
print('Taking screenshot for $componentName -> $outputPath');
89+
90+
// Navigate to the component page by pushing a route
91+
final navigator = Navigator.of(designPageElement);
92+
unawaited(navigator.push(MaterialPageRoute(builder: builder)));
93+
94+
// Wait for navigation animation to start
95+
await tester.pump(const Duration(milliseconds: 100));
96+
97+
// Verify we navigated (for debugging)
98+
if (!navigator.canPop()) {
99+
print(' ⚠ Navigation failed for $componentName');
100+
continue;
101+
}
102+
103+
// Wait for all animations and async operations to complete
104+
// Use individual pump calls to avoid timeout on continuous animations
105+
for (int i = 0; i < 30; i++) {
106+
await tester.pump(const Duration(milliseconds: 100));
107+
}
108+
109+
// Take the screenshot
110+
await binding.takeScreenshot(outputPath);
111+
112+
// Navigate back
113+
navigator.pop();
114+
115+
// Wait for back navigation to complete
116+
for (int i = 0; i < 15; i++) {
117+
await tester.pump(const Duration(milliseconds: 100));
118+
}
119+
120+
print(' ✓ Screenshot saved');
121+
}
122+
123+
print('All screenshots completed!');
124+
});
125+
}

0 commit comments

Comments
 (0)