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

Conversation

@eyebrowsoffire
Copy link
Contributor

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.

@flutter-dashboard flutter-dashboard bot added the platform-web Code specifically for the web engine label Jun 1, 2022
@flutter-dashboard
Copy link

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.

@skia-gold
Copy link

Gold has detected about 129 new digest(s) on patchset 6.
View them at https://flutter-engine-gold.skia.org/cl/github/33757

@flutter-dashboard
Copy link

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.

Changes reported for pull request #33757 at sha 7c70e2e

@skia-gold
Copy link

Gold has detected about 112 new digest(s) on patchset 7.
View them at https://flutter-engine-gold.skia.org/cl/github/33757

@flutter-dashboard
Copy link

Golden file changes are available for triage from new commit, Click here to view.

Changes reported for pull request #33757 at sha efc8011

@skia-gold
Copy link

Gold has detected about 218 new digest(s) on patchset 8.
View them at https://flutter-engine-gold.skia.org/cl/github/33757

@skia-gold
Copy link

Gold has detected about 87 new digest(s) on patchset 9.
View them at https://flutter-engine-gold.skia.org/cl/github/33757

Copy link
Contributor

@mdebbar mdebbar left a 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.

if (!file.existsSync()) {
throw Exception('Failed to read the screenshot '
'screenshot$_fileNameCounter.png.');
'screenshot$screenshotTag.png.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
'screenshot$screenshotTag.png.');
'$filename.');

final String filename = requestData['filename'] as String;

if (_screenshotManager == null) {
if (!(await _browserManager!)!.supportsScreenshots) {
Copy link
Contributor

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.

Copy link
Contributor Author

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');
Copy link
Contributor

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?

Suggested change
print('[Webdriver] $error');
print('[Webdriver][Error] $error');

Comment on lines 47 to 60
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;
}
}
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@skia-gold
Copy link

Gold has detected about 148 new digest(s) on patchset 10.
View them at https://flutter-engine-gold.skia.org/cl/github/33757

Copy link
Contributor

@yjbanov yjbanov left a 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.

/// browser instances.
Future<void> prepare();

// Perform any necessary teardown steps
Copy link
Contributor

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 (;;) {
Copy link
Contributor

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
Copy link
Contributor

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');
Copy link
Contributor

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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 {
Copy link
Contributor

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).

Copy link
Contributor Author

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 {
Copy link
Contributor

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!

@eyebrowsoffire eyebrowsoffire merged commit fc8cdcf into flutter:main Jun 9, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 9, 2022
@eyebrowsoffire eyebrowsoffire deleted the webdriver branch June 11, 2022 00:01
houhuayong pushed a commit to houhuayong/engine that referenced this pull request Jun 21, 2022
* 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.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

needs tests platform-web Code specifically for the web engine will affect goldens

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants