بنقرة واحدة
bc-golden-testing
Expert guidance for creating visual regression tests using bc_golden_plugin for Flutter applications
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Expert guidance for creating visual regression tests using bc_golden_plugin for Flutter applications
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
| name | bc-golden-testing |
| description | Expert guidance for creating visual regression tests using bc_golden_plugin for Flutter applications |
| keywords | golden, testing, visual, regression, flutter, ui, screenshot, widget, test |
| version | 1.0.0 |
| metadata | {"author":"juan-campuzano"} |
This skill provides comprehensive guidance for creating visual regression tests (golden tests) using the bc_golden_plugin Flutter package. Golden tests compare widget screenshots against reference images to catch visual regressions.
Golden testing with bc_golden_plugin enables:
Add to pubspec.yaml:
dev_dependencies:
bc_golden_plugin: ^2.0.0
Create test/flutter_test_config.dart:
import 'dart:async';
import 'package:bc_golden_plugin/bc_golden_plugin.dart';
import 'package:flutter_test/flutter_test.dart';
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
TestWidgetsFlutterBinding.ensureInitialized();
BcGoldenConfiguration bcGoldenConfiguration = BcGoldenConfiguration();
// Optional: Set acceptable difference threshold (0-100%)
bcGoldenConfiguration.goldenDifferenceThreshold = 30;
// Optional: Configure theme providers if needed
// bcGoldenConfiguration.setThemeProvider = [
// ChangeNotifierProvider(create: (_) => YourThemeNotifier()),
// ];
// Optional: Set theme data
// bcGoldenConfiguration.setThemeData = YourThemeData.lightTheme;
await loadConfiguration();
await testMain();
}
When the test is run from inside a Flutter package whose own pubspec
declares an icon font (and that icon font is referenced by IconData
with fontPackage: '<thisPackageName>'), pass currentPackage to
loadConfiguration so the font is also registered under the
packages/<thisPackageName>/<family> alias that Icon will look up.
Without it, you'll see icons rendered as empty squares (missing-glyph
placeholders) in the goldens.
await loadConfiguration(currentPackage: 'package_name');
This is only needed when the manifest entry uses a local asset path
(no packages/... prefix) — typically the case when running tests
from inside the package itself. Consumers of that package don't need
to set it, because their FontManifest.json already lists the font
under packages/<pkg>/....
Golden test files MUST follow this pattern:
test/ directory (or subdirectories)*_golden_test.dartbutton_widget_golden_test.dartThe package provides three main testing approaches:
Use BcGoldenCapture.single for testing individual widgets or screens.
import 'package:bc_golden_plugin/bc_golden_plugin.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
BcGoldenCapture.single(
'Button widget renders correctly',
(tester) async {
await bcWidgetMatchesImage(
imageName: 'button_widget',
widget: MyButton(),
tester: tester,
device: GoldenDeviceData.iPhone13,
);
},
);
}
BcGoldenCapture.single(
'Login screen on iPhone 14 Pro Max',
(tester) async {
await bcWidgetMatchesImage(
imageName: 'login_iphone_14_pro',
widget: LoginScreen(),
tester: tester,
device: GoldenDeviceData.iPhone16ProMax,
);
},
);
BcGoldenCapture.single(
'Button with large text scale',
(tester) async {
await bcWidgetMatchesImage(
imageName: 'button_large_text',
widget: MyButton(),
tester: tester,
device: GoldenDeviceData.iPhone13,
textScaleFactor: 2.0, // Simulates accessibility text scaling
);
},
);
BcGoldenCapture.single(
'Card with shadows',
(tester) async {
await bcWidgetMatchesImage(
imageName: 'card_with_shadows',
widget: MyCard(),
tester: tester,
device: GoldenDeviceData.pixel5,
);
},
shouldUseRealShadows: true, // Enable shadow rendering
);
Pre-configured device profiles:
| Device | Viewport Size | Pixel Ratio |
|---|---|---|
GoldenDeviceData.iPhone8 | 375 x 667 | 2.0 |
GoldenDeviceData.iPhone13 | 390 x 844 | 3.0 |
GoldenDeviceData.iPhone16ProMax | 430 x 932 | 3.0 |
GoldenDeviceData.pixel5 | 360 x 764 | 3.0 |
GoldenDeviceData.iPadPro | 1366 x 1024 | 2.0 |
GoldenDeviceData.galaxyS25 | Custom | 3.0 |
BcGoldenCapture.single(
'Custom device test',
(tester) async {
await bcWidgetMatchesImage(
imageName: 'custom_device',
widget: MyWidget(),
tester: tester,
device: bcCustomWindowConfigData(
name: 'Custom Phone',
pixelDensity: 3.0,
size: const Size(375, 828),
),
);
},
);
Use BcGoldenCapture.multiple to test complete user journeys by capturing multiple screens and combining them into a single golden file.
BcGoldenCapture.multiple(
'User onboarding flow',
[
GoldenStep(
stepName: 'Welcome Screen',
widgetBuilder: () => WelcomeScreen(),
),
GoldenStep(
stepName: 'Features Screen',
widgetBuilder: () => FeaturesScreen(),
),
GoldenStep(
stepName: 'Permissions Screen',
widgetBuilder: () => PermissionsScreen(),
),
],
const GoldenCaptureConfig(
testName: 'onboarding_flow',
layoutType: CaptureLayoutType.vertical,
spacing: 16.0,
),
);
BcGoldenCapture.multiple(
'Login flow with interactions',
[
GoldenStep(
stepName: 'Login Form',
widgetBuilder: () => LoginScreen(),
setupAction: (tester) async {
// Actions before screenshot
await tester.enterText(find.byKey(Key('email')), 'user@example.com');
await tester.pump();
},
),
GoldenStep(
stepName: 'Dashboard',
widgetBuilder: () => DashboardScreen(),
verifyAction: (tester) async {
// Verification after screenshot
expect(find.text('Welcome'), findsOneWidget);
},
),
],
const GoldenCaptureConfig(
testName: 'login_flow',
layoutType: CaptureLayoutType.horizontal,
spacing: 24.0,
),
);
Configure how screenshots are arranged:
// Vertical stacking
GoldenCaptureConfig(
testName: 'vertical_flow',
layoutType: CaptureLayoutType.vertical,
spacing: 16.0,
)
// Horizontal arrangement
GoldenCaptureConfig(
testName: 'horizontal_flow',
layoutType: CaptureLayoutType.horizontal,
spacing: 20.0,
)
// Grid layout
GoldenCaptureConfig(
testName: 'grid_flow',
layoutType: CaptureLayoutType.grid,
maxScreensPerRow: 2,
spacing: 24.0,
)
BcGoldenCapture.multiple(
'Checkout flow on iPhone',
[
GoldenStep(
stepName: 'Cart',
widgetBuilder: () => CartScreen(),
),
GoldenStep(
stepName: 'Payment',
widgetBuilder: () => PaymentScreen(),
),
],
GoldenCaptureConfig(
testName: 'checkout_flow',
device: GoldenDeviceData.iPhone13,
layoutType: CaptureLayoutType.horizontal,
spacing: 100,
),
);
Use BcGoldenCapture.animation to capture animation frames at specific timestamps.
BcGoldenCapture.animation(
'Button scale animation',
() => AnimatedButton(),
const GoldenAnimationConfig(
testName: 'button_animation',
totalDuration: Duration(milliseconds: 300),
animationSteps: [
GoldenAnimationStep(
timestamp: Duration.zero,
frameName: 'start',
),
GoldenAnimationStep(
timestamp: Duration(milliseconds: 150),
frameName: 'scaled',
),
GoldenAnimationStep(
timestamp: Duration(milliseconds: 300),
frameName: 'end',
),
],
layoutType: CaptureLayoutType.horizontal,
showTimelineLabels: true,
),
);
BcGoldenCapture.animation(
'Loading spinner rotation',
() => const CircularProgressIndicator(),
const GoldenAnimationConfig(
testName: 'spinner_rotation',
totalDuration: Duration(milliseconds: 1000),
animationSteps: [
GoldenAnimationStep(
timestamp: Duration.zero,
frameName: '0_degrees',
),
GoldenAnimationStep(
timestamp: Duration(milliseconds: 250),
frameName: '90_degrees',
),
GoldenAnimationStep(
timestamp: Duration(milliseconds: 500),
frameName: '180_degrees',
),
GoldenAnimationStep(
timestamp: Duration(milliseconds: 750),
frameName: '270_degrees',
),
],
layoutType: CaptureLayoutType.grid,
maxScreensPerRow: 2,
showTimelineLabels: true,
),
);
BcGoldenCapture.animation(
'Fade transition',
() => FadeTransitionWidget(),
GoldenAnimationConfig(
testName: 'fade_transition',
totalDuration: Duration(milliseconds: 500),
animationSteps: [
GoldenAnimationStep(
timestamp: Duration.zero,
frameName: 'invisible',
),
GoldenAnimationStep(
timestamp: Duration(milliseconds: 250),
frameName: 'half_visible',
),
GoldenAnimationStep(
timestamp: Duration(milliseconds: 500),
frameName: 'fully_visible',
),
],
layoutType: CaptureLayoutType.horizontal,
spacing: 20.0,
device: GoldenDeviceData.iPhone13,
),
animationSetup: (tester) async {
// Trigger the animation
await tester.tap(find.byType(FadeTransitionWidget));
await tester.pump();
},
);
For complex scenarios requiring manual control over screenshot timing:
BcGoldenCapture.single('Manual golden test', (tester) async {
await tester.runAsync(() async {
// Configure device
tester.configureWindow(GoldenDeviceData.iPhone13);
// Pump initial widget
await tester.pumpWidget(
TestBase.appGoldenTest(
widget: const HomePage(title: 'Home'),
key: GlobalKey(),
),
);
await tester.pumpAndSettle();
// Capture first screenshot
await tester.captureGoldenScreenshot();
// Perform interaction
await tester.tap(find.byKey(const Key('next_button')));
await tester.pumpAndSettle();
// Capture second screenshot
await tester.captureGoldenScreenshot();
// Combine screenshots
final combinedScreenshot = await tester.combineGoldenScreenshots(
GoldenCaptureConfig(
testName: 'manual_flow',
device: GoldenDeviceData.iPhone13,
layoutType: CaptureLayoutType.horizontal,
),
['home', 'next_page'],
);
// Assert against golden file
await expectLater(
combinedScreenshot,
matchesGoldenFile('goldens/manual_flow.png'),
);
});
});
# Generate golden files for all tests, in case there are no goldens generated
flutter test --update-goldens --tags=golden
# Generate for specific test file
flutter test test/widgets/button_golden_test.dart --update-goldens
# Generate for specific test
flutter test test/widgets/button_golden_test.dart --update-goldens --name="Button widget renders correctly"
# Run all golden tests
flutter test --tags=golden
# Run specific golden test file
flutter test test/widgets/button_golden_test.dart
# Run with coverage
flutter test --coverage --tags=golden
test/
├── flutter_test_config.dart
├── widgets/
│ ├── button_golden_test.dart
│ ├── card_golden_test.dart
│ └── goldens/
│ ├── button_widget.png
│ └── card_widget.png
├── screens/
│ ├── login_golden_test.dart
│ └── goldens/
│ └── login_screen.png
└── flows/
├── onboarding_golden_test.dart
└── goldens/
└── onboarding_flow.png
*_golden_test.dartAlways test with text scale factors:
// Test with normal text
await bcWidgetMatchesImage(
imageName: 'widget_normal',
widget: MyWidget(),
tester: tester,
);
// Test with large text (accessibility)
await bcWidgetMatchesImage(
imageName: 'widget_large_text',
widget: MyWidget(),
tester: tester,
textScaleFactor: 2.0,
);
Set acceptable difference thresholds in flutter_test_config.dart:
bcGoldenConfiguration.goldenDifferenceThreshold = 30; // 30% tolerance
Use this for:
Use for:
Benefits:
Use for:
Capture key frames:
void main() {
BcGoldenCapture.single(
'Button states',
(tester) async {
await bcWidgetMatchesImage(
imageName: 'button_enabled',
widget: MyButton(enabled: true),
tester: tester,
);
},
);
BcGoldenCapture.single(
'Button disabled state',
(tester) async {
await bcWidgetMatchesImage(
imageName: 'button_disabled',
widget: MyButton(enabled: false),
tester: tester,
);
},
);
}
BcGoldenCapture.single(
'Widget in dark theme',
(tester) async {
await bcWidgetMatchesImage(
imageName: 'widget_dark',
widget: Theme(
data: ThemeData.dark(),
child: MyWidget(),
),
tester: tester,
);
},
);
void main() {
for (final device in [
GoldenDeviceData.iPhone13,
GoldenDeviceData.iPadPro,
]) {
BcGoldenCapture.single(
'Responsive layout on ${device.name}',
(tester) async {
await bcWidgetMatchesImage(
imageName: 'responsive_${device.name}',
widget: ResponsiveWidget(),
tester: tester,
device: device,
);
},
);
}
}
flutter test --update-goldensUse shouldUseRealShadows: true in BcGoldenCapture.single
If you have existing tests using bcGoldenTest, migrate to BcGoldenCapture.single:
bcGoldenTest(
'My test',
(tester) async {
await bcWidgetMatchesImage(
imageName: 'my_widget',
widget: MyWidget(),
tester: tester,
);
},
shouldUseRealShadows: true,
);
BcGoldenCapture.single(
'My test',
(tester) async {
await bcWidgetMatchesImage(
imageName: 'my_widget',
widget: MyWidget(),
tester: tester,
);
},
shouldUseRealShadows: true,
);
The API is identical, just replace bcGoldenTest with BcGoldenCapture.single.
When creating golden tests with bc_golden_plugin:
BcGoldenCapture.single for individual widgetsBcGoldenCapture.multiple for testing variations in a screen or componentBcGoldenCapture.animation for animation testing*_golden_test.dartflutter_test_config.dartgoldens/ subdirectoriesflutter test --update-goldens to generate/update reference images