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

[java] reuse the classes created by the WebDriverDecorator #14789 #14793

Merged
merged 2 commits into from
Dec 27, 2024

Conversation

joerg1985
Copy link
Member

@joerg1985 joerg1985 commented Nov 24, 2024

User description

Description

This PR will add a cache to the WebDriverDecorator and reuse the classes created.
To enabled reusing the handler, the target object is injected to a new field when a proxy is created.
As soon as the handler must call a method, the target object is red from the field inside the proxy.

Motivation and Context

Each invocation of a decorated method did return a new class, this will end in a OutOfMemoryError as soon as a certain number of classes has been generated. See #14789

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, Enhancement, Tests


Description

  • Implemented a caching mechanism in WebDriverDecorator to reuse proxy classes, preventing excessive class creation and potential OutOfMemoryError.
  • Introduced Definition and ProxyFactory classes to manage proxy creation and caching.
  • Added HasTarget interface to facilitate access to target objects within proxies.
  • Enhanced method implementations to utilize cached proxies, improving performance and memory usage.
  • Added tests to verify that the same proxy class is reused and that the correct instance is accessed during method calls.

Changes walkthrough 📝

Relevant files
Enhancement
WebDriverDecorator.java
Implement caching and reuse of proxy classes in WebDriverDecorator

java/src/org/openqa/selenium/support/decorators/WebDriverDecorator.java

  • Introduced caching mechanism to reuse proxy classes.
  • Added Definition and ProxyFactory classes for managing proxies.
  • Implemented HasTarget interface for accessing target objects.
  • Modified method implementations to utilize cached proxies.
  • +126/-44
    Tests
    DecoratedWebDriverTest.java
    Add tests for proxy class reuse and instance correctness 

    java/test/org/openqa/selenium/support/decorators/DecoratedWebDriverTest.java

  • Added test to ensure no excessive class creation.
  • Added test to verify correct instance usage.
  • +40/-0   

    💡 PR-Agent usage: Comment /help "your question" on any pull request to receive relevant information

    @joerg1985 joerg1985 requested a review from pujagani November 24, 2024 17:05
    Copy link
    Contributor

    qodo-merge-pro bot commented Nov 24, 2024

    PR Reviewer Guide 🔍

    (Review updated until commit 906a5b3)

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
    🧪 PR contains tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Thread Safety
    The cache implementation using ConcurrentHashMap needs careful review to ensure proper synchronization and avoid race conditions when multiple threads create proxy instances simultaneously

    Error Handling
    The error handling in createProxy() could be improved - throwing AssertionError may not be the best approach for production code

    Memory Leak
    The cache holds references to proxy classes indefinitely. Consider adding a mechanism to clean up unused proxy classes to prevent memory leaks

    Copy link
    Contributor

    qodo-merge-pro bot commented Nov 24, 2024

    PR Code Suggestions ✨

    Latest suggestions up to 906a5b3

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Score
    Possible issue
    Add defensive null check to prevent NullPointerException from uninitialized decorated instance

    Add null check for the instance.getOriginal() call to prevent NullPointerException
    if the decorated instance is not properly initialized.

    java/src/org/openqa/selenium/support/decorators/WebDriverDecorator.java [421]

    +if (instance.getOriginal() == null) {
    +  throw new IllegalStateException("Decorated instance not properly initialized");
    +}
     return method.invoke(instance.getOriginal(), args);
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    Why: Adding a null check for instance.getOriginal() is important for preventing runtime NullPointerExceptions that could crash the application. This is a critical defensive programming practice for a core proxy handling method.

    8
    Add synchronization to prevent race conditions in proxy instance creation

    Add synchronization to prevent potential race condition when multiple threads try to
    create proxy instances simultaneously.

    java/src/org/openqa/selenium/support/decorators/WebDriverDecorator.java [228-234]

    -public T newInstance(Decorated<T> target) {
    +public synchronized T newInstance(Decorated<T> target) {
       T instance;
       try {
         instance = (T) clazz.newInstance();
       } catch (ReflectiveOperationException e) {
         throw new AssertionError("Unable to create new proxy", e);
       }
    • Apply this suggestion
    Suggestion importance[1-10]: 7

    Why: Adding synchronization to newInstance() helps prevent potential thread safety issues when multiple threads create proxy instances simultaneously. This is important for reliability in concurrent scenarios.

    7
    Security
    Add proper error handling for security-related reflection operations

    Add error handling for potential SecurityException when accessing toJson method to
    prevent silent failures.

    java/src/org/openqa/selenium/support/decorators/WebDriverDecorator.java [503-504]

    -Method toJson = sample.getClass().getDeclaredMethod("toJson");
    -toJson.setAccessible(true);
    +Method toJson;
    +try {
    +  toJson = sample.getClass().getDeclaredMethod("toJson");
    +  toJson.setAccessible(true);
    +} catch (SecurityException e) {
    +  throw new IllegalStateException("Unable to access toJson method", e);
    +}
    • Apply this suggestion
    Suggestion importance[1-10]: 5

    Why: Adding explicit handling of SecurityException provides better error reporting and prevents silent failures when reflection operations are blocked by security manager.

    5

    💡 Need additional feedback ? start a PR chat


    Previous suggestions

    Suggestions up to commit 35a12b1
    CategorySuggestion                                                                                                                                    Score
    Possible issue
    Add synchronization to prevent race conditions in concurrent field name generation

    Add synchronization to prevent race conditions when multiple threads access the
    field name counter. The current implementation could result in duplicate field names
    if multiple threads execute the loop simultaneously.

    java/src/org/openqa/selenium/support/decorators/WebDriverDecorator.java [437-443]

    -for (fieldName = 0; fieldName < 8192; fieldName++) {
    -  try {
    -    clazz.getDeclaredField("___target" + fieldName);
    -  } catch (NoSuchFieldException ex) {
    -    break;
    +synchronized(clazz) {
    +  for (fieldName = 0; fieldName < 8192; fieldName++) {
    +    try {
    +      clazz.getDeclaredField("___target" + fieldName);
    +    } catch (NoSuchFieldException ex) {
    +      break;
    +    }
       }
     }
    Suggestion importance[1-10]: 8

    Why: The suggestion addresses a critical thread-safety issue that could lead to duplicate field names and potential runtime errors in a concurrent environment. The synchronization block is an appropriate solution for this race condition.

    8
    Add defensive null check to prevent potential null pointer dereference

    Add null check for the instance.getOriginal() call to prevent potential
    NullPointerException if the decorated instance is corrupted.

    java/src/org/openqa/selenium/support/decorators/WebDriverDecorator.java [423]

    -return method.invoke(instance.getOriginal(), args);
    +Object original = instance.getOriginal();
    +if (original == null) {
    +  throw new IllegalStateException("Decorated instance has null original");
    +}
    +return method.invoke(original, args);
    Suggestion importance[1-10]: 7

    Why: The suggestion adds important defensive programming to prevent NullPointerException, which could crash the application. The improved error message would also help with debugging.

    7
    Suggestions up to commit d74ce74
    CategorySuggestion                                                                                                                                    Score
    Possible issue
    Add null check to prevent NullPointerException when processing class hierarchy

    Add null check for clazz.getSuperclass() in extractInterfaces() method to prevent
    NullPointerException when processing Object class which has no superclass.

    java/src/org/openqa/selenium/support/decorators/WebDriverDecorator.java [495]

    -extractInterfaces(collector, clazz.getSuperclass());
    +if (clazz.getSuperclass() != null) {
    +  extractInterfaces(collector, clazz.getSuperclass());
    +}
    Suggestion importance[1-10]: 7

    Why: This is a valid defensive programming suggestion that prevents potential NullPointerException when processing Object class, which has no superclass. The fix is simple but important for robustness.

    7
    Prevent potential integer overflow and add proper error handling for field name generation

    Add validation to ensure field name counter doesn't overflow. The current limit of
    8192 could be exceeded in long-running applications, leading to an AssertionError.

    java/src/org/openqa/selenium/support/decorators/WebDriverDecorator.java [437-443]

    -for (fieldName = 0; fieldName < 8192; fieldName++) {
    +for (fieldName = 0; fieldName < Integer.MAX_VALUE; fieldName++) {
       try {
         clazz.getDeclaredField("___target" + fieldName);
       } catch (NoSuchFieldException ex) {
    +    if (fieldName >= 8192) {
    +      throw new IllegalStateException("Too many proxy instances created");
    +    }
         break;
       }
     }
    Suggestion importance[1-10]: 4

    Why: While the suggestion adds more robust error handling, the current limit of 8192 is already quite high and practical for most use cases. The improvement offers marginal benefit with added complexity.

    4
    Add synchronization to prevent race conditions when accessing shared cache during proxy creation

    Add synchronization when accessing the shared cache map to prevent potential race
    conditions during proxy creation. Use a lock or synchronized block around the cache
    access and proxy factory creation.

    java/src/org/openqa/selenium/support/decorators/WebDriverDecorator.java [361-364]

    -ProxyFactory<Z> factory =
    -    (ProxyFactory<Z>)
    -        cache.computeIfAbsent(
    -            new Definition(decorated), (key) -> createProxyFactory(key, decorated, clazz));
    +ProxyFactory<Z> factory;
    +synchronized(cache) {
    +  factory = (ProxyFactory<Z>) cache.computeIfAbsent(
    +      new Definition(decorated), (key) -> createProxyFactory(key, decorated, clazz));
    +}
    Suggestion importance[1-10]: 3

    Why: While the suggestion aims to add thread safety, it's unnecessary since ConcurrentHashMap's computeIfAbsent is already thread-safe. The additional synchronization would only add overhead.

    3

    Copy link
    Contributor

    qodo-merge-pro bot commented Nov 24, 2024

    CI Failure Feedback 🧐

    (Checks updated until commit 0cce0cc)

    Action: Test / All RBE tests

    Failed stage: Run Bazel [❌]

    Failed test name: OpenQA.Selenium.ExecutingAsyncJavascriptTest

    Failure summary:

    The action failed due to multiple script timeout and unexpected alert errors:

  • Several async JavaScript executions timed out, indicating script execution issues
  • Unexpected alert dialogs appeared during test execution with message "Look! An alert!"
  • The test was trying to execute async scripts but kept encountering timing and alert handling issues
  • The main error was "unexpected alert open: {Alert text : Look! An alert!}" which interrupted normal
    test flow

  • Relevant error logs:
    1:  ##[group]Operating System
    2:  Ubuntu
    ...
    
    970:  Package 'php-symfony-debug-bundle' is not installed, so not removed
    971:  Package 'php-symfony-dependency-injection' is not installed, so not removed
    972:  Package 'php-symfony-deprecation-contracts' is not installed, so not removed
    973:  Package 'php-symfony-discord-notifier' is not installed, so not removed
    974:  Package 'php-symfony-doctrine-bridge' is not installed, so not removed
    975:  Package 'php-symfony-doctrine-messenger' is not installed, so not removed
    976:  Package 'php-symfony-dom-crawler' is not installed, so not removed
    977:  Package 'php-symfony-dotenv' is not installed, so not removed
    978:  Package 'php-symfony-error-handler' is not installed, so not removed
    ...
    
    2021:  �[32m[6,083 / 6,345]�[0m 78 / 679 tests;�[0m Building java/src/org/openqa/selenium/support/decorators/libdecorators.jar (3 source files); 4s remote, remote-cache ... (15 actions, 8 running)
    2022:  (11:59:23) �[32mAnalyzing:�[0m 2168 targets (1627 packages loaded, 58823 targets configured)
    2023:  �[32m[7,160 / 7,460]�[0m 81 / 759 tests;�[0m Building java/src/org/openqa/selenium/support/decorators/libdecorators.jar (3 source files); 9s remote, remote-cache ... (36 actions, 4 running)
    2024:  (11:59:28) �[32mAnalyzing:�[0m 2168 targets (1627 packages loaded, 58994 targets configured)
    2025:  �[32m[7,272 / 7,655]�[0m 93 / 770 tests;�[0m Building java/src/org/openqa/selenium/remote/libapi-class.jar (71 source files); 12s remote, remote-cache ... (50 actions, 1 running)
    2026:  (11:59:34) �[32mAnalyzing:�[0m 2168 targets (1627 packages loaded, 59265 targets configured)
    2027:  �[32m[7,333 / 7,905]�[0m 124 / 835 tests;�[0m Building java/src/org/openqa/selenium/remote/libapi-class.jar (71 source files); 17s remote, remote-cache ... (46 actions, 1 running)
    2028:  (11:59:34) �[32mINFO: �[0mFrom Building java/src/org/openqa/selenium/remote/libapi-class.jar (71 source files):
    2029:  java/src/org/openqa/selenium/remote/ErrorHandler.java:46: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2030:  private final ErrorCodes errorCodes;
    2031:  ^
    2032:  java/src/org/openqa/selenium/remote/ErrorHandler.java:60: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2033:  this.errorCodes = new ErrorCodes();
    2034:  ^
    2035:  java/src/org/openqa/selenium/remote/ErrorHandler.java:68: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2036:  public ErrorHandler(ErrorCodes codes, boolean includeServerErrors) {
    2037:  ^
    2038:  java/src/org/openqa/selenium/remote/Response.java:97: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2039:  ErrorCodes errorCodes = new ErrorCodes();
    2040:  ^
    2041:  java/src/org/openqa/selenium/remote/Response.java:97: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2042:  ErrorCodes errorCodes = new ErrorCodes();
    2043:  ^
    2044:  java/src/org/openqa/selenium/remote/ProtocolHandshake.java:181: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2045:  response.setStatus(ErrorCodes.SUCCESS);
    2046:  ^
    2047:  java/src/org/openqa/selenium/remote/ProtocolHandshake.java:182: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2048:  response.setState(ErrorCodes.SUCCESS_STRING);
    2049:  ^
    2050:  java/src/org/openqa/selenium/remote/W3CHandshakeResponse.java:53: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2051:  new ErrorCodes().toStatus((String) rawError, Optional.of(tuple.getStatusCode())));
    2052:  ^
    2053:  java/src/org/openqa/selenium/remote/W3CHandshakeResponse.java:56: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2054:  new ErrorCodes().getExceptionType((String) rawError);
    2055:  ^
    2056:  java/src/org/openqa/selenium/remote/codec/AbstractHttpResponseCodec.java:44: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2057:  private final ErrorCodes errorCodes = new ErrorCodes();
    2058:  ^
    2059:  java/src/org/openqa/selenium/remote/codec/AbstractHttpResponseCodec.java:44: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2060:  private final ErrorCodes errorCodes = new ErrorCodes();
    2061:  ^
    2062:  java/src/org/openqa/selenium/remote/codec/AbstractHttpResponseCodec.java:55: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2063:  int status = response.getStatus() == ErrorCodes.SUCCESS ? HTTP_OK : HTTP_INTERNAL_ERROR;
    2064:  ^
    2065:  java/src/org/openqa/selenium/remote/codec/AbstractHttpResponseCodec.java:101: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2066:  response.setStatus(ErrorCodes.UNKNOWN_COMMAND);
    2067:  ^
    2068:  java/src/org/openqa/selenium/remote/codec/AbstractHttpResponseCodec.java:103: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2069:  response.setStatus(ErrorCodes.UNHANDLED_ERROR);
    2070:  ^
    2071:  java/src/org/openqa/selenium/remote/codec/AbstractHttpResponseCodec.java:117: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2072:  response.setStatus(ErrorCodes.SUCCESS);
    2073:  ^
    2074:  java/src/org/openqa/selenium/remote/codec/AbstractHttpResponseCodec.java:118: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2075:  response.setState(errorCodes.toState(ErrorCodes.SUCCESS));
    2076:  ^
    2077:  java/src/org/openqa/selenium/remote/codec/AbstractHttpResponseCodec.java:124: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2078:  response.setState(errorCodes.toState(ErrorCodes.SUCCESS));
    2079:  ^
    2080:  java/src/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodec.java:70: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2081:  private final ErrorCodes errorCodes = new ErrorCodes();
    2082:  ^
    2083:  java/src/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodec.java:70: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2084:  private final ErrorCodes errorCodes = new ErrorCodes();
    2085:  ^
    2086:  java/src/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodec.java:93: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2087:  response.setStatus(ErrorCodes.UNKNOWN_COMMAND);
    2088:  ^
    2089:  java/src/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodec.java:98: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2090:  response.setStatus(ErrorCodes.UNHANDLED_ERROR);
    2091:  ^
    2092:  java/src/org/openqa/selenium/remote/codec/w3c/W3CHttpResponseCodec.java:145: warning: [removal] ErrorCodes in org.openqa.selenium.remote has been deprecated and marked for removal
    2093:  response.setStatus(ErrorCodes.SUCCESS);
    ...
    
    2136:  (12:00:00) �[35mWARNING: �[0m/home/runner/work/selenium/selenium/javascript/atoms/BUILD.bazel:398:19: runfiles symlink javascript/atoms/test/click_submit_test.html -> javascript/atoms/test/click_submit_test.html obscured by javascript/atoms/test -> bazel-out/k8-fastbuild/bin/javascript/atoms/test
    2137:  (12:00:00) �[35mWARNING: �[0m/home/runner/work/selenium/selenium/javascript/atoms/BUILD.bazel:398:19: runfiles symlink javascript/atoms/test/click_test.html -> javascript/atoms/test/click_test.html obscured by javascript/atoms/test -> bazel-out/k8-fastbuild/bin/javascript/atoms/test
    2138:  (12:00:00) �[35mWARNING: �[0m/home/runner/work/selenium/selenium/javascript/atoms/BUILD.bazel:398:19: runfiles symlink javascript/atoms/test/clientrect_test.html -> javascript/atoms/test/clientrect_test.html obscured by javascript/atoms/test -> bazel-out/k8-fastbuild/bin/javascript/atoms/test
    2139:  (12:00:00) �[35mWARNING: �[0m/home/runner/work/selenium/selenium/javascript/atoms/BUILD.bazel:398:19: runfiles symlink javascript/atoms/test/color_test.html -> javascript/atoms/test/color_test.html obscured by javascript/atoms/test -> bazel-out/k8-fastbuild/bin/javascript/atoms/test
    2140:  (12:00:00) �[35mWARNING: �[0m/home/runner/work/selenium/selenium/javascript/atoms/BUILD.bazel:398:19: runfiles symlink javascript/atoms/test/dom_test.html -> javascript/atoms/test/dom_test.html obscured by javascript/atoms/test -> bazel-out/k8-fastbuild/bin/javascript/atoms/test
    2141:  (12:00:00) �[35mWARNING: �[0m/home/runner/work/selenium/selenium/javascript/atoms/BUILD.bazel:398:19: runfiles symlink javascript/atoms/test/drag_test.html -> javascript/atoms/test/drag_test.html obscured by javascript/atoms/test -> bazel-out/k8-fastbuild/bin/javascript/atoms/test
    2142:  (12:00:00) �[35mWARNING: �[0m/home/runner/work/selenium/selenium/javascript/atoms/BUILD.bazel:398:19: runfiles symlink javascript/atoms/test/enabled_test.html -> javascript/atoms/test/enabled_test.html obscured by javascript/atoms/test -> bazel-out/k8-fastbuild/bin/javascript/atoms/test
    2143:  (12:00:00) �[35mWARNING: �[0m/home/runner/work/selenium/selenium/javascript/atoms/BUILD.bazel:398:19: runfiles symlink javascript/atoms/test/enter_submit_test.html -> javascript/atoms/test/enter_submit_test.html obscured by javascript/atoms/test -> bazel-out/k8-fastbuild/bin/javascript/atoms/test
    2144:  (12:00:00) �[35mWARNING: �[0m/home/runner/work/selenium/selenium/javascript/atoms/BUILD.bazel:398:19: runfiles symlink javascript/atoms/test/error_test.html -> javascript/atoms/test/error_test.html obscured by javascript/atoms/test -> bazel-out/k8-fastbuild/bin/javascript/atoms/test
    ...
    
    2236:  (12:00:25) �[32mINFO: �[0mFrom Compiling webdriver-net8.0 (internals ref-only dll):
    2237:  dotnet/src/webdriver/BiDi/Modules/Script/RemoteValue.cs(252,35): warning CS8766: Nullability of reference types in return type of 'string? Node.SharedId.get' doesn't match implicitly implemented member 'string ISharedReference.SharedId.get' (possibly because of nullability attributes).
    2238:  (12:00:25) �[32mINFO: �[0mFrom Compiling webdriver-net8.0:
    2239:  bazel-out/k8-fastbuild/bin/dotnet/src/webdriver/cdp/v129/Target/TargetInfo.cs(22,149): warning CS1570: XML comment has badly formed XML -- 'Reference to undefined entity 'q'.'
    2240:  bazel-out/k8-fastbuild/bin/dotnet/src/webdriver/cdp/v130/Target/TargetInfo.cs(22,149): warning CS1570: XML comment has badly formed XML -- 'Reference to undefined entity 'q'.'
    2241:  bazel-out/k8-fastbuild/bin/dotnet/src/webdriver/cdp/v131/Target/TargetInfo.cs(22,149): warning CS1570: XML comment has badly formed XML -- 'Reference to undefined entity 'q'.'
    2242:  dotnet/src/webdriver/BiDi/Modules/Script/RemoteValue.cs(252,35): warning CS8766: Nullability of reference types in return type of 'string? Node.SharedId.get' doesn't match implicitly implemented member 'string ISharedReference.SharedId.get' (possibly because of nullability attributes).
    2243:  dotnet/src/webdriver/WebElement.cs(621,34): warning CS0618: 'WebElement.GetAttribute(string)' is obsolete: 'Use GetDomAttribute(string attributeName) or GetDomProperty(string propertyName). GetAttribute(string attributeName) will be removed in Selenium 6.'
    2244:  dotnet/src/webdriver/WebDriver.cs(778,30): warning CS0618: 'WebDriverResult.ElementNotDisplayed' is obsolete: 'This error status is no longer returned by the WebDriver Specification https://www.w3.org/TR/webdriver2/#errors. Will be removed in 4.30'
    2245:  dotnet/src/webdriver/WebDriver.cs(782,30): warning CS0618: 'WebDriverResult.ElementNotSelectable' is obsolete: 'This error status is no longer returned by the WebDriver Specification https://www.w3.org/TR/webdriver2/#errors. Will be removed in 4.30'
    2246:  dotnet/src/webdriver/WebDriver.cs(785,30): warning CS0618: 'WebDriverResult.NoSuchDocument' is obsolete: 'This error status is no longer returned by the WebDriver Specification https://www.w3.org/TR/webdriver2/#errors. Will be removed in 4.30'
    ...
    
    3027:  (12:02:06) �[32m[13,367 / 14,160]�[0m 818 / 2168 tests;�[0m Compiling webdriver-netstandard2.0; 54s remote, remote-cache ... (22 actions, 6 running)
    3028:  (12:02:09) �[32mINFO: �[0mFrom Compiling webdriver-netstandard2.0:
    3029:  bazel-out/k8-fastbuild/bin/dotnet/src/webdriver/cdp/v131/Target/TargetInfo.cs(22,149): warning CS1570: XML comment has badly formed XML -- 'Reference to undefined entity 'q'.'
    3030:  bazel-out/k8-fastbuild/bin/dotnet/src/webdriver/cdp/v129/Target/TargetInfo.cs(22,149): warning CS1570: XML comment has badly formed XML -- 'Reference to undefined entity 'q'.'
    3031:  bazel-out/k8-fastbuild/bin/dotnet/src/webdriver/cdp/v130/Target/TargetInfo.cs(22,149): warning CS1570: XML comment has badly formed XML -- 'Reference to undefined entity 'q'.'
    3032:  dotnet/src/webdriver/BiDi/Modules/Script/RemoteValue.cs(252,35): warning CS8766: Nullability of reference types in return type of 'string? Node.SharedId.get' doesn't match implicitly implemented member 'string ISharedReference.SharedId.get' (possibly because of nullability attributes).
    3033:  dotnet/src/webdriver/WebElement.cs(621,34): warning CS0618: 'WebElement.GetAttribute(string)' is obsolete: 'Use GetDomAttribute(string attributeName) or GetDomProperty(string propertyName). GetAttribute(string attributeName) will be removed in Selenium 6.'
    3034:  dotnet/src/webdriver/VirtualAuth/Credential.cs(120,61): warning CS8604: Possible null reference argument for parameter 'rpId' in 'Credential.Credential(byte[] id, bool isResidentCredential, string rpId, string privateKey, byte[]? userHandle, int signCount)'.
    3035:  dotnet/src/webdriver/WebDriver.cs(778,30): warning CS0618: 'WebDriverResult.ElementNotDisplayed' is obsolete: 'This error status is no longer returned by the WebDriver Specification https://www.w3.org/TR/webdriver2/#errors. Will be removed in 4.30'
    3036:  dotnet/src/webdriver/WebDriver.cs(782,30): warning CS0618: 'WebDriverResult.ElementNotSelectable' is obsolete: 'This error status is no longer returned by the WebDriver Specification https://www.w3.org/TR/webdriver2/#errors. Will be removed in 4.30'
    3037:  dotnet/src/webdriver/WebDriver.cs(785,30): warning CS0618: 'WebDriverResult.NoSuchDocument' is obsolete: 'This error status is no longer returned by the WebDriver Specification https://www.w3.org/TR/webdriver2/#errors. Will be removed in 4.30'
    ...
    
    3119:  12:03:10.374 DEBUG HttpCommandExecutor: Response: ( Success: )
    3120:  12:03:10.376 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: executeAsyncScript {"script":"setTimeout(arguments[0], 200) ; setTimeout(function() { window.alert(\u0027Look! An alert!\u0027); }, 50);","args":[]}
    3121:  12:03:10.376 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/execute/async, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3122:  {"script":"setTimeout(arguments[0], 200) ; setTimeout(function() { window.alert(\u0027Look! An alert!\u0027); }, 50);","args":[]}
    3123:  12:03:10.611 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3124:  12:03:10.612 DEBUG HttpCommandExecutor: Response: ( Success: )
    3125:  12:03:10.666 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: getTitle {}
    3126:  12:03:10.667 TRACE HttpCommandExecutor: >> GET RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/title, Content: null, Headers: 3
    3127:  12:03:10.724 TRACE HttpCommandExecutor: << StatusCode: 500, ReasonPhrase: Internal Server Error, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3128:  {"value":{"data":{"text":"Look! An alert!"},"error":"unexpected alert open","message":"unexpected alert open: {Alert text : Look! An alert!}\n  (Session info: MicrosoftEdge=131.0.2903.112)","stacktrace":"#0 0x5606cef8b94e \u003Cunknown>\n#1 0x5606cea40b76 \u003Cunknown>\n#2 0x5606ceacfed3 \u003Cunknown>\n#3 0x5606ceab1f83 \u003Cunknown>\n#4 0x5606cea8201e \u003Cunknown>\n#5 0x5606cea82cfe \u003Cunknown>\n#6 0x5606cef609e1 \u003Cunknown>\n#7 0x5606cef63bde \u003Cunknown>\n#8 0x5606cef6365b \u003Cunknown>\n#9 0x5606cef64005 \u003Cunknown>\n#10 0x5606cef523f2 \u003Cunknown>\n#11 0x5606cef6438d \u003Cunknown>\n#12 0x5606cef3c5b5 \u003Cunknown>\n#13 0x5606cef7c008 \u003Cunknown>\n#14 0x5606cef7c21f \u003Cunknown>\n#15 0x5606cef8a53c \u003Cunknown>\n#16 0x7f463e43f609 start_thread\n"}}
    ...
    
    3157:  {"using":"css selector","value":"*[name =\u0022typer\u0022]"}
    3158:  12:03:11.461 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3159:  12:03:11.462 DEBUG HttpCommandExecutor: Response: ( Success: System.Collections.Generic.Dictionary`2[System.String,System.Object])
    3160:  12:03:11.467 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: sendKeysToElement {"id":"f.9886718C9DE6EE8026E45EBD8A61C827.d.176C6A5205EEB3B9B50684A1C283D06D.e.2","text":"bob","value":["b","o","b"]}
    3161:  12:03:11.468 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/element/f.9886718C9DE6EE8026E45EBD8A61C827.d.176C6A5205EEB3B9B50684A1C283D06D.e.2/value, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3162:  {"text":"bob","value":["b","o","b"]}
    3163:  12:03:11.699 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3164:  12:03:11.700 DEBUG HttpCommandExecutor: Response: ( Success: )
    3165:  12:03:11.711 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: executeScript {"script":"/* get-attribute */return (function(){return (function(){var d=this||self;function f(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a};var h=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(\u0022string\u0022===typeof a)return\u0022string\u0022!==typeof b||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c\u003Ca.length;c\u002B\u002B)if(c in a\u0026\u0026a[c]===b)return c;return-1},k=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,e=\u0022string\u0022===typeof a?a.split(\u0022\u0022):a,g=0;g\u003Cc;g\u002B\u002B)g in e\u0026\u0026b.call(void 0,e[g],g,a)};function l(a,b){this.code=a;this.a=m[a]||n;this.message=b||\u0022\u0022;a=this.a.replace(/((?:^|\\s\u002B)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\\s\\xa0]\u002B/g,\u0022\u0022)});b=a.length-5;if(0\u003Eb||a.indexOf(\u0022Error\u0022,b)!=b)a\u002B=\u0022Error\u0022;this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||\u0022\u0022}f(l,Error);var n=\u0022unknown error\u0022,m={15:\u0022element not selectable\u0022,11:\u0022element not visible\u0022};m[31]=n;m[30]=n;m[24]=\u0022invalid cookie domain\u0022;m[29]=\u0022invalid element coordinates\u0022;m[12]=\u0022invalid element state\u0022;m[32]=\u0022invalid selector\u0022;\nm[51]=\u0022invalid selector\u0022;m[52]=\u0022invalid selector\u0022;m[17]=\u0022javascript error\u0022;m[405]=\u0022unsupported operation\u0022;m[34]=\u0022move target out of bounds\u0022;m[27]=\u0022no such alert\u0022;m[7]=\u0022no such element\u0022;m[8]=\u0022no such frame\u0022;m[23]=\u0022no such window\u0022;m[28]=\u0022script timeout\u0022;m[33]=\u0022session not created\u0022;m[10]=\u0022stale element reference\u0022;m[21]=\u0022timeout\u0022;m[25]=\u0022unable to set cookie\u0022;m[26]=\u0022unexpected alert open\u0022;m[13]=n;m[9]=\u0022unknown command\u0022;var p;a:{var q=d.navigator;if(q){var r=q.userAgent;if(r){p=r;break a}}p=\u0022\u0022}function t(a){return-1!=p.indexOf(a)};function u(){return t(\u0022Firefox\u0022)||t(\u0022FxiOS\u0022)}function v(){return(t(\u0022Chrome\u0022)||t(\u0022CriOS\u0022))\u0026\u0026!t(\u0022Edge\u0022)};function w(){return t(\u0022iPhone\u0022)\u0026\u0026!t(\u0022iPod\u0022)\u0026\u0026!t(\u0022iPad\u0022)};var y=t(\u0022Opera\u0022),z=t(\u0022Trident\u0022)||t(\u0022MSIE\u0022),A=t(\u0022Edge\u0022),B=t(\u0022Gecko\u0022)\u0026\u0026!(-1!=p.toLowerCase().indexOf(\u0022webkit\u0022)\u0026\u0026!t(\u0022Edge\u0022))\u0026\u0026!(t(\u0022Trident\u0022)||t(\u0022MSIE\u0022))\u0026\u0026!t(\u0022Edge\u0022),C=-1!=p.toLowerCase().indexOf(\u0022webkit\u0022)\u0026\u0026!t(\u0022Edge\u0022);function D(){var a=d.document;return a?a.documentMode:void 0}var E;\na:{var F=\u0022\u0022,G=function(){var a=p;if(B)return/rv:([^\\);]\u002B)(\\)|;)/.exec(a);if(A)return/Edge\\/([\\d\\.]\u002B)/.exec(a);if(z)return/\\b(?:MSIE|rv)[: ]([^\\);]\u002B)(\\)|;)/.exec(a);if(C)return/WebKit\\/(\\S\u002B)/.exec(a);if(y)return/(?:Version)[ \\/]?(\\S\u002B)/.exec(a)}();G\u0026\u0026(F=G?G[1]:\u0022\u0022);if(z){var H=D();if(null!=H\u0026\u0026H\u003EparseFloat(F)){E=String(H);break a}}E=F}var I;I=d.document\u0026\u0026z?D():void 0;var J=u(),K=w()||t(\u0022iPod\u0022),L=t(\u0022iPad\u0022),M=t(\u0022Android\u0022)\u0026\u0026!(v()||u()||t(\u0022Opera\u0022)||t(\u0022Silk\u0022)),N=v(),aa=t(\u0022Safari\u0022)\u0026\u0026!(v()||t(\u0022Coast\u0022)||t(\u0022Opera\u0022)||t(\u0022Edge\u0022)||t(\u0022Edg/\u0022)||t(\u0022OPR\u0022)||u()||t(\u0022Silk\u0022)||t(\u0022Android\u0022))\u0026\u0026!(w()||t(\u0022iPad\u0022)||t(\u0022iPod\u0022));function O(a){return(a=a.exec(p))?a[1]:\u0022\u0022}(function(){if(J)return O(/Firefox\\/([0-9.]\u002B)/);if(z||A||y)return E;if(N)return w()||t(\u0022iPad\u0022)||t(\u0022iPod\u0022)?O(/CriOS\\/([0-9.]\u002B)/):O(/Chrome\\/([0-9.]\u002B)/);if(aa\u0026\u0026!(w()||t(\u0022iPad\u0022)||t(\u0022iPod\u0022)))return O(/Version\\/([0-9.]\u002B)/);if(K||L){var a=/Version\\/(\\S\u002B).*Mobile\\/(\\S\u002B)/.exec(p);if(a)return a[1]\u002B\u0022.\u0022\u002Ba[2]}else if(M)return(a=O(/Android\\s\u002B([0-9.]\u002B)/))?a:O(/Version\\/([0-9.]\u002B)/);return\u0022\u0022})();var P=z\u0026\u0026!(8\u003C=Number(I)),ba=z\u0026\u0026!(9\u003C=Number(I));var ca={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},Q={IMG:\u0022 \u0022,BR:\u0022\\n\u0022};function R(a,b,c){if(!(a.nodeName in ca))if(3==a.nodeType)c?b.push(String(a.nodeValue).replace(/(\\r\\n|\\r|\\n)/g,\u0022\u0022)):b.push(a.nodeValue);else if(a.nodeName in Q)b.push(Q[a.nodeName]);else for(a=a.firstChild;a;)R(a,b,c),a=a.nextSibling};function S(a,b){b=b.toLowerCase();return\u0022style\u0022==b?da(a.style.cssText):P\u0026\u0026\u0022value\u0022==b\u0026\u0026T(a,\u0022INPUT\u0022)?a.value:ba\u0026\u0026!0===a[b]?String(a.getAttribute(b)):(a=a.getAttributeNode(b))\u0026\u0026a.specified?a.value:null}var ea=/[;]\u002B(?=(?:(?:[^\u0022]*\u0022){2})*[^\u0022]*$)(?=(?:(?:[^\u0027]*\u0027){2})*[^\u0027]*$)(?=(?:[^()]*\\([^()]*\\))*[^()]*$)/;\nfunction da(a){var b=[];k(a.split(ea),function(c){var e=c.indexOf(\u0022:\u0022);0\u003Ce\u0026\u0026(c=[c.slice(0,e),c.slice(e\u002B1)],2==c.length\u0026\u0026b.push(c[0].toLowerCase(),\u0022:\u0022,c[1],\u0022;\u0022))});b=b.join(\u0022\u0022);return b=\u0022;\u0022==b.charAt(b.length-1)?b:b\u002B\u0022;\u0022}function U(a,b){P\u0026\u0026\u0022value\u0022==b\u0026\u0026T(a,\u0022OPTION\u0022)\u0026\u0026null===S(a,\u0022value\u0022)?(b=[],R(a,b,!1),a=b.join(\u0022\u0022)):a=a[b];return a}\nfunction T(a,b){b\u0026\u0026\u0022string\u0022!==typeof b\u0026\u0026(b=b.toString());return a instanceof HTMLFormElement?!!a\u0026\u00261==a.nodeType\u0026\u0026(!b||\u0022FORM\u0022==b):!!a\u0026\u00261==a.nodeType\u0026\u0026(!b||a.tagName.toUpperCase()==b)}function V(a){return T(a,\u0022OPTION\u0022)?!0:T(a,\u0022INPUT\u0022)?(a=a.type.toLowerCase(),\u0022checkbox\u0022==a||\u0022radio\u0022==a):!1};var fa={\u0022class\u0022:\u0022className\u0022,readonly:\u0022readOnly\u0022},ha=\u0022allowfullscreen allowpaymentrequest allowusermedia async autofocus autoplay checked compact complete controls declare default defaultchecked defaultselected defer disabled ended formnovalidate hidden indeterminate iscontenteditable ismap itemscope loop multiple muted nohref nomodule noresize noshade novalidate nowrap open paused playsinline pubdate readonly required reversed scoped seamless seeking selected truespeed typemustmatch willvalidate\u0022.split(\u0022 \u0022);function W(a,b){var c=null,e=b.toLowerCase();if(\u0022style\u0022==e)return(c=a.style)\u0026\u0026\u0022string\u0022!=typeof c\u0026\u0026(c=c.cssText),c;if((\u0022selected\u0022==e||\u0022checked\u0022==e)\u0026\u0026V(a)){if(!V(a))throw new l(15,\u0022Element is not selectable\u0022);b=\u0022selected\u0022;c=a.type\u0026\u0026a.type.toLowerCase();if(\u0022checkbox\u0022==c||\u0022radio\u0022==c)b=\u0022checked\u0022;return U(a,b)?\u0022true\u0022:null}var g=T(a,\u0022A\u0022);if(T(a,\u0022IMG\u0022)\u0026\u0026\u0022src\u0022==e||g\u0026\u0026\u0022href\u0022==e)return(c=S(a,e))\u0026\u0026(c=U(a,e)),c;if(\u0022spellcheck\u0022==e){c=S(a,e);if(null!==c){if(\u0022false\u0022==c.toLowerCase())return\u0022false\u0022;if(\u0022true\u0022==c.toLowerCase())return\u0022true\u0022}return U(a,\ne)\u002B\u0022\u0022}g=fa[b]||b;if(0\u003C=h(ha,e))return(c=null!==S(a,b)||U(a,g))?\u0022true\u0022:null;try{var x=U(a,g)}catch(ia){}(e=null==x)||(e=typeof x,e=\u0022object\u0022==e\u0026\u0026null!=x||\u0022function\u0022==e);e?c=S(a,b):c=x;return null!=c?c.toString():null}var X=[\u0022_\u0022],Y=d;X[0]in Y||\u0022undefined\u0022==typeof Y.execScript||Y.execScript(\u0022var \u0022\u002BX[0]);for(var Z;X.length\u0026\u0026(Z=X.shift());)X.length||void 0===W?Y[Z]\u0026\u0026Y[Z]!==Object.prototype[Z]?Y=Y[Z]:Y=Y[Z]={}:Y[Z]=W;; return this._.apply(null,arguments);}).apply({navigator:typeof window!=\u0027undefined\u0027?window.navigator:null,document:typeof window!=\u0027undefined\u0027?window.document:null}, arguments);}\n).apply(null, arguments);","args":[{"element-6066-11e4-a52e-4f735466cecf":"f.9886718C9DE6EE8026E45EBD8A61C827.d.176C6A5205EEB3B9B50684A1C283D06D.e.2"},"value"]}
    3166:  12:03:11.713 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/execute/sync, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3167:  {"script":"/* get-attribute */return (function(){return (function(){var d=this||self;function f(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a};var h=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(\u0022string\u0022===typeof a)return\u0022string\u0022!==typeof b||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c\u003Ca.length;c\u002B\u002B)if(c in a\u0026\u0026a[c]===b)return c;return-1},k=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,e=\u0022string\u0022===typeof a?a.split(\u0022\u0022):a,g=0;g\u003Cc;g\u002B\u002B)g in e\u0026\u0026b.call(void 0,e[g],g,a)};function l(a,b){this.code=a;this.a=m[a]||n;this.message=b||\u0022\u0022;a=this.a.replace(/((?:^|\\s\u002B)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\\s\\xa0]\u002B/g,\u0022\u0022)});b=a.length-5;if(0\u003Eb||a.indexOf(\u0022Error\u0022,b)!=b)a\u002B=\u0022Error\u0022;this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||\u0022\u0022}f(l,Error);var n=\u0022unknown error\u0022,m={15:\u0022element not selectable\u0022,11:\u0022element not visible\u0022};m[31]=n;m[30]=n;m[24]=\u0022invalid cookie domain\u0022;m[29]=\u0022invalid element coordinates\u0022;m[12]=\u0022invalid element state\u0022;m[32]=\u0022invalid selector\u0022;\nm[51]=\u0022invalid selector\u0022;m[52]=\u0022invalid selector\u0022;m[17]=\u0022javascript error\u0022;m[405]=\u0022unsupported operation\u0022;m[34]=\u0022move target out of bounds\u0022;m[27]=\u0022no such alert\u0022;m[7]=\u0022no such element\u0022;m[8]=\u0022no such frame\u0022;m[23]=\u0022no such window\u0022;m[28]=\u0022script timeout\u0022;m[33]=\u0022session not created\u0022;m[10]=\u0022stale element reference\u0022;m[21]=\u0022timeout\u0022;m[25]=\u0022unable to set cookie\u0022;m[26]=\u0022unexpected alert open\u0022;m[13]=n;m[9]=\u0022unknown command\u0022;var p;a:{var q=d.navigator;if(q){var r=q.userAgent;if(r){p=r;break a}}p=\u0022\u0022}function t(a){return-1!=p.indexOf(a)};function u(){return t(\u0022Firefox\u0022)||t(\u0022FxiOS\u0022)}function v(){return(t(\u0022Chrome\u0022)||t(\u0022CriOS\u0022))\u0026\u0026!t(\u0022Edge\u0022)};function w(){return t(\u0022iPhone\u0022)\u0026\u0026!t(\u0022iPod\u0022)\u0026\u0026!t(\u0022iPad\u0022)};var y=t(\u0022Opera\u0022),z=t(\u0022Trident\u0022)||t(\u0022MSIE\u0022),A=t(\u0022Edge\u0022),B=t(\u0022Gecko\u0022)\u0026\u0026!(-1!=p.toLowerCase().indexOf(\u0022webkit\u0022)\u0026\u0026!t(\u0022Edge\u0022))\u0026\u0026!(t(\u0022Trident\u0022)||t(\u0022MSIE\u0022))\u0026\u0026!t(\u0022Edge\u0022),C=-1!=p.toLowerCase().indexOf(\u0022webkit\u0022)\u0026\u0026!t(\u0022Edge\u0022);function D(){var a=d.document;return a?a.documentMode:void 0}var E;\na:{var F=\u0022\u0022,G=function(){var a=p;if(B)return/rv:([^\\);]\u002B)(\\)|;)/.exec(a);if(A)return/Edge\\/([\\d\\.]\u002B)/.exec(a);if(z)return/\\b(?:MSIE|rv)[: ]([^\\);]\u002B)(\\)|;)/.exec(a);if(C)return/WebKit\\/(\\S\u002B)/.exec(a);if(y)return/(?:Version)[ \\/]?(\\S\u002B)/.exec(a)}();G\u0026\u0026(F=G?G[1]:\u0022\u0022);if(z){var H=D();if(null!=H\u0026\u0026H\u003EparseFloat(F)){E=String(H);break a}}E=F}var I;I=d.document\u0026\u0026z?D():void 0;var J=u(),K=w()||t(\u0022iPod\u0022),L=t(\u0022iPad\u0022),M=t(\u0022Android\u0022)\u0026\u0026!(v()||u()||t(\u0022Opera\u0022)||t(\u0022Silk\u0022)),N=v(),aa=t(\u0022Safari\u0022)\u0026\u0026!(v()||t(\u0022Coast\u0022)||t(\u0022Opera\u0022)||t(\u0022Edge\u0022)||t(\u0022Edg/\u0022)||t(\u0022OPR\u0022)||u()||t(\u0022Silk\u0022)||t(\u0022Android\u0022))\u0026\u0026!(w()||t(\u0022iPad\u0022)||t(\u0022iPod\u0022));function O(a){return(a=a.exec(p))?a[1]:\u0022\u0022}(function(){if(J)return O(/Firefox\\/([0-9.]\u002B)/);if(z||A||y)return E;if(N)return w()||t(\u0022iPad\u0022)||t(\u0022iPod\u0022)?O(/CriOS\\/([0-9.]\u002B)/):O(/Chrome\\/([0-9.]\u002B)/);if(aa\u0026\u0026!(w()||t(\u0022iPad\u0022)||t(\u0022iPod\u0022)))return O(/Version\\/([0-9.]\u002B)/);if(K||L){var a=/Version\\/(\\S\u002B).*Mobile\\/(\\S\u002B)/.exec(p);if(a)return a[1]\u002B\u0022.\u0022\u002Ba[2]}else if(M)return(a=O(/Android\\s\u002B([0-9.]\u002B)/))?a:O(/Version\\/([0-9.]\u002B)/);return\u0022\u0022})();var P=z\u0026\u0026!(8\u003C=Number(I)),ba=z\u0026\u0026!(9\u003C=Number(I));var ca={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},Q={IMG:\u0022 \u0022,BR:\u0022\\n\u0022};function R(a,b,c){if(!(a.nodeName in ca))if(3==a.nodeType)c?b.push(String(a.nodeValue).replace(/(\\r\\n|\\r|\\n)/g,\u0022\u0022)):b.push(a.nodeValue);else if(a.nodeName in Q)b.push(Q[a.nodeName]);else for(a=a.firstChild;a;)R(a,b,c),a=a.nextSibling};function S(a,b){b=b.toLowerCase();return\u0022style\u0022==b?da(a.style.cssText):P\u0026\u0026\u0022value\u0022==b\u0026\u0026T(a,\u0022INPUT\u0022)?a.value:ba\u0026\u0026!0===a[b]?String(a.getAttribute(b)):(a=a.getAttributeNode(b))\u0026\u0026a.specified?a.value:null}var ea=/[;]\u002B(?=(?:(?:[^\u0022]*\u0022){2})*[^\u0022]*$)(?=(?:(?:[^\u0027]*\u0027){2})*[^\u0027]*$)(?=(?:[^()]*\\([^()]*\\))*[^()]*$)/;\nfunction da(a){var b=[];k(a.split(ea),function(c){var e=c.indexOf(\u0022:\u0022);0\u003Ce\u0026\u0026(c=[c.slice(0,e),c.slice(e\u002B1)],2==c.length\u0026\u0026b.push(c[0].toLowerCase(),\u0022:\u0022,c[1],\u0022;\u0022))});b=b.join(\u0022\u0022);return b=\u0022;\u0022==b.charAt(b.length-1)?b:b\u002B\u0022;\u0022}function U(a,b){P\u0026\u0026\u0022value\u0022==b\u0026\u0026T(a,\u0022OPTION\u0022)\u0026\u0026null===S(a,\u0022value\u0022)?(b=[],R(a,b,!1),a=b.join(\u0022\u0022)):a=a[b];return a}\nfunction T(a,b){b\u0026\u0026\u0022string\u0022!==typeof b\u0026\u0026(b=b.toString());return a instanceof HTMLFormElement?!!a\u0026\u00261==a.nodeType\u0026\u0026(!b||\u0022FORM\u0022==b):!!a\u0026\u00261==a.nodeType\u0026\u0026(!b||a.tagName.toUpperCase()==b)}function V(a){return T(a,\u0022OPTION\u0022)?!0:T(a,\u0022INPUT\u0022)?(a=a.type.toLowerCase(),\u0022checkbox\u0022==a||\u0022radio\u0022==a):!1};var fa={\u0022class\u0022:\u0022className\u0022,readonly:\u0022readOnly\u0022},ha=\u0022allowfullscreen allowpaymentrequest allowusermedia async autofocus autoplay checked compact complete controls declare default defaultchecked defaultselected defer disabled ended formnovalidate hidden indeterminate iscontenteditable ismap itemscope loop multiple muted nohref nomodule noresize noshade novalidate nowrap open paused playsinline pubdate readonly required reversed scoped seamless seeking selected truespeed typemustmatch willvalidate\u0022.split(\u0022 \u0022);function W(a,b){var c=null,e=b.toLowerCase();if(\u0022style\u0022==e)return(c=a.style)\u0026\u0026\u0022string\u0022!=typeof c\u0026\u0026(c=c.cssText),c;if((\u0022selected\u0022==e||\u0022checked\u0022==e)\u0026\u0026V(a)){if(!V(a))throw new l(15,\u0022Element is not selectable\u0022);b=\u0022selected\u0022;c=a.type\u0026\u0026a.type.toLowerCase();if(\u0022checkbox\u0022==c||\u0022radio\u0022==c)b=\u0022checked\u0022;return U(a,b)?\u0022true\u0022:null}var g=T(a,\u0022A\u0022);if(T(a,\u0022IMG\u0022)\u0026\u0026\u0022src\u0022==e||g\u0026\u0026\u0022href\u0022==e)return(c=S(a,e))\u0026\u0026(c=U(a,e)),c;if(\u0022spellcheck\u0022==e){c=S(a,e);if(null!==c){if(\u0022false\u0022==c.toLowerCase())return\u0022false\u0022;if(\u0022true\u0022==c.toLowerCase())return\u0022true\u0022}return U(a,\ne)\u002B\u0022\u0022}g=fa[b]||b;if(0\u003C=h(ha,e))return(c=null!==S(a,b)||U(a,g))?\u0022true\u0022:null;try{var x=U(a,g)}catch(ia){}(e=null==x)||(e=typeof x,e=\u0022object\u0022==e\u0026\u0026null!=x||\u0022function\u0022==e);e?c=S(a,b):c=x;return null!=c?c.toString():null}var X=[\u0022_\u0022],Y=d;X[0]in Y||\u0022undefined\u0022==typeof Y.execScript||Y.execScript(\u0022var \u0022\u002BX[0]);for(var Z;X.length\u0026\u0026(Z=X.shift());)X.length||void 0===W?Y[Z]\u0026\u0026Y[Z]!==Object.prototype[Z]?Y=Y[Z]:Y=Y[Z]={}:Y[Z]=W;; return this._.apply(null,arguments);}).apply({navigator:typeof window!=\u0027undefined\u0027?window.navigator:null,document:typeof window!=\u0027undefined\u0027?window.document:null}, arguments);}\n).apply(null, arguments);","args":[{"element-6066-11e4-a52e-4f735466cecf":"f.9886718C9DE6EE8026E45EBD8A61C827.d.176C6A5205EEB3B9B50684A1C283D06D.e.2"},"value"]}
    ...
    
    3197:  {"script":10000}
    3198:  12:03:12.206 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3199:  12:03:12.206 DEBUG HttpCommandExecutor: Response: ( Success: )
    3200:  12:03:12.207 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: executeAsyncScript {"script":"var callback = arguments[arguments.length - 1];window.registerListener(arguments[arguments.length - 1]);","args":[]}
    3201:  12:03:12.207 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/execute/async, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3202:  {"script":"var callback = arguments[arguments.length - 1];window.registerListener(arguments[arguments.length - 1]);","args":[]}
    3203:  12:03:17.702 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3204:  12:03:17.703 DEBUG HttpCommandExecutor: Response: ( Success: bob)
    3205:  12:03:17.704 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: executeScript {"script":"/* get-attribute */return (function(){return (function(){var d=this||self;function f(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a};var h=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(\u0022string\u0022===typeof a)return\u0022string\u0022!==typeof b||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c\u003Ca.length;c\u002B\u002B)if(c in a\u0026\u0026a[c]===b)return c;return-1},k=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,e=\u0022string\u0022===typeof a?a.split(\u0022\u0022):a,g=0;g\u003Cc;g\u002B\u002B)g in e\u0026\u0026b.call(void 0,e[g],g,a)};function l(a,b){this.code=a;this.a=m[a]||n;this.message=b||\u0022\u0022;a=this.a.replace(/((?:^|\\s\u002B)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\\s\\xa0]\u002B/g,\u0022\u0022)});b=a.length-5;if(0\u003Eb||a.indexOf(\u0022Error\u0022,b)!=b)a\u002B=\u0022Error\u0022;this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||\u0022\u0022}f(l,Error);var n=\u0022unknown error\u0022,m={15:\u0022element not selectable\u0022,11:\u0022element not visible\u0022};m[31]=n;m[30]=n;m[24]=\u0022invalid cookie domain\u0022;m[29]=\u0022invalid element coordinates\u0022;m[12]=\u0022invalid element state\u0022;m[32]=\u0022invalid selector\u0022;\nm[51]=\u0022invalid selector\u0022;m[52]=\u0022invalid selector\u0022;m[17]=\u0022javascript error\u0022;m[405]=\u0022unsupported operation\u0022;m[34]=\u0022move target out of bounds\u0022;m[27]=\u0022no such alert\u0022;m[7]=\u0022no such element\u0022;m[8]=\u0022no such frame\u0022;m[23]=\u0022no such window\u0022;m[28]=\u0022script timeout\u0022;m[33]=\u0022session not created\u0022;m[10]=\u0022stale element reference\u0022;m[21]=\u0022timeout\u0022;m[25]=\u0022unable to set cookie\u0022;m[26]=\u0022unexpected alert open\u0022;m[13]=n;m[9]=\u0022unknown command\u0022;var p;a:{var q=d.navigator;if(q){var r=q.userAgent;if(r){p=r;break a}}p=\u0022\u0022}function t(a){return-1!=p.indexOf(a)};function u(){return t(\u0022Firefox\u0022)||t(\u0022FxiOS\u0022)}function v(){return(t(\u0022Chrome\u0022)||t(\u0022CriOS\u0022))\u0026\u0026!t(\u0022Edge\u0022)};function w(){return t(\u0022iPhone\u0022)\u0026\u0026!t(\u0022iPod\u0022)\u0026\u0026!t(\u0022iPad\u0022)};var y=t(\u0022Opera\u0022),z=t(\u0022Trident\u0022)||t(\u0022MSIE\u0022),A=t(\u0022Edge\u0022),B=t(\u0022Gecko\u0022)\u0026\u0026!(-1!=p.toLowerCase().indexOf(\u0022webkit\u0022)\u0026\u0026!t(\u0022Edge\u0022))\u0026\u0026!(t(\u0022Trident\u0022)||t(\u0022MSIE\u0022))\u0026\u0026!t(\u0022Edge\u0022),C=-1!=p.toLowerCase().indexOf(\u0022webkit\u0022)\u0026\u0026!t(\u0022Edge\u0022);function D(){var a=d.document;return a?a.documentMode:void 0}var E;\na:{var F=\u0022\u0022,G=function(){var a=p;if(B)return/rv:([^\\);]\u002B)(\\)|;)/.exec(a);if(A)return/Edge\\/([\\d\\.]\u002B)/.exec(a);if(z)return/\\b(?:MSIE|rv)[: ]([^\\);]\u002B)(\\)|;)/.exec(a);if(C)return/WebKit\\/(\\S\u002B)/.exec(a);if(y)return/(?:Version)[ \\/]?(\\S\u002B)/.exec(a)}();G\u0026\u0026(F=G?G[1]:\u0022\u0022);if(z){var H=D();if(null!=H\u0026\u0026H\u003EparseFloat(F)){E=String(H);break a}}E=F}var I;I=d.document\u0026\u0026z?D():void 0;var J=u(),K=w()||t(\u0022iPod\u0022),L=t(\u0022iPad\u0022),M=t(\u0022Android\u0022)\u0026\u0026!(v()||u()||t(\u0022Opera\u0022)||t(\u0022Silk\u0022)),N=v(),aa=t(\u0022Safari\u0022)\u0026\u0026!(v()||t(\u0022Coast\u0022)||t(\u0022Opera\u0022)||t(\u0022Edge\u0022)||t(\u0022Edg/\u0022)||t(\u0022OPR\u0022)||u()||t(\u0022Silk\u0022)||t(\u0022Android\u0022))\u0026\u0026!(w()||t(\u0022iPad\u0022)||t(\u0022iPod\u0022));function O(a){return(a=a.exec(p))?a[1]:\u0022\u0022}(function(){if(J)return O(/Firefox\\/([0-9.]\u002B)/);if(z||A||y)return E;if(N)return w()||t(\u0022iPad\u0022)||t(\u0022iPod\u0022)?O(/CriOS\\/([0-9.]\u002B)/):O(/Chrome\\/([0-9.]\u002B)/);if(aa\u0026\u0026!(w()||t(\u0022iPad\u0022)||t(\u0022iPod\u0022)))return O(/Version\\/([0-9.]\u002B)/);if(K||L){var a=/Version\\/(\\S\u002B).*Mobile\\/(\\S\u002B)/.exec(p);if(a)return a[1]\u002B\u0022.\u0022\u002Ba[2]}else if(M)return(a=O(/Android\\s\u002B([0-9.]\u002B)/))?a:O(/Version\\/([0-9.]\u002B)/);return\u0022\u0022})();var P=z\u0026\u0026!(8\u003C=Number(I)),ba=z\u0026\u0026!(9\u003C=Number(I));var ca={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},Q={IMG:\u0022 \u0022,BR:\u0022\\n\u0022};function R(a,b,c){if(!(a.nodeName in ca))if(3==a.nodeType)c?b.push(String(a.nodeValue).replace(/(\\r\\n|\\r|\\n)/g,\u0022\u0022)):b.push(a.nodeValue);else if(a.nodeName in Q)b.push(Q[a.nodeName]);else for(a=a.firstChild;a;)R(a,b,c),a=a.nextSibling};function S(a,b){b=b.toLowerCase();return\u0022style\u0022==b?da(a.style.cssText):P\u0026\u0026\u0022value\u0022==b\u0026\u0026T(a,\u0022INPUT\u0022)?a.value:ba\u0026\u0026!0===a[b]?String(a.getAttribute(b)):(a=a.getAttributeNode(b))\u0026\u0026a.specified?a.value:null}var ea=/[;]\u002B(?=(?:(?:[^\u0022]*\u0022){2})*[^\u0022]*$)(?=(?:(?:[^\u0027]*\u0027){2})*[^\u0027]*$)(?=(?:[^()]*\\([^()]*\\))*[^()]*$)/;\nfunction da(a){var b=[];k(a.split(ea),function(c){var e=c.indexOf(\u0022:\u0022);0\u003Ce\u0026\u0026(c=[c.slice(0,e),c.slice(e\u002B1)],2==c.length\u0026\u0026b.push(c[0].toLowerCase(),\u0022:\u0022,c[1],\u0022;\u0022))});b=b.join(\u0022\u0022);return b=\u0022;\u0022==b.charAt(b.length-1)?b:b\u002B\u0022;\u0022}function U(a,b){P\u0026\u0026\u0022value\u0022==b\u0026\u0026T(a,\u0022OPTION\u0022)\u0026\u0026null===S(a,\u0022value\u0022)?(b=[],R(a,b,!1),a=b.join(\u0022\u0022)):a=a[b];return a}\nfunction T(a,b){b\u0026\u0026\u0022string\u0022!==typeof b\u0026\u0026(b=b.toString());return a instanceof HTMLFormElement?!!a\u0026\u00261==a.nodeType\u0026\u0026(!b||\u0022FORM\u0022==b):!!a\u0026\u00261==a.nodeType\u0026\u0026(!b||a.tagName.toUpperCase()==b)}function V(a){return T(a,\u0022OPTION\u0022)?!0:T(a,\u0022INPUT\u0022)?(a=a.type.toLowerCase(),\u0022checkbox\u0022==a||\u0022radio\u0022==a):!1};var fa={\u0022class\u0022:\u0022className\u0022,readonly:\u0022readOnly\u0022},ha=\u0022allowfullscreen allowpaymentrequest allowusermedia async autofocus autoplay checked compact complete controls declare default defaultchecked defaultselected defer disabled ended formnovalidate hidden indeterminate iscontenteditable ismap itemscope loop multiple muted nohref nomodule noresize noshade novalidate nowrap open paused playsinline pubdate readonly required reversed scoped seamless seeking selected truespeed typemustmatch willvalidate\u0022.split(\u0022 \u0022);function W(a,b){var c=null,e=b.toLowerCase();if(\u0022style\u0022==e)return(c=a.style)\u0026\u0026\u0022string\u0022!=typeof c\u0026\u0026(c=c.cssText),c;if((\u0022selected\u0022==e||\u0022checked\u0022==e)\u0026\u0026V(a)){if(!V(a))throw new l(15,\u0022Element is not selectable\u0022);b=\u0022selected\u0022;c=a.type\u0026\u0026a.type.toLowerCase();if(\u0022checkbox\u0022==c||\u0022radio\u0022==c)b=\u0022checked\u0022;return U(a,b)?\u0022true\u0022:null}var g=T(a,\u0022A\u0022);if(T(a,\u0022IMG\u0022)\u0026\u0026\u0022src\u0022==e||g\u0026\u0026\u0022href\u0022==e)return(c=S(a,e))\u0026\u0026(c=U(a,e)),c;if(\u0022spellcheck\u0022==e){c=S(a,e);if(null!==c){if(\u0022false\u0022==c.toLowerCase())return\u0022false\u0022;if(\u0022true\u0022==c.toLowerCase())return\u0022true\u0022}return U(a,\ne)\u002B\u0022\u0022}g=fa[b]||b;if(0\u003C=h(ha,e))return(c=null!==S(a,b)||U(a,g))?\u0022true\u0022:null;try{var x=U(a,g)}catch(ia){}(e=null==x)||(e=typeof x,e=\u0022object\u0022==e\u0026\u0026null!=x||\u0022function\u0022==e);e?c=S(a,b):c=x;return null!=c?c.toString():null}var X=[\u0022_\u0022],Y=d;X[0]in Y||\u0022undefined\u0022==typeof Y.execScript||Y.execScript(\u0022var \u0022\u002BX[0]);for(var Z;X.length\u0026\u0026(Z=X.shift());)X.length||void 0===W?Y[Z]\u0026\u0026Y[Z]!==Object.prototype[Z]?Y=Y[Z]:Y=Y[Z]={}:Y[Z]=W;; return this._.apply(null,arguments);}).apply({navigator:typeof window!=\u0027undefined\u0027?window.navigator:null,document:typeof window!=\u0027undefined\u0027?window.document:null}, arguments);}\n).apply(null, arguments);","args":[{"element-6066-11e4-a52e-4f735466cecf":"f.9886718C9DE6EE8026E45EBD8A61C827.d.176C6A5205EEB3B9B50684A1C283D06D.e.2"},"value"]}
    3206:  12:03:17.718 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/execute/sync, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3207:  {"script":"/* get-attribute */return (function(){return (function(){var d=this||self;function f(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a};var h=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(\u0022string\u0022===typeof a)return\u0022string\u0022!==typeof b||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c\u003Ca.length;c\u002B\u002B)if(c in a\u0026\u0026a[c]===b)return c;return-1},k=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,e=\u0022string\u0022===typeof a?a.split(\u0022\u0022):a,g=0;g\u003Cc;g\u002B\u002B)g in e\u0026\u0026b.call(void 0,e[g],g,a)};function l(a,b){this.code=a;this.a=m[a]||n;this.message=b||\u0022\u0022;a=this.a.replace(/((?:^|\\s\u002B)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\\s\\xa0]\u002B/g,\u0022\u0022)});b=a.length-5;if(0\u003Eb||a.indexOf(\u0022Error\u0022,b)!=b)a\u002B=\u0022Error\u0022;this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||\u0022\u0022}f(l,Error);var n=\u0022unknown error\u0022,m={15:\u0022element not selectable\u0022,11:\u0022element not visible\u0022};m[31]=n;m[30]=n;m[24]=\u0022invalid cookie domain\u0022;m[29]=\u0022invalid element coordinates\u0022;m[12]=\u0022invalid element state\u0022;m[32]=\u0022invalid selector\u0022;\nm[51]=\u0022invalid selector\u0022;m[52]=\u0022invalid selector\u0022;m[17]=\u0022javascript error\u0022;m[405]=\u0022unsupported operation\u0022;m[34]=\u0022move target out of bounds\u0022;m[27]=\u0022no such alert\u0022;m[7]=\u0022no such element\u0022;m[8]=\u0022no such frame\u0022;m[23]=\u0022no such window\u0022;m[28]=\u0022script timeout\u0022;m[33]=\u0022session not created\u0022;m[10]=\u0022stale element reference\u0022;m[21]=\u0022timeout\u0022;m[25]=\u0022unable to set cookie\u0022;m[26]=\u0022unexpected alert open\u0022;m[13]=n;m[9]=\u0022unknown command\u0022;var p;a:{var q=d.navigator;if(q){var r=q.userAgent;if(r){p=r;break a}}p=\u0022\u0022}function t(a){return-1!=p.indexOf(a)};function u(){return t(\u0022Firefox\u0022)||t(\u0022FxiOS\u0022)}function v(){return(t(\u0022Chrome\u0022)||t(\u0022CriOS\u0022))\u0026\u0026!t(\u0022Edge\u0022)};function w(){return t(\u0022iPhone\u0022)\u0026\u0026!t(\u0022iPod\u0022)\u0026\u0026!t(\u0022iPad\u0022)};var y=t(\u0022Opera\u0022),z=t(\u0022Trident\u0022)||t(\u0022MSIE\u0022),A=t(\u0022Edge\u0022),B=t(\u0022Gecko\u0022)\u0026\u0026!(-1!=p.toLowerCase().indexOf(\u0022webkit\u0022)\u0026\u0026!t(\u0022Edge\u0022))\u0026\u0026!(t(\u0022Trident\u0022)||t(\u0022MSIE\u0022))\u0026\u0026!t(\u0022Edge\u0022),C=-1!=p.toLowerCase().indexOf(\u0022webkit\u0022)\u0026\u0026!t(\u0022Edge\u0022);function D(){var a=d.document;return a?a.documentMode:void 0}var E;\na:{var F=\u0022\u0022,G=function(){var a=p;if(B)return/rv:([^\\);]\u002B)(\\)|;)/.exec(a);if(A)return/Edge\\/([\\d\\.]\u002B)/.exec(a);if(z)return/\\b(?:MSIE|rv)[: ]([^\\);]\u002B)(\\)|;)/.exec(a);if(C)return/WebKit\\/(\\S\u002B)/.exec(a);if(y)return/(?:Version)[ \\/]?(\\S\u002B)/.exec(a)}();G\u0026\u0026(F=G?G[1]:\u0022\u0022);if(z){var H=D();if(null!=H\u0026\u0026H\u003EparseFloat(F)){E=String(H);break a}}E=F}var I;I=d.document\u0026\u0026z?D():void 0;var J=u(),K=w()||t(\u0022iPod\u0022),L=t(\u0022iPad\u0022),M=t(\u0022Android\u0022)\u0026\u0026!(v()||u()||t(\u0022Opera\u0022)||t(\u0022Silk\u0022)),N=v(),aa=t(\u0022Safari\u0022)\u0026\u0026!(v()||t(\u0022Coast\u0022)||t(\u0022Opera\u0022)||t(\u0022Edge\u0022)||t(\u0022Edg/\u0022)||t(\u0022OPR\u0022)||u()||t(\u0022Silk\u0022)||t(\u0022Android\u0022))\u0026\u0026!(w()||t(\u0022iPad\u0022)||t(\u0022iPod\u0022));function O(a){return(a=a.exec(p))?a[1]:\u0022\u0022}(function(){if(J)return O(/Firefox\\/([0-9.]\u002B)/);if(z||A||y)return E;if(N)return w()||t(\u0022iPad\u0022)||t(\u0022iPod\u0022)?O(/CriOS\\/([0-9.]\u002B)/):O(/Chrome\\/([0-9.]\u002B)/);if(aa\u0026\u0026!(w()||t(\u0022iPad\u0022)||t(\u0022iPod\u0022)))return O(/Version\\/([0-9.]\u002B)/);if(K||L){var a=/Version\\/(\\S\u002B).*Mobile\\/(\\S\u002B)/.exec(p);if(a)return a[1]\u002B\u0022.\u0022\u002Ba[2]}else if(M)return(a=O(/Android\\s\u002B([0-9.]\u002B)/))?a:O(/Version\\/([0-9.]\u002B)/);return\u0022\u0022})();var P=z\u0026\u0026!(8\u003C=Number(I)),ba=z\u0026\u0026!(9\u003C=Number(I));var ca={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},Q={IMG:\u0022 \u0022,BR:\u0022\\n\u0022};function R(a,b,c){if(!(a.nodeName in ca))if(3==a.nodeType)c?b.push(String(a.nodeValue).replace(/(\\r\\n|\\r|\\n)/g,\u0022\u0022)):b.push(a.nodeValue);else if(a.nodeName in Q)b.push(Q[a.nodeName]);else for(a=a.firstChild;a;)R(a,b,c),a=a.nextSibling};function S(a,b){b=b.toLowerCase();return\u0022style\u0022==b?da(a.style.cssText):P\u0026\u0026\u0022value\u0022==b\u0026\u0026T(a,\u0022INPUT\u0022)?a.value:ba\u0026\u0026!0===a[b]?String(a.getAttribute(b)):(a=a.getAttributeNode(b))\u0026\u0026a.specified?a.value:null}var ea=/[;]\u002B(?=(?:(?:[^\u0022]*\u0022){2})*[^\u0022]*$)(?=(?:(?:[^\u0027]*\u0027){2})*[^\u0027]*$)(?=(?:[^()]*\\([^()]*\\))*[^()]*$)/;\nfunction da(a){var b=[];k(a.split(ea),function(c){var e=c.indexOf(\u0022:\u0022);0\u003Ce\u0026\u0026(c=[c.slice(0,e),c.slice(e\u002B1)],2==c.length\u0026\u0026b.push(c[0].toLowerCase(),\u0022:\u0022,c[1],\u0022;\u0022))});b=b.join(\u0022\u0022);return b=\u0022;\u0022==b.charAt(b.length-1)?b:b\u002B\u0022;\u0022}function U(a,b){P\u0026\u0026\u0022value\u0022==b\u0026\u0026T(a,\u0022OPTION\u0022)\u0026\u0026null===S(a,\u0022value\u0022)?(b=[],R(a,b,!1),a=b.join(\u0022\u0022)):a=a[b];return a}\nfunction T(a,b){b\u0026\u0026\u0022string\u0022!==typeof b\u0026\u0026(b=b.toString());return a instanceof HTMLFormElement?!!a\u0026\u00261==a.nodeType\u0026\u0026(!b||\u0022FORM\u0022==b):!!a\u0026\u00261==a.nodeType\u0026\u0026(!b||a.tagName.toUpperCase()==b)}function V(a){return T(a,\u0022OPTION\u0022)?!0:T(a,\u0022INPUT\u0022)?(a=a.type.toLowerCase(),\u0022checkbox\u0022==a||\u0022radio\u0022==a):!1};var fa={\u0022class\u0022:\u0022className\u0022,readonly:\u0022readOnly\u0022},ha=\u0022allowfullscreen allowpaymentrequest allowusermedia async autofocus autoplay checked compact complete controls declare default defaultchecked defaultselected defer disabled ended formnovalidate hidden indeterminate iscontenteditable ismap itemscope loop multiple muted nohref nomodule noresize noshade novalidate nowrap open paused playsinline pubdate readonly required reversed scoped seamless seeking selected truespeed typemustmatch willvalidate\u0022.split(\u0022 \u0022);function W(a,b){var c=null,e=b.toLowerCase();if(\u0022style\u0022==e)return(c=a.style)\u0026\u0026\u0022string\u0022!=typeof c\u0026\u0026(c=c.cssText),c;if((\u0022selected\u0022==e||\u0022checked\u0022==e)\u0026\u0026V(a)){if(!V(a))throw new l(15,\u0022Element is not selectable\u0022);b=\u0022selected\u0022;c=a.type\u0026\u0026a.type.toLowerCase();if(\u0022checkbox\u0022==c||\u0022radio\u0022==c)b=\u0022checked\u0022;return U(a,b)?\u0022true\u0022:null}var g=T(a,\u0022A\u0022);if(T(a,\u0022IMG\u0022)\u0026\u0026\u0022src\u0022==e||g\u0026\u0026\u0022href\u0022==e)return(c=S(a,e))\u0026\u0026(c=U(a,e)),c;if(\u0022spellcheck\u0022==e){c=S(a,e);if(null!==c){if(\u0022false\u0022==c.toLowerCase())return\u0022false\u0022;if(\u0022true\u0022==c.toLowerCase())return\u0022true\u0022}return U(a,\ne)\u002B\u0022\u0022}g=fa[b]||b;if(0\u003C=h(ha,e))return(c=null!==S(a,b)||U(a,g))?\u0022true\u0022:null;try{var x=U(a,g)}catch(ia){}(e=null==x)||(e=typeof x,e=\u0022object\u0022==e\u0026\u0026null!=x||\u0022function\u0022==e);e?c=S(a,b):c=x;return null!=c?c.toString():null}var X=[\u0022_\u0022],Y=d;X[0]in Y||\u0022undefined\u0022==typeof Y.execScript||Y.execScript(\u0022var \u0022\u002BX[0]);for(var Z;X.length\u0026\u0026(Z=X.shift());)X.length||void 0===W?Y[Z]\u0026\u0026Y[Z]!==Object.prototype[Z]?Y=Y[Z]:Y=Y[Z]={}:Y[Z]=W;; return this._.apply(null,arguments);}).apply({navigator:typeof window!=\u0027undefined\u0027?window.navigator:null,document:typeof window!=\u0027undefined\u0027?window.document:null}, arguments);}\n).apply(null, arguments);","args":[{"element-6066-11e4-a52e-4f735466cecf":"f.9886718C9DE6EE8026E45EBD8A61C827.d.176C6A5205EEB3B9B50684A1C283D06D.e.2"},"value"]}
    ...
    
    3232:  {"url":"http://localhost:44935/common/ajaxy_page.html"}
    3233:  12:03:18.298 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3234:  12:03:18.298 DEBUG HttpCommandExecutor: Response: ( Success: )
    3235:  12:03:18.298 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: setTimeouts {"script":3000}
    3236:  12:03:18.298 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/timeouts, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3237:  {"script":3000}
    3238:  12:03:18.299 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3239:  12:03:18.309 DEBUG HttpCommandExecutor: Response: ( Success: )
    3240:  12:03:18.310 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: executeAsyncScript {"script":"var url = arguments[0];var callback = arguments[arguments.length - 1];var XMLHttpFactories = [  function () {return new XMLHttpRequest()},  function () {return new ActiveXObject(\u0027Msxml2.XMLHTTP\u0027)},  function () {return new ActiveXObject(\u0027Msxml3.XMLHTTP\u0027)},  function () {return new ActiveXObject(\u0027Microsoft.XMLHTTP\u0027)}];var xhr = false;while (!xhr \u0026\u0026 XMLHttpFactories.length) {  try {    xhr = XMLHttpFactories.shift().call();  } catch (e) {}}if (!xhr) throw Error(\u0027unable to create XHR object\u0027);xhr.open(\u0027GET\u0027, url, true);xhr.onreadystatechange = function() {  if (xhr.readyState == 4) callback(xhr.responseText);};xhr.send();","args":["http://localhost:44935/common/sleep?time=2"]}
    3241:  12:03:18.311 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/execute/async, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3242:  {"script":"var url = arguments[0];var callback = arguments[arguments.length - 1];var XMLHttpFactories = [  function () {return new XMLHttpRequest()},  function () {return new ActiveXObject(\u0027Msxml2.XMLHTTP\u0027)},  function () {return new ActiveXObject(\u0027Msxml3.XMLHTTP\u0027)},  function () {return new ActiveXObject(\u0027Microsoft.XMLHTTP\u0027)}];var xhr = false;while (!xhr \u0026\u0026 XMLHttpFactories.length) {  try {    xhr = XMLHttpFactories.shift().call();  } catch (e) {}}if (!xhr) throw Error(\u0027unable to create XHR object\u0027);xhr.open(\u0027GET\u0027, url, true);xhr.onreadystatechange = function() {  if (xhr.readyState == 4) callback(xhr.responseText);};xhr.send();","args":["http://localhost:44935/common/sleep?time=2"]}
    ...
    
    3485:  12:03:27.174 TRACE HttpCommandExecutor: >> GET RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/element/f.9886718C9DE6EE8026E45EBD8A61C827.d.F0DDB07A7FAEDD89D1048196C8454CFA.e.103/name, Content: null, Headers: 3
    3486:  12:03:27.224 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3487:  12:03:27.224 DEBUG HttpCommandExecutor: Response: ( Success: body)
    3488:  12:03:27.224 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: setTimeouts {"script":30000}
    3489:  12:03:27.225 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/timeouts, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3490:  {"script":30000}
    3491:  12:03:27.225 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3492:  12:03:27.226 DEBUG HttpCommandExecutor: Response: ( Success: )
    3493:  => OpenQA.Selenium.ExecutingAsyncJavascriptTest.ShouldCatchErrorsWhenExecutingInitialScript
    ...
    
    3500:  {"script":1000}
    3501:  12:03:27.257 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3502:  12:03:27.258 DEBUG HttpCommandExecutor: Response: ( Success: )
    3503:  12:03:27.258 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: get {"url":"http://localhost:44935/common/ajaxy_page.html"}
    3504:  12:03:27.270 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/url, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3505:  {"url":"http://localhost:44935/common/ajaxy_page.html"}
    3506:  12:03:27.610 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3507:  12:03:27.610 DEBUG HttpCommandExecutor: Response: ( Success: )
    3508:  12:03:27.634 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: executeAsyncScript {"script":"throw Error(\u0027you should catch this!\u0027);","args":[]}
    3509:  12:03:27.634 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/execute/async, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3510:  {"script":"throw Error(\u0027you should catch this!\u0027);","args":[]}
    3511:  12:03:27.714 TRACE HttpCommandExecutor: << StatusCode: 500, ReasonPhrase: Internal Server Error, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3512:  {"value":{"error":"javascript error","message":"javascript error: you should catch this!\nJavaScript stack:\nError: you should catch this!\n    at eval (eval at \u003Canonymous> (:468:26), \u003Canonymous>:3:32)\n    at eval (eval at \u003Canonymous> (:468:26), \u003Canonymous>:3:67)\n    at \u003Canonymous>:468:47\n    at new Promise (\u003Canonymous>)\n    at executeAsyncScript (\u003Canonymous>:462:17)\n    at \u003Canonymous>:492:29\n    at callFunction (\u003Canonymous>:397:22)\n    at \u003Canonymous>:411:23\n    at \u003Canonymous>:412:3\n  (Session info: MicrosoftEdge=131.0.2903.112)","stacktrace":"#0 0x5606cef8b94e \u003Cunknown>\n#1 0x5606cea40b76 \u003Cunknown>\n#2 0x5606cea48e1d \u003Cunknown>\n#3 0x5606cea488e6 \u003Cunknown>\n#4 0x5606cead12be \u003Cunknown>\n#5 0x5606ceab21e2 \u003Cunknown>\n#6 0x5606cead018a \u003Cunknown>\n#7 0x5606ceab1f83 \u003Cunknown>\n#8 0x5606cea8201e \u003Cunknown>\n#9 0x5606cea82cfe \u003Cunknown>\n#10 0x5606cef609e1 \u003Cunknown>\n#11 0x5606cef63bde \u003Cunknown>\n#12 0x5606cef6365b \u003Cunknown>\n#13 0x5606cef64005 \u003Cunknown>\n#14 0x5606cef523f2 \u003Cunknown>\n#15 0x5606cef6438d \u003Cunknown>\n#16 0x5606cef3c5b5 \u003Cunknown>\n#17 0x5606cef7c008 \u003Cunknown>\n#18 0x5606cef7c21f \u003Cunknown>\n#19 0x5606cef8a53c \u003Cunknown>\n#20 0x7f463e43f609 start_thread\n"}}
    3513:  12:03:27.715 DEBUG HttpCommandExecutor: Response: ( UnexpectedJavaScriptError: System.Collections.Generic.Dictionary`2[System.String,System.Object])
    3514:  12:03:27.732 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: setTimeouts {"script":30000}
    3515:  12:03:27.732 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/timeouts, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3516:  {"script":30000}
    3517:  12:03:27.733 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3518:  12:03:27.733 DEBUG HttpCommandExecutor: Response: ( Success: )
    3519:  => OpenQA.Selenium.ExecutingAsyncJavascriptTest.ShouldDetectPageLoadsWhileWaitingOnAnAsyncScriptAndReturnAnError
    ...
    
    3529:  12:03:27.737 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: get {"url":"http://localhost:44935/common/ajaxy_page.html"}
    3530:  12:03:27.760 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/url, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3531:  {"url":"http://localhost:44935/common/ajaxy_page.html"}
    3532:  12:03:28.401 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3533:  12:03:28.442 DEBUG HttpCommandExecutor: Response: ( Success: )
    3534:  12:03:28.444 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: executeAsyncScript {"script":"window.location = \u0027http://localhost:44935/common/dynamic.html\u0027;","args":[]}
    3535:  12:03:28.478 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/execute/async, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3536:  {"script":"window.location = \u0027http://localhost:44935/common/dynamic.html\u0027;","args":[]}
    3537:  12:03:29.435 TRACE HttpCommandExecutor: << StatusCode: 500, ReasonPhrase: Internal Server Error, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3538:  {"value":{"error":"script timeout","message":"script timeout\n  (Session info: MicrosoftEdge=131.0.2903.112)","stacktrace":"#0 0x5606cef8b94e \u003Cunknown>\n#1 0x5606cea40a34 \u003Cunknown>\n#2 0x5606cead12f9 \u003Cunknown>\n#3 0x5606ceab21e2 \u003Cunknown>\n#4 0x5606cead018a \u003Cunknown>\n#5 0x5606ceab1f83 \u003Cunknown>\n#6 0x5606cea8201e \u003Cunknown>\n#7 0x5606cea82cfe \u003Cunknown>\n#8 0x5606cef609e1 \u003Cunknown>\n#9 0x5606cef63bde \u003Cunknown>\n#10 0x5606cef6365b \u003Cunknown>\n#11 0x5606cef64005 \u003Cunknown>\n#12 0x5606cef523f2 \u003Cunknown>\n#13 0x5606cef6438d \u003Cunknown>\n#14 0x5606cef3c5b5 \u003Cunknown>\n#15 0x5606cef7c008 \u003Cunknown>\n#16 0x5606cef7c21f \u003Cunknown>\n#17 0x5606cef8a53c \u003Cunknown>\n#18 0x7f463e43f609 start_thread\n"}}
    ...
    
    3641:  12:03:31.381 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/url, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3642:  {"url":"http://localhost:44935/common/ajaxy_page.html"}
    3643:  12:03:31.911 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: OK, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3644:  12:03:31.912 DEBUG HttpCommandExecutor: Response: ( Success: )
    3645:  12:03:31.912 DEBUG HttpCommandExecutor: Executing command: [bcea218afafe2c011147f53ee4aaa391]: executeAsyncScript {"script":"return 1 \u002B 2;","args":[]}
    3646:  12:03:31.913 TRACE HttpCommandExecutor: >> POST RequestUri: http://localhost:41735/session/bcea218afafe2c011147f53ee4aaa391/execute/async, Content: System.Net.Http.ByteArrayContent, Headers: 2
    3647:  {"script":"return 1 \u002B 2;","args":[]}
    3648:  [1735301012.943][SEVERE]: Timed out receiving message from renderer: 1.000
    3649:  12:03:33.117 TRACE HttpCommandExecutor: << StatusCode: 500, ReasonPhrase: Internal Server Error, Content: System.Net.Http.HttpConnectionResponseContent, Headers: 1
    3650:  {"value":{"error":"script timeout","message":"script timeout\n  (Session info: MicrosoftEdge=131.0.2903.112)","stacktrace":"#0 0x5606cef8b94e \u003Cunknown>\n#1 0x5606cea40a34 \u003Cunknown>\n#2 0x5606cead12f9 \u003Cunknown>\n#3 0x5606ceab21e2 \u003Cunknown>\n#4 0x5606cead018a \u003Cunknown>\n#5 0x5606ceab1f83 \u003Cunknown>\n#6 0x5606cea8201e \u003Cunknown>\n#7 0x5606cea82cfe \u003Cunknown>\n#8 0x5606cef609e1 \u003Cun...

    @joerg1985 joerg1985 marked this pull request as draft November 24, 2024 17:56
    @joerg1985 joerg1985 marked this pull request as ready for review November 24, 2024 18:27
    Copy link
    Contributor

    Persistent review updated to latest commit 35a12b1

    @pujagani
    Copy link
    Contributor

    @mykola-mokhnach Can you please help review this PR?

    @joerg1985 joerg1985 marked this pull request as draft November 28, 2024 20:02
    @joerg1985 joerg1985 marked this pull request as ready for review November 28, 2024 20:56
    Copy link
    Contributor

    Persistent review updated to latest commit 906a5b3

    @joerg1985
    Copy link
    Member Author

    As soon as a static cache is used the tests break with for me unexplainable ClassCastExceptions.
    This is kind of strange, especially the unit tests start with an empty cache, so the instances are not reused at all.

    @mykola-mokhnach could you review the other changes, it now looks mutch cleaner.

    @Auto81
    Copy link

    Auto81 commented Dec 18, 2024

    Eagerly awaiting this fix going in, has it stalled?

    @pujagani pujagani removed their request for review December 20, 2024 08:45
    @pujagani
    Copy link
    Contributor

    I appreciate the ongoing effort by @joerg1985 and @mykola-mokhnach. Let me know when this is in a good shape and we can merge it.

    @joerg1985
    Copy link
    Member Author

    I my mind this is ready to get merged, @mykola-mokhnach do you agree?

    @mykola-mokhnach
    Copy link

    go for it if tests pass

    Copy link
    Member

    @diemol diemol left a comment

    Choose a reason for hiding this comment

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

    Thank you, @joerg1985!

    Thanks, @mykola-mokhnach for the review!

    @diemol diemol merged commit bb4153f into trunk Dec 27, 2024
    34 checks passed
    @diemol diemol deleted the too-many-classes branch December 27, 2024 15:33
    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