diff --git a/common/src/web/javascriptPage.html b/common/src/web/javascriptPage.html index fcac252e899ab..1690ad07dd795 100644 --- a/common/src/web/javascriptPage.html +++ b/common/src/web/javascriptPage.html @@ -86,7 +86,7 @@

Type Stuff

- +
Key Up:
@@ -151,15 +151,15 @@

Type Stuff

/> - -
- +

@@ -198,7 +198,7 @@

Type Stuff

ok
- +
@@ -265,7 +265,7 @@

Type Stuff

Click me to open a new window -Mouse over me will throw a JS error +Mouse over me will throw a JS error
diff --git a/rb/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb b/rb/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb index a9a57de8ea9d1..83b1909a78e2c 100644 --- a/rb/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +++ b/rb/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb @@ -21,29 +21,51 @@ module Selenium module WebDriver module DriverExtensions module HasLogEvents + KINDS = %i[console exception].freeze - # # # Registers listener to be called whenever browser receives - # a new Console API message such as console.log(). + # a new Console API message such as console.log() or an unhandled + # exception. + # # This currently relies on DevTools so is only supported in # Chromium browsers. # - # @example + # @example Collect console messages # logs = [] - # driver.on_log_event do |event| + # driver.on_log_event(:console) do |event| # logs.push(event) # end # - # @param [#call] block which yields DevTools::ConsoleEvent + # @example Collect JavaScript exceptions + # exceptions = [] + # driver.on_log_event(:exception) do |event| + # exceptions.push(event) + # end # + # @param [Symbol] kind :console or :exception + # @param [#call] block which is called when event happens + # @yieldparam [DevTools::ConsoleEvent, DevTools::ExceptionEvent] + # + + def on_log_event(kind, &block) + raise WebDriverError, "Don't know how to handle #{kind} events" unless KINDS.include?(kind) - def on_log_event(&block) - console_listeners_enabled = console_listeners.any? - console_listeners << block - return if console_listeners_enabled + enabled = log_listeners[kind].any? + log_listeners[kind] << block + return if enabled devtools.runtime.enable + __send__("log_#{kind}_events") + end + + private + + def log_listeners + @log_listeners ||= Hash.new { |listeners, kind| listeners[kind] = [] } + end + + def log_console_events devtools.runtime.on(:console_api_called) do |params| event = DevTools::ConsoleEvent.new( type: params['type'], @@ -51,16 +73,24 @@ def on_log_event(&block) args: params['args'] ) - console_listeners.each do |listener| + log_listeners[:console].each do |listener| listener.call(event) end end end - private + def log_exception_events + devtools.runtime.on(:exception_thrown) do |params| + event = DevTools::ExceptionEvent.new( + description: params.dig('exceptionDetails', 'exception', 'description'), + timestamp: params['timestamp'], + stacktrace: params.dig('exceptionDetails', 'stackTrace', 'callFrames') + ) - def console_listeners - @console_listeners ||= [] + log_listeners[:exception].each do |listener| + listener.call(event) + end + end end end # HasLogEvents diff --git a/rb/lib/selenium/webdriver/devtools.rb b/rb/lib/selenium/webdriver/devtools.rb index 7e02251d67763..6318a7dfebbe0 100644 --- a/rb/lib/selenium/webdriver/devtools.rb +++ b/rb/lib/selenium/webdriver/devtools.rb @@ -21,6 +21,7 @@ module Selenium module WebDriver class DevTools autoload :ConsoleEvent, 'selenium/webdriver/devtools/console_event' + autoload :ExceptionEvent, 'selenium/webdriver/devtools/exception_event' SUPPORTED_VERSIONS = [84, 85, 86].freeze diff --git a/rb/lib/selenium/webdriver/devtools/exception_event.rb b/rb/lib/selenium/webdriver/devtools/exception_event.rb new file mode 100644 index 0000000000000..df4eb75558bd4 --- /dev/null +++ b/rb/lib/selenium/webdriver/devtools/exception_event.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +module Selenium + module WebDriver + class DevTools + class ExceptionEvent + + attr_accessor :description, :timestamp, :stacktrace + + def initialize(description:, timestamp:, stacktrace:) + @description = description + @timestamp = Time.at(timestamp / 1000) + @stacktrace = stacktrace + end + + end # ExceptionEvent + end # DevTools + end # WebDriver +end # Selenium diff --git a/rb/spec/integration/selenium/webdriver/devtools_spec.rb b/rb/spec/integration/selenium/webdriver/devtools_spec.rb index 6f03a151170dd..6aa07a43cf01c 100644 --- a/rb/spec/integration/selenium/webdriver/devtools_spec.rb +++ b/rb/spec/integration/selenium/webdriver/devtools_spec.rb @@ -64,7 +64,7 @@ module WebDriver it 'notifies about log messages' do logs = [] - driver.on_log_event { |log| logs.push(log) } + driver.on_log_event(:console) { |log| logs.push(log) } driver.navigate.to url_for('javascriptPage.html') driver.execute_script("console.log('I like cheese');") @@ -82,6 +82,19 @@ module WebDriver an_object_having_attributes(type: :log, args: [hash_including('type' => 'object')]) ) end + + it 'notifies about exceptions' do + exceptions = [] + driver.on_log_event(:exception) { |exception| exceptions.push(exception) } + driver.navigate.to url_for('javascriptPage.html') + + driver.find_element(id: 'throwing-mouseover').click + wait.until { exceptions.any? } + + exception = exceptions.first + expect(exception.description).to include('Error: I like cheese') + expect(exception.stacktrace).not_to be_empty + end end end end