$npx -y skills add LambdaTest/agent-skills --skill flutter-testing-skillGenerates Flutter widget tests, integration tests, and golden tests in Dart. Supports local execution and TestMu AI cloud for real device testing. Use when user mentions "Flutter", "widget test", "WidgetTester", "testWidgets", "flutter_test", "integration_test". Triggers on: "Flu
| 1 | # Flutter Testing Skill |
| 2 | |
| 3 | You are a senior Flutter developer specializing in testing. |
| 4 | |
| 5 | ## Step 1 — Test Type |
| 6 | |
| 7 | ``` |
| 8 | ├─ "unit test", "business logic", "model test" |
| 9 | │ └─ Unit test: test/ directory, flutter_test package |
| 10 | │ |
| 11 | ├─ "widget test", "component test", "UI test" |
| 12 | │ └─ Widget test: test/ directory, testWidgets() |
| 13 | │ |
| 14 | ├─ "integration test", "E2E", "full app test" |
| 15 | │ └─ Integration test: integration_test/ directory |
| 16 | │ |
| 17 | ├─ "golden test", "snapshot", "visual regression" |
| 18 | │ └─ Golden test: matchesGoldenFile() |
| 19 | │ |
| 20 | └─ Ambiguous? → Widget test (most common) |
| 21 | ``` |
| 22 | |
| 23 | ## Core Patterns — Dart |
| 24 | |
| 25 | ### Widget Test (Most Common) |
| 26 | |
| 27 | ```dart |
| 28 | import 'package:flutter/material.dart'; |
| 29 | import 'package:flutter_test/flutter_test.dart'; |
| 30 | import 'package:my_app/screens/login_screen.dart'; |
| 31 | |
| 32 | void main() { |
| 33 | testWidgets('Login screen shows email and password fields', (WidgetTester tester) async { |
| 34 | await tester.pumpWidget(const MaterialApp(home: LoginScreen())); |
| 35 | |
| 36 | // Verify fields exist |
| 37 | expect(find.byType(TextField), findsNWidgets(2)); |
| 38 | expect(find.text('Email'), findsOneWidget); |
| 39 | expect(find.text('Password'), findsOneWidget); |
| 40 | expect(find.byType(ElevatedButton), findsOneWidget); |
| 41 | }); |
| 42 | |
| 43 | testWidgets('Login with valid credentials navigates to dashboard', (WidgetTester tester) async { |
| 44 | await tester.pumpWidget(const MaterialApp(home: LoginScreen())); |
| 45 | |
| 46 | // Enter credentials |
| 47 | await tester.enterText(find.byKey(const Key('emailField')), 'user@test.com'); |
| 48 | await tester.enterText(find.byKey(const Key('passwordField')), 'password123'); |
| 49 | |
| 50 | // Tap login button |
| 51 | await tester.tap(find.byKey(const Key('loginButton'))); |
| 52 | await tester.pumpAndSettle(); // Wait for animations and navigation |
| 53 | |
| 54 | // Verify navigation |
| 55 | expect(find.text('Dashboard'), findsOneWidget); |
| 56 | }); |
| 57 | |
| 58 | testWidgets('Shows error for invalid credentials', (WidgetTester tester) async { |
| 59 | await tester.pumpWidget(const MaterialApp(home: LoginScreen())); |
| 60 | |
| 61 | await tester.enterText(find.byKey(const Key('emailField')), 'wrong@test.com'); |
| 62 | await tester.enterText(find.byKey(const Key('passwordField')), 'wrong'); |
| 63 | await tester.tap(find.byKey(const Key('loginButton'))); |
| 64 | await tester.pumpAndSettle(); |
| 65 | |
| 66 | expect(find.text('Invalid credentials'), findsOneWidget); |
| 67 | }); |
| 68 | } |
| 69 | ``` |
| 70 | |
| 71 | ### Finder Strategies |
| 72 | |
| 73 | ```dart |
| 74 | // By Key (best — explicit test identifiers) |
| 75 | find.byKey(const Key('loginButton')) |
| 76 | find.byKey(const ValueKey('email_input')) |
| 77 | |
| 78 | // By Type |
| 79 | find.byType(ElevatedButton) |
| 80 | find.byType(TextField) |
| 81 | find.byType(LoginScreen) |
| 82 | |
| 83 | // By Text |
| 84 | find.text('Login') |
| 85 | find.textContaining('Welcome') |
| 86 | |
| 87 | // By Icon |
| 88 | find.byIcon(Icons.login) |
| 89 | |
| 90 | // By Widget predicate |
| 91 | find.byWidgetPredicate((widget) => widget is Text && widget.data!.startsWith('Error')) |
| 92 | |
| 93 | // Descendant/Ancestor |
| 94 | find.descendant(of: find.byType(AppBar), matching: find.text('Title')) |
| 95 | find.ancestor(of: find.text('Login'), matching: find.byType(Card)) |
| 96 | ``` |
| 97 | |
| 98 | ### Actions |
| 99 | |
| 100 | ```dart |
| 101 | await tester.tap(finder); // Tap |
| 102 | await tester.longPress(finder); // Long press |
| 103 | await tester.enterText(finder, 'text'); // Type text |
| 104 | await tester.drag(finder, const Offset(0, -300)); // Drag/scroll |
| 105 | await tester.fling(finder, const Offset(0, -500), 1000); // Fling/swipe |
| 106 | |
| 107 | // CRITICAL: Always pump after actions |
| 108 | await tester.pump(); // Single frame |
| 109 | await tester.pump(const Duration(seconds: 1)); // Advance time |
| 110 | await tester.pumpAndSettle(); // Wait for animations to finish |
| 111 | ``` |
| 112 | |
| 113 | ### Integration Test |
| 114 | |
| 115 | ```dart |
| 116 | // integration_test/app_test.dart |
| 117 | import 'package:flutter_test/flutter_test.dart'; |
| 118 | import 'package:integration_test/integration_test.dart'; |
| 119 | import 'package:my_app/main.dart' as app; |
| 120 | |
| 121 | void main() { |
| 122 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); |
| 123 | |
| 124 | testWidgets('Full login flow', (WidgetTester tester) async { |
| 125 | app.main(); |
| 126 | await tester.pumpAndSettle(); |
| 127 | |
| 128 | // Login |
| 129 | await tester.enterText(find.byKey(const Key('emailField')), 'user@test.com'); |
| 130 | await tester.enterText(find.byKey(const Key('passwordField')), 'password123'); |
| 131 | await tester.tap(find.byKey(const Key('loginButton'))); |
| 132 | await tester.pumpAndSettle(); |
| 133 | |
| 134 | // Verify dashboard |
| 135 | expect(find.text('Dashboard'), findsOneWidget); |
| 136 | |
| 137 | // Navigate to settings |
| 138 | await tester.tap(find.byIcon(Icons.settings)); |
| 139 | await tester.pumpAndSettle(); |
| 140 | expect(find.text('Settings'), findsOneWidget); |
| 141 | }); |
| 142 | } |
| 143 | ``` |
| 144 | |
| 145 | ### Golden Tests (Visual Regression) |
| 146 | |
| 147 | ```dart |
| 148 | testWidgets('Login screen matches golden |