Skip to content
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

[grid] Eliminate vendor-specific handling of extension capabilities #14485

Open
wants to merge 1 commit into
base: trunk
Choose a base branch
from

Conversation

sbabcoc
Copy link
Contributor

@sbabcoc sbabcoc commented Sep 10, 2024

User description

Description

The existing handling of extension capabilities assigns special significance to four recognized prefixes: goog:, moz:, ms:, and se:. Capabilities with these prefixes are entirely ignored by the current default slot matcher implementation, but custom prefixes are considered, as well as those for Safari and Appium. This inconsistency means that properties in extension capabilities can cause affected newSession requests to fail even though extension capabilities are supposed to be vendor-specific and should probably not be evaluated by the default slot matcher.

This PR retains the "special" status of the "se:" prefix and specifically filters out all extension capabilities with names that end with "options", "Options", "loggingPrefs", and "debuggerAddress". This maintains existing behavior while allowing node configurations and newSession requests to include extension capabilities that won't affect slot matching.

Motivation and Context

Revolves #14461

Note that this is technically a breaking change, because it revises the default slot matcher to consider "special" extension capabilities without name suffix "options" that would previously have been ignored, and the matcher will now ignore "non-special" extension capabilities with name suffix "options" that would previously have been considered.
However, I'm unaware of any current use cases that will be adversely affected by this change.

The strategy employed by the PR is that extension capabilities which should be ignored for matching can be expressed as capabilities with name prefix "se:" or one of the "special" name suffixes. All other extension capabilities will be considered for slot matching. This is a generalization/extrapolation of existing patterns from current use cases.

Recommended slot matcher enhancements

To reduce the need for custom slot matchers, we could extend the WebDriverInfo interface to add a new method that vendors could implement to evaluate their own criteria:

Boolean matches(Capabilities stereotype, Capabilities capabilities);

The default implementation would return null to indicate that no evaluation has been performed. If the vendor chooses to implement the matches method, their initial step must be to invoke their own isSupporting method, returning null if the corresponding driver doesn't support the specified capabilities.

The implementation in DefaultSlotMatcher would be updated to iterate over the available WebDriverInfo providers, retaining only those whose isSupporting methods return true. The matches methods of this filtered list of providers will then be applied to the available nodes to determine which of these satisfies the criteria specified by the client request. The nodes that satisfy these evaluations (if any) would then be evaluated against the remaining common criteria.

Another potential enhancement would be to enable vendor-specific matches methods to return a weighted integer result instead of a simple Boolean. This would enable best-match behavior. Perhaps this is getting too complicated, though.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • I have read the contributing document.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

PR Type

Bug fix, Tests


Description

  • Removed the special status of certain extension capability prefixes in DefaultSlotMatcher, opting to ignore all extension capabilities with names ending in "options" (case-insensitive).
  • Simplified the logic for matching capabilities by removing unnecessary checks and conditions.
  • Updated RelaySessionFactory to streamline session creation by removing redundant capability filtering.
  • Revised test cases in DefaultSlotMatcherTest to align with the new handling of extension capabilities.

Changes walkthrough 📝

Relevant files
Bug fix
DefaultSlotMatcher.java
Revise extension capabilities handling in DefaultSlotMatcher

java/src/org/openqa/selenium/grid/data/DefaultSlotMatcher.java

  • Removed special handling for specific extension capability prefixes.
  • Updated logic to ignore all extension capabilities with names ending in "options" (case-insensitive).
  • Simplified capability matching logic.
  • +31/-51 
    RelaySessionFactory.java
    Simplify session creation in RelaySessionFactory                 

    java/src/org/openqa/selenium/grid/node/relay/RelaySessionFactory.java

  • Removed logic to filter out browserName when appium:app is present.
  • Simplified session creation process.
  • +0/-11   
    Tests
    DefaultSlotMatcherTest.java
    Update DefaultSlotMatcher tests for revised capability handling

    java/test/org/openqa/selenium/grid/data/DefaultSlotMatcherTest.java

  • Updated test cases to reflect changes in extension capability
    handling.
  • +10/-8   

    💡 PR-Agent usage:
    Comment /help on the PR to get a list of all available PR-Agent tools and their descriptions

    Copy link
    Contributor

    PR Reviewer Guide 🔍

    ⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
    🧪 PR contains tests
    🔒 No security concerns identified
    ⚡ Key issues to review

    Potential Performance Issue
    The new implementation of initialMatch method uses reduce(Boolean::logicalAnd) which may not short-circuit on the first false condition. Consider using allMatch for better performance.

    Possible Bug
    The extensionCapabilitiesMatch method now ignores complex objects and arrays, which might lead to unintended matching behavior for certain extension capabilities.

    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch from a82c998 to 14999b7 Compare September 10, 2024 18:15
    Copy link
    Contributor

    PR Code Suggestions ✨

    CategorySuggestion                                                                                                                                    Score
    Possible issue
    Improve precision in matching complex capability values

    Consider using a more specific check for complex objects instead of returning true
    for all non-String, non-Number, and non-Boolean values. This could prevent
    unintended matches for complex objects.

    java/src/org/openqa/selenium/grid/data/DefaultSlotMatcher.java [144-148]

     if (capabilities.getCapability(name) instanceof Number ||
    -  capabilities.getCapability(name) instanceof Boolean) {
    +  capabilities.getCapability(name) instanceof Boolean ||
    +  capabilities.getCapability(name) instanceof Map) {
       return Objects.equals(stereotype.getCapability(name), capabilities.getCapability(name));
     }
    -return true;
    +return false;
     
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    Why: This suggestion enhances the precision of the matching logic by returning false for unsupported complex objects, preventing unintended matches and improving the accuracy of the matching process.

    8
    Enhancement
    Improve handling of complex capability values in extension capabilities matching

    Consider using Map.of() or ImmutableMap.of() instead of creating a new HashMap for
    the complex capability values. This can make the code more concise and potentially
    more efficient.

    java/src/org/openqa/selenium/grid/data/DefaultSlotMatcher.java [144-148]

     if (capabilities.getCapability(name) instanceof Number ||
    -  capabilities.getCapability(name) instanceof Boolean) {
    +  capabilities.getCapability(name) instanceof Boolean ||
    +  capabilities.getCapability(name) instanceof Map) {
       return Objects.equals(stereotype.getCapability(name), capabilities.getCapability(name));
     }
     return true;
     
    • Apply this suggestion
    Suggestion importance[1-10]: 7

    Why: The suggestion correctly identifies the need to handle complex capability values like Maps, which aligns with the changes in the test cases. This improves the robustness of the capability matching logic.

    7
    Add logging for skipped extension capabilities to improve debugging

    Consider adding a log statement when skipping extension capabilities to improve
    debugging and traceability of the matching process.

    java/src/org/openqa/selenium/grid/data/DefaultSlotMatcher.java [88]

    -.filter(name -> !name.contains(":"))
    +.filter(name -> {
    +  boolean isExtensionCapability = name.contains(":");
    +  if (isExtensionCapability) {
    +    LOG.fine("Skipping extension capability: " + name);
    +  }
    +  return !isExtensionCapability;
    +})
     
    • Apply this suggestion
    Suggestion importance[1-10]: 6

    Why: Adding logging for skipped extension capabilities can aid in debugging and understanding the matching process, though it is not critical for functionality.

    6
    Best practice
    Use a constant for the extension capability separator to improve code maintainability

    Consider using a constant for the ":" character used to identify extension
    capabilities. This would improve maintainability and reduce the risk of typos.

    java/src/org/openqa/selenium/grid/data/DefaultSlotMatcher.java [88]

    -.filter(name -> !name.contains(":"))
    +private static final String EXTENSION_CAPABILITY_SEPARATOR = ":";
     
    +// In the method:
    +.filter(name -> !name.contains(EXTENSION_CAPABILITY_SEPARATOR))
    +
    • Apply this suggestion
    Suggestion importance[1-10]: 5

    Why: Using a constant for the separator improves maintainability and reduces the risk of errors, but it is a minor improvement in terms of code quality.

    5

    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch from 14999b7 to dbe50be Compare September 10, 2024 18:41
    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch 2 times, most recently from ac4802b to 2d9609b Compare September 10, 2024 19:39
    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch from 2d9609b to cca2c28 Compare September 11, 2024 06:48
    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch from d5aeb2e to 84b6795 Compare September 16, 2024 21:35
    @sbabcoc sbabcoc requested a review from pujagani September 16, 2024 21:39
    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch 9 times, most recently from 6a5663f to d993e8c Compare September 22, 2024 19:54
    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch from d993e8c to 378cb93 Compare September 27, 2024 05:42
    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch from 98354f4 to bd93f46 Compare January 21, 2025 17:06
    @sbabcoc
    Copy link
    Contributor Author

    sbabcoc commented Jan 21, 2025

    @diemol I force-push after rebasing to keep the commit history clean. My attempt to rebase after @VietND96 added his unit test went horribly wrong and I had to check out a prior commit to recover, which removed his test. I let him know, and he created a separate PR to add his test to trunk.

    @sbabcoc
    Copy link
    Contributor Author

    sbabcoc commented Jan 21, 2025

    @diemol It seems like you're advocating for ignoring all extension capabilities for purposes of slot matching. This approach would mean that custom matchers would be required in all scenarios where matching based on extension capabilities is desired (e.g. - appium:automationName). The approach implemented by this PR is a compromise. It enables clients add slot matching criteria by adding extension capabilities with "simple" values, while extension capabilities specified as "complex" will be ignored.

    The enhancements I recommended in the PR description provide the outline of a mechanism for vendors to define slot matcher extensions that evaluate their specific matching criteria. This would enable vendors to define their own custom criteria without the need to implement custom slot matchers.

    @diemol
    Copy link
    Member

    diemol commented Jan 21, 2025

    @diemol It seems like you're advocating for ignoring all extension capabilities for purposes of slot matching.

    Not really. Just the ones named options, as in nord:options.

    @sbabcoc
    Copy link
    Contributor Author

    sbabcoc commented Jan 21, 2025

    @diemol I can revise my PR to implement this strategy. It is significantly less complex to implement and much easier to describe. I'll update the unit tests accordingly.

    Is the concept of a slot matcher extension mechanism worth exploring further? This would be an additional method for the WebDriverInfo interface, which is already being scanned for service providers.

    @diemol
    Copy link
    Member

    diemol commented Jan 21, 2025

    You can already implement your own SlotMatcher, see --slot-matcher

    @sbabcoc
    Copy link
    Contributor Author

    sbabcoc commented Jan 21, 2025

    @diemol Yes, but you can only specify one custom matcher. If you need sessions from multiple vendors, this means running with multiple hubs. With a slot matcher extension mechanism, vendor-specific matching gets activated automatically and standing up a grid that supplies sessions from multiple vendors is much easier.

    @diemol
    Copy link
    Member

    diemol commented Jan 21, 2025

    The matcher is used both at the Distributor and the Node. You would need to decouple that logic to allow more matchers.

    @sbabcoc
    Copy link
    Contributor Author

    sbabcoc commented Jan 21, 2025

    @diemol The extension would be implemented in DefaultSlotMatcher, which is already integrated with Distributor and Node.

    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch 3 times, most recently from 283dae7 to 7727862 Compare January 22, 2025 01:50
    @sbabcoc
    Copy link
    Contributor Author

    sbabcoc commented Jan 22, 2025

    @diemol I've revised the matcher to only ignore extension capabilities with suffix "options" (case-insensitive) and updated the affected unit test accordingly. I also updated the PR description to indicate the revised implementation.

    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch 2 times, most recently from 376bfb7 to 63f9b9a Compare January 22, 2025 21:35
    @sbabcoc sbabcoc changed the title [grid] Only ignore extension caps with object/array values [grid] Only ignore extension caps with names ending "options" Jan 23, 2025
    List of prefixed extension capabilities we never should try to match, they should be
    matched in the Node or in the browser driver.
    */
    private static final List<String> EXTENSION_CAPABILITIES_PREFIXES =
    Copy link
    Member

    Choose a reason for hiding this comment

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

    I want to keep this because the browser vendors can make changes and we don't want to change things here every time they do it. For example, moz:debuggerAddress is now being sent from the client side to enable CDP in Firefox.

    Copy link
    Contributor Author

    @sbabcoc sbabcoc Jan 23, 2025

    Choose a reason for hiding this comment

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

    The core objective of this PR is to migrate all vendor-specific capabilities that should be ignored for purposes of slot matching into "options" objects. (I had initially implemented this to ignore all extension capabilities with "complex" values.) The current strategy of ignoring all extension capabilities with a small number of "special" prefixes while considering all extension capabilities with "non-special" prefixes produces inconsistent behavior and requires vendors to implement special-case slot matchers, each of which is mutually exclusive to every other custom slot matcher.

    In your example of moz:debuggerAddress, the desire is to ignore this capability for slot matching. What happens if Mozilla adds a new extension capability that should be considered for slot matching? The current strategy doesn't allow for this possibility, forcing Mozilla to implement a custom slot matcher, and for all affected clients to update their Grid configurations to add this matcher. For clients with Grid configurations that supply sessions of browsers from multiple vendors, this could force complete reconfiguration of their automation infrastructure, segregating their session providers into multiple grids by vendor.

    With the revised behavior implemented by this PR, moz:debuggerAddress would be considered for slot matching. To ignore this option for slot matching, it would be migrated into moz:firefoxOptions, I believe this is a more sensible approach, because this capability truly is an option, not an identity specifier.

    Copy link
    Member

    Choose a reason for hiding this comment

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

    The current strategy of ignoring all extension capabilities with a small number of "special" prefixes while considering all extension capabilities with "non-special" prefixes produces inconsistent behavior and requires vendors to implement special-case slot matchers, each of which is mutually exclusive to every other custom slot matcher

    This is an assumption.

    If Mozilla adds a new capability for matching, we ignore it and pass it along to GeckoDriver.

    Copy link
    Contributor Author

    @sbabcoc sbabcoc Jan 26, 2025

    Choose a reason for hiding this comment

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

    This is precisely my point. Outside of mutually exclusive custom slot matchers, vendors have no control over how their extension capabilities are handled. Capabilities with "special" prefixes will always be ignored and capabilities with "non-special" prefixes will always be considered. This PR eliminates the inconsistency and adds the ability for vendors to control whether their extension capabilities will be treated as identifying characteristics or as end-node configuration options.

    Copy link
    Member

    Choose a reason for hiding this comment

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

    Please leave the prefixes. Then we can merge the PR.

    Copy link
    Member

    Choose a reason for hiding this comment

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

    Apple does not need to be there; this might change in the future.

    However, Appium does need to be considered, except the options one.

    Browser vendors just need to have their capabilities passed along. Why do you keep saying that they will use their capabilities to do matching in Grid when this is not true?

    Copy link
    Contributor Author

    Choose a reason for hiding this comment

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

    Appium extension capabilities are most certainly being considered for slot matching. This is still true in the revised functionality implemented by this PR. I think the behavior of the current matcher is what necessitated the merge operation and explicit check for 'appium:app'.

    Copy link
    Contributor Author

    Choose a reason for hiding this comment

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

    With my current implementation, all existing Appium extension capabilities are considered except for the four suffixes: "options", "Options", "loggingPrefs", and "debuggerAddress". There's implementation in the Appium server that merges the prefixed capabilities into their "options" collection with no thought to which capabilities are "identity" values and which are configuration options. I don't know if this gets reflected back to Grid in a way that's visible to DefaultSlotMatcher, but their configuration examples indicate that every option can be declared in either place. I'm attempting to engage the Appium folks to see how they feel about deprecating this pattern in favor of clear differentiation between "identity" values and configuration options.

    Copy link
    Member

    Choose a reason for hiding this comment

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

    I would not prefer to leave loggingPrefs, and debuggerAddress hardcoded. I prefer to have the 4 prefixes ignored as it was before.

    I don't mean to be rude, I am taking your considerations, and I strongly prefer the approach I've been suggesting in previous comments. Please let me know if that works for you or if I should raise the change in a different PR.

    Copy link
    Contributor Author

    Choose a reason for hiding this comment

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

    Thanks for remaining engaged in this conversation! I understand your hesitation to alter the established behavior. Perhaps the new behavior can be activated by an optional core Selenium capability. That way, the new behavior will only be present when it's explicitly requested.

    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch 8 times, most recently from 790f82e to 1ead41a Compare January 30, 2025 18:50
    @sbabcoc sbabcoc changed the title [grid] Only ignore extension caps with names ending "options" [grid] Eliminate vendor-specific handling of extension capabilities Jan 30, 2025
    filteredStereotype.setCapability(CapabilityType.BROWSER_NAME, (String) null);
    }

    capabilities = capabilities.merge(filteredStereotype);
    Copy link
    Member

    Choose a reason for hiding this comment

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

    Now I remember. This was done because the user can specify something in the stereotype that will be sent to the Node that will receive the request.

    List of prefixed extension capabilities we never should try to match, they should be
    matched in the Node or in the browser driver.
    */
    private static final List<String> EXTENSION_CAPABILITIES_PREFIXES =
    Copy link
    Member

    Choose a reason for hiding this comment

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

    Why is it an inconsistency? Browser vendors just need those capabilities to be passed along. They do not use the Grid and configure stereotypes to do matching.

    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch from 1ead41a to e4762bb Compare February 1, 2025 00:32
    @sbabcoc sbabcoc force-pushed the pr/revise-extension-capabilities-handling branch from e4762bb to 2f55884 Compare February 1, 2025 20:53
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    5 participants