Skip to content

Commit 2ec7533

Browse files
committed
* Refactor Ferrum module
* Split methods into Utils
1 parent c0d52c0 commit 2ec7533

File tree

18 files changed

+224
-181
lines changed

18 files changed

+224
-181
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ a block with this page, after which the page is closed.
2626
- `Ferrum::Page#bypass_csp` accepts hash as argument `enabled: true` by default
2727
- `Ferrum::Context#has_target?` -> `Ferrum::Context#target?`
2828
- We now start looking for Chrome first instead of Chromium, the order for checking binaries has changed
29+
- Multiple methods are moved into `Utils`:
30+
- Ferrum.with_attempts -> Ferrum::Utils::Attempt.with_retry
31+
- Ferrum.started -> Ferrum::Utils::ElapsedTime.start
32+
- Ferrum.elapsed_time -> Ferrum::Utils::ElapsedTime.elapsed_time
33+
- Ferrum.monotonic_time -> Ferrum::Utils::ElapsedTime.monotonic_time
34+
- Ferrum.timeout? -> Ferrum::Utils::ElapsedTime.timeout?
35+
- Ferrum.windows? -> Ferrum::Utils::Platform.windows?
36+
- Ferrum.mac? -> Ferrum::Utils::Platform.mac?
37+
- Ferrum.mri? -> Ferrum::Utils::Platform.mri?
2938

3039
## [0.11](https://github.com/rubycdp/ferrum/compare/v0.10.2...v0.11) - (Mar 11, 2021) ##
3140

lib/ferrum.rb

Lines changed: 4 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,161 +1,11 @@
11
# frozen_string_literal: true
22

3-
require "concurrent-ruby"
3+
require "ferrum/utils/platform"
4+
require "ferrum/utils/elapsed_time"
5+
require "ferrum/utils/attempt"
6+
require "ferrum/errors"
47
require "ferrum/browser"
58
require "ferrum/node"
69

710
module Ferrum
8-
class Error < StandardError; end
9-
10-
class NoSuchPageError < Error; end
11-
12-
class NoSuchTargetError < Error; end
13-
14-
class NotImplementedError < Error; end
15-
16-
class StatusError < Error
17-
def initialize(url, message = nil)
18-
super(message || "Request to #{url} failed to reach server, check DNS and server status")
19-
end
20-
end
21-
22-
class PendingConnectionsError < StatusError
23-
attr_reader :pendings
24-
25-
def initialize(url, pendings = [])
26-
@pendings = pendings
27-
28-
message = "Request to #{url} reached server, but there are still pending connections: #{pendings.join(', ')}"
29-
30-
super(url, message)
31-
end
32-
end
33-
34-
class TimeoutError < Error
35-
def message
36-
"Timed out waiting for response. It's possible that this happened " \
37-
"because something took a very long time (for example a page load " \
38-
"was slow). If so, setting the :timeout option to a higher value might " \
39-
"help."
40-
end
41-
end
42-
43-
class ScriptTimeoutError < Error
44-
def message
45-
"Timed out waiting for evaluated script to return a value"
46-
end
47-
end
48-
49-
class ProcessTimeoutError < Error
50-
attr_reader :output
51-
52-
def initialize(timeout, output)
53-
@output = output
54-
super("Browser did not produce websocket url within #{timeout} seconds, try to increase `:process_timeout`. See https://github.com/rubycdp/ferrum#customization")
55-
end
56-
end
57-
58-
class DeadBrowserError < Error
59-
def initialize(message = "Browser is dead or given window is closed")
60-
super
61-
end
62-
end
63-
64-
class NodeMovingError < Error
65-
def initialize(node, prev, current)
66-
@node = node
67-
@prev = prev
68-
@current = current
69-
super(message)
70-
end
71-
72-
def message
73-
"#{@node.inspect} that you're trying to click is moving, hence " \
74-
"we cannot. Previously it was at #{@prev.inspect} but now at " \
75-
"#{@current.inspect}."
76-
end
77-
end
78-
79-
class CoordinatesNotFoundError < Error
80-
def initialize(message = "Could not compute content quads")
81-
super
82-
end
83-
end
84-
85-
class BrowserError < Error
86-
attr_reader :response
87-
88-
def initialize(response)
89-
@response = response
90-
super(response["message"])
91-
end
92-
93-
def code
94-
response["code"]
95-
end
96-
97-
def data
98-
response["data"]
99-
end
100-
end
101-
102-
class NodeNotFoundError < BrowserError; end
103-
104-
class NoExecutionContextError < BrowserError
105-
def initialize(response = nil)
106-
response ||= { "message" => "There's no context available" }
107-
super(response)
108-
end
109-
end
110-
111-
class JavaScriptError < BrowserError
112-
attr_reader :class_name, :message, :stack_trace
113-
114-
def initialize(response, stack_trace = nil)
115-
@class_name, @message = response.values_at("className", "description")
116-
@stack_trace = stack_trace
117-
super(response.merge("message" => @message))
118-
end
119-
end
120-
121-
class << self
122-
def windows?
123-
RbConfig::CONFIG["host_os"] =~ /mingw|mswin|cygwin/
124-
end
125-
126-
def mac?
127-
RbConfig::CONFIG["host_os"] =~ /darwin/
128-
end
129-
130-
def mri?
131-
defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby"
132-
end
133-
134-
def started
135-
@started ||= monotonic_time
136-
end
137-
138-
def elapsed_time(start = nil)
139-
monotonic_time - (start || @started)
140-
end
141-
142-
def monotonic_time
143-
Concurrent.monotonic_time
144-
end
145-
146-
def timeout?(start, timeout)
147-
elapsed_time(start) > timeout
148-
end
149-
150-
def with_attempts(errors:, max:, wait:)
151-
attempts ||= 1
152-
yield
153-
rescue *Array(errors)
154-
raise if attempts >= max
155-
156-
attempts += 1
157-
sleep(wait)
158-
retry
159-
end
160-
end
16111
end

lib/ferrum/browser.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def crash
152152
private
153153

154154
def start
155-
Ferrum.started
155+
Utils::ElapsedTime.start
156156
@process = Process.start(@options)
157157
@client = Client.new(self, @process.ws_url)
158158
@contexts = Contexts.new(self)

lib/ferrum/browser/options/base.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ def except(*keys)
2424
end
2525

2626
def detect_path
27-
if Ferrum.mac?
27+
if Utils::Platform.mac?
2828
self.class::MAC_BIN_PATH.find { |n| File.exist?(n) }
29-
elsif Ferrum.windows?
29+
elsif Utils::Platform.windows?
3030
self.class::WINDOWS_BIN_PATH.find { |path| File.exist?(path) }
3131
else
3232
self.class::LINUX_BIN_PATH.find do |name|

lib/ferrum/browser/process.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ def self.start(*args)
3131

3232
def self.process_killer(pid)
3333
proc do
34-
if Ferrum.windows?
34+
if Utils::Platform.windows?
3535
# Process.kill is unreliable on Windows
3636
::Process.kill("KILL", pid) unless system("taskkill /f /t /pid #{pid} >NUL 2>NUL")
3737
else
3838
::Process.kill("USR1", pid)
39-
start = Ferrum.monotonic_time
39+
start = Utils::ElapsedTime.monotonic_time
4040
while ::Process.wait(pid, ::Process::WNOHANG).nil?
4141
sleep(WAIT_KILLED)
42-
next unless Ferrum.timeout?(start, KILL_TIMEOUT)
42+
next unless Utils::ElapsedTime.timeout?(start, KILL_TIMEOUT)
4343

4444
::Process.kill("KILL", pid)
4545
::Process.wait(pid)
@@ -88,7 +88,7 @@ def start
8888
begin
8989
read_io, write_io = IO.pipe
9090
process_options = { in: File::NULL }
91-
process_options[:pgroup] = true unless Ferrum.windows?
91+
process_options[:pgroup] = true unless Utils::Platform.windows?
9292
process_options[:out] = process_options[:err] = write_io
9393

9494
if @command.xvfb?
@@ -135,10 +135,10 @@ def remove_user_data_dir
135135

136136
def parse_ws_url(read_io, timeout)
137137
output = ""
138-
start = Ferrum.monotonic_time
138+
start = Utils::ElapsedTime.monotonic_time
139139
max_time = start + timeout
140140
regexp = %r{DevTools listening on (ws://.*)}
141-
while (now = Ferrum.monotonic_time) < max_time
141+
while (now = Utils::ElapsedTime.monotonic_time) < max_time
142142
begin
143143
output += read_io.read_nonblock(512)
144144
rescue IO::WaitReadable

lib/ferrum/browser/web_socket.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def on_message(event)
6161
output.sub!(/{"data":"(.*)"}/, %("Set FERRUM_LOGGING_SCREENSHOTS=true to see screenshots in Base64"))
6262
end
6363

64-
@logger&.puts(" ◀ #{Ferrum.elapsed_time} #{output}\n")
64+
@logger&.puts(" ◀ #{Utils::ElapsedTime.elapsed_time} #{output}\n")
6565
end
6666

6767
def on_close(_event)
@@ -74,7 +74,7 @@ def send_message(data)
7474

7575
json = data.to_json
7676
@driver.text(json)
77-
@logger&.puts("\n\n#{Ferrum.elapsed_time} #{json}")
77+
@logger&.puts("\n\n#{Utils::ElapsedTime.elapsed_time} #{json}")
7878
end
7979

8080
def write(data)

lib/ferrum/errors.rb

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# frozen_string_literal: true
2+
3+
module Ferrum
4+
class Error < StandardError; end
5+
6+
class NoSuchPageError < Error; end
7+
8+
class NoSuchTargetError < Error; end
9+
10+
class NotImplementedError < Error; end
11+
12+
class StatusError < Error
13+
def initialize(url, message = nil)
14+
super(message || "Request to #{url} failed to reach server, check DNS and server status")
15+
end
16+
end
17+
18+
class PendingConnectionsError < StatusError
19+
attr_reader :pendings
20+
21+
def initialize(url, pendings = [])
22+
@pendings = pendings
23+
24+
message = "Request to #{url} reached server, but there are still pending connections: #{pendings.join(', ')}"
25+
26+
super(url, message)
27+
end
28+
end
29+
30+
class TimeoutError < Error
31+
def message
32+
"Timed out waiting for response. It's possible that this happened " \
33+
"because something took a very long time (for example a page load " \
34+
"was slow). If so, setting the :timeout option to a higher value might " \
35+
"help."
36+
end
37+
end
38+
39+
class ScriptTimeoutError < Error
40+
def message
41+
"Timed out waiting for evaluated script to return a value"
42+
end
43+
end
44+
45+
class ProcessTimeoutError < Error
46+
attr_reader :output
47+
48+
def initialize(timeout, output)
49+
@output = output
50+
super("Browser did not produce websocket url within #{timeout} seconds, try to increase `:process_timeout`. See https://github.com/rubycdp/ferrum#customization")
51+
end
52+
end
53+
54+
class DeadBrowserError < Error
55+
def initialize(message = "Browser is dead or given window is closed")
56+
super
57+
end
58+
end
59+
60+
class NodeMovingError < Error
61+
def initialize(node, prev, current)
62+
@node = node
63+
@prev = prev
64+
@current = current
65+
super(message)
66+
end
67+
68+
def message
69+
"#{@node.inspect} that you're trying to click is moving, hence " \
70+
"we cannot. Previously it was at #{@prev.inspect} but now at " \
71+
"#{@current.inspect}."
72+
end
73+
end
74+
75+
class CoordinatesNotFoundError < Error
76+
def initialize(message = "Could not compute content quads")
77+
super
78+
end
79+
end
80+
81+
class BrowserError < Error
82+
attr_reader :response
83+
84+
def initialize(response)
85+
@response = response
86+
super(response["message"])
87+
end
88+
89+
def code
90+
response["code"]
91+
end
92+
93+
def data
94+
response["data"]
95+
end
96+
end
97+
98+
class NodeNotFoundError < BrowserError; end
99+
100+
class NoExecutionContextError < BrowserError
101+
def initialize(response = nil)
102+
response ||= { "message" => "There's no context available" }
103+
super(response)
104+
end
105+
end
106+
107+
class JavaScriptError < BrowserError
108+
attr_reader :class_name, :message, :stack_trace
109+
110+
def initialize(response, stack_trace = nil)
111+
@class_name, @message = response.values_at("className", "description")
112+
@stack_trace = stack_trace
113+
super(response.merge("message" => @message))
114+
end
115+
end
116+
end

lib/ferrum/frame/runtime.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def call(expression:, arguments: [], on: nil, wait: 0, handle: true, **options)
122122
sleep = INTERMITTENT_SLEEP
123123
attempts = INTERMITTENT_ATTEMPTS
124124

125-
Ferrum.with_attempts(errors: errors, max: attempts, wait: sleep) do
125+
Utils::Attempt.with_retry(errors: errors, max: attempts, wait: sleep) do
126126
params = options.dup
127127

128128
if on

0 commit comments

Comments
 (0)