cuprite 0.14.1 → 0.14.3

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