cuprite 0.6.0 → 0.7.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 +103 -67
- data/lib/capybara/cuprite.rb +8 -21
- data/lib/capybara/cuprite/browser.rb +66 -224
- data/lib/capybara/cuprite/driver.rb +87 -44
- data/lib/capybara/cuprite/errors.rb +1 -64
- data/lib/capybara/cuprite/{browser/javascripts → javascripts}/index.js +26 -20
- data/lib/capybara/cuprite/node.rb +37 -31
- data/lib/capybara/cuprite/page.rb +166 -0
- data/lib/capybara/cuprite/version.rb +1 -1
- metadata +8 -42
- data/lib/capybara/cuprite/browser/client.rb +0 -74
- data/lib/capybara/cuprite/browser/dom.rb +0 -50
- data/lib/capybara/cuprite/browser/frame.rb +0 -115
- data/lib/capybara/cuprite/browser/input.json +0 -1341
- data/lib/capybara/cuprite/browser/input.rb +0 -200
- data/lib/capybara/cuprite/browser/net.rb +0 -90
- data/lib/capybara/cuprite/browser/page.rb +0 -378
- data/lib/capybara/cuprite/browser/process.rb +0 -223
- data/lib/capybara/cuprite/browser/runtime.rb +0 -182
- data/lib/capybara/cuprite/browser/targets.rb +0 -129
- data/lib/capybara/cuprite/browser/web_socket.rb +0 -69
- data/lib/capybara/cuprite/network/error.rb +0 -25
- data/lib/capybara/cuprite/network/request.rb +0 -33
- data/lib/capybara/cuprite/network/response.rb +0 -44
@@ -1,24 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "forwardable"
|
4
|
+
|
3
5
|
module Capybara::Cuprite
|
4
6
|
class Node < Capybara::Driver::Node
|
5
|
-
attr_reader :
|
7
|
+
attr_reader :node
|
6
8
|
|
7
|
-
|
8
|
-
super(driver, self)
|
9
|
-
@target_id, @node = target_id, node
|
10
|
-
end
|
9
|
+
extend Forwardable
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
delegate %i(description) => :node
|
12
|
+
delegate %i(browser) => :driver
|
13
|
+
|
14
|
+
def initialize(driver, node)
|
15
|
+
super(driver, self)
|
16
|
+
@node = node
|
14
17
|
end
|
15
18
|
|
16
19
|
def command(name, *args)
|
17
|
-
browser.send(name,
|
18
|
-
rescue
|
20
|
+
browser.send(name, node, *args)
|
21
|
+
rescue Ferrum::NodeNotFoundError => e
|
22
|
+
raise ObsoleteNode.new(self, e.response)
|
23
|
+
rescue Ferrum::BrowserError => e
|
19
24
|
case e.message
|
20
|
-
when "No node with given id found"
|
21
|
-
raise ObsoleteNode.new(self, e.response)
|
22
25
|
when "Cuprite.MouseEventFailed"
|
23
26
|
raise MouseEventFailed.new(self, e.response)
|
24
27
|
else
|
@@ -28,13 +31,7 @@ module Capybara::Cuprite
|
|
28
31
|
|
29
32
|
def parents
|
30
33
|
command(:parents).map do |parent|
|
31
|
-
self.class.new(driver, parent
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def find(method, selector)
|
36
|
-
command(:find_within, method, selector).map do |node|
|
37
|
-
self.class.new(driver, @target_id, node)
|
34
|
+
self.class.new(driver, parent)
|
38
35
|
end
|
39
36
|
end
|
40
37
|
|
@@ -46,6 +43,12 @@ module Capybara::Cuprite
|
|
46
43
|
find(:css, selector)
|
47
44
|
end
|
48
45
|
|
46
|
+
def find(method, selector)
|
47
|
+
command(:find_within, method, selector).map do |node|
|
48
|
+
self.class.new(driver, node)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
49
52
|
def all_text
|
50
53
|
filter_text(command(:all_text))
|
51
54
|
end
|
@@ -69,7 +72,8 @@ module Capybara::Cuprite
|
|
69
72
|
def [](name)
|
70
73
|
# Although the attribute matters, the property is consistent. Return that in
|
71
74
|
# preference to the attribute for links and images.
|
72
|
-
if (
|
75
|
+
if (tag_name == "img" && name == "src") ||
|
76
|
+
(tag_name == "a" && name == "href")
|
73
77
|
# if attribute exists get the property
|
74
78
|
return command(:attribute, name) && command(:property, name)
|
75
79
|
end
|
@@ -121,7 +125,7 @@ module Capybara::Cuprite
|
|
121
125
|
end
|
122
126
|
|
123
127
|
def tag_name
|
124
|
-
@tag_name ||=
|
128
|
+
@tag_name ||= description["nodeName"].downcase
|
125
129
|
end
|
126
130
|
|
127
131
|
def visible?
|
@@ -141,15 +145,15 @@ module Capybara::Cuprite
|
|
141
145
|
end
|
142
146
|
|
143
147
|
def click(keys = [], offset = {})
|
144
|
-
|
148
|
+
prepare_and_click(:left, __method__, keys, offset)
|
145
149
|
end
|
146
150
|
|
147
151
|
def right_click(keys = [], offset = {})
|
148
|
-
|
152
|
+
prepare_and_click(:right, __method__, keys, offset)
|
149
153
|
end
|
150
154
|
|
151
155
|
def double_click(keys = [], offset = {})
|
152
|
-
|
156
|
+
prepare_and_click(:double, __method__, keys, offset)
|
153
157
|
end
|
154
158
|
|
155
159
|
def hover
|
@@ -157,7 +161,7 @@ module Capybara::Cuprite
|
|
157
161
|
end
|
158
162
|
|
159
163
|
def drag_to(other)
|
160
|
-
command(:drag, other
|
164
|
+
command(:drag, other)
|
161
165
|
end
|
162
166
|
|
163
167
|
def drag_by(x, y)
|
@@ -192,10 +196,7 @@ module Capybara::Cuprite
|
|
192
196
|
end
|
193
197
|
|
194
198
|
def ==(other)
|
195
|
-
|
196
|
-
# never returns same nodeId sending 0. In other words frontend is
|
197
|
-
# responsible for keeping track of node ids.
|
198
|
-
@target_id == other.target_id && @node["backendNodeId"] == other.node["backendNodeId"]
|
199
|
+
node == other.native.node
|
199
200
|
end
|
200
201
|
|
201
202
|
def send_keys(*keys)
|
@@ -208,7 +209,7 @@ module Capybara::Cuprite
|
|
208
209
|
end
|
209
210
|
|
210
211
|
def inspect
|
211
|
-
%(#<#{self.class} @
|
212
|
+
%(#<#{self.class} @node=#{@node.inspect}>)
|
212
213
|
end
|
213
214
|
|
214
215
|
# @api private
|
@@ -218,12 +219,17 @@ module Capybara::Cuprite
|
|
218
219
|
|
219
220
|
# @api private
|
220
221
|
def as_json(*)
|
221
|
-
# FIXME: Where this method
|
222
|
-
{ ELEMENT: {
|
222
|
+
# FIXME: Where is this method used and why attr is called id?
|
223
|
+
{ ELEMENT: { node: node, id: node.node_id } }
|
223
224
|
end
|
224
225
|
|
225
226
|
private
|
226
227
|
|
228
|
+
def prepare_and_click(mode, name, keys, offset)
|
229
|
+
command(:before_click, name, keys, offset)
|
230
|
+
node.click(mode: mode, keys: keys, offset: offset)
|
231
|
+
end
|
232
|
+
|
227
233
|
def filter_text(text)
|
228
234
|
if Capybara::VERSION.to_f < 3
|
229
235
|
Capybara::Helpers.normalize_whitespace(text.to_s)
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara::Cuprite
|
4
|
+
module Page
|
5
|
+
MODAL_WAIT = ENV.fetch("CUPRITE_MODAL_WAIT", 0.05).to_f
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
super
|
9
|
+
@accept_modal = []
|
10
|
+
@modal_messages = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def set(node, value)
|
14
|
+
object_id = command("DOM.resolveNode", nodeId: node.node_id).dig("object", "objectId")
|
15
|
+
evaluate("_cuprite.set(arguments[0], arguments[1])", { "objectId" => object_id }, value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def select(node, value)
|
19
|
+
evaluate_on(node: node, expression: "_cuprite.select(this, #{value})")
|
20
|
+
end
|
21
|
+
|
22
|
+
def trigger(node, event)
|
23
|
+
options = {}
|
24
|
+
options.merge!(wait: Ferrum::Mouse::CLICK_WAIT) if event.to_s == "click"
|
25
|
+
evaluate_on(node: node, expression: %(_cuprite.trigger(this, "#{event}")), **options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def hover(node)
|
29
|
+
evaluate_on(node: node, expression: "_cuprite.scrollIntoViewport(this)")
|
30
|
+
x, y = find_position(node)
|
31
|
+
command("Input.dispatchMouseEvent", type: "mouseMoved", x: x, y: y)
|
32
|
+
end
|
33
|
+
|
34
|
+
def send_keys(node, keys)
|
35
|
+
if !evaluate_on(node: node, expression: %(_cuprite.containsSelection(this)))
|
36
|
+
before_click(node, "click")
|
37
|
+
node.click(mode: :left, keys: keys)
|
38
|
+
end
|
39
|
+
|
40
|
+
keyboard.type(keys)
|
41
|
+
end
|
42
|
+
|
43
|
+
def accept_confirm
|
44
|
+
@accept_modal << true
|
45
|
+
end
|
46
|
+
|
47
|
+
def dismiss_confirm
|
48
|
+
@accept_modal << false
|
49
|
+
end
|
50
|
+
|
51
|
+
def accept_prompt(modal_response)
|
52
|
+
@accept_modal << true
|
53
|
+
@modal_response = modal_response
|
54
|
+
end
|
55
|
+
|
56
|
+
def dismiss_prompt
|
57
|
+
@accept_modal << false
|
58
|
+
end
|
59
|
+
|
60
|
+
def find_modal(options)
|
61
|
+
start = Ferrum.monotonic_time
|
62
|
+
timeout = options.fetch(:wait) { session_wait_time }
|
63
|
+
expect_text = options[:text]
|
64
|
+
expect_regexp = expect_text.is_a?(Regexp) ? expect_text : Regexp.escape(expect_text.to_s)
|
65
|
+
not_found_msg = "Unable to find modal dialog"
|
66
|
+
not_found_msg += " with #{expect_text}" if expect_text
|
67
|
+
|
68
|
+
begin
|
69
|
+
modal_text = @modal_messages.shift
|
70
|
+
raise Capybara::ModalNotFound if modal_text.nil? || (expect_text && !modal_text.match(expect_regexp))
|
71
|
+
rescue Capybara::ModalNotFound => e
|
72
|
+
raise e, not_found_msg if Ferrum.timeout?(start, timeout)
|
73
|
+
sleep(MODAL_WAIT)
|
74
|
+
retry
|
75
|
+
end
|
76
|
+
|
77
|
+
modal_text
|
78
|
+
end
|
79
|
+
|
80
|
+
def reset_modals
|
81
|
+
@accept_modal = []
|
82
|
+
@modal_response = nil
|
83
|
+
@modal_messages = []
|
84
|
+
end
|
85
|
+
|
86
|
+
def before_click(node, name, keys = [], offset = {})
|
87
|
+
evaluate_on(node: node, expression: "_cuprite.scrollIntoViewport(this)")
|
88
|
+
x, y = find_position(node, offset[:x], offset[:y])
|
89
|
+
evaluate_on(node: node, expression: "_cuprite.mouseEventTest(this, '#{name}', #{x}, #{y})")
|
90
|
+
true
|
91
|
+
rescue Ferrum::JavaScriptError => e
|
92
|
+
raise MouseEventFailed.new(e.message) if e.class_name == "MouseEventFailed"
|
93
|
+
end
|
94
|
+
|
95
|
+
def switch_to_frame(handle)
|
96
|
+
case handle
|
97
|
+
when :parent
|
98
|
+
@frame_stack.pop
|
99
|
+
when :top
|
100
|
+
@frame_stack = []
|
101
|
+
else
|
102
|
+
@frame_stack << handle
|
103
|
+
inject_extensions
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def prepare_page
|
110
|
+
super
|
111
|
+
|
112
|
+
intercept_request if !Array(@browser.url_whitelist).empty? ||
|
113
|
+
!Array(@browser.url_blacklist).empty?
|
114
|
+
|
115
|
+
on(:request_intercepted) do |request, index, total|
|
116
|
+
if @browser.url_blacklist && !@browser.url_blacklist.empty?
|
117
|
+
if @browser.url_blacklist.any? { |r| request.match?(r) }
|
118
|
+
request.abort and return
|
119
|
+
else
|
120
|
+
request.continue and return
|
121
|
+
end
|
122
|
+
elsif @browser.url_whitelist && !@browser.url_whitelist.empty?
|
123
|
+
if @browser.url_whitelist.any? { |r| request.match?(r) }
|
124
|
+
request.continue and return
|
125
|
+
else
|
126
|
+
request.abort and return
|
127
|
+
end
|
128
|
+
elsif index + 1 < total
|
129
|
+
# There are other callbacks that may handle this request
|
130
|
+
next
|
131
|
+
else
|
132
|
+
# If there are no callbacks then just continue
|
133
|
+
request.continue
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
@client.on("Page.javascriptDialogOpening") do |params|
|
138
|
+
accept_modal = @accept_modal.last
|
139
|
+
if accept_modal == true || accept_modal == false
|
140
|
+
@accept_modal.pop
|
141
|
+
@modal_messages << params["message"]
|
142
|
+
options = { accept: accept_modal }
|
143
|
+
response = @modal_response || params["defaultPrompt"]
|
144
|
+
options.merge!(promptText: response) if response
|
145
|
+
command("Page.handleJavaScriptDialog", **options)
|
146
|
+
else
|
147
|
+
warn "Modal window has been opened, but you didn't wrap your code into (`accept_prompt` | `dismiss_prompt` | `accept_confirm` | `dismiss_confirm` | `accept_alert`), accepting by default"
|
148
|
+
options = { accept: true }
|
149
|
+
response = params["defaultPrompt"]
|
150
|
+
options.merge!(promptText: response) if response
|
151
|
+
command("Page.handleJavaScriptDialog", **options)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def find_position(node, *args)
|
157
|
+
x, y = node.find_position(*args)
|
158
|
+
rescue Ferrum::BrowserError => e
|
159
|
+
if e.message == "Could not compute content quads."
|
160
|
+
raise MouseEventFailed.new("MouseEventFailed: click, none, 0, 0")
|
161
|
+
else
|
162
|
+
raise
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
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
|
+
version: 0.7.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-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|
@@ -31,39 +31,19 @@ dependencies:
|
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '4'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
34
|
+
name: ferrum
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
40
|
-
- - "<"
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
version: '0.8'
|
39
|
+
version: 0.2.1
|
43
40
|
type: :runtime
|
44
41
|
prerelease: false
|
45
42
|
version_requirements: !ruby/object:Gem::Requirement
|
46
43
|
requirements:
|
47
44
|
- - ">="
|
48
45
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
50
|
-
- - "<"
|
51
|
-
- !ruby/object:Gem::Version
|
52
|
-
version: '0.8'
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
|
-
name: cliver
|
55
|
-
requirement: !ruby/object:Gem::Requirement
|
56
|
-
requirements:
|
57
|
-
- - "~>"
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: '0.3'
|
60
|
-
type: :runtime
|
61
|
-
prerelease: false
|
62
|
-
version_requirements: !ruby/object:Gem::Requirement
|
63
|
-
requirements:
|
64
|
-
- - "~>"
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version: '0.3'
|
46
|
+
version: 0.2.1
|
67
47
|
- !ruby/object:Gem::Dependency
|
68
48
|
name: image_size
|
69
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -202,25 +182,12 @@ files:
|
|
202
182
|
- README.md
|
203
183
|
- lib/capybara/cuprite.rb
|
204
184
|
- lib/capybara/cuprite/browser.rb
|
205
|
-
- lib/capybara/cuprite/browser/client.rb
|
206
|
-
- lib/capybara/cuprite/browser/dom.rb
|
207
|
-
- lib/capybara/cuprite/browser/frame.rb
|
208
|
-
- lib/capybara/cuprite/browser/input.json
|
209
|
-
- lib/capybara/cuprite/browser/input.rb
|
210
|
-
- lib/capybara/cuprite/browser/javascripts/index.js
|
211
|
-
- lib/capybara/cuprite/browser/net.rb
|
212
|
-
- lib/capybara/cuprite/browser/page.rb
|
213
|
-
- lib/capybara/cuprite/browser/process.rb
|
214
|
-
- lib/capybara/cuprite/browser/runtime.rb
|
215
|
-
- lib/capybara/cuprite/browser/targets.rb
|
216
|
-
- lib/capybara/cuprite/browser/web_socket.rb
|
217
185
|
- lib/capybara/cuprite/cookie.rb
|
218
186
|
- lib/capybara/cuprite/driver.rb
|
219
187
|
- lib/capybara/cuprite/errors.rb
|
220
|
-
- lib/capybara/cuprite/
|
221
|
-
- lib/capybara/cuprite/network/request.rb
|
222
|
-
- lib/capybara/cuprite/network/response.rb
|
188
|
+
- lib/capybara/cuprite/javascripts/index.js
|
223
189
|
- lib/capybara/cuprite/node.rb
|
190
|
+
- lib/capybara/cuprite/page.rb
|
224
191
|
- lib/capybara/cuprite/version.rb
|
225
192
|
homepage: https://github.com/machinio/cuprite
|
226
193
|
licenses:
|
@@ -241,8 +208,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
241
208
|
- !ruby/object:Gem::Version
|
242
209
|
version: '0'
|
243
210
|
requirements: []
|
244
|
-
|
245
|
-
rubygems_version: 2.7.6
|
211
|
+
rubygems_version: 3.0.3
|
246
212
|
signing_key:
|
247
213
|
specification_version: 4
|
248
214
|
summary: Headless Chrome driver for Capybara
|
@@ -1,74 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "timeout"
|
4
|
-
require "capybara/cuprite/browser/web_socket"
|
5
|
-
|
6
|
-
module Capybara::Cuprite
|
7
|
-
class Browser
|
8
|
-
class Client
|
9
|
-
class IdError < RuntimeError; end
|
10
|
-
|
11
|
-
def initialize(browser, ws_url, allow_slowmo = true)
|
12
|
-
@command_id = 0
|
13
|
-
@subscribed = Hash.new { |h, k| h[k] = [] }
|
14
|
-
@browser, @allow_slowmo = browser, allow_slowmo
|
15
|
-
@commands = Queue.new
|
16
|
-
@ws = WebSocket.new(ws_url, @browser.logger)
|
17
|
-
|
18
|
-
@thread = Thread.new do
|
19
|
-
while message = @ws.messages.pop
|
20
|
-
method, params = message.values_at("method", "params")
|
21
|
-
if method
|
22
|
-
@subscribed[method].each { |b| b.call(params) }
|
23
|
-
else
|
24
|
-
@commands.push(message)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
@commands.close
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def command(method, params = {})
|
33
|
-
message = build_message(method, params)
|
34
|
-
sleep(@browser.slowmo) if !@browser.slowmo.nil? && @allow_slowmo
|
35
|
-
@ws.send_message(message)
|
36
|
-
message[:id]
|
37
|
-
end
|
38
|
-
|
39
|
-
def wait(id:)
|
40
|
-
message = Timeout.timeout(@browser.timeout, TimeoutError) { @commands.pop }
|
41
|
-
raise DeadBrowser unless message
|
42
|
-
raise IdError if message["id"] != id
|
43
|
-
error, response = message.values_at("error", "result")
|
44
|
-
raise BrowserError.new(error) if error
|
45
|
-
response
|
46
|
-
rescue IdError
|
47
|
-
retry
|
48
|
-
end
|
49
|
-
|
50
|
-
def subscribe(event, &block)
|
51
|
-
@subscribed[event] << block
|
52
|
-
true
|
53
|
-
end
|
54
|
-
|
55
|
-
def close
|
56
|
-
@ws.close
|
57
|
-
# Give a thread some time to handle a tail of messages
|
58
|
-
Timeout.timeout(1) { @thread.join }
|
59
|
-
rescue Timeout::Error
|
60
|
-
@thread.kill
|
61
|
-
end
|
62
|
-
|
63
|
-
private
|
64
|
-
|
65
|
-
def build_message(method, params)
|
66
|
-
{ method: method, params: params }.merge(id: next_command_id)
|
67
|
-
end
|
68
|
-
|
69
|
-
def next_command_id
|
70
|
-
@command_id += 1
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|