cuprite 0.14.1 → 0.14.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba6b2d59f81358541ceedf4d5897c776ec4e36f2749894ec989b7e00d239f5dc
4
- data.tar.gz: 8c387e1473c47a21ae3f21641f9a9d63084c4ed042b94c07fcc9376f568f28d6
3
+ metadata.gz: e6480f3033eff59cc4f7d261e47c37ae7169ef8dd806c9b1f471d4d59be5b5a7
4
+ data.tar.gz: 4d35f4b5df0457b7ea51842738e66c1b6aecc2d1753bdec5d7dbf82d28273ecd
5
5
  SHA512:
6
- metadata.gz: 736a95aefb32b8704be3c44f3b0e22ebae5ea7f17b61a235bf9093618093c2edb3693c831be11d7d58d1f16429a1472ea73d6e7a8228ec395b7b808471ad410b
7
- data.tar.gz: f3ecd0e01f4cb54c9e1623a30c50c369c526e19b57d4157a84a6368ae9db722000323fdd011d3375186fdac7e9307727c5a15203b10b9c8a5ae29f4e18755dc0
6
+ metadata.gz: c347f3f5555138a6ae5660de778ec13d9e7686007ffdf9d635b826e6fa13a42520cb137693ee0142395b7cb00efa3a5939c9ccfc6fa75411397310f0593fef33
7
+ data.tar.gz: 82dd53121faab3a9f700928873d13dc42c33aae6a6065b88efcbadf02094356226ef8d72fa2d3ccfaf460a89ecfaf53950db49a57e6d9222ef9cf968eea7d6d2
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018-2022 Dmitry Vorotilin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,212 @@
1
+ # Cuprite - Headless Chrome driver for Capybara
2
+
3
+ Cuprite is a pure Ruby driver (read as _no_ Selenium/WebDriver/ChromeDriver
4
+ dependency) for [Capybara](https://github.com/teamcapybara/capybara). It allows
5
+ you to run Capybara tests on a headless Chrome or Chromium. Under the hood it
6
+ uses [Ferrum](https://github.com/rubycdp/ferrum#index) which is high-level API
7
+ to the browser by CDP protocol. The design of the driver is as close to
8
+ [Poltergeist](https://github.com/teampoltergeist/poltergeist) as possible though
9
+ it's not a goal.
10
+
11
+
12
+ ## Install
13
+
14
+ Add this to your `Gemfile` and run `bundle install`.
15
+
16
+ ``` ruby
17
+ group :test do
18
+ gem "cuprite"
19
+ end
20
+ ```
21
+
22
+ In your test setup add:
23
+
24
+ ``` ruby
25
+ require "capybara/cuprite"
26
+ Capybara.javascript_driver = :cuprite
27
+ Capybara.register_driver(:cuprite) do |app|
28
+ Capybara::Cuprite::Driver.new(app, window_size: [1200, 800])
29
+ end
30
+ ```
31
+
32
+ if you use `Docker` don't forget to pass `no-sandbox` option:
33
+
34
+ ```ruby
35
+ Capybara::Cuprite::Driver.new(app, browser_options: { 'no-sandbox': nil })
36
+ ```
37
+
38
+ Since Cuprite uses [Ferrum](https://github.com/rubycdp/ferrum#examples) there
39
+ are many useful methods you can call even using this driver:
40
+
41
+ ```ruby
42
+ browser = page.driver.browser
43
+ browser.mouse.move(x: 123, y: 456).down.up
44
+ ```
45
+
46
+ If you already have tests on Poltergeist then it should simply work, for
47
+ Selenium you better check your code for `manage` calls because it works
48
+ differently in Cuprite, see the documentation below.
49
+
50
+
51
+ ## Customization
52
+
53
+ See the full list of options for
54
+ [Ferrum](https://github.com/rubycdp/ferrum#customization).
55
+
56
+ You can pass options with the following code in your test setup:
57
+
58
+ ``` ruby
59
+ Capybara.register_driver(:cuprite) do |app|
60
+ Capybara::Cuprite::Driver.new(app, options)
61
+ end
62
+ ```
63
+
64
+ `Cuprite`-specific options are:
65
+
66
+ * options `Hash`
67
+ * `:url_blacklist` (Array) - array of strings to match against requested URLs
68
+ * `:url_whitelist` (Array) - array of strings to match against requested URLs
69
+
70
+
71
+ ## Debugging
72
+
73
+ If you pass `inspector` option, remote debugging will be enabled if you run
74
+ tests with `INSPECTOR=true`. Then you can put `page.driver.debug` or
75
+ `page.driver.debug(binding)` in your test to pause it. This will launch the
76
+ browser where you can inspect the content.
77
+
78
+ ```ruby
79
+ Capybara.register_driver :cuprite do |app|
80
+ Capybara::Cuprite::Driver.new(app, inspector: ENV['INSPECTOR'])
81
+ end
82
+ ```
83
+
84
+ then somewhere in the test:
85
+
86
+ ```ruby
87
+ it "does something useful" do
88
+ visit root_path
89
+
90
+ fill_in "field", with: "value"
91
+ page.driver.debug(binding)
92
+
93
+ expect(page).to have_content("value")
94
+ end
95
+ ```
96
+
97
+ In the middle of the execution Chrome will open a new tab where you can inspect
98
+ the content and also if you passed `binding` an `irb` or `pry` console will be
99
+ opened where you can further experiment with the test.
100
+
101
+
102
+ ## Clicking/Scrolling
103
+
104
+ * `page.driver.click(x, y)` Click a very specific area of the screen.
105
+ * `page.driver.scroll_to(left, top)` Scroll to a given position.
106
+ * `element.send_keys(*keys)` Send keys to a given node.
107
+
108
+
109
+ ## Request headers
110
+
111
+ Manipulate HTTP request headers like a boss:
112
+
113
+ ``` ruby
114
+ page.driver.headers # => {}
115
+ page.driver.headers = { "User-Agent" => "Cuprite" }
116
+ page.driver.add_headers("Referer" => "https://example.com")
117
+ page.driver.headers # => { "User-Agent" => "Cuprite", "Referer" => "https://example.com" }
118
+ ```
119
+
120
+ Notice that `headers=` will overwrite already set headers. You should use
121
+ `add_headers` if you want to add a few more. These headers will apply to all
122
+ subsequent HTTP requests (including requests for assets, AJAX, etc). They will
123
+ be automatically cleared at the end of the test.
124
+
125
+
126
+ ## Network traffic
127
+
128
+ * `page.driver.network_traffic` Inspect network traffic (loaded resources) on
129
+ the current page. This returns an array of request objects.
130
+
131
+ ```ruby
132
+ page.driver.network_traffic # => [Request, ...]
133
+ request = page.driver.network_traffic.first
134
+ request.response
135
+ ```
136
+
137
+ * `page.driver.wait_for_network_idle` Natively waits for network idle and if
138
+ there are no active connections returns or raises `TimeoutError` error. Accepts
139
+ the same options as
140
+ [`wait_for_idle`](https://github.com/rubycdp/ferrum#wait_for_idleoptions)
141
+
142
+ ```ruby
143
+ page.driver.wait_for_network_idle
144
+ page.driver.refresh
145
+ ```
146
+
147
+ Please note that network traffic is not cleared when you visit new page. You can
148
+ manually clear the network traffic by calling `page.driver.clear_network_traffic`
149
+ or `page.driver.reset`
150
+
151
+ * `page.driver.wait_for_reload` unlike `wait_for_network_idle` will wait until
152
+ the whole page is reloaded or raise a timeout error. It's useful when you know
153
+ that for example after clicking autocomplete suggestion you expect page to be
154
+ reloaded, you have a few choices - put sleep or wait for network idle, but both
155
+ are bad. Sleep makes you wait longer or less than needed, network idle can
156
+ return earlier even before the whole page is started to reload. Here's the
157
+ rescue.
158
+
159
+
160
+ ## Manipulating cookies
161
+
162
+ The following methods are used to inspect and manipulate cookies:
163
+
164
+ * `page.driver.cookies` - a hash of cookies accessible to the current
165
+ page. The keys are cookie names. The values are `Cookie` objects, with
166
+ the following methods: `name`, `value`, `domain`, `path`, `size`, `secure?`,
167
+ `httponly?`, `session?`, `expires`.
168
+ * `page.driver.set_cookie(name, value, options = {})` - set a cookie.
169
+ The options hash can take the following keys: `:domain`, `:path`,
170
+ `:secure`, `:httponly`, `:expires`. `:expires` should be a
171
+ `Time` object.
172
+ * `page.driver.remove_cookie(name)` - remove a cookie
173
+ * `page.driver.clear_cookies` - clear all cookies
174
+
175
+
176
+ ## Screenshot
177
+
178
+ Besides capybara screenshot method you can get image as Base64:
179
+
180
+ * `page.driver.render_base64(format, options)`
181
+
182
+
183
+ ## Authorization
184
+
185
+ * `page.driver.basic_authorize(user, password)`
186
+ * `page.driver.set_proxy(ip, port, type, user, password)`
187
+
188
+
189
+ ## URL Blacklisting & Whitelisting
190
+
191
+ Cuprite supports URL blacklisting, which allows you to prevent scripts from
192
+ running on designated domains:
193
+
194
+ ```ruby
195
+ page.driver.browser.url_blacklist = ["http://www.example.com"]
196
+ ```
197
+
198
+ and also URL whitelisting, which allows scripts to only run on designated
199
+ domains:
200
+
201
+ ```ruby
202
+ page.driver.browser.url_whitelist = ["http://www.example.com"]
203
+ ```
204
+
205
+ If you are experiencing slower run times, consider creating a URL whitelist of
206
+ domains that are essential or a blacklist of domains that are not essential,
207
+ such as ad networks or analytics, to your testing environment.
208
+
209
+ ## License
210
+
211
+ The gem is available as open source under the terms of the
212
+ [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Capybara
6
+ module Cuprite
7
+ class Browser < Ferrum::Browser
8
+ extend Forwardable
9
+
10
+ delegate %i[send_keys select set hover trigger before_click switch_to_frame
11
+ find_modal accept_confirm dismiss_confirm accept_prompt
12
+ dismiss_prompt reset_modals] => :page
13
+
14
+ attr_reader :url_blacklist, :url_whitelist
15
+
16
+ def initialize(options = nil)
17
+ options ||= {}
18
+ @client = nil
19
+ self.url_blacklist = options[:url_blacklist]
20
+ self.url_whitelist = options[:url_whitelist]
21
+
22
+ super
23
+ @page = false
24
+ end
25
+
26
+ def timeout=(value)
27
+ super
28
+ @page.timeout = value unless @page.nil?
29
+ end
30
+
31
+ def page
32
+ raise Ferrum::NoSuchPageError if @page.nil?
33
+
34
+ @page ||= attach_page
35
+ end
36
+
37
+ def reset
38
+ super
39
+ @page = attach_page
40
+ end
41
+
42
+ def quit
43
+ super
44
+ @page = false
45
+ end
46
+
47
+ def url_whitelist=(patterns)
48
+ @url_whitelist = prepare_wildcards(patterns)
49
+ page.network.intercept if @client && !@url_whitelist.empty?
50
+ end
51
+
52
+ def url_blacklist=(patterns)
53
+ @url_blacklist = prepare_wildcards(patterns)
54
+ page.network.intercept if @client && !@url_blacklist.empty?
55
+ end
56
+
57
+ def visit(*args)
58
+ goto(*args)
59
+ end
60
+
61
+ def status_code
62
+ network.status
63
+ end
64
+
65
+ def find(method, selector)
66
+ find_all(method, selector)
67
+ end
68
+
69
+ def property(node, name)
70
+ node.property(name)
71
+ end
72
+
73
+ def find_within(node, method, selector)
74
+ resolved = page.command("DOM.resolveNode", nodeId: node.node_id)
75
+ object_id = resolved.dig("object", "objectId")
76
+ find_all(method, selector, { "objectId" => object_id })
77
+ end
78
+
79
+ def window_handle
80
+ page.target_id
81
+ end
82
+
83
+ def window_handles
84
+ targets.keys
85
+ end
86
+
87
+ def within_window(locator = nil)
88
+ original = window_handle
89
+ raise Ferrum::NoSuchPageError unless window_handles.include?(locator)
90
+
91
+ switch_to_window(locator)
92
+ yield
93
+ ensure
94
+ switch_to_window(original)
95
+ end
96
+
97
+ def switch_to_window(target_id)
98
+ target = targets[target_id]
99
+ raise Ferrum::NoSuchPageError unless target
100
+
101
+ @page = attach_page(target.id)
102
+ end
103
+
104
+ def close_window(target_id)
105
+ target = targets[target_id]
106
+ raise Ferrum::NoSuchPageError unless target
107
+
108
+ @page = nil if @page.target_id == target.id
109
+ target.page.close
110
+ end
111
+
112
+ def browser_error
113
+ evaluate("_cuprite.browserError()")
114
+ end
115
+
116
+ def source
117
+ raise NotImplementedError
118
+ end
119
+
120
+ def drag(node, other)
121
+ x1, y1 = node.find_position
122
+ x2, y2 = other.find_position
123
+
124
+ mouse.move(x: x1, y: y1)
125
+ mouse.down
126
+ mouse.move(x: x2, y: y2)
127
+ mouse.up
128
+ end
129
+
130
+ def drag_by(node, x, y)
131
+ x1, y1 = node.find_position
132
+ x2 = x1 + x
133
+ y2 = y1 + y
134
+
135
+ mouse.move(x: x1, y: y1)
136
+ mouse.down
137
+ mouse.move(x: x2, y: y2)
138
+ mouse.up
139
+ end
140
+
141
+ def select_file(node, value)
142
+ node.select_file(value)
143
+ end
144
+
145
+ def parents(node)
146
+ evaluate_on(node: node, expression: "_cuprite.parents(this)", by_value: false)
147
+ end
148
+
149
+ def visible_text(node)
150
+ evaluate_on(node: node, expression: "_cuprite.visibleText(this)")
151
+ end
152
+
153
+ def delete_text(node)
154
+ evaluate_on(node: node, expression: "_cuprite.deleteText(this)")
155
+ end
156
+
157
+ def attributes(node)
158
+ value = evaluate_on(node: node, expression: "_cuprite.getAttributes(this)")
159
+ JSON.parse(value)
160
+ end
161
+
162
+ def attribute(node, name)
163
+ evaluate_on(node: node, expression: %(_cuprite.getAttribute(this, "#{name}")))
164
+ end
165
+
166
+ def value(node)
167
+ evaluate_on(node: node, expression: "_cuprite.value(this)")
168
+ end
169
+
170
+ def visible?(node)
171
+ evaluate_on(node: node, expression: "_cuprite.isVisible(this)")
172
+ end
173
+
174
+ def disabled?(node)
175
+ evaluate_on(node: node, expression: "_cuprite.isDisabled(this)")
176
+ end
177
+
178
+ def path(node)
179
+ evaluate_on(node: node, expression: "_cuprite.path(this)")
180
+ end
181
+
182
+ def all_text(node)
183
+ node.text
184
+ end
185
+
186
+ private
187
+
188
+ def find_all(method, selector, within = nil)
189
+ nodes = if within
190
+ evaluate("_cuprite.find(arguments[0], arguments[1], arguments[2])", method, selector, within)
191
+ else
192
+ evaluate("_cuprite.find(arguments[0], arguments[1])", method, selector)
193
+ end
194
+
195
+ nodes.select(&:node?)
196
+ rescue Ferrum::JavaScriptError => e
197
+ raise InvalidSelector.new(e.response, method, selector) if e.class_name == "InvalidSelector"
198
+
199
+ raise
200
+ end
201
+
202
+ def prepare_wildcards(wc)
203
+ Array(wc).map do |wildcard|
204
+ if wildcard.is_a?(Regexp)
205
+ wildcard
206
+ else
207
+ wildcard = wildcard.gsub("*", ".*")
208
+ Regexp.new(wildcard, Regexp::IGNORECASE)
209
+ end
210
+ end
211
+ end
212
+
213
+ def attach_page(target_id = nil)
214
+ target = targets[target_id] if target_id
215
+ target ||= default_context.default_target
216
+ return target.page if target.attached?
217
+
218
+ target.maybe_sleep_if_new_window
219
+ target.page = Page.new(target.id, self)
220
+ target.page
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Cuprite
5
+ class Cookie
6
+ def initialize(attributes)
7
+ @attributes = attributes
8
+ end
9
+
10
+ def name
11
+ @attributes["name"]
12
+ end
13
+
14
+ def value
15
+ @attributes["value"]
16
+ end
17
+
18
+ def domain
19
+ @attributes["domain"]
20
+ end
21
+
22
+ def path
23
+ @attributes["path"]
24
+ end
25
+
26
+ def size
27
+ @attributes["size"]
28
+ end
29
+
30
+ def secure?
31
+ @attributes["secure"]
32
+ end
33
+
34
+ def httponly?
35
+ @attributes["httpOnly"]
36
+ end
37
+
38
+ def session?
39
+ @attributes["session"]
40
+ end
41
+
42
+ def expires
43
+ Time.at(@attributes["expires"]) if (@attributes["expires"]).positive?
44
+ end
45
+ end
46
+ end
47
+ end