cuprite 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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