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 +4 -4
- data/README.md +8 -2
- data/lib/capybara/cuprite.rb +4 -0
- data/lib/capybara/cuprite/browser.rb +51 -28
- data/lib/capybara/cuprite/browser/client.rb +3 -2
- data/lib/capybara/cuprite/browser/frame.rb +2 -0
- data/lib/capybara/cuprite/browser/javascripts/index.js +15 -5
- data/lib/capybara/cuprite/browser/net.rb +90 -0
- data/lib/capybara/cuprite/browser/page.rb +19 -10
- data/lib/capybara/cuprite/browser/process.rb +41 -15
- data/lib/capybara/cuprite/browser/runtime.rb +9 -4
- data/lib/capybara/cuprite/driver.rb +8 -14
- data/lib/capybara/cuprite/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f883874c9b6d85c217809bb7423c5dc7b31db4932b9bf8ad8e8c469eb0820973
|
4
|
+
data.tar.gz: c29bbb53cd0a8153988e4d26dc709a3aa49ad94114a7c175d636705f94eb263c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 = [
|
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 = [
|
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
|
data/lib/capybara/cuprite.rb
CHANGED
@@ -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?
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
accept_prompt dismiss_prompt reset_modals
|
36
|
-
|
37
|
-
|
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
|
-
|
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
|
173
|
-
|
174
|
-
|
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=(
|
186
|
-
|
187
|
-
|
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
|
@@ -124,12 +124,12 @@ class Cuprite {
|
|
124
124
|
}
|
125
125
|
|
126
126
|
this.trigger(node, "focus");
|
127
|
-
node
|
127
|
+
this.setValue(node, "");
|
128
128
|
|
129
129
|
if (node.type == "number" || node.type == "date") {
|
130
|
-
node
|
130
|
+
this.setValue(node, value);
|
131
131
|
} else if (node.type == "time") {
|
132
|
-
node
|
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
|
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
|
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
|
-
|
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
|
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("
|
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
|
-
|
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(
|
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
|
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
|
-
|
136
|
-
|
137
|
-
|
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]
|
133
|
-
browser.url_whitelist = @options[: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[:
|
196
|
-
@options[:
|
197
|
-
@options[:
|
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.
|
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.
|
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
|
+
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-
|
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
|