원클릭으로
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