-
Notifications
You must be signed in to change notification settings - Fork 6k
Implement Webdriver-based Safari test harness #33757
Conversation
|
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!). If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
|
Gold has detected about 129 new digest(s) on patchset 6. |
|
Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change). If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review. |
|
Gold has detected about 112 new digest(s) on patchset 7. |
|
Golden file changes are available for triage from new commit, Click here to view. |
|
Gold has detected about 218 new digest(s) on patchset 8. |
|
Gold has detected about 87 new digest(s) on patchset 9. |
mdebbar
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, looks good to me with some minor nits and comments.
lib/web_ui/dev/safari_ios.dart
Outdated
| if (!file.existsSync()) { | ||
| throw Exception('Failed to read the screenshot ' | ||
| 'screenshot$_fileNameCounter.png.'); | ||
| 'screenshot$screenshotTag.png.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
| 'screenshot$screenshotTag.png.'); | |
| '$filename.'); |
lib/web_ui/dev/test_platform.dart
Outdated
| final String filename = requestData['filename'] as String; | ||
|
|
||
| if (_screenshotManager == null) { | ||
| if (!(await _browserManager!)!.supportsScreenshots) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to think of a way to avoid the double ! in multiple places. How about adding something like:
Future<BrowserManager> get browserManager async {
return (await _browserManager!)!;
}then in your if (and in the other place below):
if (!(await browserManager).supportsScreenshots) {I'm not sure how much improvement that makes. I feel like the main problem here is that _browserManager is a future that has to be awaited everywhere. Ideally, _browserManager should simply be an instance of BrowserManager (or BrowserManager?). The future should be awaited and resolved somewhere else before any of this code runs.
But that's outside the scope of this PR, so I digress.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you and I actually spent some time bikeshedding and trying to get rid of some of the layers of optionality/futures here. However, I ended up backing out those changes because their scope was creeping outside of really how far I wanted to go with this change. A convenience method like you suggest would be simple enough to add though, good suggestion.
| .transform(utf8.decoder) | ||
| .transform(const LineSplitter()) | ||
| .listen((String error) { | ||
| print('[Webdriver] $error'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add "[Error]" so it's easier to distinguish errors from normal logs?
| print('[Webdriver] $error'); | |
| print('[Webdriver][Error] $error'); |
| for(;;) { | ||
| try { | ||
| final WebDriver driver = await createDriver( | ||
| uri: driverUri, desired: <String, dynamic>{'browserName': packageTestRuntime.identifier}); | ||
| return WebDriverBrowser(driver, url); | ||
| } on SocketException { | ||
| // Sometimes we may try to connect before the web driver port is ready. Retry. | ||
| print('Failed to connect to webdriver process. Retrying in 100 ms'); | ||
| await Future<void>.delayed(const Duration(milliseconds: 100)); | ||
| } catch (exception) { | ||
| print('Exception while creating webdriver: $exception'); | ||
| rethrow; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could become a real infinite loop (e.g. if webdriver never starts for whatever reason). Since there's no timeout or max retry logic here, are you relying on the test suite's timeout to stop this loop? If that's the case, it would be nice to clarify that in a comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'll add a comment.
|
Gold has detected about 148 new digest(s) on patchset 10. |
yjbanov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A handful of nitpicks. LGTM overall.
lib/web_ui/dev/browser.dart
Outdated
| /// browser instances. | ||
| Future<void> prepare(); | ||
|
|
||
| // Perform any necessary teardown steps |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use /// for doc comments
|
|
||
| @override | ||
| Future<Browser> launchBrowserInstance(Uri url, {bool debug = false}) async { | ||
| for (;;) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think while (true) is more idiomatic in Dart
| return WebDriverBrowser(driver, url); | ||
| } on SocketException { | ||
| // Sometimes we may try to connect before the web driver port is ready. | ||
| // So we should here. Note that if there was some issue with the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: missing word between "should" and "here"
| print('Failed to connect to webdriver process. Retrying in 100 ms'); | ||
| await Future<void>.delayed(const Duration(milliseconds: 100)); | ||
| } catch (exception) { | ||
| print('Exception while creating webdriver: $exception'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When rethrowing you don't need to also print, because the rethrow will retain all the information about the original error, including its stack trace.
| final WebDriver driver = await createDriver( | ||
| uri: driverUri, desired: <String, dynamic>{'browserName': packageTestRuntime.identifier}); | ||
| return WebDriverBrowser(driver, url); | ||
| } on SocketException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would be more appropriate to smoke test (and retry if necessary) the driver process in the prepare method. This way other methods that need the driver to be ready don't need to mistrust the initialization process of the driver.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried coming up with a good solution here based on your suggestion, but I'm not really sure I really like anything I could come up with. I understand where you're coming from in that it seems like the responsibility of "prepare" to ensure that the web driver process is actually ready to go, but I think actually trying to enforce this only ends up with negatives practically speaking. For one thing, doing the smoke test in prepare results in opening an extra socket just to check if the driver process is ready, which is a non-trivial operation from a perf perspective. In practice, this is being validated the first time launchBrowserInstance is invoked, and after that, other browser launches should end up in the happy path. The only real benefit I could see to doing the validation in prepare is that if there is some situation where the web driver launches and the smoke test passes, but something bad happens in the web driver process before a subsequent invocation of launchBrowserInstance then we would fail fast rather than have to wait for the test timeout. But this seems like an edge case to optimize for that (hopefully) wouldn't happen too often in practice anyway.
| Uri get driverUri; | ||
|
|
||
| @override | ||
| Future<void> prepare() async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sometimes there's an existing driver process hanging around on the system. It may be useful to check that the port is not already taken. Otherwise, the test might end up using the wrong driver process (depending on when exactly the newly spawned process crashes due to the taken port).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found a nice simple implementation of a "find unused port" function in the dart SDK, which I am shamefully stealing here. That should solve this problem.
| } | ||
| } | ||
|
|
||
| class WebDriverBrowser extends Browser { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love how simple the driver-based implementation is!
* WebDriver stuff. * Some refactoring and WebDriver improvements. * Add license comments. * TODO needs to have two slashes, not three. * Remove dependency on OS/Xcode * Revert "Remove dependency on OS/Xcode" This reverts commit 1d02dc7. * Disable golden tests for macOS Safari that were disabled for iOS Safari. * Don't run tests that didn't run on ios Safari on Desktop Safari. * Newline at end of file. * Fixes as per Mouad's comments. * Changes as per Yegor's comments. * Fix analyzer issue.
This adds support for Webdriver-based browser tests and changes the macos Safari implementation to use this method. There is a fair amount of refactoring to the Browser and BrowserEnvironment classes and the test platform that uses them.
This is part of a fix for flutter/flutter#99885. We still need to make some changes to the LUCI recipe repo in order to fix the rest.