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.
@@ -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 :target_id, :node
7
+ attr_reader :node
6
8
 
7
- def initialize(driver, target_id, node)
8
- super(driver, self)
9
- @target_id, @node = target_id, node
10
- end
9
+ extend Forwardable
11
10
 
12
- def browser
13
- driver.browser
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, @node, *args)
18
- rescue BrowserError => e
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["target_id"], parent["node"])
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 ((tag_name == "img") && (name == "src")) || ((tag_name == "a") && (name == "href"))
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 ||= @node["nodeName"].downcase
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
- command(:click, keys, offset)
148
+ prepare_and_click(:left, __method__, keys, offset)
145
149
  end
146
150
 
147
151
  def right_click(keys = [], offset = {})
148
- command(:right_click, keys, offset)
152
+ prepare_and_click(:right, __method__, keys, offset)
149
153
  end
150
154
 
151
155
  def double_click(keys = [], offset = {})
152
- command(:double_click, keys, offset)
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.node)
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
- # We compare backendNodeId because once nodeId is sent to frontend backend
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} @target_id=#{@target_id.inspect} @node=#{@node.inspect}>)
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 is used and why attr is called id?
222
- { ELEMENT: { target_id: @target_id, id: @node } }
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Cuprite
5
- VERSION = "0.6.0"
5
+ VERSION = "0.7.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.6.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-03-21 00:00:00.000000000 Z
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: websocket-driver
34
+ name: ferrum
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '0.6'
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: '0.6'
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/network/error.rb
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
- rubyforge_project:
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