Flutter web apps render their UI to a canvas element, which makes traditional DOM-based testing approaches ineffective. You cannot:
- ❌ Use CSS selectors to find buttons, text, or other UI elements
- ❌ Directly interact with Flutter widgets via Playwright
- ❌ Inspect the visual content rendered on the canvas
Flutter creates DOM elements for accessibility through the Semantics widget. These elements are specifically designed for screen readers but also serve as a reliable testing interface.
- Flutter Semantics Widget → Creates ARIA labels in the DOM
- Screen readers → Read these ARIA labels
- Playwright tests → Can also read these ARIA labels
Flutter Code:
Semantics(
label: 'Drinks tab, browse all festival drinks',
child: const Icon(Icons.local_drink_outlined),
)Generated DOM (simplified):
<flt-semantics aria-label="Drinks tab, browse all festival drinks">
<!-- Flutter renders icon to canvas -->
</flt-semantics>Playwright Test:
const drinksTabLabel = page.locator('[aria-label*="Drinks tab"]');
await expect(drinksTabLabel.first()).toBeAttached();✅ Page loads successfully - Check for Flutter embedder elements
✅ URL routing - Verify URLs change correctly during navigation
✅ Browser history - Test back/forward button functionality
✅ Console errors - Monitor for JavaScript errors
✅ Network requests - Verify API calls are made
✅ Screen verification - Use ARIA labels to confirm which screen is displayed
✅ Accessibility - Ensure proper ARIA labels exist for screen readers
❌ Visual appearance - Colors, fonts, layout (use visual regression testing or Flutter integration tests) ❌ Canvas interactions - Clicking specific points on the canvas ❌ Gesture detection - Swipes, drags, pinch-to-zoom ❌ Text content - Reading text rendered on canvas (unless it has ARIA labels)
Always wrap important UI elements with Semantics widgets:
// Good
Semantics(
label: 'View source code on GitHub',
hint: 'Double tap to open GitHub repository in browser',
button: true,
child: IconButton(
icon: Icon(Icons.code),
onPressed: _openGitHub,
),
)
// Bad - no Semantics, cannot be tested or used by screen readers
IconButton(
icon: Icon(Icons.code),
onPressed: _openGitHub,
)Make labels unique enough to identify specific screens:
// Good - unique to About screen
Semantics(
label: 'View source code on GitHub',
// ...
)
// Bad - too generic, could be on any screen
Semantics(
label: 'Button',
// ...
)For go_router navigation tests, focus on:
test('should navigate to about screen', async ({ page }) => {
await page.goto('http://localhost:8080/about');
await waitForPageReady(page);
// 1. Verify URL changed
expect(page.url()).toBe('http://localhost:8080/about');
// 2. Verify correct screen via unique ARIA label
const aboutLabel = page.locator('[aria-label*="View source code on GitHub"]');
await expect(aboutLabel.first()).toBeAttached();
// 3. Verify no console errors
// (setup error listeners before navigation)
});Don't try to test complex user interactions in E2E tests. Use Flutter integration tests for those:
// This belongs in Flutter integration tests, not Playwright:
testWidgets('tapping favorite button adds drink to favorites', (tester) async {
await tester.pumpWidget(MyApp());
await tester.tap(find.byIcon(Icons.favorite_border));
await tester.pump();
expect(find.byIcon(Icons.favorite), findsOneWidget);
});test-e2e/
├── app.spec.ts # Basic app loading tests
├── routing.spec.ts # Navigation/routing tests (uses ARIA labels)
└── network.spec.ts # API request tests (optional)
- Accessibility First - Tests ensure the app is usable by screen readers
- Stable Selectors - ARIA labels are less likely to change than internal Flutter DOM structure
- Meaningful Tests - Verifies actual user-facing behavior (navigation, errors)
- Dual Purpose - Same Semantics widgets benefit both testing and accessibility
- Fast Feedback - Catch routing issues in CI before manual testing
This app has 53+ Semantics widgets across all screens and widgets. See ../code/accessibility.md for the full inventory. These provide both accessibility and testability.