Conversation
PR Summary by QodoFix By.getJavascriptExecutor() to validate unwrapped driver JS support
AI Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo
1.
|
9ce385e to
bdc7fc4
Compare
|
Code review by qodo was updated up to the latest commit bdc7fc4 |
|
Quick follow-up, since this has been sitting a while. CI never started on this fork PR (the workflow runs are On the cross-binding note: I compared this with .NET. Python does not hit this path, since |
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
bdc7fc4 to
689eff9
Compare
|
@diemol @pujagani would you have a few minutes to review this? It was suggested I also ping @SeleniumHQ/selenium-tlc. Small Java fix: |
Code Review by Qodo
1. Single shadow finds lack coverage
|
diemol
left a comment
There was a problem hiding this comment.
This fix unblocks element/shadow-root-scoped relative locators, but neither call site (RelativeLocator.java nor RelativeLocatorServerSide.java) passes the context through to the FIND_ELEMENTS atom, so the atom's root always defaults to document.
That means calls like element.findElements(with(...)) will now silently search the entire page instead of just the element's subtree, whereas before they at least failed with a clear exception.
|
@diemol There's a new test in |
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
|
Code review by qodo was updated up to the latest commit eaeffda |
|
@Mochxd it seems the new added test is failing. Were you able to execute it on your end before submitting the code? |
|
@diemol I went through the remote log before changing anything.
Grid is calling On the client, I was going to convert that locator to a JSON-safe map first, same as the client, and keep the test. I have not pushed that yet. If you would rather keep relative locators document-scoped on the server, or do this a different way, say so and I will follow that. |
String.valueOf() turned relative locator maps into Map.toString() on the wire, so Grid could not run element-scoped relative locators.
|
@diemol I reproduced The locator was going over the wire as I dropped the stringify so the nested map stays JSON. After that the scoped Grid test passes. |
|
@diemol any updates? |
|
Code review by qodo was updated up to the latest commit 279eafc |
diemol
left a comment
There was a problem hiding this comment.
I still don't see a change in the shadowRoot.java because searching from a normal element will work, but if I search inside the shadowRoot, it won't work.
Also, in the findElements.ts atom, the resolveAnchor method won't work if we pass a by locator, so that also needs to be fixed to get the whole thing working.
And the last thing I saw is that it is very unlikely, but there might be someone extending the find child element and the find child elements, so we need to deprecate and, after that, remove.
| } | ||
|
|
||
| static CommandPayload FIND_CHILD_ELEMENT(String id, String strategy, String value) { | ||
| // Custom locators (relative) send a nested map. A String value becomes Map.toString() on the wire. |
There was a problem hiding this comment.
I don't see how this comment is needed.
There was a problem hiding this comment.
This also needs to be deprecated first. There might be someone who is using this outside the project. We need to mark this as deprecated and in two releases we remove it.
| } | ||
|
|
||
| static CommandPayload FIND_CHILD_ELEMENTS(String id, String strategy, String value) { | ||
| static CommandPayload FIND_CHILD_ELEMENTS(String id, String strategy, Object value) { |
There was a problem hiding this comment.
There might be someone who is using this outside the project. We need to mark this as deprecated and in two releases we remove it.
Shadow root find was still turning locator values into strings, and a By anchor was resolved from the document instead of the search root. The old String methods stay and delegate to the Object ones so existing callers keep compiling.
|
@diemol the review changes are in. Shadow root find passes the locator value through instead of turning it into a string. resolveAnchor looks up a By inside the root it was given. The String find child element and find child elements methods are deprecated and call the Object versions, and I did the same for the shadow root find methods because those signatures changed too. |
|
Code review by qodo was updated up to the latest commit 0a1b85c |
A root without getElementsByTagName was passing the tag string to querySelectorAll, so values like div.foo were parsed as CSS. HTML tags stay case-insensitive. The shadow find calls are wrapped the way the formatter expects.
|
Code review by qodo was updated up to the latest commit d834e30 |
… the document for anchors Grid's CustomLocatorHandler only handled driver and element find endpoints, so a relative locator used from a shadow root reached the driver as an unknown strategy once the remote finder was cached. Route the shadow root find endpoints too. The atom now resolves a locator anchor inside the search root first and falls back to the document, so anchors inside a shadow root work while element-scoped searches can still use an anchor anywhere on the page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019EiJ67BcQH7RspGXoyi4YR
|
@diemol about the qodo comment on the tag fallback: I am leaving that line as it is. The difference it describes is real for an element created with a namespace prefix, searched from a shadow root. For a normal HTML tag there is no prefix, so localName is already what getElementsByTagName compares, and the lowercase check stays. I do not think that edge belongs in this PR. |
Widen the root type so shadow root support is visible in the signature and the tag name fallback no longer needs a cast. Fold the fake-root tag name test into the shadow root test, which already covers the same cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019EiJ67BcQH7RspGXoyi4YR
|
I pushed two commits on top of your changes:
|
|
Code review by qodo was updated up to the latest commit 77b3d1c |
RelativeBy never passed its search context to the find-elements atom, so element.FindElements(RelativeBy...) searched the whole page. Pass elements and shadow roots as the atom root; the driver and contexts that cannot be sent as a script argument keep searching the document. ShadowRoot sent RelativeBy to the driver as a find command with an empty strategy, which drivers reject. Let the locator resolve itself instead, as WebElement already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019EiJ67BcQH7RspGXoyi4YR
|
@diemol the RBE job is red because of //dotnet/test/webdriver:DevTools/DevToolsTabsTests-chrome, ClosingTabDoesNotBreakDevToolsSession. It failed both attempts. The test closes the original tab and then Console.enable returns "Session with given id not found." This PR does not touch DevTools or the .NET bindings. Two Python BiDi tests failed once and passed on retry, so they are marked flaky and did not fail the job: browsingContext.locateNodes on Edge ("execution contexts cleared") and a Chrome network test that timed out waiting for a BiDi response. I went through the two commits. Routing the shadow find endpoints through CustomLocatorHandler is the right fix for the cached remote path, and looking up the anchor in the root before the document matches the tests you added. RelativeLocatorTest, RelativeLocatorTest-remote, and CustomLocatorHandlerTest passed. The qodo comment on shouldBeAbleToRootASearchWithinAShadowRoot is that the test only posts to /elements. The /element route uses the same shadowRoot() helper and leaves findMultiple false. I would not add a second test for that. |
|
Thank you for your work on this, I believe this was long pending to be fixed. I also pushed in this same PR a change for .NET since it was having the same issue. |
|
Code review by qodo was updated up to the latest commit b0f9598 |
🔗 Related Issues
No existing issue. I hit this while looking at how relative locators resolve their search context.
💥 What does this PR do?
By.getJavascriptExecutor()resolves the driver withgetWebDriver(context), which unwraps aWrapsDriver, but then checkscontext instanceof JavascriptExecutorand castsdriver. The guard and the cast look at two different objects.The practical effect is that element-scoped relative locators throw.
RemoteWebElementimplementsWrapsDriverbut notJavascriptExecutor, so:fails with
IllegalArgumentException: Context does not provide a mechanism to execute JS: ...even though the driver it just unwrapped can execute JavaScript perfectly well. The same call works when made on the driver.The mismatch can also turn an intended
IllegalArgumentExceptioninto aClassCastException, when a context is itself aJavascriptExecutorbut the driver it wraps is not.Checking
driverinstead ofcontextfixes both. On its own that would have been incomplete: both relative-locator call sites invoke theFIND_ELEMENTSatom without a root, so the atom falls back todocumentand an element-scoped call would silently search the whole page. This PR also passes the search context as the atom root when it is not the driver, on both the client and server-side paths.🔧 Implementation Notes
By.getJavascriptExecutor(): one-word change. Left the exception message pointing atcontext, as that's the object the caller passed in.RelativeLocator/RelativeLocatorServerSide: passcontextas the atom's second argument when it is not aWebDriver; passnullfor a driver context so the atom keeps using the document.javascript/atoms/. The atom already accepts an optional root and already scopes candidate search with it.A unit test covers a context that wraps a JavaScript-capable driver. It reuses
StubDriverrather than a mock, which meant adding:helpersto theSmallTestsdeps.A browser test covers the scoped-search path: two matching candidates, one inside the context element and one outside. Element-scoped search returns only the inner one; driver-level search on the same page still returns both.
💡 Additional Considerations
For comparison, .NET already unwraps
IWrapsDriverinRelativeBy.GetExecutorwhen the context itself isn't anIJavaScriptExecutor. Its documented contract is "context is not IJavaScriptExecutor or wraps a driver that does." This brings Java in line with that.One difference I'm deliberately leaving alone: .NET walks the wrapper chain;
getWebDriver()unwraps a single level.Python doesn't hit this, since
RelativeByis only handled at the driver level.On shadow roots: I had this wrong in an earlier version of this description.
ShadowRootdoes implementWrapsDriver, andRelativeByis notBy.Remotable, soElementLocationfalls through tolocator.findElements(context)with the shadow root as the context. It therefore reaches this path and is passed as the atom root, wherequerySelectorAllscopes it correctly. I would still not call shadow DOM covered: there is no fixture for it here, and XPath inside a shadow tree is a separate weak spot in the atom.🔄 Types of changes