cuprite 0.14.1 → 0.14.2

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: 4146607eaf82561d128b2f4501f688b3104fae6ce4f70ec6350b24518a85ad23
4
+ data.tar.gz: 71c489599d22691f4270f1f87a85885a7bddd15bc0bfe0baa3acc2e8b13d8ce0
5
5
  SHA512:
6
- metadata.gz: 736a95aefb32b8704be3c44f3b0e22ebae5ea7f17b61a235bf9093618093c2edb3693c831be11d7d58d1f16429a1472ea73d6e7a8228ec395b7b808471ad410b
7
- data.tar.gz: f3ecd0e01f4cb54c9e1623a30c50c369c526e19b57d4157a84a6368ae9db722000323fdd011d3375186fdac7e9307727c5a15203b10b9c8a5ae29f4e18755dc0
6
+ metadata.gz: 503980e052b9a720b1e65d714d1b3ddfaa7f3c4d821c3044f4dbe397f4b0b6e18058b620b43c2095e04520f8f41eb23694f61d4a22f31a8a3617773a886552f0
7
+ data.tar.gz: ce6d5ec7d83c8e9c4036971e3d97075b199042825317d725a0f1ba9b5b4b6d77296d7c18299f8d437e25bd246c429467723bdecb8ec21ac866a6dc394ee6bb97
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,219 @@
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 page
27
+ raise Ferrum::NoSuchPageError if @page.nil?
28
+
29
+ @page ||= attach_page
30
+ end
31
+
32
+ def reset
33
+ super
34
+ @page = attach_page
35
+ end
36
+
37
+ def quit
38
+ super
39
+ @page = false
40
+ end
41
+
42
+ def url_whitelist=(patterns)
43
+ @url_whitelist = prepare_wildcards(patterns)
44
+ page.network.intercept if @client && !@url_whitelist.empty?
45
+ end
46
+
47
+ def url_blacklist=(patterns)
48
+ @url_blacklist = prepare_wildcards(patterns)
49
+ page.network.intercept if @client && !@url_blacklist.empty?
50
+ end
51
+
52
+ def visit(*args)
53
+ goto(*args)
54
+ end
55
+
56
+ def status_code
57
+ network.status
58
+ end
59
+
60
+ def find(method, selector)
61
+ find_all(method, selector)
62
+ end
63
+
64
+ def property(node, name)
65
+ node.property(name)
66
+ end
67
+
68
+ def find_within(node, method, selector)
69
+ resolved = page.command("DOM.resolveNode", nodeId: node.node_id)
70
+ object_id = resolved.dig("object", "objectId")
71
+ find_all(method, selector, { "objectId" => object_id })
72
+ end
73
+
74
+ def window_handle
75
+ page.target_id
76
+ end
77
+
78
+ def window_handles
79
+ targets.keys
80
+ end
81
+
82
+ def within_window(locator = nil)
83
+ original = window_handle
84
+ raise Ferrum::NoSuchPageError unless window_handles.include?(locator)
85
+
86
+ switch_to_window(locator)
87
+ yield
88
+ ensure
89
+ switch_to_window(original)
90
+ end
91
+
92
+ def switch_to_window(target_id)
93
+ target = targets[target_id]
94
+ raise Ferrum::NoSuchPageError unless target
95
+
96
+ @page = attach_page(target.id)
97
+ end
98
+
99
+ def close_window(target_id)
100
+ target = targets[target_id]
101
+ raise Ferrum::NoSuchPageError unless target
102
+
103
+ @page = nil if @page.target_id == target.id
104
+ target.page.close
105
+ end
106
+
107
+ def browser_error
108
+ evaluate("_cuprite.browserError()")
109
+ end
110
+
111
+ def source
112
+ raise NotImplementedError
113
+ end
114
+
115
+ def drag(node, other)
116
+ x1, y1 = node.find_position
117
+ x2, y2 = other.find_position
118
+
119
+ mouse.move(x: x1, y: y1)
120
+ mouse.down
121
+ mouse.move(x: x2, y: y2)
122
+ mouse.up
123
+ end
124
+
125
+ def drag_by(node, x, y)
126
+ x1, y1 = node.find_position
127
+ x2 = x1 + x
128
+ y2 = y1 + y
129
+
130
+ mouse.move(x: x1, y: y1)
131
+ mouse.down
132
+ mouse.move(x: x2, y: y2)
133
+ mouse.up
134
+ end
135
+
136
+ def select_file(node, value)
137
+ node.select_file(value)
138
+ end
139
+
140
+ def parents(node)
141
+ evaluate_on(node: node, expression: "_cuprite.parents(this)", by_value: false)
142
+ end
143
+
144
+ def visible_text(node)
145
+ evaluate_on(node: node, expression: "_cuprite.visibleText(this)")
146
+ end
147
+
148
+ def delete_text(node)
149
+ evaluate_on(node: node, expression: "_cuprite.deleteText(this)")
150
+ end
151
+
152
+ def attributes(node)
153
+ value = evaluate_on(node: node, expression: "_cuprite.getAttributes(this)")
154
+ JSON.parse(value)
155
+ end
156
+
157
+ def attribute(node, name)
158
+ evaluate_on(node: node, expression: %(_cuprite.getAttribute(this, "#{name}")))
159
+ end
160
+
161
+ def value(node)
162
+ evaluate_on(node: node, expression: "_cuprite.value(this)")
163
+ end
164
+
165
+ def visible?(node)
166
+ evaluate_on(node: node, expression: "_cuprite.isVisible(this)")
167
+ end
168
+
169
+ def disabled?(node)
170
+ evaluate_on(node: node, expression: "_cuprite.isDisabled(this)")
171
+ end
172
+
173
+ def path(node)
174
+ evaluate_on(node: node, expression: "_cuprite.path(this)")
175
+ end
176
+
177
+ def all_text(node)
178
+ node.text
179
+ end
180
+
181
+ private
182
+
183
+ def find_all(method, selector, within = nil)
184
+ nodes = if within
185
+ evaluate("_cuprite.find(arguments[0], arguments[1], arguments[2])", method, selector, within)
186
+ else
187
+ evaluate("_cuprite.find(arguments[0], arguments[1])", method, selector)
188
+ end
189
+
190
+ nodes.select(&:node?)
191
+ rescue Ferrum::JavaScriptError => e
192
+ raise InvalidSelector.new(e.response, method, selector) if e.class_name == "InvalidSelector"
193
+
194
+ raise
195
+ end
196
+
197
+ def prepare_wildcards(wc)
198
+ Array(wc).map do |wildcard|
199
+ if wildcard.is_a?(Regexp)
200
+ wildcard
201
+ else
202
+ wildcard = wildcard.gsub("*", ".*")
203
+ Regexp.new(wildcard, Regexp::IGNORECASE)
204
+ end
205
+ end
206
+ end
207
+
208
+ def attach_page(target_id = nil)
209
+ target = targets[target_id] if target_id
210
+ target ||= default_context.default_target
211
+ return target.page if target.attached?
212
+
213
+ target.maybe_sleep_if_new_window
214
+ target.page = Page.new(target.id, self)
215
+ target.page
216
+ end
217
+ end
218
+ end
219
+ 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