Skip to content

Commit

Permalink
Allow to listen for JavaScript exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
p0deje committed Sep 28, 2020
1 parent 7cec87e commit 0b5766a
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 21 deletions.
14 changes: 7 additions & 7 deletions common/src/web/javascriptPage.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ <h1>Type Stuff</h1>
</div>

</div>

<div id="formageddon">
<form action="#">
Key Up: <input type="text" id="keyUp" onkeyup="javascript:updateContent(this)"/><br/>
Expand Down Expand Up @@ -151,15 +151,15 @@ <h1>Type Stuff</h1>
/>

<input id="changeable" name="changeable" onfocus="appendMessage('focus')" onchange="appendMessage('change')" onblur="appendMessage('blur')"/>
<button type="button" id="plainButton"

<button type="button" id="plainButton"
onfocus="appendMessage('focus')"
onkeydown="appendMessage('keydown')"
onkeypress="appendMessage('keypress')"
onkeyup="appendMessage('keyup')"
onblur="appendMessage('blur')"
onclick="appendMessage('click')"
onmousedown="appendMessage('mousedown ')"
onmousedown="appendMessage('mousedown ')"
onmouseup="appendMessage('mouseup ')"
onmouseover="register('mouseover ')"
onmousemove="register('mousemove ')"
Expand All @@ -173,7 +173,7 @@ <h1>Type Stuff</h1>
<button type="button" onclick="var element = document.getElementById('switchFocus'); var clickEvent = document.createEvent('MouseEvents'); clickEvent.initMouseEvent('click', true, true, null, 0, 0, 0, 0, 0,false, false, false, false, 0, element);element.dispatchEvent(clickEvent);">Do magic</button><br/>
<label id="labelForCheckbox" for="labeledCheckbox" onclick="appendMessage('labelclick')">Toggle checkbox</label><input type="checkbox" id="labeledCheckbox" onclick="appendMessage('chboxclick')"/>
</form>

<form action="javascriptPage.html" id="submitListeningForm" onsubmit="appendMessage('form-onsubmit '); return false;">
<p>
<input id="submitListeningForm-text" type="text" onsubmit="appendMessage('text-onsubmit ')" onclick="appendMessage('text-onclick ');" />
Expand All @@ -198,7 +198,7 @@ <h1>Type Stuff</h1>
<a href="#" id="hiddenlink">ok</a>
</div>
</div>

<div style="visibility: hidden;">
<span>
<input id="unclickable" />
Expand Down Expand Up @@ -265,7 +265,7 @@ <h1>Type Stuff</h1>

<a id="new_window" onmouseup="window.open('closeable_window.html', 'close_me')" href="#">Click me to open a new window</a>

<a id="throwing-mouseover" onmouseover="throw new Error()" href="#throwing-mouseover">Mouse over me will throw a JS error</a>
<a id="throwing-mouseover" onmouseover="throw new Error('I like cheese')" href="#throwing-mouseover">Mouse over me will throw a JS error</a>

<div id="parent">
<span id="movable" onmouseover="var p = document.getElementById('movable'); displayMessage('parent matches? ' + (p != event.relatedTarget));">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,46 +21,76 @@ 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'],
timestamp: params['timestamp'],
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
Expand Down
1 change: 1 addition & 0 deletions rb/lib/selenium/webdriver/devtools.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
36 changes: 36 additions & 0 deletions rb/lib/selenium/webdriver/devtools/exception_event.rb
Original file line number Diff line number Diff line change
@@ -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
15 changes: 14 additions & 1 deletion rb/spec/integration/selenium/webdriver/devtools_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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');")
Expand All @@ -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

0 comments on commit 0b5766a

Please sign in to comment.