ferrum 0.8 → 0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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