Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit b7b03bd

Browse files
authored
Retry when safaridriver fails (#48791)
Starting `safaridriver` is flakey sometimes on macOS 13. It will occasionally error with "Operation not permitted". As a workaround, if it fails with that message, retry starting `safaridriver`. Fixes flutter/flutter#136972. Example of fix on macOS 13 bot: https://ci.chromium.org/ui/p/flutter/builders/try/Mac%20Engine%20Drone/564967/overview Note: The test is still failing due to flutter/flutter#136279, but you can see it first has error "Operation not permitted" and retries and connects on second attempt. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 991676f commit b7b03bd

2 files changed

Lines changed: 56 additions & 3 deletions

File tree

lib/web_ui/dev/safari_macos.dart

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:convert';
67
import 'dart:io';
78

89
import 'package:test_api/src/backend/runtime.dart';
@@ -23,6 +24,58 @@ class SafariMacOsEnvironment extends WebDriverBrowserEnvironment {
2324
@override
2425
Uri get driverUri => Uri(scheme: 'http', host: 'localhost', port: portNumber);
2526

27+
late Process _driverProcess;
28+
int _retryCount = 0;
29+
static const int _waitBetweenRetryInSeconds = 1;
30+
static const int _maxRetryCount = 10;
31+
2632
@override
2733
Future<Process> spawnDriverProcess() => Process.start('safaridriver', <String>['-p', portNumber.toString()]);
34+
35+
@override
36+
Future<void> prepare() async {
37+
await _startDriverProcess();
38+
}
39+
40+
/// Pick an unused port and start `safaridriver` using that port.
41+
///
42+
/// On macOS 13, starting `safaridriver` can be flaky so if it returns an
43+
/// "Operation not permitted" error, kill the `safaridriver` process and try
44+
/// again with a different port. Wait [_waitBetweenRetryInSeconds] seconds
45+
/// between retries. Try up to [_maxRetryCount] times.
46+
Future<void> _startDriverProcess() async {
47+
_retryCount += 1;
48+
if (_retryCount > 1) {
49+
await Future<void>.delayed(const Duration(seconds: _waitBetweenRetryInSeconds));
50+
}
51+
portNumber = await pickUnusedPort();
52+
53+
print('Attempt $_retryCount to start safaridriver on port $portNumber');
54+
55+
_driverProcess = await spawnDriverProcess();
56+
57+
_driverProcess.stderr
58+
.transform(utf8.decoder)
59+
.transform(const LineSplitter())
60+
.listen((String error) {
61+
print('[Webdriver][Error] $error');
62+
if (_retryCount > _maxRetryCount) {
63+
print('[Webdriver][Error] Failed to start after $_maxRetryCount tries.');
64+
} else if (error.contains('Operation not permitted')) {
65+
_driverProcess.kill();
66+
_startDriverProcess();
67+
}
68+
});
69+
_driverProcess.stdout
70+
.transform(utf8.decoder)
71+
.transform(const LineSplitter())
72+
.listen((String log) {
73+
print('[Webdriver] $log');
74+
});
75+
}
76+
77+
@override
78+
Future<void> cleanup() async {
79+
_driverProcess.kill();
80+
}
2881
}

lib/web_ui/dev/webdriver_browser.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ import 'package:webdriver/async_io.dart' show WebDriver, createDriver;
1313
import 'browser.dart';
1414

1515
abstract class WebDriverBrowserEnvironment extends BrowserEnvironment {
16-
late final int portNumber;
16+
late int portNumber;
1717
late final Process _driverProcess;
1818

1919
Future<Process> spawnDriverProcess();
2020
Uri get driverUri;
2121

2222
/// Finds and returns an unused port on the test host in the local port range.
23-
Future<int> _pickUnusedPort() async {
23+
Future<int> pickUnusedPort() async {
2424
// Use bind to allocate an unused port, then unbind from that port to
2525
// make it available for use.
2626
final ServerSocket socket = await ServerSocket.bind('localhost', 0);
@@ -33,7 +33,7 @@ abstract class WebDriverBrowserEnvironment extends BrowserEnvironment {
3333

3434
@override
3535
Future<void> prepare() async {
36-
portNumber = await _pickUnusedPort();
36+
portNumber = await pickUnusedPort();
3737

3838
_driverProcess = await spawnDriverProcess();
3939

0 commit comments

Comments
 (0)