cuprite 0.6.0 → 0.7.1

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.
@@ -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
+ network.intercept if !Array(@browser.url_whitelist).empty? ||
113
+ !Array(@browser.url_blacklist).empty?
114
+
115
+ on(:request) 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
+ 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.1"
6
6
  end
7
7
  end
@@ -1,31 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ferrum"
3
4
  require "capybara"
4
5
 
5
- Thread.abort_on_exception = true
6
- Thread.report_on_exception = true if Thread.respond_to?(:report_on_exception=)
7
-
8
6
  module Capybara::Cuprite
9
- require "capybara/cuprite/driver"
10
- require "capybara/cuprite/browser"
11
- require "capybara/cuprite/node"
12
- require "capybara/cuprite/errors"
13
- require "capybara/cuprite/cookie"
14
-
15
- class << self
16
- def windows?
17
- RbConfig::CONFIG["host_os"] =~ /mingw|mswin|cygwin/
18
- end
7
+ end
19
8
 
20
- def mac?
21
- RbConfig::CONFIG["host_os"] =~ /darwin/
22
- end
9
+ require "capybara/cuprite/driver"
10
+ require "capybara/cuprite/browser"
11
+ require "capybara/cuprite/page"
12
+ require "capybara/cuprite/node"
13
+ require "capybara/cuprite/errors"
23
14
 
24
- def mri?
25
- defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby"
26
- end
27
- end
28
- end
15
+ Ferrum::Page.prepend(Capybara::Cuprite::Page)
29
16
 
30
17
  Capybara.register_driver(:cuprite) do |app|
31
18
  Capybara::Cuprite::Driver.new(app)
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.1
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-20 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
@@ -1,50 +0,0 @@
1
- module Capybara::Cuprite
2
- class Browser
3
- module DOM
4
- def current_url
5
- evaluate_in(execution_context_id, "window.top.location.href")
6
- end
7
-
8
- def title
9
- evaluate_in(execution_context_id, "window.top.document.title")
10
- end
11
-
12
- def body
13
- evaluate("document.documentElement.outerHTML")
14
- end
15
-
16
- def all_text(node)
17
- evaluate_on(node: node, expr: "this.textContent")
18
- end
19
-
20
- def property(node, name)
21
- evaluate_on(node: node, expr: %Q(this["#{name}"]))
22
- end
23
-
24
- def attributes(node)
25
- value = evaluate_on(node: node, expr: "_cuprite.getAttributes(this)")
26
- JSON.parse(value)
27
- end
28
-
29
- def attribute(node, name)
30
- evaluate_on(node: node, expr: %Q(_cuprite.getAttribute(this, "#{name}")))
31
- end
32
-
33
- def value(node)
34
- evaluate_on(node: node, expr: "_cuprite.value(this)")
35
- end
36
-
37
- def visible?(node)
38
- evaluate_on(node: node, expr: "_cuprite.isVisible(this)")
39
- end
40
-
41
- def disabled?(node)
42
- evaluate_on(node: node, expr: "_cuprite.isDisabled(this)")
43
- end
44
-
45
- def path(node)
46
- evaluate_on(node: node, expr: "_cuprite.path(this)")
47
- end
48
- end
49
- end
50
- end
@@ -1,115 +0,0 @@
1
- module Capybara::Cuprite
2
- class Browser
3
- module Frame
4
- def execution_context_id
5
- @mutex.synchronize do
6
- if !@frame_stack.empty?
7
- @frames[@frame_stack.last]["execution_context_id"]
8
- else
9
- @execution_context_id
10
- end
11
- end
12
- end
13
-
14
- def frame_name
15
- evaluate("window.name")
16
- end
17
-
18
- def frame_url
19
- evaluate("window.location.href")
20
- end
21
-
22
- def frame_title
23
- evaluate("document.title")
24
- end
25
-
26
- def switch_to_frame(handle)
27
- case handle
28
- when Capybara::Node::Base
29
- @frame_stack << handle.native.node["frameId"]
30
- inject_extensions
31
- when :parent
32
- @frame_stack.pop
33
- when :top
34
- @frame_stack = []
35
- end
36
- end
37
-
38
- private
39
-
40
- def subscribe_events
41
- super if defined?(super)
42
-
43
- @client.subscribe("Page.frameAttached") do |params|
44
- @frames[params["frameId"]] = { "parent_id" => params["parentFrameId"] }
45
- end
46
-
47
- @client.subscribe("Page.frameStartedLoading") do |params|
48
- @waiting_frames << params["frameId"]
49
- @mutex.try_lock
50
- end
51
-
52
- @client.subscribe("Page.frameNavigated") do |params|
53
- id = params["frame"]["id"]
54
- if frame = @frames[id]
55
- frame.merge!(params["frame"].select { |k, v| k == "name" || k == "url" })
56
- end
57
- end
58
-
59
- @client.subscribe("Page.frameScheduledNavigation") do |params|
60
- # Trying to lock mutex if frame is the main frame
61
- @waiting_frames << params["frameId"]
62
- @mutex.try_lock
63
- end
64
-
65
- @client.subscribe("Page.frameStoppedLoading") do |params|
66
- # `DOM.performSearch` doesn't work without getting #document node first.
67
- # It returns node with nodeId 1 and nodeType 9 from which descend the
68
- # tree and we save it in a variable because if we call that again root
69
- # node will change the id and all subsequent nodes have to change id too.
70
- # `command` is not allowed in the block as it will deadlock the process.
71
- if params["frameId"] == @frame_id
72
- signal if @waiting_frames.empty?
73
- @client.command("DOM.getDocument", depth: 0)
74
- end
75
-
76
- if @waiting_frames.include?(params["frameId"])
77
- @waiting_frames.delete(params["frameId"])
78
- signal if @waiting_frames.empty?
79
- end
80
- end
81
-
82
- @client.subscribe("Runtime.executionContextCreated") do |params|
83
- frame_id = params.dig("context", "auxData", "frameId")
84
- execution_context_id = params.dig("context", "id")
85
-
86
- # Remember the very first frame since it's the main one
87
- @frame_id ||= frame_id
88
- @execution_context_id ||= execution_context_id
89
-
90
- if @frames[frame_id]
91
- @frames[frame_id].merge!("execution_context_id" => execution_context_id)
92
- else
93
- @frames[frame_id] = { "execution_context_id" => execution_context_id }
94
- end
95
- end
96
-
97
- @client.subscribe("Runtime.executionContextDestroyed") do |params|
98
- execution_context_id = params["executionContextId"]
99
- id, frame = @frames.find { |_, p| p["execution_context_id"] == execution_context_id }
100
- frame["execution_context_id"] = nil if frame
101
-
102
- if @execution_context_id == execution_context_id
103
- @execution_context_id = nil
104
- end
105
- end
106
-
107
- @client.subscribe("Runtime.executionContextsCleared") do
108
- # If we didn't have time to set context id at the beginning we have
109
- # to set lock and release it when we set something.
110
- @execution_context_id = nil
111
- end
112
- end
113
- end
114
- end
115
- end