Skip to content

Commit

Permalink
* Add elapsed time
Browse files Browse the repository at this point in the history
* JRuby support
* Extract sleep and wait
* Rename :timeout to :wait
  • Loading branch information
route committed Sep 10, 2019
1 parent c38b8ac commit 120dfe7
Show file tree
Hide file tree
Showing 14 changed files with 87 additions and 92 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ rvm:
- 2.4
- 2.5
- 2.6
- jruby-9.2.8.0
matrix:
allow_failures:
- rvm: jruby-9.2.8.0
2 changes: 0 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@

source "https://rubygems.org"

ruby File.read(".ruby-version").chomp

gemspec
5 changes: 4 additions & 1 deletion ferrum.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ Gem::Specification.new do |s|
s.add_development_dependency "rspec", "~> 3.8"
s.add_development_dependency "sinatra", "~> 2.0"
s.add_development_dependency "puma", "~> 4.1"
s.add_development_dependency "byebug", "~> 10.0"
s.add_development_dependency "image_size", "~> 2.0"
s.add_development_dependency "pdf-reader", "~> 2.2"
s.add_development_dependency "chunky_png", "~> 1.3"

if RUBY_PLATFORM !~ /java/
s.add_development_dependency "byebug", "~> 10.0"
end
end
16 changes: 16 additions & 0 deletions lib/ferrum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,22 @@ def mri?
defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby"
end

def started
@@started ||= monotonic_time
end

def elapsed_time(start = nil)
monotonic_time - (start || @@started)
end

def monotonic_time
Concurrent.monotonic_time
end

def timeout?(start, timeout)
elapsed_time(start) > timeout
end

def with_attempts(errors:, max:, wait:)
attempts ||= 1
yield
Expand Down
5 changes: 3 additions & 2 deletions lib/ferrum/browser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

module Ferrum
class Browser
TIMEOUT = 5
DEFAULT_TIMEOUT = ENV.fetch("FERRUM_DEFAULT_TIMEOUT", 5).to_i
WINDOW_SIZE = [1024, 768].freeze
BASE_URL_SCHEMA = %w[http https].freeze

Expand Down Expand Up @@ -75,7 +75,7 @@ def extensions
end

def timeout
@timeout || TIMEOUT
@timeout || DEFAULT_TIMEOUT
end

def command(*args)
Expand Down Expand Up @@ -121,6 +121,7 @@ def crash
private

def start
Ferrum.started
@process = Process.start(@options)
@client = Client.new(self, @process.ws_url, 0, false)
end
Expand Down
4 changes: 1 addition & 3 deletions lib/ferrum/browser/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ def close
@ws.close
# Give a thread some time to handle a tail of messages
@pendings.clear
Timeout.timeout(1) { @thread.join }
rescue Timeout::Error
@thread.kill
@thread.kill unless @thread.join(1)
end

private
Expand Down
44 changes: 12 additions & 32 deletions lib/ferrum/browser/process.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ module Ferrum
class Browser
class Process
KILL_TIMEOUT = 2
PROCESS_TIMEOUT = 2
WAIT_KILLED = 0.05
PROCESS_TIMEOUT = ENV.fetch("FERRUM_PROCESS_TIMEOUT", 2).to_i
BROWSER_PATH = ENV["BROWSER_PATH"]
BROWSER_HOST = "127.0.0.1"
BROWSER_PORT = "0"
Expand Down Expand Up @@ -69,10 +70,10 @@ def self.process_killer(pid)
::Process.kill("KILL", pid)
else
::Process.kill("USR1", pid)
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
start = Ferrum.monotonic_time
while ::Process.wait(pid, ::Process::WNOHANG).nil?
sleep 0.05
next unless (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start) > KILL_TIMEOUT
sleep(WAIT_KILLED)
next unless Ferrum.timeout?(start, KILL_TIMEOUT)
::Process.kill("KILL", pid)
::Process.wait(pid)
break
Expand Down Expand Up @@ -142,17 +143,13 @@ def start
read_io, write_io = IO.pipe
process_options = { in: File::NULL }
process_options[:pgroup] = true unless Ferrum.windows?
if Ferrum.mri?
process_options[:out] = process_options[:err] = write_io
end
process_options[:out] = process_options[:err] = write_io

raise Cliver::Dependency::NotFound.new(NOT_FOUND) unless @path

redirect_stdout(write_io) do
@cmd = [@path] + @options.map { |k, v| v.nil? ? "--#{k}" : "--#{k}=#{v}" }
@pid = ::Process.spawn(*@cmd, process_options)
ObjectSpace.define_finalizer(self, self.class.process_killer(@pid))
end
@cmd = [@path] + @options.map { |k, v| v.nil? ? "--#{k}" : "--#{k}=#{v}" }
@pid = ::Process.spawn(*@cmd, process_options)
ObjectSpace.define_finalizer(self, self.class.process_killer(@pid))

parse_ws_url(read_io, @process_timeout)
ensure
Expand All @@ -173,34 +170,17 @@ def restart

private

def redirect_stdout(write_io)
if Ferrum.mri?
yield
else
begin
prev = STDOUT.dup
$stdout = write_io
STDOUT.reopen(write_io)
yield
ensure
STDOUT.reopen(prev)
$stdout = STDOUT
prev.close
end
end
end

def kill
self.class.process_killer(@pid).call
@pid = nil
end

def parse_ws_url(read_io, timeout = PROCESS_TIMEOUT)
def parse_ws_url(read_io, timeout)
output = ""
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
start = Ferrum.monotonic_time
max_time = start + timeout
regexp = /DevTools listening on (ws:\/\/.*)/
while (now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)) < max_time
while (now = Ferrum.monotonic_time) < max_time
begin
output += read_io.read_nonblock(512)
rescue IO::WaitReadable
Expand Down
6 changes: 2 additions & 4 deletions lib/ferrum/browser/web_socket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ def initialize(url, logger)
end
end

@thread.priority = 1

@driver.start
end

Expand All @@ -49,7 +47,7 @@ def on_open(_event)
def on_message(event)
data = JSON.parse(event.data)
@messages.push(data)
@logger&.puts(" ◀ #{event.data}\n")
@logger&.puts(" ◀ #{Ferrum.elapsed_time} #{event.data}\n")
end

def on_close(_event)
Expand All @@ -60,7 +58,7 @@ def on_close(_event)
def send_message(data)
json = data.to_json
@driver.text(json)
@logger&.puts("\n\n#{json}")
@logger&.puts("\n\n#{Ferrum.elapsed_time} #{json}")
end

def write(data)
Expand Down
12 changes: 7 additions & 5 deletions lib/ferrum/mouse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

module Ferrum
class Mouse
CLICK_WAIT = ENV.fetch("FERRUM_CLICK_WAIT", 0.05).to_f
VALID_BUTTONS = %w[none left middle right back forward].freeze

def initialize(page)
Expand All @@ -13,12 +14,13 @@ def scroll_to(top, left)
tap { @page.execute("window.scrollTo(#{top}, #{left})") }
end

def click(x:, y:, delay: 0, timeout: 0, **options)
def click(x:, y:, delay: 0, wait: CLICK_WAIT, **options)
move(x: x, y: y)
down(**options)
sleep(delay)
# Potential wait because if network event is triggered then we have to wait until it's over.
up(timeout: timeout, **options)
# Potential wait because if some network event is triggered then we have
# to wait until it's over and frame is loaded or failed to load.
up(wait: wait, **options)
self
end

Expand All @@ -39,11 +41,11 @@ def move(x:, y:, steps: 1)

private

def mouse_event(type:, button: :left, count: 1, modifiers: nil, timeout: 0)
def mouse_event(type:, button: :left, count: 1, modifiers: nil, wait: 0)
button = validate_button(button)
options = { x: @x, y: @y, type: type, button: button, clickCount: count }
options.merge!(modifiers: modifiers) if modifiers
@page.command("Input.dispatchMouseEvent", timeout: timeout, **options)
@page.command("Input.dispatchMouseEvent", wait: wait, **options)
end

def validate_button(button)
Expand Down
4 changes: 1 addition & 3 deletions lib/ferrum/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

module Ferrum
class Node
CLICK_WAIT = ENV.fetch("FERRUM_CLICK_WAIT", 0.05).to_f

attr_reader :page, :target_id, :node_id, :description, :tag_name

def initialize(page, target_id, node_id, description)
Expand Down Expand Up @@ -45,7 +43,7 @@ def click(mode: :left, keys: [], offset: {})
page.mouse.down(modifiers: modifiers, count: 2)
page.mouse.up(modifiers: modifiers, count: 2)
when :left
page.mouse.click(x: x, y: y, modifiers: modifiers, timeout: CLICK_WAIT)
page.mouse.click(x: x, y: y, modifiers: modifiers)
end

self
Expand Down
35 changes: 19 additions & 16 deletions lib/ferrum/page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
# details (DOM.describeNode).
module Ferrum
class Page
NEW_WINDOW_BUG_SLEEP = 0.3
MODAL_WAIT = ENV.fetch("FERRUM_MODAL_WAIT", 0.05).to_f
NEW_WINDOW_WAIT = ENV.fetch("FERRUM_NEW_WINDOW_WAIT", 0.3).to_f

class Event < Concurrent::Event
def iteration
Expand All @@ -45,7 +46,7 @@ def reset
synchronize do
@iteration += 1
@set = false if @set
true
@iteration
end
end
end
Expand All @@ -70,7 +71,7 @@ def initialize(target_id, browser, new_window = false)
@modal_messages = []

# Dirty hack because new window doesn't have events at all
sleep(NEW_WINDOW_BUG_SLEEP) if new_window
sleep(NEW_WINDOW_WAIT) if new_window

@session_id = @browser.command("Target.attachToTarget", targetId: @target_id)["sessionId"]

Expand All @@ -93,7 +94,7 @@ def timeout
def goto(url = nil)
options = { url: combine_url!(url) }
options.merge!(referrer: referrer) if referrer
response = command("Page.navigate", timeout: timeout, **options)
response = command("Page.navigate", wait: timeout, **options)
# https://cs.chromium.org/chromium/src/net/base/net_error_list.h
if %w[net::ERR_NAME_NOT_RESOLVED
net::ERR_NAME_RESOLUTION_FAILED
Expand Down Expand Up @@ -129,7 +130,7 @@ def resize(width: nil, height: nil, fullscreen: false)
end

def refresh
command("Page.reload", timeout: timeout)
command("Page.reload", wait: timeout)
end

def network_traffic(type = nil)
Expand Down Expand Up @@ -173,9 +174,9 @@ def dismiss_prompt
end

def find_modal(options)
start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
timeout_sec = options.fetch(:wait) { session_wait_time }
expect_text = options[:text]
start = Ferrum.monotonic_time
timeout = options.fetch(:wait) { session_wait_time }
expect_text = options[:text]
expect_regexp = expect_text.is_a?(Regexp) ? expect_text : Regexp.escape(expect_text.to_s)
not_found_msg = "Unable to find modal dialog"
not_found_msg += " with #{expect_text}" if expect_text
Expand All @@ -184,8 +185,8 @@ def find_modal(options)
modal_text = @modal_messages.shift
raise ModalNotFoundError if modal_text.nil? || (expect_text && !modal_text.match(expect_regexp))
rescue ModalNotFoundError => e
raise e, not_found_msg if (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_time) >= timeout_sec
sleep(0.05)
raise e, not_found_msg if Ferrum.timeout?(start, timeout)
sleep(MODAL_WAIT)
retry
end

Expand All @@ -198,12 +199,13 @@ def reset_modals
@modal_messages = []
end

def command(method, timeout: 0, **params)
@event.reset if timeout > 0
iteration = @event.iteration
def command(method, wait: 0, **params)
iteration = @event.reset if wait > 0
result = @client.command(method, params)
@event.wait(timeout) if timeout > 0
@event.wait(@browser.timeout) if iteration != @event.iteration
if wait > 0
@event.wait(wait)
@event.wait(@browser.timeout) if iteration != @event.iteration
end
result
end

Expand All @@ -220,6 +222,7 @@ def subscribe

if @browser.js_errors
@client.on("Runtime.exceptionThrown") do |params|
# FIXME https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/
Thread.main.raise JavaScriptError.new(params.dig("exceptionDetails", "exception"))
end
end
Expand Down Expand Up @@ -363,7 +366,7 @@ def history_navigate(delta:)

if entry = entries[index + delta]
# Potential wait because of network event
command("Page.navigateToHistoryEntry", timeout: 0.05, entryId: entry["id"])
command("Page.navigateToHistoryEntry", wait: Mouse::CLICK_WAIT, entryId: entry["id"])
end
end

Expand Down
Loading

0 comments on commit 120dfe7

Please sign in to comment.