cuprite 0.6.0 → 0.7.1

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