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

[rb] Add backtrace locations and cause to errors #14170

Merged
merged 42 commits into from
Jul 16, 2024

Conversation

aguspe
Copy link
Contributor

@aguspe aguspe commented Jun 22, 2024

User description

Description

The goal of this PR is to provide backtrace_locations and cause for users that need to consume it

Motivation and Context

A great detail explanation of the motivation is described here #13221 and also more information is provided in #13222

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

Enhancement, Tests


Description

  • Enhanced WebDriverError class by adding backtrace_locations method to fetch current thread's backtrace locations.
  • Enhanced WebDriverError class by adding cause method to fetch the error information.
  • Included English module for improved error handling.
  • Added tests to verify the presence of backtrace locations and cause in NoSuchElementError.

Changes walkthrough 📝

Relevant files
Enhancement
error.rb
Add backtrace locations and cause methods to WebDriverError class

rb/lib/selenium/webdriver/common/error.rb

  • Added backtrace_locations method to WebDriverError class to fetch
    current thread's backtrace locations.
  • Added cause method to WebDriverError class to fetch the error
    information.
  • Included English module for error handling.
  • +10/-1   
    Tests
    error_spec.rb
    Add tests for backtrace locations and cause in NoSuchElementError

    rb/spec/integration/selenium/webdriver/error_spec.rb

  • Added test to check presence of backtrace locations in
    NoSuchElementError.
  • Added test to check presence of cause in NoSuchElementError.
  • +12/-0   

    💡 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 [1-5] 2
    🧪 Relevant tests Yes
    🔒 Security concerns No
    ⚡ Key issues to review Thread Safety:
    The method backtrace_locations uses Thread.current.backtrace_locations which is thread-specific. Ensure that this usage is safe in the context of how the WebDriverError class is utilized across different threads.
    Error Handling:
    The cause method returns $ERROR_INFO, which is a global variable provided by the 'English' library for the last raised exception. Confirm that this approach correctly captures the intended exceptions in all scenarios.

    Copy link
    Contributor

    PR Code Suggestions ✨

    CategorySuggestion                                                                                                                                    Score
    Best practice
    Use super to call the parent class's cause method instead of $ERROR_INFO

    Instead of using $ERROR_INFO to get the cause, use super to call the parent class's cause
    method. This ensures better compatibility and maintainability.

    rb/lib/selenium/webdriver/common/error.rb [46-48]

     def cause
    -  $ERROR_INFO
    +  super
     end
     
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    Why: Using super instead of $ERROR_INFO leverages Ruby's inheritance and is more maintainable and idiomatic in Ruby, addressing a significant maintainability issue.

    8
    Enhancement
    Ensure backtrace_locations returns an array of Thread::Backtrace::Location objects

    Add a check to ensure that backtrace_locations returns an array of
    Thread::Backtrace::Location objects to make the test more robust.

    rb/spec/integration/selenium/webdriver/error_spec.rb [36]

    -expect(e.backtrace_locations).not_to be_empty
    +expect(e.backtrace_locations).to all(be_a(Thread::Backtrace::Location))
     
    • Apply this suggestion
    Suggestion importance[1-10]: 7

    Why: This suggestion improves the robustness of the test by ensuring that backtrace_locations not only exists but contains the correct type of objects.

    7
    Ensure the cause method returns a non-nil value and is an instance of the expected error class

    Improve the test for the cause method by checking that the cause is not nil, in addition
    to being an instance of WebDriver::Error::NoSuchElementError.

    rb/spec/integration/selenium/webdriver/error_spec.rb [42]

    +expect(e.cause).not_to be_nil
     expect(e.cause).to be_a(WebDriver::Error::NoSuchElementError)
     
    • Apply this suggestion
    Suggestion importance[1-10]: 6

    Why: This suggestion enhances the test by ensuring that the cause method returns meaningful and correct results, although the existing test already checks for the correct instance type.

    6
    Possible issue
    Add a guard clause to handle nil values in backtrace_locations

    Add a guard clause to backtrace_locations to handle cases where
    Thread.current.backtrace_locations might return nil, preventing potential errors.

    rb/lib/selenium/webdriver/common/error.rb [42-44]

     def backtrace_locations
    -  Thread.current.backtrace_locations
    +  Thread.current.backtrace_locations || []
     end
     
    • Apply this suggestion
    Suggestion importance[1-10]: 7

    Why: Adding a guard clause is a good practice to prevent potential runtime errors if Thread.current.backtrace_locations returns nil, thus improving the code's robustness.

    7

    @aguspe aguspe changed the title Add backtrace locations and cause to errors [rb] Add backtrace locations and cause to errors Jun 22, 2024
    @aguspe
    Copy link
    Contributor Author

    aguspe commented Jun 25, 2024

    I resolved the conflicts after the merge of #14174 and updated the error class, all the tests seem to be working, could I get a review? @p0deje

    Copy link
    Member

    @p0deje p0deje left a comment

    Choose a reason for hiding this comment

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

    I am not sure I follow the implementation and how it works. What we discussed in #13222 was to stop manipulating backtrace, create a nextra error instance and explicitly pass cause for errors created from the remote server in

    ex = klass.new(message)
    ex.set_backtrace(caller)
    add_backtrace ex, backtrace
    .

    Does this PR achieve the same in some other manner?

    @aguspe
    Copy link
    Contributor Author

    aguspe commented Jun 26, 2024

    I am not sure I follow the implementation and how it works. What we discussed in #13222 was to stop manipulating backtrace, create a nextra error instance and explicitly pass cause for errors created from the remote server in

    ex = klass.new(message)
    ex.set_backtrace(caller)
    add_backtrace ex, backtrace

    .
    Does this PR achieve the same in some other manner?

    Oh Maybe I miss understood this, because I tried to call both backtrace_locations and case on the ex, and I noticed I couldn't so I implemented the methods that are being inherited from exception, so now when creating an error both the case and backtrace_locations are set automatically

    So for example this test:

          it 'has cause' do
            driver.find_element(id: 'nonexistent')
          rescue WebDriver::Error::NoSuchElementError => e
            expect(e.cause).to be_a(WebDriver::Error::NoSuchElementError)
          end

    returns cause as:

    #<Selenium::WebDriver::Error::NoSuchElementError: no such element: Unable to locate element: {"method":"css selector","selector":"#nonexistent"}
      (Session info: chrome=126.0.6478.116); For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception>
    

    And this test :

    it 'has backtrace locations' do
      driver.find_element(id: 'nonexistent')
      rescue WebDriver::Error::NoSuchElementError => e
      expect(e.backtrace_locations).not_to be_empty
    end
      Returns backtrace locations as:
     
     ["/Users/apeque01/Desktop/main_folder/Projects/open_source/selenium/rb/lib/selenium/webdriver/common/error.rb:57:in `backtrace_locations'",
     "/Users/apeque01/Desktop/main_folder/Projects/open_source/selenium/rb/lib/selenium/webdriver/common/error.rb:57:in `backtrace_locations'",
     "/Users/apeque01/Desktop/main_folder/Projects/open_source/selenium/rb/spec/integration/selenium/webdriver/error_spec.rb:36:in `rescue in block (2 levels) in <module:WebDriver>'",
     "/Users/apeque01/Desktop/main_folder/Projects/open_source/selenium/rb/spec/integration/selenium/webdriver/error_spec.rb:34:in `block (2 levels) in <module:WebDriver>'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:263:in `instance_exec'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:263:in `block in run'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:511:in `block in with_around_and_singleton_context_hooks'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:468:in `block in with_around_example_hooks'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb:486:in `block in run'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb:626:in `block in run_around_example_hooks_for'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:352:in `call'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-expectations-3.13.1/lib/rspec/expectations/failure_aggregator.rb:25:in `block in aggregate'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-support-3.13.1/lib/rspec/support.rb:126:in `with_failure_notifier'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-expectations-3.13.1/lib/rspec/expectations/failure_aggregator.rb:23:in `aggregate'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-expectations-3.13.1/lib/rspec/matchers.rb:306:in `aggregate_failures'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/configuration.rb:2281:in `block in define_built_in_hooks'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:457:in `instance_exec'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:457:in `instance_exec'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb:390:in `execute_with'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb:628:in `block (2 levels) in run_around_example_hooks_for'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:352:in `call'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb:629:in `run_around_example_hooks_for'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb:486:in `run'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:468:in `with_around_example_hooks'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:511:in `with_around_and_singleton_context_hooks'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example.rb:259:in `run'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example_group.rb:646:in `block in run_examples'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example_group.rb:642:in `map'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example_group.rb:642:in `run_examples'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/example_group.rb:607:in `run'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:121:in `block (3 levels) in run_specs'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:121:in `map'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:121:in `block (2 levels) in run_specs'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/configuration.rb:2091:in `with_suite_hooks'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:116:in `block in run_specs'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/reporter.rb:74:in `report'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:115:in `run_specs'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:89:in `run'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:71:in `run'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/lib/rspec/core/runner.rb:45:in `invoke'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/gems/rspec-core-3.13.0/exe/rspec:4:in `<top (required)>'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/bin/rspec:25:in `load'",
     "/Users/apeque01/.rvm/gems/ruby-3.0.0/bin/rspec:25:in `<main>'"]
     
    

    Also these are the methods from the exception class that I'm overriding, that returns nil even if the backtrace is not set:

       # This method is not affected by Exception#set_backtrace().
      def backtrace_locations; end
    
      # Returns the previous exception ($!) at the time this exception was raised.
      # This is useful for wrapping exceptions and retaining the original exception
      # information.
      def cause; end

    @aguspe
    Copy link
    Contributor Author

    aguspe commented Jun 26, 2024

    @p0deje I see what you are saying and I updated the implementation by removing the backtrace manipulation, and the test kept working, now I will update the cause, thank you! it's my first time working with exceptions so sorry for the confusion

    @ioquatix
    Copy link

    This looks like it's going in the right direction! Nice work.

    @aguspe
    Copy link
    Contributor Author

    aguspe commented Jul 13, 2024

    This looks like it's going in the right direction! Nice work.

    Thank you so much, hopefully based on my last comment regarding the backtrace and cause we know where to go from here :) and also thank you @p0deje for all the help

    @aguspe
    Copy link
    Contributor Author

    aguspe commented Jul 13, 2024

    @p0deje I looked into the failures, and they seems to be related to the action builder and Firefox on windows, I can try to use parallels on my mac to look into them, but I'm not sure if those tests should be fixed on this PR

    @p0deje
    Copy link
    Member

    p0deje commented Jul 14, 2024

    @p0deje I looked into the failures, and they seems to be related to the action builder and Firefox on windows, I can try to use parallels on my mac to look into them, but I'm not sure if those tests should be fixed on this PR

    Don't worry about these, they are not related.

    @ioquatix
    Copy link

    You might want to split it if it's not an array of strings - you might want to test it.

    You can also see the output by using Exception#full_message which prints the message and backtrace.

    @p0deje p0deje merged commit d883028 into SeleniumHQ:trunk Jul 16, 2024
    20 of 23 checks passed
    server_trace.split("\n")
    end

    ex.set_backtrace(backtrace + ex.backtrace)

    Choose a reason for hiding this comment

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

    This is probably the only line that should have been changed.

    Copy link
    Contributor Author

    Choose a reason for hiding this comment

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

    I noticed during testing that when setting the backtrace to the exception a couple of things happened:

    • The cause was not set which was the issue

    • Depending on the type of ex.backtrace this ended up in a type error

    • As the example in the previous conversation and the testing done with the selenium grid, it did not seem to have the effect we expected in the formatting of the backtrace, I added examples and my reasoning here: [rb] Add backtrace locations and cause to errors #14170 (comment)

    Let me know if this doesn't make much sense to you and I will gladly make a PR to update this :) thank you so much for the help!

    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.

    3 participants