ferrum 0.8 → 0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1fb53f7db2767597ff5d6e7ff08536562edec30f5d68fbb2dcac7bf9943e056d
4
- data.tar.gz: 46f9f75de5f4c0a4531dbbeda25c538350c3daa167f5de53c9248dbfc6958e34
3
+ metadata.gz: 8b4d6dc7aa1827fbf559e6025b82d29d15ed0e36a89793049266d8049fadabb9
4
+ data.tar.gz: 6fab0202e85a17971d613db12e37a7ef85325eaf23f718b6801812df565ac64c
5
5
  SHA512:
6
- metadata.gz: 0e15da4027c44cd38e3d4eaf6f3384085fbb85fe5760ade585bbaaf2c4ea4fa79db98c23fee3099b33e1d09ab50e1ce8b979ec8848203a3d0c4b45fbe6208b7f
7
- data.tar.gz: 7038d36ca9cca2204d9a489380ae0db7d75f202921a3b31b4bd9cbed3c54fb4bd187d5270052dfcdeba300ba615140b33660982dfaa6f99759a3463c52434fc1
6
+ metadata.gz: fb109c1b65e73e8d0088734fa004fae3d15650121d01b44dc9086cdb193bb3c7f56c3ad911a2f6de5cd28522231b4e695b67eb515d90afe93b862eb64cfe7050
7
+ data.tar.gz: a4d5e4c192cbd634c640d86027f3a8faeaf42efcd8fbaa9b0e8c6e81c04aa9921b7f2353eb739c35850363f1ed58f84d5895714f52ecd333d9ebdb50c1de4031
data/README.md CHANGED
@@ -90,7 +90,7 @@ Interact with a page:
90
90
  ```ruby
91
91
  browser = Ferrum::Browser.new
92
92
  browser.goto("https://google.com")
93
- input = browser.at_xpath("//div[@id='searchform']/form//input[@type='text']")
93
+ input = browser.at_xpath("//input[@name='q']")
94
94
  input.focus.type("Ruby headless driver for Chrome", :Enter)
95
95
  browser.at_css("a > h3").text # => "rubycdp/ferrum: Ruby Chrome/Chromium driver - GitHub"
96
96
  browser.quit
@@ -147,6 +147,7 @@ Ferrum::Browser.new(options)
147
147
 
148
148
  * options `Hash`
149
149
  * `:headless` (Boolean) - Set browser as headless or not, `true` by default.
150
+ * `:xvfb` (Boolean) - Run browser in a virtual framebuffer, `false` by default.
150
151
  * `:window_size` (Array) - The dimensions of the browser window in which to
151
152
  test, expressed as a 2-element array, e.g. [1024, 768]. Default: [1024, 768]
152
153
  * `:extensions` (Array[String | Hash]) - An array of paths to files or JS
@@ -166,6 +167,10 @@ Ferrum::Browser.new(options)
166
167
  * `:browser_options` (Hash) - Additional command line options,
167
168
  [see them all](https://peter.sh/experiments/chromium-command-line-switches/)
168
169
  e.g. `{ "ignore-certificate-errors" => nil }`
170
+ * `:ignore_default_browser_options` (Boolean) - Ferrum has a number of default
171
+ options it passes to the browser, if you set this to `true` then only
172
+ options you put in `:browser_options` will be passed to the browser,
173
+ except required ones of course.
169
174
  * `:port` (Integer) - Remote debugging port for headless Chrome
170
175
  * `:host` (String) - Remote debugging address for headless Chrome
171
176
  * `:url` (String) - URL for a running instance of Chrome. If this is set, a
@@ -48,6 +48,19 @@ module Ferrum
48
48
  end
49
49
  end
50
50
 
51
+ class NodeIsMovingError < Error
52
+ def initialize(node, prev, current)
53
+ @node, @prev, @current = node, prev, current
54
+ super(message)
55
+ end
56
+
57
+ def message
58
+ "#{@node.inspect} that you're trying to click is moving, hence " \
59
+ "we cannot. Previosuly it was at #{@prev.inspect} but now at " \
60
+ "#{@current.inspect}."
61
+ end
62
+ end
63
+
51
64
  class BrowserError < Error
52
65
  attr_reader :response
53
66
 
@@ -4,6 +4,7 @@ require "base64"
4
4
  require "forwardable"
5
5
  require "ferrum/page"
6
6
  require "ferrum/contexts"
7
+ require "ferrum/browser/xvfb"
7
8
  require "ferrum/browser/process"
8
9
  require "ferrum/browser/client"
9
10
 
@@ -16,7 +17,7 @@ module Ferrum
16
17
  extend Forwardable
17
18
  delegate %i[default_context] => :contexts
18
19
  delegate %i[targets create_target create_page page pages windows] => :default_context
19
- delegate %i[goto back forward refresh reload stop
20
+ delegate %i[goto back forward refresh reload stop wait_for_reload
20
21
  at_css at_xpath css xpath current_url current_title url title
21
22
  body doctype set_content
22
23
  headers cookies network
@@ -3,8 +3,6 @@
3
3
  module Ferrum
4
4
  class Browser
5
5
  class Command
6
- BROWSER_HOST = "127.0.0.1"
7
- BROWSER_PORT = "0"
8
6
  NOT_FOUND = "Could not find an executable for the browser. Try to make " \
9
7
  "it available on the PATH or set environment varible for " \
10
8
  "example BROWSER_PATH=\"/usr/bin/chrome\"".freeze
@@ -12,44 +10,47 @@ module Ferrum
12
10
  # Currently only these browsers support CDP:
13
11
  # https://github.com/cyrus-and/chrome-remote-interface#implementations
14
12
  def self.build(options, user_data_dir)
15
- case options[:browser_name]
16
- when :firefox
17
- Firefox
18
- when :chrome, :opera, :edge, nil
19
- Chrome
20
- else
21
- raise NotImplementedError, "not supported browser"
22
- end.new(options, user_data_dir)
13
+ defaults = case options[:browser_name]
14
+ when :firefox
15
+ Options::Firefox.options
16
+ when :chrome, :opera, :edge, nil
17
+ Options::Chrome.options
18
+ else
19
+ raise NotImplementedError, "not supported browser"
20
+ end
21
+
22
+ new(defaults, options, user_data_dir)
23
23
  end
24
24
 
25
- attr_reader :path, :flags, :options
25
+ attr_reader :defaults, :path, :options
26
26
 
27
- def initialize(options, user_data_dir)
27
+ def initialize(defaults, options, user_data_dir)
28
28
  @flags = {}
29
+ @defaults = defaults
29
30
  @options, @user_data_dir = options, user_data_dir
30
- @path = options[:browser_path] || ENV["BROWSER_PATH"] || detect_path
31
+ @path = options[:browser_path] || ENV["BROWSER_PATH"] || defaults.detect_path
31
32
  raise Cliver::Dependency::NotFound.new(NOT_FOUND) unless @path
33
+ merge_options
34
+ end
32
35
 
33
- combine_flags
36
+ def xvfb?
37
+ !!options[:xvfb]
34
38
  end
35
39
 
36
40
  def to_a
37
- [path] + flags.map { |k, v| v.nil? ? "--#{k}" : "--#{k}=#{v}" }
41
+ [path] + @flags.map { |k, v| v.nil? ? "--#{k}" : "--#{k}=#{v}" }
38
42
  end
39
43
 
40
44
  private
41
45
 
42
- def detect_path
43
- if Ferrum.mac?
44
- self.class::MAC_BIN_PATH.find { |b| File.exist?(b) }
45
- else
46
- self.class::LINUX_BIN_PATH
47
- .find { |b| p = Cliver.detect(b) and break(p) }
46
+ def merge_options
47
+ @flags = defaults.merge_required(@flags, options, @user_data_dir)
48
+
49
+ unless options[:ignore_default_browser_options]
50
+ @flags = defaults.merge_default(@flags, options)
48
51
  end
49
- end
50
52
 
51
- def combine_flags
52
- raise NotImplementedError
53
+ @flags.merge!(options.fetch(:browser_options, {}))
53
54
  end
54
55
  end
55
56
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module Ferrum
6
+ class Browser
7
+ module Options
8
+ class Base
9
+ BROWSER_HOST = "127.0.0.1"
10
+ BROWSER_PORT = "0"
11
+
12
+ include Singleton
13
+
14
+ def self.options
15
+ instance
16
+ end
17
+
18
+ def to_h
19
+ self.class::DEFAULT_OPTIONS
20
+ end
21
+
22
+ def except(*keys)
23
+ to_h.reject { |n, _| keys.include?(n) }
24
+ end
25
+
26
+ def detect_path
27
+ if Ferrum.mac?
28
+ self.class::MAC_BIN_PATH.find { |n| File.exist?(n) }
29
+ else
30
+ self.class::LINUX_BIN_PATH.find do |name|
31
+ path = Cliver.detect(name) and break(path)
32
+ end
33
+ end
34
+ end
35
+
36
+ def merge_required(flags, options, user_data_dir)
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def merge_default(flags, options)
41
+ raise NotImplementedError
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ferrum
4
+ class Browser
5
+ module Options
6
+ class Chrome < Base
7
+ DEFAULT_OPTIONS = {
8
+ "headless" => nil,
9
+ "disable-gpu" => nil,
10
+ "hide-scrollbars" => nil,
11
+ "mute-audio" => nil,
12
+ "enable-automation" => nil,
13
+ "disable-web-security" => nil,
14
+ "disable-session-crashed-bubble" => nil,
15
+ "disable-breakpad" => nil,
16
+ "disable-sync" => nil,
17
+ "no-first-run" => nil,
18
+ "use-mock-keychain" => nil,
19
+ "keep-alive-for-test" => nil,
20
+ "disable-popup-blocking" => nil,
21
+ "disable-extensions" => nil,
22
+ "disable-hang-monitor" => nil,
23
+ "disable-features" => "site-per-process,TranslateUI",
24
+ "disable-translate" => nil,
25
+ "disable-background-networking" => nil,
26
+ "enable-features" => "NetworkService,NetworkServiceInProcess",
27
+ "disable-background-timer-throttling" => nil,
28
+ "disable-backgrounding-occluded-windows" => nil,
29
+ "disable-client-side-phishing-detection" => nil,
30
+ "disable-default-apps" => nil,
31
+ "disable-dev-shm-usage" => nil,
32
+ "disable-ipc-flooding-protection" => nil,
33
+ "disable-prompt-on-repost" => nil,
34
+ "disable-renderer-backgrounding" => nil,
35
+ "force-color-profile" => "srgb",
36
+ "metrics-recording-only" => nil,
37
+ "safebrowsing-disable-auto-update" => nil,
38
+ "password-store" => "basic",
39
+ # Note: --no-sandbox is not needed if you properly setup a user in the container.
40
+ # https://github.com/ebidel/lighthouse-ci/blob/master/builder/Dockerfile#L35-L40
41
+ # "no-sandbox" => nil,
42
+ }.freeze
43
+
44
+ MAC_BIN_PATH = [
45
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
46
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
47
+ ].freeze
48
+ LINUX_BIN_PATH = %w[chromium google-chrome-unstable google-chrome-beta
49
+ google-chrome chrome chromium-browser
50
+ google-chrome-stable].freeze
51
+
52
+ def merge_required(flags, options, user_data_dir)
53
+ port = options.fetch(:port, BROWSER_PORT)
54
+ host = options.fetch(:host, BROWSER_HOST)
55
+ flags.merge("remote-debugging-port" => port,
56
+ "remote-debugging-address" => host,
57
+ # Doesn't work on MacOS, so we need to set it by CDP
58
+ "window-size" => options[:window_size].join(","),
59
+ "user-data-dir" => user_data_dir)
60
+ end
61
+
62
+ def merge_default(flags, options)
63
+ unless options.fetch(:headless, true)
64
+ defaults = except("headless", "disable-gpu")
65
+ end
66
+
67
+ defaults ||= DEFAULT_OPTIONS
68
+ defaults.merge(flags)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ferrum
4
+ class Browser
5
+ module Options
6
+ class Firefox < Base
7
+ DEFAULT_OPTIONS = {
8
+ "headless" => nil,
9
+ }.freeze
10
+
11
+ MAC_BIN_PATH = [
12
+ "/Applications/Firefox.app/Contents/MacOS/firefox-bin"
13
+ ].freeze
14
+ LINUX_BIN_PATH = %w[firefox].freeze
15
+
16
+ def merge_required(flags, options, user_data_dir)
17
+ port = options.fetch(:port, BROWSER_PORT)
18
+ host = options.fetch(:host, BROWSER_HOST)
19
+ flags.merge("remote-debugger" => "#{host}:#{port}",
20
+ "profile" => user_data_dir)
21
+ end
22
+
23
+ def merge_default(flags, options)
24
+ unless options.fetch(:headless, true)
25
+ defaults = except("headless")
26
+ end
27
+
28
+ defaults ||= DEFAULT_OPTIONS
29
+ defaults.merge(flags)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -6,9 +6,10 @@ require "json"
6
6
  require "addressable"
7
7
  require "tmpdir"
8
8
  require "forwardable"
9
+ require "ferrum/browser/options/base"
10
+ require "ferrum/browser/options/chrome"
11
+ require "ferrum/browser/options/firefox"
9
12
  require "ferrum/browser/command"
10
- require "ferrum/browser/chrome"
11
- require "ferrum/browser/firefox"
12
13
 
13
14
  module Ferrum
14
15
  class Browser
@@ -19,7 +20,7 @@ module Ferrum
19
20
 
20
21
  attr_reader :host, :port, :ws_url, :pid, :command,
21
22
  :default_user_agent, :browser_version, :protocol_version,
22
- :v8_version, :webkit_version
23
+ :v8_version, :webkit_version, :xvfb
23
24
 
24
25
 
25
26
  extend Forwardable
@@ -81,7 +82,12 @@ module Ferrum
81
82
  process_options[:pgroup] = true unless Ferrum.windows?
82
83
  process_options[:out] = process_options[:err] = write_io
83
84
 
84
- @pid = ::Process.spawn(*@command.to_a, process_options)
85
+ if @command.xvfb?
86
+ @xvfb = Xvfb.start(@command.options)
87
+ ObjectSpace.define_finalizer(self, self.class.process_killer(@xvfb.pid))
88
+ end
89
+
90
+ @pid = ::Process.spawn(Hash(@xvfb&.to_env), *@command.to_a, process_options)
85
91
  ObjectSpace.define_finalizer(self, self.class.process_killer(@pid))
86
92
 
87
93
  parse_ws_url(read_io, @process_timeout)
@@ -92,7 +98,12 @@ module Ferrum
92
98
  end
93
99
 
94
100
  def stop
95
- kill if @pid
101
+ if @pid
102
+ kill(@pid)
103
+ kill(@xvfb.pid) if @xvfb&.pid
104
+ @pid = nil
105
+ end
106
+
96
107
  remove_user_data_dir if @user_data_dir
97
108
  ObjectSpace.undefine_finalizer(self)
98
109
  end
@@ -104,9 +115,8 @@ module Ferrum
104
115
 
105
116
  private
106
117
 
107
- def kill
108
- self.class.process_killer(@pid).call
109
- @pid = nil
118
+ def kill(pid)
119
+ self.class.process_killer(pid).call
110
120
  end
111
121
 
112
122
  def remove_user_data_dir
@@ -13,7 +13,7 @@ module Ferrum
13
13
 
14
14
  def initialize
15
15
  super
16
- @on = Hash.new { |h, k| h[k] = [] }
16
+ @on = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
17
17
  end
18
18
 
19
19
  def on(event, &block)
@@ -8,6 +8,7 @@ module Ferrum
8
8
  class Browser
9
9
  class WebSocket
10
10
  WEBSOCKET_BUG_SLEEP = 0.01
11
+ SKIP_LOGGING_SCREENSHOTS = !ENV["FERRUM_LOGGING_SCREENSHOTS"]
11
12
 
12
13
  attr_reader :url, :messages
13
14
 
@@ -20,6 +21,10 @@ module Ferrum
20
21
  @driver = ::WebSocket::Driver.client(self, max_length: max_receive_size)
21
22
  @messages = Queue.new
22
23
 
24
+ if SKIP_LOGGING_SCREENSHOTS
25
+ @screenshot_commands = Concurrent::Hash.new
26
+ end
27
+
23
28
  @driver.on(:open, &method(:on_open))
24
29
  @driver.on(:message, &method(:on_message))
25
30
  @driver.on(:close, &method(:on_close))
@@ -50,7 +55,14 @@ module Ferrum
50
55
  def on_message(event)
51
56
  data = JSON.parse(event.data)
52
57
  @messages.push(data)
53
- @logger&.puts(" ◀ #{Ferrum.elapsed_time} #{event.data}\n")
58
+
59
+ output = event.data
60
+ if SKIP_LOGGING_SCREENSHOTS && @screenshot_commands[data["id"]]
61
+ @screenshot_commands.delete(data["id"])
62
+ output.sub!(/{"data":"(.*)"}/, %("Set FERRUM_LOGGING_SCREENSHOTS=true to see screenshots in Base64"))
63
+ end
64
+
65
+ @logger&.puts(" ◀ #{Ferrum.elapsed_time} #{output}\n")
54
66
  end
55
67
 
56
68
  def on_close(_event)
@@ -59,6 +71,10 @@ module Ferrum
59
71
  end
60
72
 
61
73
  def send_message(data)
74
+ if SKIP_LOGGING_SCREENSHOTS
75
+ @screenshot_commands[data[:id]] = true
76
+ end
77
+
62
78
  json = data.to_json
63
79
  @driver.text(json)
64
80
  @logger&.puts("\n\n▶ #{Ferrum.elapsed_time} #{json}")
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ferrum
4
+ class Browser
5
+ class Xvfb
6
+ NOT_FOUND = "Could not find an executable for the Xvfb. Try to install " \
7
+ "it with your package manager".freeze
8
+
9
+ def self.start(*args)
10
+ new(*args).tap(&:start)
11
+ end
12
+
13
+ def self.xvfb_path
14
+ Cliver.detect("Xvfb")
15
+ end
16
+
17
+ attr_reader :screen_size, :display_id, :pid
18
+
19
+ def initialize(options)
20
+ @path = self.class.xvfb_path
21
+ raise Cliver::Dependency::NotFound.new(NOT_FOUND) unless @path
22
+
23
+ @screen_size = options.fetch(:window_size, [1024, 768]).join("x") + "x24"
24
+ @display_id = (Time.now.to_f * 1000).to_i % 100_000_000
25
+ end
26
+
27
+ def start
28
+ @pid = ::Process.spawn("#{@path} :#{display_id} -screen 0 #{screen_size}")
29
+ ::Process.detach(@pid)
30
+ end
31
+
32
+ def to_env
33
+ { "DISPLAY" => ":#{display_id}" }
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "singleton"
4
+
3
5
  module Ferrum
4
6
  class Frame
5
7
  module Runtime
@@ -215,7 +217,7 @@ module Ferrum
215
217
 
216
218
  def reduce_props(object_id, to)
217
219
  if cyclic?(object_id).dig("result", "value")
218
- return "(cyclic structure)"
220
+ return to.is_a?(Array) ? [cyclic_object] : cyclic_object
219
221
  else
220
222
  props = @page.command("Runtime.getProperties", ownProperties: true, objectId: object_id)
221
223
  props["result"].reduce(to) do |memo, prop|
@@ -259,6 +261,18 @@ module Ferrum
259
261
  JS
260
262
  )
261
263
  end
264
+
265
+ def cyclic_object
266
+ CyclicObject.instance
267
+ end
268
+ end
269
+ end
270
+
271
+ class CyclicObject
272
+ include Singleton
273
+
274
+ def inspect
275
+ %(#<#{self.class} JavaScript object that cannot be represented in Ruby>)
262
276
  end
263
277
  end
264
278
  end
@@ -39,7 +39,7 @@ module Ferrum
39
39
  requestId: request_id,
40
40
  responseHeaders: header_array(headers),
41
41
  })
42
- options = options.merge(body: Base64.encode64(options.fetch(:body, "")).strip) if has_body
42
+ options = options.merge(body: Base64.strict_encode64(options.fetch(:body, ""))) if has_body
43
43
 
44
44
  @status = :responded
45
45
  @page.command("Fetch.fulfillRequest", **options)
@@ -2,6 +2,9 @@
2
2
 
3
3
  module Ferrum
4
4
  class Node
5
+ MOVING_WAIT = ENV.fetch("FERRUM_NODE_MOVING_WAIT", 0.01).to_f
6
+ MOVING_ATTEMPTS = ENV.fetch("FERRUM_NODE_MOVING_ATTEMPTS", 50).to_i
7
+
5
8
  attr_reader :page, :target_id, :node_id, :description, :tag_name
6
9
 
7
10
  def initialize(frame, target_id, node_id, description)
@@ -121,16 +124,42 @@ module Ferrum
121
124
  end
122
125
 
123
126
  def find_position(x: nil, y: nil, position: :top)
124
- offset_x, offset_y = x, y
125
- quads = get_content_quads
127
+ prev = get_content_quads
128
+
129
+ # FIXME: Case when a few quads returned
130
+ points = Ferrum.with_attempts(errors: NodeIsMovingError, max: MOVING_ATTEMPTS, wait: 0) do
131
+ sleep(MOVING_WAIT)
132
+ current = get_content_quads
133
+
134
+ if current != prev
135
+ error = NodeIsMovingError.new(self, prev, current)
136
+ prev = current
137
+ raise(error)
138
+ end
139
+
140
+ current
141
+ end.map { |q| to_points(q) }.first
142
+
143
+ get_position(points, x, y, position)
144
+ end
145
+
146
+ private
147
+
148
+ def get_content_quads
149
+ quads = page.command("DOM.getContentQuads", nodeId: node_id)["quads"]
150
+ raise "Node is either not visible or not an HTMLElement" if quads.size == 0
151
+ quads
152
+ end
153
+
154
+ def get_position(points, offset_x, offset_y, position)
126
155
  x = y = nil
127
156
 
128
157
  if offset_x && offset_y && position == :top
129
- point = quads.first
158
+ point = points.first
130
159
  x = point[:x] + offset_x.to_i
131
160
  y = point[:y] + offset_y.to_i
132
161
  else
133
- x, y = quads.inject([0, 0]) do |memo, point|
162
+ x, y = points.inject([0, 0]) do |memo, point|
134
163
  [memo[0] + point[:x],
135
164
  memo[1] + point[:y]]
136
165
  end
@@ -147,19 +176,11 @@ module Ferrum
147
176
  [x, y]
148
177
  end
149
178
 
150
- private
151
-
152
- def get_content_quads
153
- result = page.command("DOM.getContentQuads", nodeId: node_id)
154
- raise "Node is either not visible or not an HTMLElement" if result["quads"].size == 0
155
-
156
- # FIXME: Case when a few quads returned
157
- result["quads"].map do |quad|
158
- [{x: quad[0], y: quad[1]},
159
- {x: quad[2], y: quad[3]},
160
- {x: quad[4], y: quad[5]},
161
- {x: quad[6], y: quad[7]}]
162
- end.first
179
+ def to_points(quad)
180
+ [{x: quad[0], y: quad[1]},
181
+ {x: quad[2], y: quad[3]},
182
+ {x: quad[4], y: quad[5]},
183
+ {x: quad[6], y: quad[7]}]
163
184
  end
164
185
  end
165
186
  end
@@ -44,7 +44,7 @@ module Ferrum
44
44
 
45
45
  def initialize(target_id, browser)
46
46
  @frames = {}
47
- @main_frame = Frame.new(nil, self)
47
+ @main_frame = Frame.new(nil, self)
48
48
  @target_id, @browser = target_id, browser
49
49
  @event = Event.new.tap(&:set)
50
50
 
@@ -125,6 +125,12 @@ module Ferrum
125
125
  history_navigate(delta: 1)
126
126
  end
127
127
 
128
+ def wait_for_reload(sec = 1)
129
+ @event.reset if @event.set?
130
+ @event.wait(sec)
131
+ @event.set
132
+ end
133
+
128
134
  def bypass_csp(value = true)
129
135
  enabled = !!value
130
136
  command("Page.setBypassCSP", enabled: enabled)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ferrum
4
- VERSION = "0.8"
4
+ VERSION = "0.9"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ferrum
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.8'
4
+ version: '0.9'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Vorotilin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-07 00:00:00.000000000 Z
11
+ date: 2020-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: websocket-driver
@@ -64,14 +64,14 @@ dependencies:
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '2.6'
67
+ version: '2.5'
68
68
  type: :runtime
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '2.6'
74
+ version: '2.5'
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: rake
77
77
  requirement: !ruby/object:Gem::Requirement
@@ -181,13 +181,15 @@ files:
181
181
  - README.md
182
182
  - lib/ferrum.rb
183
183
  - lib/ferrum/browser.rb
184
- - lib/ferrum/browser/chrome.rb
185
184
  - lib/ferrum/browser/client.rb
186
185
  - lib/ferrum/browser/command.rb
187
- - lib/ferrum/browser/firefox.rb
186
+ - lib/ferrum/browser/options/base.rb
187
+ - lib/ferrum/browser/options/chrome.rb
188
+ - lib/ferrum/browser/options/firefox.rb
188
189
  - lib/ferrum/browser/process.rb
189
190
  - lib/ferrum/browser/subscriber.rb
190
191
  - lib/ferrum/browser/web_socket.rb
192
+ - lib/ferrum/browser/xvfb.rb
191
193
  - lib/ferrum/context.rb
192
194
  - lib/ferrum/contexts.rb
193
195
  - lib/ferrum/cookies.rb
@@ -1,76 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ferrum
4
- class Browser
5
- class Chrome < Command
6
- DEFAULT_OPTIONS = {
7
- "headless" => nil,
8
- "disable-gpu" => nil,
9
- "hide-scrollbars" => nil,
10
- "mute-audio" => nil,
11
- "enable-automation" => nil,
12
- "disable-web-security" => nil,
13
- "disable-session-crashed-bubble" => nil,
14
- "disable-breakpad" => nil,
15
- "disable-sync" => nil,
16
- "no-first-run" => nil,
17
- "use-mock-keychain" => nil,
18
- "keep-alive-for-test" => nil,
19
- "disable-popup-blocking" => nil,
20
- "disable-extensions" => nil,
21
- "disable-hang-monitor" => nil,
22
- "disable-features" => "site-per-process,TranslateUI",
23
- "disable-translate" => nil,
24
- "disable-background-networking" => nil,
25
- "enable-features" => "NetworkService,NetworkServiceInProcess",
26
- "disable-background-timer-throttling" => nil,
27
- "disable-backgrounding-occluded-windows" => nil,
28
- "disable-client-side-phishing-detection" => nil,
29
- "disable-default-apps" => nil,
30
- "disable-dev-shm-usage" => nil,
31
- "disable-ipc-flooding-protection" => nil,
32
- "disable-prompt-on-repost" => nil,
33
- "disable-renderer-backgrounding" => nil,
34
- "force-color-profile" => "srgb",
35
- "metrics-recording-only" => nil,
36
- "safebrowsing-disable-auto-update" => nil,
37
- "password-store" => "basic",
38
- # Note: --no-sandbox is not needed if you properly setup a user in the container.
39
- # https://github.com/ebidel/lighthouse-ci/blob/master/builder/Dockerfile#L35-L40
40
- # "no-sandbox" => nil,
41
- }.freeze
42
-
43
- MAC_BIN_PATH = [
44
- "/Applications/Chromium.app/Contents/MacOS/Chromium",
45
- "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
46
- ].freeze
47
- LINUX_BIN_PATH = %w[chromium google-chrome-unstable google-chrome-beta
48
- google-chrome chrome chromium-browser
49
- google-chrome-stable].freeze
50
-
51
- private
52
-
53
- def combine_flags
54
- # Doesn't work on MacOS, so we need to set it by CDP as well
55
- @flags.merge!("window-size" => options[:window_size].join(","))
56
-
57
- port = options.fetch(:port, BROWSER_PORT)
58
- @flags.merge!("remote-debugging-port" => port)
59
-
60
- host = options.fetch(:host, BROWSER_HOST)
61
- @flags.merge!("remote-debugging-address" => host)
62
-
63
- @flags.merge!("user-data-dir" => @user_data_dir)
64
-
65
- @flags = DEFAULT_OPTIONS.merge(@flags)
66
-
67
- unless options.fetch(:headless, true)
68
- @flags.delete("headless")
69
- @flags.delete("disable-gpu")
70
- end
71
-
72
- @flags.merge!(options.fetch(:browser_options, {}))
73
- end
74
- end
75
- end
76
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ferrum
4
- class Browser
5
- class Firefox < Command
6
- DEFAULT_OPTIONS = {
7
- "headless" => nil,
8
- }.freeze
9
-
10
- MAC_BIN_PATH = [
11
- "/Applications/Firefox.app/Contents/MacOS/firefox-bin"
12
- ].freeze
13
- LINUX_BIN_PATH = %w[firefox].freeze
14
-
15
- private
16
-
17
- def combine_flags
18
- port = options.fetch(:port, BROWSER_PORT)
19
- host = options.fetch(:host, BROWSER_HOST)
20
- @flags.merge!("remote-debugger" => "#{host}:#{port}")
21
-
22
- @flags.merge!("profile" => @user_data_dir)
23
-
24
- @flags = DEFAULT_OPTIONS.merge(@flags)
25
-
26
- unless options.fetch(:headless, true)
27
- @flags.delete("headless")
28
- end
29
-
30
- @flags.merge!(options.fetch(:browser_options, {}))
31
- end
32
- end
33
- end
34
- end