Skip to content

Commit

Permalink
Allow to listen for DOM mutations
Browse files Browse the repository at this point in the history
This for now only implements attribute changes.
  • Loading branch information
p0deje committed Oct 5, 2020
1 parent 3f210c1 commit abe3e50
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 5 deletions.
3 changes: 3 additions & 0 deletions rb/build.desc
Expand Up @@ -291,6 +291,9 @@ ruby_library(name = "devtools",
":cdp-v84",
":cdp-v85",
":cdp-v86"
],
resources = [
{ "../javascript/cdp-support/mutation-listener.js": "rb/lib/selenium/webdriver/atoms/mutationListener.js" }
]
)

Expand Down
2 changes: 1 addition & 1 deletion rb/lib/selenium/webdriver.rb
Expand Up @@ -25,8 +25,8 @@
require 'set'
require 'websocket'

require 'selenium/webdriver/common'
require 'selenium/webdriver/atoms'
require 'selenium/webdriver/common'
require 'selenium/webdriver/version'

module Selenium
Expand Down
Expand Up @@ -21,7 +21,9 @@ module Selenium
module WebDriver
module DriverExtensions
module HasLogEvents
KINDS = %i[console exception].freeze
include Atoms

KINDS = %i[console exception mutation].freeze

#
# Registers listener to be called whenever browser receives
Expand All @@ -43,13 +45,19 @@ module HasLogEvents
# exceptions.push(event)
# end
#
# @param [Symbol] kind :console or :exception
# @example Collect DOM mutations
# mutations = []
# driver.on_log_event(:mutation) do |event|
# mutations.push(event)
# end
#
# @param [Symbol] kind :console, :exception or :mutation
# @param [#call] block which is called when event happens
# @yieldparam [DevTools::ConsoleEvent, DevTools::ExceptionEvent]
# @yieldparam [DevTools::ConsoleEvent, DevTools::ExceptionEvent, DevTools::MutationEvent]
#

def on_log_event(kind, &block)
raise WebDriverError, "Don't know how to handle #{kind} events" unless KINDS.include?(kind)
raise Error::WebDriverError, "Don't know how to handle #{kind} events" unless KINDS.include?(kind)

enabled = log_listeners[kind].any?
log_listeners[kind] << block
Expand Down Expand Up @@ -93,6 +101,42 @@ def log_exception_events
end
end

def log_mutation_events
devtools.page.enable

devtools.runtime.add_binding(name: '__webdriver_attribute')
execute_script(mutation_listener)
script = devtools.page.add_script_to_evaluate_on_new_document(source: mutation_listener)
pinned_scripts[mutation_listener] = script['identifier']

devtools.runtime.on(:binding_called, &method(:log_mutation_event))
end

def log_mutation_event(params)
payload = JSON.parse(params['payload'])
elements = find_elements(css: "*[data-__webdriver_id='#{payload['target']}']")
return if elements.empty?

event = DevTools::MutationEvent.new(
element: elements.first,
attribute_name: payload['name'],
current_value: payload['value'],
old_value: payload['oldValue']
)

log_listeners[:mutation].each do |log_listener|
log_listener.call(event)
end
end

def mutation_listener
@mutation_listener ||= read_atom(:mutationListener)
end

def pinned_scripts
@pinned_scripts ||= {}
end

end # HasLogEvents
end # DriverExtensions
end # WebDriver
Expand Down
1 change: 1 addition & 0 deletions rb/lib/selenium/webdriver/devtools.rb
Expand Up @@ -22,6 +22,7 @@ module WebDriver
class DevTools
autoload :ConsoleEvent, 'selenium/webdriver/devtools/console_event'
autoload :ExceptionEvent, 'selenium/webdriver/devtools/exception_event'
autoload :MutationEvent, 'selenium/webdriver/devtools/mutation_event'

SUPPORTED_VERSIONS = [84, 85, 86].freeze

Expand Down
37 changes: 37 additions & 0 deletions rb/lib/selenium/webdriver/devtools/mutation_event.rb
@@ -0,0 +1,37 @@
# 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 MutationEvent

attr_accessor :element, :attribute_name, :current_value, :old_value

def initialize(element:, attribute_name:, current_value:, old_value:)
@element = element
@attribute_name = attribute_name
@current_value = current_value
@old_value = old_value
end

end # MutationEvent
end # DevTools
end # WebDriver
end # Selenium
15 changes: 15 additions & 0 deletions rb/spec/integration/selenium/webdriver/devtools_spec.rb
Expand Up @@ -95,6 +95,21 @@ module WebDriver
expect(exception.description).to include('Error: I like cheese')
expect(exception.stacktrace).not_to be_empty
end

it 'notifies about DOM mutations' do
mutations = []
driver.on_log_event(:mutation) { |mutation| mutations.push(mutation) }
driver.navigate.to url_for('dynamic.html')

driver.find_element(id: 'reveal').click
wait.until { mutations.any? }

mutation = mutations.first
expect(mutation.element).to eq(driver.find_element(id: 'revealed'))
expect(mutation.attribute_name).to eq('style')
expect(mutation.current_value).to eq('')
expect(mutation.old_value).to eq('display:none;')
end
end
end
end

0 comments on commit abe3e50

Please sign in to comment.