-
-
Notifications
You must be signed in to change notification settings - Fork 8.6k
make augmentation of HasBiDi/HasDevTools lazy-loaded #16338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
7bf31b3
to
6607c45
Compare
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
6607c45
to
9e23bc1
Compare
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
9e23bc1
to
7e7bcf8
Compare
I have some concerns about this PR:
|
@joerg1985
No, it should not. The goal of augmentation is NOT to check all the connectivity issues. The goal is to CAST
But the augmentation is NOT automatically called on startup. AND if I really want to check BiDi connectivity, I would explicitly call some BiDi method on startup. One argument for this PR:
public HasDebugger getImplementation(Capabilities capabilities, ExecuteMethod executeMethod) {
return () -> { ... ALL THE WORK ONLY HERE ...}
}
// or
public HasPermissions getImplementation(Capabilities capabilities, ExecuteMethod executeMethod) {
return new HasPermissions() {
... ALL THE WORK ONLY HERE ...
};
}
|
@asolntsev i only wanted to point to this change in behaviour, i think having the same behaviour for all augmentations is a good idea. So i only have one comment left, all other areas rely on external synchronizations in case multiple threads are involved. |
If I understand your question correctly, then yes, the synchronization is needed in this case. @Test void augmenterThreadSafety() {
RemoteWebDriver driver = new RemoteWebDriver(gridUrl(), new ChromeOptions());
WebDriver webDriver = new Augmenter().augment(driver);
HasDevTools devTools = (HasDevTools) webDriver;
for (int i = 0; i < 10; i++) {
new Thread(() -> {
devTools.getDevTools().getDomains(); // would create multiple instances of `DevTools` without synchronization
}).start();
}
} |
@asolntsev in my mind the client code has to take care about synchronization, in your example:
Other areas of the selenium code do not expect the client code will use different threads. e.g. the code below will end in chaos without synchronization in the client code:
|
Yes, maybe this code wouldn't do anything reasonable, but it's safe. I mean, it doesn't cause connection leak etc. |
@asolntsev lets see what others think about this |
Easy, should be thread-safe. |
@nvborisenko Great! Then can we already merge this PR? |
Please. Please? Please! @mkurz @diemol @iampopovich @VietND96 @cgoldberg @joerg1985 @navin772 @vicky-iv @giulong @pujagani |
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.
If you don't have socket support...
By default webSocketUrl
is false, so BiDi is not augmented
CDP is on by default, so you can toggle it off with "se:cdpEnabled": false
Please explain why a user should not have to set the capabilities to match the behavior they get?
As for the code itself, I'm a little concerned about doing synchronization things inside this method, but not in other methods. I'd have to think through the implications. If Diego or Joerg say it is fine, then I'll go with their expertise, but in general I'd say not to add the extra code if it isn't needed.
Look, we care about simplicity for our users, right? Honestly, I don't understand why are you trying to make your users more work than needed.
I don't see problems here. Synchronized are exactly those methods which establish a new connection. Exactly as it should be. |
.NET binding never establishes new network connection if it is not required. |
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 think if we decide to lazy load DevTools we should do the same for BiDi. Eventually that's going to be default.
I took at deeper look at code and noted some things.
Maybe we can log more info when getDevTools / getBiDi fail
Let's at least log that we're lazy loading since it's a change.
Can we add a test for these?
I'm still deferring to @diemol if he says no. 😄
Otherwise, command `new Augmenter().augment(remoteWebDriver)` fails immediately (even if I don't want to use CDP or BiDi). * Augmenter should only augment (create instance of interfaces). * Augmenter should not perform any other actions (establish CDP connection, establish BiDi connection etc.)
1. method `getBiDi`/`getDevTools` should establish a connection, BUT 1. method `maybeGet*()` returns connection only if the connection is already established, but should NOT establish a new connection. It's because method `maybeGet*()` is used from `WebDriver.close()` and `WebDriver.quite()`. At this moment, we don't want a new connection, we only want to close the existing connection.
fb4b4f7
to
b9a4b95
Compare
@titusfortner I've extracted the "synchronization" code to a separate class @Override
public HasBiDi getImplementation(Capabilities caps, ExecuteMethod executeMethod) {
return new HasBiDi() {
private final Lazy<BiDi> biDi = lazy(() -> establishBiDiConnection(caps));
@Override
public Optional<BiDi> maybeGetBiDi() {
return biDi.getIfInitialized();
}
@Override
public BiDi getBiDi() {
return biDi.get();
}
};
} |
b9a4b95
to
d17ad21
Compare
I know it is just an abstraction, but the new code feels so much better to me. Thanks also for the tests. To clarify for @diemol the use case that made me think we should do this now is that a user might need to run their code on a remote grid that doesn't support sockets, the grid config would need to change not just the client code. So the user can't just toggle this from their capabilities and ensure it works as I previously suggested. |
Now we have a convenient factory method `lazy` for declaring lazy-initialized values like BiDi or DevTools. This simplifies BiDiProvider and DevToolsProvider code.
d17ad21
to
8eab877
Compare
Sure, we can add the logging. So you are suggesting that during
Right? Do the texts sound good? Should it be "INFO" or "WARNING" or "DEBUG"? P.S. If you really assume some users want to check-up all connections during augmentation, we could add an optional parameter to Augmenter augmenter = new Augmenter();
WebDriver wd1 = augmenter.augment(webdriver); // lazy-initialized
WebDriver wd1 = augmenter.augment(webdriver, LAZY); // lazy-initialized
WebDriver wd2 = augmenter.augment(webdriver, EAGER); // checks all connections right now
// where the second parameter is:
enum InitializationMode {LAZY, EAGER} |
I was thinking of when you use webdriver builder, it automatically augments and that's on startup. The get implementation method is only called during augmentation and we already know it matched on is applicable. Info is good for this now since this is a behavior change. I only do warnings if I think it's something the user needs to change. I would say something like "driver augmented with bidi/devtools interface, but the connection is not verified until first use." If someone really needs to check right away, they can cast and call the get bidi command, right? No need to make the implementation more complicated with an optional parameter. |
…mediately anymore
268225f
to
976ffda
Compare
@titusfortner Ok, added the log: LOG.log(INFO, "WebDriver augmented with DevTools interface; connection will not be verified until first use.");
LOG.log(INFO, "WebDriver augmented with BiDi interface; connection will not be verified until first use."); |
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.
Please run ./scripts/format.sh
so we can get this merged. Thanks for the effort to explain your approach.
Strange... I am sure I already executed UPD Now I see the problem: this script always fails on my machine:
|
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.
Looks good, thank you for the updates and your patience 😄
User description
When CDP url is not accessible, the command
new Augmenter().augment(remoteWebDriver)
fails immediately - even if I don't want to use CDP or BiDi.🔗 Related Issues
This PR fixes the problem described in #9803, #10132, selenide/selenide#2797, selenide/selenide#3107 etc.
💥 What does this PR do?
This PR fixes
DevToolsProvider
andBiDiProvider
, so that augmentation ofHasBiDi
andHasDevTools
interfaces is now lazy-loaded.Context:
JavascriptExecutor
:This code fails because of CDP connectivity issue during augmentation:
🔄 Types of changes
PR Type
Bug fix
Description
Make
HasBiDi
andHasDevTools
augmentation lazy-loadedFix connection failures when CDP/BiDi URLs are inaccessible
Enable augmentation to succeed even without CDP/BiDi access
Implement double-checked locking pattern for thread safety
Diagram Walkthrough
File Walkthrough
BiDiProvider.java
Implement lazy BiDi connection initialization
java/src/org/openqa/selenium/bidi/BiDiProvider.java
implementation
maybeGetBiDi()
methodgetBiDiUrl()
method staticDevToolsProvider.java
Implement lazy DevTools connection initialization
java/src/org/openqa/selenium/devtools/DevToolsProvider.java
implementation
maybeGetDevTools()
method