cuprite 0.4.0 → 0.5.0

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: d183c86ecad6aaaf681364bda5ee919e5952a2b3a8d55cad40b3a6cec57ff26f
4
- data.tar.gz: cccf18bb4e24c66262a0a6302ea7611e3aa1897c9b2442c24cfbc0feedfb46b1
3
+ metadata.gz: f883874c9b6d85c217809bb7423c5dc7b31db4932b9bf8ad8e8c469eb0820973
4
+ data.tar.gz: c29bbb53cd0a8153988e4d26dc709a3aa49ad94114a7c175d636705f94eb263c
5
5
  SHA512:
6
- metadata.gz: '056827d707f4e8388aa2350b53791d060f7c30872d5930311606d750822483a667b5d74093e03217cfa3a342c4fe8cd5d5b766fd3155de9c5ff0c6da6b335a17'
7
- data.tar.gz: 389f981e6ae4b3a06cc3babbb70b942b0e2a8844bee690fc508af111bab65b333777f7b330554d2ae0f43abae918e3395ff1ae306b17df015efe94eaebd8c0e8
6
+ metadata.gz: b0f9a932c1e3640eac45e9b1f3af0964f0f8e9c24037d961f6d10825bcbc97f323b324ae58595de0c84c73d900add6ef96357c6555796938fc3e8d2c261217b1
7
+ data.tar.gz: 4de0397ab55e6863d64a2faa20a63bc6b97b585308b6f8d5694c75dbc10b90b18b1b202a7501b51a409bd59eaa2fa415d637816060550927751d42047bf4a530
data/README.md CHANGED
@@ -67,6 +67,10 @@ All the mandatory capybara features plus optional ones:
67
67
  * `page.response_headers`
68
68
  * `page.save_screenshot`
69
69
  * `page.driver.render_base64(format, options)`
70
+ * `page.driver.scroll_to(left, top)`
71
+ * `page.driver.basic_authorize(user, password)`
72
+ * `element.send_keys(*keys)`
73
+ * `page.driver.set_proxy(ip, port, type, user, password)`
70
74
  * window API
71
75
  * cookie handling
72
76
 
@@ -134,6 +138,8 @@ end
134
138
  * `:browser_path` (String) - Path to chrome binary, you can also set ENV
135
139
  variable as `BROWSER_PATH=some/path/chrome bundle exec rspec`.
136
140
  * `:headless` (Boolean) - Set browser as headless or not, `true` by default.
141
+ * `:slowmo` (Integer | Float) - Set a delay to wait before sending command.
142
+ Usefull companion of headless option, so that you have time to see changes.
137
143
  * `:logger` (Object responding to `puts`) - When present, debug output is
138
144
  written to this object.
139
145
  * `:timeout` (Numeric) - The number of seconds we'll wait for a response when
@@ -156,14 +162,14 @@ Cuprite supports URL blacklisting, which allows you to prevent scripts from
156
162
  running on designated domains:
157
163
 
158
164
  ```ruby
159
- page.driver.browser.url_blacklist = ['http://www.example.com']
165
+ page.driver.browser.url_blacklist = ["http://www.example.com"]
160
166
  ```
161
167
 
162
168
  and also URL whitelisting, which allows scripts to only run
163
169
  on designated domains:
164
170
 
165
171
  ```ruby
166
- page.driver.browser.url_whitelist = ['http://www.example.com']
172
+ page.driver.browser.url_whitelist = ["http://www.example.com"]
167
173
  ```
168
174
 
169
175
  If you are experiencing slower run times, consider creating a URL whitelist of
@@ -17,6 +17,10 @@ module Capybara::Cuprite
17
17
  RbConfig::CONFIG["host_os"] =~ /mingw|mswin|cygwin/
18
18
  end
19
19
 
20
+ def mac?
21
+ RbConfig::CONFIG["host_os"] =~ /darwin/
22
+ end
23
+
20
24
  def mri?
21
25
  defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby"
22
26
  end
@@ -10,13 +10,14 @@ require "capybara/cuprite/browser/page"
10
10
  module Capybara::Cuprite
11
11
  class Browser
12
12
  TIMEOUT = 5
13
+ WINDOW_SIZE = [1024, 768].freeze
13
14
  EXTENSIONS = [
14
15
  File.expand_path("browser/javascripts/index.js", __dir__)
15
16
  ].freeze
16
17
 
17
18
  extend Forwardable
18
19
 
19
- attr_reader :headers
20
+ attr_reader :headers, :window_size
20
21
 
21
22
  def self.start(*args)
22
23
  new(*args)
@@ -26,27 +27,41 @@ module Capybara::Cuprite
26
27
  delegate %i(window_handle window_handles switch_to_window open_new_window
27
28
  close_window within_window page) => :targets
28
29
  delegate %i(visit status_code body all_text property attributes attribute
29
- value visible? disabled? resize path network_traffic
30
- clear_network_traffic response_headers refresh click right_click
31
- double_click hover set click_coordinates drag drag_by select
32
- trigger scroll_to send_keys evaluate evaluate_on evaluate_async
33
- execute frame_url frame_title switch_to_frame current_url title
34
- go_back go_forward find_modal accept_confirm dismiss_confirm
35
- accept_prompt dismiss_prompt reset_modals) => :page
36
-
37
- attr_reader :process, :logger, :js_errors
30
+ value visible? disabled? network_traffic clear_network_traffic
31
+ path response_headers refresh click right_click double_click
32
+ hover set click_coordinates drag drag_by select trigger
33
+ scroll_to send_keys evaluate evaluate_on evaluate_async execute
34
+ frame_url frame_title switch_to_frame current_url title go_back
35
+ go_forward find_modal accept_confirm dismiss_confirm
36
+ accept_prompt dismiss_prompt reset_modals authorize
37
+ proxy_authorize) => :page
38
+
39
+ attr_reader :process, :logger, :js_errors, :slowmo,
40
+ :url_blacklist, :url_whitelist
38
41
  attr_writer :timeout
39
42
 
40
43
  def initialize(options = nil)
41
- @options = Hash(options)
44
+ # Doesn't work on MacOS, so we need to set it by CDP as well
45
+ options ||= {}
46
+ @window_size = options.fetch(:window_size, WINDOW_SIZE)
47
+ @original_window_size = @window_size
48
+
49
+ @options = Hash(options.merge(window_size: @window_size))
42
50
  @logger, @timeout = @options.values_at(:logger, :timeout)
43
51
  @js_errors = @options.fetch(:js_errors, false)
52
+ @slowmo = @options[:slowmo]
53
+
54
+ self.url_blacklist = @options[:url_blacklist]
55
+ self.url_whitelist = @options[:url_whitelist]
44
56
 
45
- if ENV["CUPRITE_DEBUG"]
57
+ if ENV["CUPRITE_DEBUG"] && !@logger
46
58
  STDOUT.sync = true
47
59
  @logger = STDOUT
60
+ @options[:logger] = @logger
48
61
  end
49
62
 
63
+ @options.freeze
64
+
50
65
  start
51
66
  end
52
67
 
@@ -169,23 +184,14 @@ module Capybara::Cuprite
169
184
  page.command("Network.clearBrowserCookies")
170
185
  end
171
186
 
172
- def set_http_auth(user, password)
173
- raise NotImplementedError
174
- end
175
-
176
- def page_settings=(settings)
177
- raise NotImplementedError
178
- end
179
-
180
- def url_whitelist=(whitelist)
181
- @url_whitelist = Array(whitelist).map { |p| { urlPattern: p } }
182
- page.command("Network.setRequestInterception", patterns: @url_whitelist)
187
+ def url_whitelist=(wildcards)
188
+ @url_whitelist = prepare_wildcards(wildcards)
189
+ page.intercept_request("*") if @client && !@url_whitelist.empty?
183
190
  end
184
191
 
185
- def url_blacklist=(blacklist)
186
- # FIXME: We have to change the format and make it compatible with Chrome not PhantomJS
187
- @url_blacklist = Array(blacklist).map { |p| { urlPattern: p.include?("*") ? p : "*#{p}*" } }
188
- page.command("Network.setRequestInterception", patterns: @url_blacklist)
192
+ def url_blacklist=(wildcards)
193
+ @url_blacklist = prepare_wildcards(wildcards)
194
+ page.intercept_request("*") if @client && !@url_blacklist.empty?
189
195
  end
190
196
 
191
197
  def clear_memory_cache
@@ -195,6 +201,7 @@ module Capybara::Cuprite
195
201
  def reset
196
202
  @headers = {}
197
203
  @zoom_factor = nil
204
+ @window_size = @original_window_size
198
205
  targets.reset
199
206
  end
200
207
 
@@ -217,6 +224,11 @@ module Capybara::Cuprite
217
224
  page.evaluate("_cuprite.browserError()")
218
225
  end
219
226
 
227
+ def resize(**options)
228
+ @window_size = [options[:width], options[:height]]
229
+ page.resize(**options)
230
+ end
231
+
220
232
  def command(*args)
221
233
  id = @client.command(*args)
222
234
  @client.wait(id: id)
@@ -234,7 +246,7 @@ module Capybara::Cuprite
234
246
  def start
235
247
  @headers = {}
236
248
  @process = Process.start(@options)
237
- @client = Client.new(self, @process.ws_url)
249
+ @client = Client.new(self, @process.ws_url, false)
238
250
  end
239
251
 
240
252
  def render_options(format, opts)
@@ -290,5 +302,16 @@ module Capybara::Cuprite
290
302
  raise
291
303
  end
292
304
  end
305
+
306
+ def prepare_wildcards(wc)
307
+ Array(wc).map do |wildcard|
308
+ if wildcard.is_a?(Regexp)
309
+ wildcard
310
+ else
311
+ wildcard = wildcard.gsub("*", ".*")
312
+ Regexp.new(wildcard, Regexp::IGNORECASE)
313
+ end
314
+ end
315
+ end
293
316
  end
294
317
  end
@@ -8,10 +8,10 @@ module Capybara::Cuprite
8
8
  class Client
9
9
  class IdError < RuntimeError; end
10
10
 
11
- def initialize(browser, ws_url)
11
+ def initialize(browser, ws_url, allow_slowmo = true)
12
12
  @command_id = 0
13
13
  @subscribed = Hash.new { |h, k| h[k] = [] }
14
- @browser = browser
14
+ @browser, @allow_slowmo = browser, allow_slowmo
15
15
  @commands = Queue.new
16
16
  @ws = WebSocket.new(ws_url, @browser.logger)
17
17
 
@@ -31,6 +31,7 @@ module Capybara::Cuprite
31
31
 
32
32
  def command(method, params = {})
33
33
  message = build_message(method, params)
34
+ sleep(@browser.slowmo) if !@browser.slowmo.nil? && @allow_slowmo
34
35
  @ws.send_message(message)
35
36
  message[:id]
36
37
  end
@@ -38,6 +38,8 @@ module Capybara::Cuprite
38
38
  private
39
39
 
40
40
  def subscribe_events
41
+ super if defined?(super)
42
+
41
43
  @client.subscribe("Page.frameAttached") do |params|
42
44
  @frames[params["frameId"]] = { "parent_id" => params["parentFrameId"] }
43
45
  end
@@ -124,12 +124,12 @@ class Cuprite {
124
124
  }
125
125
 
126
126
  this.trigger(node, "focus");
127
- node.value = "";
127
+ this.setValue(node, "");
128
128
 
129
129
  if (node.type == "number" || node.type == "date") {
130
- node.value = value;
130
+ this.setValue(node, value);
131
131
  } else if (node.type == "time") {
132
- node.value = new Date(value).toTimeString().split(" ")[0];
132
+ this.setValue(node, new Date(value).toTimeString().split(" ")[0]);
133
133
  } else if (node.type == "datetime-local") {
134
134
  value = new Date(value);
135
135
  let year = value.getFullYear();
@@ -138,13 +138,13 @@ class Cuprite {
138
138
  let hour = ("0" + value.getHours()).slice(-2);
139
139
  let min = ("0" + value.getMinutes()).slice(-2);
140
140
  let sec = ("0" + value.getSeconds()).slice(-2);
141
- node.value = `${year}-${month}-${date}T${hour}:${min}:${sec}`;
141
+ this.setValue(node, `${year}-${month}-${date}T${hour}:${min}:${sec}`);
142
142
  } else {
143
143
  for (let i = 0; i < value.length; i++) {
144
144
  let char = value[i];
145
145
  let keyCode = this.characterToKeyCode(char);
146
146
  this.keyupdowned(node, "keydown", keyCode);
147
- node.value += char;
147
+ this.setValue(node, node.value + char);
148
148
 
149
149
  this.keypressed(node, false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));
150
150
  this.keyupdowned(node, "keyup", keyCode);
@@ -156,6 +156,16 @@ class Cuprite {
156
156
  this.trigger(node, "blur");
157
157
  }
158
158
 
159
+ setValue(node, value) {
160
+ let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
161
+ let nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
162
+
163
+ if (node.tagName.toLowerCase() === 'input') {
164
+ return nativeInputValueSetter.call(node, value);
165
+ }
166
+ return nativeTextareaValueSetter.call(node, value);
167
+ }
168
+
159
169
  input(node) {
160
170
  let event = document.createEvent("HTMLEvents");
161
171
  event.initEvent("input", true, false);
@@ -0,0 +1,90 @@
1
+ module Capybara::Cuprite
2
+ class Browser
3
+ module Net
4
+ def proxy_authorize(user, password)
5
+ if user && password
6
+ @proxy_username, @proxy_password = user, password
7
+ intercept_request("*")
8
+ end
9
+ end
10
+
11
+ def authorize(user, password)
12
+ @username, @password = user, password
13
+ intercept_request("*")
14
+ end
15
+
16
+ def intercept_request(patterns)
17
+ patterns = Array(patterns).map { |p| { urlPattern: p } }
18
+ @client.command("Network.setRequestInterception", patterns: patterns)
19
+ end
20
+
21
+ def continue_request(interception_id, options = nil)
22
+ options ||= {}
23
+ options = options.merge(interceptionId: interception_id)
24
+ @client.command("Network.continueInterceptedRequest", **options)
25
+ end
26
+
27
+ private
28
+
29
+ def subscribe_events
30
+ super if defined?(super)
31
+
32
+ @client.subscribe("Network.loadingFailed") do |params|
33
+ # Free mutex as we aborted main request we are waiting for
34
+ if params["requestId"] == @request_id && params["canceled"] == true
35
+ signal
36
+ @client.command("DOM.getDocument", depth: 0)
37
+ end
38
+ end
39
+
40
+ @client.subscribe("Network.requestIntercepted") do |params|
41
+ @authorized_ids ||= []
42
+ @proxy_authorized_ids ||= []
43
+ url = params.dig("request", "url")
44
+ interception_id = params["interceptionId"]
45
+
46
+ if params["authChallenge"]
47
+ response = if params.dig("authChallenge", "source") == "Proxy"
48
+ if @proxy_authorized_ids.include?(interception_id)
49
+ { response: "CancelAuth" }
50
+ elsif @proxy_username && @proxy_password
51
+ { response: "ProvideCredentials",
52
+ username: @proxy_username,
53
+ password: @proxy_password }
54
+ else
55
+ { response: "CancelAuth" }
56
+ end
57
+ else
58
+ if @authorized_ids.include?(interception_id)
59
+ { response: "CancelAuth" }
60
+ elsif @username && @password
61
+ { response: "ProvideCredentials",
62
+ username: @username,
63
+ password: @password }
64
+ else
65
+ { response: "CancelAuth" }
66
+ end
67
+ end
68
+
69
+ @authorized_ids << interception_id
70
+ continue_request(interception_id, authChallengeResponse: response)
71
+ elsif @browser.url_blacklist && !@browser.url_blacklist.empty?
72
+ if @browser.url_blacklist.any? { |r| r.match(url) }
73
+ continue_request(interception_id, errorReason: "Aborted")
74
+ else
75
+ continue_request(interception_id)
76
+ end
77
+ elsif @browser.url_whitelist && !@browser.url_whitelist.empty?
78
+ if @browser.url_whitelist.any? { |r| r.match(url) }
79
+ continue_request(interception_id)
80
+ else
81
+ continue_request(interception_id, errorReason: "Aborted")
82
+ end
83
+ else
84
+ continue_request(interception_id)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -5,6 +5,7 @@ require "capybara/cuprite/browser/input"
5
5
  require "capybara/cuprite/browser/runtime"
6
6
  require "capybara/cuprite/browser/frame"
7
7
  require "capybara/cuprite/browser/client"
8
+ require "capybara/cuprite/browser/net"
8
9
  require "capybara/cuprite/network/error"
9
10
  require "capybara/cuprite/network/request"
10
11
  require "capybara/cuprite/network/response"
@@ -29,7 +30,7 @@ require "capybara/cuprite/network/response"
29
30
  module Capybara::Cuprite
30
31
  class Browser
31
32
  class Page
32
- include Input, DOM, Runtime, Frame
33
+ include Input, DOM, Runtime, Frame, Net
33
34
 
34
35
  attr_accessor :referrer
35
36
  attr_reader :target_id, :status_code, :response_headers
@@ -74,7 +75,11 @@ module Capybara::Cuprite
74
75
  options = { url: url }
75
76
  options.merge!(referrer: referrer) if referrer
76
77
  response = command("Page.navigate", **options)
77
- if %w[net::ERR_NAME_NOT_RESOLVED net::ERR_NAME_RESOLUTION_FAILED].include?(response["errorText"])
78
+ # https://cs.chromium.org/chromium/src/net/base/net_error_list.h
79
+ if %w[net::ERR_NAME_NOT_RESOLVED
80
+ net::ERR_NAME_RESOLUTION_FAILED
81
+ net::ERR_INTERNET_DISCONNECTED
82
+ net::ERR_CONNECTION_TIMED_OUT].include?(response["errorText"])
78
83
  raise StatusFailError, "url" => url
79
84
  end
80
85
  response["frameId"]
@@ -109,13 +114,13 @@ module Capybara::Cuprite
109
114
  end
110
115
 
111
116
  def network_traffic(type = nil)
112
- case type
117
+ case type.to_s
113
118
  when "all"
114
119
  @network_traffic
115
120
  when "blocked"
116
- @network_traffic # when request blocked
121
+ @network_traffic.select { |r| r.response.nil? } # when request blocked
117
122
  else
118
- @network_traffic # when not request blocked
123
+ @network_traffic.select { |r| r.response } # when request isn't blocked
119
124
  end
120
125
  end
121
126
 
@@ -274,10 +279,6 @@ module Capybara::Cuprite
274
279
  end
275
280
  end
276
281
 
277
- @client.subscribe("Network.requestIntercepted") do |params|
278
- @client.command("Network.continueInterceptedRequest", interceptionId: params["interceptionId"], errorReason: "Aborted")
279
- end
280
-
281
282
  @client.subscribe("Log.entryAdded") do |params|
282
283
  source = params.dig("entry", "source")
283
284
  level = params.dig("entry", "level")
@@ -297,8 +298,9 @@ module Capybara::Cuprite
297
298
  command("Runtime.enable")
298
299
  command("Log.enable")
299
300
  command("Network.enable")
301
+
300
302
  if Capybara.save_path
301
- command("Page.setDownloadBehavior", behavior: "allow", downloadPath: Capybara.save_path)
303
+ command("Page.setDownloadBehavior", behavior: "allow", downloadPath: Capybara.save_path.to_s)
302
304
  end
303
305
 
304
306
  @browser.extensions.each do |extension|
@@ -307,6 +309,13 @@ module Capybara::Cuprite
307
309
 
308
310
  inject_extensions
309
311
 
312
+ width, height = @browser.window_size
313
+ resize(width: width, height: height)
314
+
315
+ url_whitelist = Array(@browser.url_whitelist)
316
+ url_blacklist = Array(@browser.url_blacklist)
317
+ intercept_request("*") if !url_whitelist.empty? || !url_blacklist.empty?
318
+
310
319
  response = command("Page.getNavigationHistory")
311
320
  if response.dig("entries", 0, "transitionType") != "typed"
312
321
  # If we create page by clicking links, submiting forms and so on it
@@ -6,26 +6,47 @@ module Capybara::Cuprite
6
6
  class Browser
7
7
  class Process
8
8
  KILL_TIMEOUT = 2
9
-
10
9
  BROWSER_PATH = ENV["BROWSER_PATH"]
11
10
  BROWSER_HOST = "127.0.0.1"
12
11
  BROWSER_PORT = "0"
13
-
14
- # Chromium command line options
15
- # https://peter.sh/experiments/chromium-command-line-switches/
16
12
  DEFAULT_OPTIONS = {
17
13
  "headless" => nil,
18
14
  "disable-gpu" => nil,
19
15
  "hide-scrollbars" => nil,
20
16
  "mute-audio" => nil,
17
+ "enable-automation" => nil,
18
+ "disable-web-security" => nil,
19
+ "disable-session-crashed-bubble" => nil,
20
+ "disable-breakpad" => nil,
21
+ "disable-sync" => nil,
22
+ "no-first-run" => nil,
23
+ "use-mock-keychain" => nil,
24
+ "keep-alive-for-test" => nil,
25
+ "disable-popup-blocking" => nil,
26
+ "disable-extensions" => nil,
27
+ "disable-hang-monitor" => nil,
28
+ "disable-features" => "site-per-process,TranslateUI",
29
+ "disable-translate" => nil,
30
+ "disable-background-networking" => nil,
31
+ "enable-features" => "NetworkService,NetworkServiceInProcess",
32
+ "disable-background-timer-throttling" => nil,
33
+ "disable-backgrounding-occluded-windows" => nil,
34
+ "disable-client-side-phishing-detection" => nil,
35
+ "disable-default-apps" => nil,
36
+ "disable-dev-shm-usage" => nil,
37
+ "disable-ipc-flooding-protection" => nil,
38
+ "disable-prompt-on-repost" => nil,
39
+ "disable-renderer-backgrounding" => nil,
40
+ "force-color-profile" => "srgb",
41
+ "metrics-recording-only" => nil,
42
+ "safebrowsing-disable-auto-update" => nil,
43
+ "password-store" => "basic",
21
44
  # Note: --no-sandbox is not needed if you properly setup a user in the container.
22
45
  # https://github.com/ebidel/lighthouse-ci/blob/master/builder/Dockerfile#L35-L40
23
46
  # "no-sandbox" => nil,
24
- "enable-automation" => nil,
25
- "disable-web-security" => nil,
26
47
  }.freeze
27
48
 
28
- attr_reader :host, :port, :ws_url, :pid, :path, :options
49
+ attr_reader :host, :port, :ws_url, :pid, :path, :options, :cmd
29
50
 
30
51
  def self.start(*args)
31
52
  new(*args).tap(&:start)
@@ -37,7 +58,7 @@ module Capybara::Cuprite
37
58
  if Capybara::Cuprite.windows?
38
59
  ::Process.kill("KILL", pid)
39
60
  else
40
- ::Process.kill("TERM", pid)
61
+ ::Process.kill("USR1", pid)
41
62
  start = Time.now
42
63
  while ::Process.wait(pid, ::Process::WNOHANG).nil?
43
64
  sleep 0.05
@@ -57,8 +78,8 @@ module Capybara::Cuprite
57
78
 
58
79
  detect_browser_path(options)
59
80
 
60
- window_size = options.fetch(:window_size, [1024, 768])
61
- @options.merge!("window-size" => window_size.join(","))
81
+ # Doesn't work on MacOS, so we need to set it by CDP as well
82
+ @options.merge!("window-size" => options[:window_size].join(","))
62
83
 
63
84
  port = options.fetch(:port, BROWSER_PORT)
64
85
  @options.merge!("remote-debugging-port" => port)
@@ -66,6 +87,8 @@ module Capybara::Cuprite
66
87
  host = options.fetch(:host, BROWSER_HOST)
67
88
  @options.merge!("remote-debugging-address" => host)
68
89
 
90
+ @options.merge!("user-data-dir" => Dir.mktmpdir)
91
+
69
92
  @options = DEFAULT_OPTIONS.merge(@options)
70
93
 
71
94
  unless options.fetch(:headless, true)
@@ -74,6 +97,8 @@ module Capybara::Cuprite
74
97
  end
75
98
 
76
99
  @options.merge!(options.fetch(:browser_options, {}))
100
+
101
+ @logger = options.fetch(:logger, nil)
77
102
  end
78
103
 
79
104
  def start
@@ -85,8 +110,8 @@ module Capybara::Cuprite
85
110
  end
86
111
 
87
112
  redirect_stdout(write_io) do
88
- cmd = [@path] + @options.map { |k, v| v.nil? ? "--#{k}" : "--#{k}=#{v}" }
89
- @pid = ::Process.spawn(*cmd, process_options)
113
+ @cmd = [@path] + @options.map { |k, v| v.nil? ? "--#{k}" : "--#{k}=#{v}" }
114
+ @pid = ::Process.spawn(*@cmd, process_options)
90
115
  ObjectSpace.define_finalizer(self, self.class.process_killer(@pid))
91
116
  end
92
117
 
@@ -118,7 +143,7 @@ module Capybara::Cuprite
118
143
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
119
144
  ].find { |path| File.exist?(path) }
120
145
  else
121
- %w[chromium google-chrome-unstable google-chrome-beta google-chrome chrome].reduce(nil) do |path, exe|
146
+ %w[chromium google-chrome-unstable google-chrome-beta google-chrome chrome chromium-browser].reduce(nil) do |path, exe|
122
147
  path = Cliver.detect(exe)
123
148
  break path if path
124
149
  end
@@ -126,7 +151,7 @@ module Capybara::Cuprite
126
151
  )
127
152
 
128
153
  unless @path
129
- message = "Could not find an executable `#{exe}`. Try to make it " \
154
+ message = "Could not find an executable for chrome. Try to make it " \
130
155
  "available on the PATH or set environment varible for " \
131
156
  "example BROWSER_PATH=\"/Applications/Chromium.app/Contents/MacOS/Chromium\""
132
157
  raise Cliver::Dependency::NotFound.new(message)
@@ -167,7 +192,7 @@ module Capybara::Cuprite
167
192
  IO.select([read_io], nil, nil, max_time - now)
168
193
  else
169
194
  if output.match(regexp)
170
- @ws_url = Addressable::URI.parse(output.match(regexp)[1])
195
+ @ws_url = Addressable::URI.parse(output.match(regexp)[1].strip)
171
196
  @host = @ws_url.host
172
197
  @port = @ws_url.port
173
198
  break
@@ -176,6 +201,7 @@ module Capybara::Cuprite
176
201
  end
177
202
 
178
203
  unless @ws_url
204
+ @logger.puts output if @logger
179
205
  raise "Chrome process did not produce websocket url within #{timeout} seconds"
180
206
  end
181
207
  end
@@ -132,15 +132,20 @@ module Capybara::Cuprite
132
132
 
133
133
  case response["subtype"]
134
134
  when "node"
135
- node_id = command("DOM.requestNode", objectId: object_id)["nodeId"]
136
- node = command("DOM.describeNode", nodeId: node_id)["node"].merge("nodeId" => node_id)
137
- { "target_id" => target_id, "node" => node }
135
+ begin
136
+ node_id = command("DOM.requestNode", objectId: object_id)["nodeId"]
137
+ node = command("DOM.describeNode", nodeId: node_id)["node"].merge("nodeId" => node_id)
138
+ { "target_id" => target_id, "node" => node }
139
+ rescue BrowserError => e
140
+ # Node has disappeared while we were trying to get it
141
+ raise if e.message != "Could not find node with given id"
142
+ end
138
143
  when "array"
139
144
  reduce_props(object_id, []) do |memo, key, value|
140
145
  next(memo) unless (Integer(key) rescue nil)
141
146
  value = value["objectId"] ? handle(value) : value["value"]
142
147
  memo.insert(key.to_i, value)
143
- end
148
+ end.compact
144
149
  when "date"
145
150
  response["description"]
146
151
  when "null"
@@ -129,8 +129,8 @@ module Capybara::Cuprite
129
129
 
130
130
  def reset!
131
131
  browser.reset
132
- browser.url_blacklist = @options[:url_blacklist] if @options.key?(:url_blacklist)
133
- browser.url_whitelist = @options[:url_whitelist] if @options.key?(:url_whitelist)
132
+ browser.url_blacklist = @options[:url_blacklist]
133
+ browser.url_whitelist = @options[:url_whitelist]
134
134
  @started = false
135
135
  end
136
136
 
@@ -190,11 +190,11 @@ module Capybara::Cuprite
190
190
  browser.clear_network_traffic
191
191
  end
192
192
 
193
- # FIXME: check user/pass proxy auth
194
193
  def set_proxy(ip, port, type = "http", user = nil, password = nil, bypass = nil)
195
- @options[:browser] ||= {}
196
- @options[:browser].merge!("proxy-server" => "#{type}=#{ip}:#{port}")
197
- @options[:browser].merge!("proxy-bypass-list" => bypass) if bypass
194
+ @options[:browser_options] ||= {}
195
+ @options[:browser_options].merge!("proxy-server" => "#{type}=#{ip}:#{port}")
196
+ @options[:browser_options].merge!("proxy-bypass-list" => bypass) if bypass
197
+ browser.proxy_authorize(user, password)
198
198
  end
199
199
 
200
200
  def headers
@@ -246,16 +246,10 @@ module Capybara::Cuprite
246
246
  browser.clear_memory_cache
247
247
  end
248
248
 
249
- # * Browser with set settings does not send `Authorize` on POST request
250
- # * With manually set header browser makes next request with
251
- # `Authorization: Basic Og==` header when settings are empty and the
252
- # response was `401 Unauthorized` (which means Base64.encode64(":")).
253
- # Combining both methods to reach proper behavior.
254
249
  def basic_authorize(user, password)
255
- browser.set_http_auth(user, password)
256
- credentials = ["#{user}:#{password}"].pack("m*").strip
257
- add_header("Authorization", "Basic #{credentials}")
250
+ browser.authorize(user, password)
258
251
  end
252
+ alias_method :authorize, :basic_authorize
259
253
 
260
254
  def pause
261
255
  # STDIN is not necessarily connected to a keyboard. It might even be closed.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Cuprite
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cuprite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Vorotilin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-31 00:00:00.000000000 Z
11
+ date: 2019-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: capybara
@@ -208,6 +208,7 @@ files:
208
208
  - lib/capybara/cuprite/browser/input.json
209
209
  - lib/capybara/cuprite/browser/input.rb
210
210
  - lib/capybara/cuprite/browser/javascripts/index.js
211
+ - lib/capybara/cuprite/browser/net.rb
211
212
  - lib/capybara/cuprite/browser/page.rb
212
213
  - lib/capybara/cuprite/browser/process.rb
213
214
  - lib/capybara/cuprite/browser/runtime.rb