Skip to content

Commit f649f17

Browse files
committed
code review updates
1 parent 3834ffc commit f649f17

1 file changed

Lines changed: 62 additions & 16 deletions

File tree

integration_test/payment/desktop_stripe_checkout_smoke_test.dart

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'dart:async';
12
import 'dart:io';
23
import 'dart:math';
34
import 'dart:typed_data';
@@ -15,6 +16,10 @@ import 'package:lantern/main.dart' as app;
1516

1617
const _stripeHost = 'checkout.stripe.com';
1718
const _screenshotPath = String.fromEnvironment('PAYMENT_SMOKE_SCREENSHOT_PATH');
19+
const _screenshotRenderTimeout = Duration(seconds: 30);
20+
const _screenshotPollInterval = Duration(milliseconds: 250);
21+
const _minimumScreenshotContrast = 64;
22+
const _darkPixelLuminance = 192;
1823

1924
void main() {
2025
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -108,12 +113,9 @@ void main() {
108113
expect(observer.documentLength, greaterThan(0));
109114
if (Platform.isMacOS) {
110115
final screenshot = observer.screenshot;
111-
expect(
112-
screenshot,
113-
isNotNull,
114-
reason: 'The Stripe WebView did not return a screenshot',
115-
);
116-
await _verifyScreenshot(screenshot!);
116+
if (screenshot == null) {
117+
fail('The Stripe WebView did not return a screenshot');
118+
}
117119
if (_screenshotPath.isNotEmpty) {
118120
final file = File(_screenshotPath);
119121
await file.parent.create(recursive: true);
@@ -130,14 +132,63 @@ void main() {
130132
);
131133
}
132134

133-
Future<void> _verifyScreenshot(Uint8List screenshot) async {
134-
expect(screenshot.lengthInBytes, greaterThan(1024));
135+
Future<Uint8List> _waitForRenderedScreenshot(
136+
Future<Uint8List?> Function() captureScreenshot,
137+
) async {
138+
final deadline = DateTime.now().add(_screenshotRenderTimeout);
139+
Object? lastError;
140+
while (DateTime.now().isBefore(deadline)) {
141+
try {
142+
final screenshot = await captureScreenshot().timeout(
143+
const Duration(seconds: 5),
144+
);
145+
if (screenshot != null && await _hasVisibleContent(screenshot)) {
146+
return screenshot;
147+
}
148+
} catch (error) {
149+
lastError = error;
150+
}
151+
await Future<void>.delayed(_screenshotPollInterval);
152+
}
153+
final detail = lastError == null ? '' : ': $lastError';
154+
throw TimeoutException(
155+
'Stripe Checkout did not become visually ready$detail',
156+
_screenshotRenderTimeout,
157+
);
158+
}
159+
160+
Future<bool> _hasVisibleContent(Uint8List screenshot) async {
161+
if (screenshot.lengthInBytes <= 1024) return false;
162+
135163
final codec = await ui.instantiateImageCodec(screenshot);
136164
try {
137165
final frame = await codec.getNextFrame();
138166
try {
139-
expect(frame.image.width, greaterThan(100));
140-
expect(frame.image.height, greaterThan(100));
167+
if (frame.image.width <= 100 || frame.image.height <= 100) return false;
168+
final data = await frame.image.toByteData(
169+
format: ui.ImageByteFormat.rawRgba,
170+
);
171+
if (data == null) return false;
172+
173+
var darkest = 255;
174+
var lightest = 0;
175+
var darkPixels = 0;
176+
final pixelCount = data.lengthInBytes ~/ 4;
177+
final minimumDarkPixels = max(64, pixelCount ~/ 1000);
178+
for (var offset = 0; offset < data.lengthInBytes; offset += 4) {
179+
final red = data.getUint8(offset);
180+
final green = data.getUint8(offset + 1);
181+
final blue = data.getUint8(offset + 2);
182+
final luminance = (299 * red + 587 * green + 114 * blue) ~/ 1000;
183+
if (luminance < darkest) darkest = luminance;
184+
if (luminance > lightest) lightest = luminance;
185+
if (luminance <= _darkPixelLuminance) darkPixels++;
186+
if (lightest - darkest >= _minimumScreenshotContrast &&
187+
darkPixels >= minimumDarkPixels) {
188+
return true;
189+
}
190+
}
191+
return false;
141192
} finally {
142193
frame.image.dispose();
143194
}
@@ -195,12 +246,7 @@ class _StripeCheckoutObserver implements AppWebViewObserver {
195246
if (uri.host != _stripeHost) return;
196247
if (Platform.isMacOS) {
197248
try {
198-
screenshot = await captureScreenshot().timeout(
199-
const Duration(seconds: 10),
200-
);
201-
if (screenshot == null || screenshot!.isEmpty) {
202-
checkoutFailure = 'WebView screenshot was empty';
203-
}
249+
screenshot = await _waitForRenderedScreenshot(captureScreenshot);
204250
} catch (error) {
205251
checkoutFailure = 'Unable to capture WebView screenshot: $error';
206252
}

0 commit comments

Comments
 (0)