capybara-httpx 1.0.0.preview1
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 +7 -0
- data/LICENSE +20 -0
- data/README.md +80 -0
- data/lib/capybara/httpx/browser.rb +250 -0
- data/lib/capybara/httpx/cucumber.rb +6 -0
- data/lib/capybara/httpx/driver.rb +102 -0
- data/lib/capybara/httpx/session.rb +50 -0
- data/lib/capybara/httpx.rb +24 -0
- data/spec/session/httpx_spec.rb +237 -0
- data/spec/spec_helper.rb +8 -0
- metadata +156 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 46bee3c9c3076e589388e150cfd02f6f45d8ff11065b2072590e39f702bff1ba
|
|
4
|
+
data.tar.gz: 8d7855cd4fa44d789f58738a3b25acf934879538f1efacdda84b830fc6b11782
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a6f652a7fa29d6e6f5e181e0adc628ebd4702ad3954cd406bd28b380fd1ea982ca56bbf1fcb6061c5ac8895f375d9078ffc2f1e325514402d1ca2f01b3b92aee
|
|
7
|
+
data.tar.gz: 7ffa6525764f05bb4a9e570e9d3104840c213b2d2c5b2125c130a8c5c71302b3e89701c3f365f05003a58e81ce7f7752961d4219a1cf1d0d907be090ceedf3a1
|
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2010 Tron Jonathan and HALTER Joseph
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Capybara-HTTPX
|
|
2
|
+
==============
|
|
3
|
+
|
|
4
|
+
[](https://github.com/TalentBox/capybara-httpx/actions)
|
|
5
|
+
|
|
6
|
+
A [Capybara](https://github.com/teamcapybara/capybara) driver that uses
|
|
7
|
+
[HTTPX](https://github.com/HoneyryderChuck/httpx) to make HTTP requests to a
|
|
8
|
+
Rack application, including applications running on a remote host. This project
|
|
9
|
+
is based on [TalentBox/capybara-typhoeus](https://github.com/TalentBox/capybara-typhoeus),
|
|
10
|
+
with HTTPX replacing Typhoeus as its HTTP client.
|
|
11
|
+
|
|
12
|
+
This driver is intended for testing APIs. It does not implement browser features
|
|
13
|
+
such as JavaScript, clicking controls, or submitting forms through the DOM.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
gem install capybara-httpx
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or add `capybara-httpx` to your Gemfile. HTTPX is installed as a runtime
|
|
22
|
+
dependency.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
require "capybara/httpx"
|
|
28
|
+
|
|
29
|
+
session = Capybara::Session.new(:httpx, app)
|
|
30
|
+
session.get "/users"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For remote testing, configure the host as usual:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
Capybara.app_host = "https://api.example.com"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The driver can also be selected in Cucumber using the `@httpx` tag. Require
|
|
40
|
+
`capybara/httpx/cucumber` from `env.rb`:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
require "capybara/httpx/cucumber"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```gherkin
|
|
47
|
+
@httpx
|
|
48
|
+
Scenario: request API data
|
|
49
|
+
When I send a GET request to /users
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The driver provides `authenticate_with`, `request_body`, and `as=` helpers on
|
|
53
|
+
its session. Request options set through `driver.with_options` are passed to
|
|
54
|
+
HTTPX. The default request timeout is three seconds and can be customized when
|
|
55
|
+
registering a driver:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
Capybara.register_driver :httpx_with_custom_timeout do |app|
|
|
59
|
+
Capybara::HTTPX::Driver.new(app, timeout: 1)
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Array query parameters use HTTPX-style bracket notation by default. The
|
|
64
|
+
`params_encoding` driver setting can also be set to `:rack`, `:multi`, or
|
|
65
|
+
`:none`.
|
|
66
|
+
|
|
67
|
+
## Running tests
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
bundle install
|
|
71
|
+
bundle exec rake
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
CI runs the test suite on currently supported Ruby release series: 3.4, 4.0,
|
|
75
|
+
and 4.1.
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
Copyright (c) 2010 Tron Jonathan and HALTER Joseph. See [LICENSE](LICENSE) for
|
|
80
|
+
details.
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
require "base64"
|
|
2
|
+
require "json"
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
class Capybara::HTTPX::Browser < Capybara::RackTest::Browser
|
|
6
|
+
LastRequest = Struct.new(:method, :url, :params, :headers) do
|
|
7
|
+
def path
|
|
8
|
+
URI.parse(url).path
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def fullpath
|
|
12
|
+
URI.parse(url).request_uri
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def request_method
|
|
16
|
+
method.to_s.upcase
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def env
|
|
20
|
+
headers
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class ResponseAdapter
|
|
25
|
+
attr_reader :response
|
|
26
|
+
|
|
27
|
+
def initialize(response)
|
|
28
|
+
@response = response
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def status
|
|
32
|
+
response.status
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def headers
|
|
36
|
+
response.headers
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def body
|
|
40
|
+
response.body
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def uri
|
|
44
|
+
response.uri
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def [](key)
|
|
48
|
+
headers[key]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def redirect?
|
|
52
|
+
[301, 302, 303, 307, 308].include?(status)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class TimeoutResponse
|
|
57
|
+
attr_reader :uri, :status, :headers, :body
|
|
58
|
+
|
|
59
|
+
def initialize(uri)
|
|
60
|
+
@uri = URI.parse(uri)
|
|
61
|
+
@status = 0
|
|
62
|
+
@headers = {}
|
|
63
|
+
@body = ""
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def [](key)
|
|
67
|
+
headers[key]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def redirect?
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
attr_accessor :request_body
|
|
76
|
+
attr_reader :last_request, :last_response
|
|
77
|
+
|
|
78
|
+
def reset_cache!
|
|
79
|
+
@xml = nil
|
|
80
|
+
@json = nil
|
|
81
|
+
super
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def refresh
|
|
85
|
+
reset_cache!
|
|
86
|
+
send(last_request.method, last_request.url, last_request.params, last_request.headers)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def current_url
|
|
90
|
+
return "" unless last_response
|
|
91
|
+
|
|
92
|
+
uri = build_uri(last_response.uri.to_s)
|
|
93
|
+
uri.fragment = @current_fragment if @current_fragment
|
|
94
|
+
uri.to_s
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def dom
|
|
98
|
+
@dom ||= begin
|
|
99
|
+
content_type = if last_response
|
|
100
|
+
last_response.headers["Content-Type"].to_s[/\A[^;]+/] || ""
|
|
101
|
+
else
|
|
102
|
+
""
|
|
103
|
+
end
|
|
104
|
+
content_type.include?("/xml") ? xml : Capybara::HTML(html)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def html
|
|
109
|
+
last_response ? last_response.body.to_s : ""
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def xml
|
|
113
|
+
@xml ||= Nokogiri::XML(html)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def json
|
|
117
|
+
@json ||= JSON.parse(html)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def timed_out?
|
|
121
|
+
@timed_out || false
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
%i[get post put delete head patch request].each do |method|
|
|
125
|
+
define_method(method) do |url, params = {}, headers = {}, &block|
|
|
126
|
+
@last_request = LastRequest.new(method, url, params.dup, headers.dup)
|
|
127
|
+
uri = URI.parse(url)
|
|
128
|
+
request_headers = driver.with_headers.merge(
|
|
129
|
+
{ "Content-Type" => driver.as, "Accept" => driver.as }.merge(headers)
|
|
130
|
+
)
|
|
131
|
+
referer = request_headers.delete("HTTP_REFERER")
|
|
132
|
+
request_headers["Referer"] = referer if referer
|
|
133
|
+
request_headers["Cookie"] ||= cookie_jar.for(uri)
|
|
134
|
+
|
|
135
|
+
if driver.auth?
|
|
136
|
+
credentials = Base64.strict_encode64("#{driver.login}:#{driver.password}")
|
|
137
|
+
request_headers["Authorization"] ||= "Basic #{credentials}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
request_params = driver.with_params.merge(params)
|
|
141
|
+
request_params.delete(:params_encoding)
|
|
142
|
+
encoded_params = encode_params(request_params, params[:params_encoding] || driver.params_encoding)
|
|
143
|
+
unless encoded_params.empty?
|
|
144
|
+
uri.query = [uri.query, encoded_params].compact.reject(&:empty?).join("&")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
options = {
|
|
148
|
+
timeout: { request_timeout: driver.options[:timeout] },
|
|
149
|
+
max_redirects: driver.follow_redirects? ? driver.redirect_limit : 0
|
|
150
|
+
}.merge(driver.with_options)
|
|
151
|
+
if options[:timeout].is_a?(Numeric)
|
|
152
|
+
options[:timeout] = { request_timeout: options[:timeout] }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
http = ::HTTPX.plugin(:follow_redirects).with(options)
|
|
156
|
+
@timed_out = false
|
|
157
|
+
response = http.request(method.to_s.upcase, uri.to_s, headers: request_headers, body: request_body)
|
|
158
|
+
@request_body = nil
|
|
159
|
+
if response.is_a?(::HTTPX::ErrorResponse)
|
|
160
|
+
raise response.error unless response.error.is_a?(::HTTPX::TimeoutError)
|
|
161
|
+
|
|
162
|
+
@timed_out = true
|
|
163
|
+
@last_response = TimeoutResponse.new(uri.to_s)
|
|
164
|
+
else
|
|
165
|
+
cookie_jar.merge(response.headers["set-cookie"], uri)
|
|
166
|
+
@last_response = ResponseAdapter.new(response)
|
|
167
|
+
end
|
|
168
|
+
rescue ::HTTPX::TimeoutError
|
|
169
|
+
@timed_out = true
|
|
170
|
+
@request_body = nil
|
|
171
|
+
@last_response = TimeoutResponse.new(uri.to_s)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
def encode_params(params, encoding)
|
|
178
|
+
pairs = case encoding
|
|
179
|
+
when :httpx
|
|
180
|
+
flatten_httpx_params(params)
|
|
181
|
+
when :rack
|
|
182
|
+
flatten_rack_params(params)
|
|
183
|
+
when :multi
|
|
184
|
+
flatten_multi_params(params)
|
|
185
|
+
when :none
|
|
186
|
+
params.map { |key, value| [key.to_s, value.to_s] }
|
|
187
|
+
end
|
|
188
|
+
URI.encode_www_form(pairs)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def flatten_httpx_params(params, prefix = nil, pairs = [])
|
|
192
|
+
params.each do |key, value|
|
|
193
|
+
name = prefix ? "#{prefix}[#{key}]" : key.to_s
|
|
194
|
+
case value
|
|
195
|
+
when Hash
|
|
196
|
+
flatten_httpx_params(value, name, pairs)
|
|
197
|
+
when Array
|
|
198
|
+
value.each { |entry| flatten_httpx_array(name, entry, pairs) }
|
|
199
|
+
else
|
|
200
|
+
pairs << [name, value]
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
pairs
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def flatten_httpx_array(name, value, pairs)
|
|
207
|
+
case value
|
|
208
|
+
when Hash
|
|
209
|
+
value.each { |key, entry| flatten_httpx_array("#{name}[#{key}]", entry, pairs) }
|
|
210
|
+
when Array
|
|
211
|
+
value.each { |entry| flatten_httpx_array("#{name}[]", entry, pairs) }
|
|
212
|
+
else
|
|
213
|
+
pairs << ["#{name}[]", value]
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def flatten_rack_params(params, prefix = nil, pairs = [])
|
|
218
|
+
params.each do |key, value|
|
|
219
|
+
name = prefix ? "#{prefix}[#{key}]" : key.to_s
|
|
220
|
+
case value
|
|
221
|
+
when Hash
|
|
222
|
+
flatten_rack_params(value, name, pairs)
|
|
223
|
+
when Array
|
|
224
|
+
value.each { |entry| pairs << ["#{name}[]", entry] }
|
|
225
|
+
else
|
|
226
|
+
pairs << [name, value]
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
pairs
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def flatten_multi_params(params, prefix = nil, pairs = [])
|
|
233
|
+
params.each do |key, value|
|
|
234
|
+
name = prefix ? "#{prefix}[#{key}]" : key.to_s
|
|
235
|
+
case value
|
|
236
|
+
when Hash
|
|
237
|
+
flatten_multi_params(value, name, pairs)
|
|
238
|
+
when Array
|
|
239
|
+
value.each { |entry| pairs << [name, entry] }
|
|
240
|
+
else
|
|
241
|
+
pairs << [name, value]
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
pairs
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def cookie_jar
|
|
248
|
+
@cookie_jar ||= Rack::Test::CookieJar.new [], current_host
|
|
249
|
+
end
|
|
250
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
class Capybara::HTTPX::Driver < Capybara::RackTest::Driver
|
|
2
|
+
attr_writer :as, :with_headers, :with_params, :with_options
|
|
3
|
+
attr_reader :login, :password, :params_encoding
|
|
4
|
+
|
|
5
|
+
def initialize(app, **options)
|
|
6
|
+
raise ArgumentError, "httpx requires a rack application, but none was given" unless app
|
|
7
|
+
|
|
8
|
+
super app, **{ timeout: 3 }.merge(options)
|
|
9
|
+
@params_encoding = :httpx
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def browser
|
|
13
|
+
@browser ||= Capybara::HTTPX::Browser.new self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def needs_server?
|
|
17
|
+
true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
PARAMS_ENCODING_ALLOWED_VALUES = %i[httpx rack multi none].freeze
|
|
21
|
+
|
|
22
|
+
def params_encoding=(value)
|
|
23
|
+
if PARAMS_ENCODING_ALLOWED_VALUES.include?(value)
|
|
24
|
+
@params_encoding = value
|
|
25
|
+
else
|
|
26
|
+
allowed = PARAMS_ENCODING_ALLOWED_VALUES.map(&:inspect).join(", ")
|
|
27
|
+
raise ArgumentError, "Allowed values are: #{allowed}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
%i[get post put delete head patch request].each do |method|
|
|
32
|
+
define_method(method) do |url, params = {}, headers = {}, &block|
|
|
33
|
+
browser.reset_host!
|
|
34
|
+
browser.process method, url, params, headers, &block
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def reset!
|
|
39
|
+
@login = nil
|
|
40
|
+
@password = nil
|
|
41
|
+
super
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def timed_out?
|
|
45
|
+
browser.timed_out?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def reset_with!
|
|
49
|
+
@with_headers = {}
|
|
50
|
+
@with_params = {}
|
|
51
|
+
@with_options = {}
|
|
52
|
+
@as = nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def as
|
|
56
|
+
@as ||= "application/json"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def with_headers
|
|
60
|
+
@with_headers ||= {}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def with_params
|
|
64
|
+
@with_params ||= {}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def with_options
|
|
68
|
+
@with_options ||= {}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def authenticate_with(login, password)
|
|
72
|
+
@login, @password = login, password
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def auth?
|
|
76
|
+
login && password
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def status_code
|
|
80
|
+
response.status
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def body
|
|
84
|
+
browser.html
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def xml
|
|
88
|
+
browser.xml
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def json
|
|
92
|
+
browser.json
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def request_body
|
|
96
|
+
browser.request_body
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def request_body=(value)
|
|
100
|
+
browser.request_body = value
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class Capybara::HTTPX::Session < Capybara::Session
|
|
2
|
+
%i[get post put delete head patch request].each do |method|
|
|
3
|
+
define_method(method) do |url, params = {}, headers = {}, &block|
|
|
4
|
+
@touched = true
|
|
5
|
+
driver.public_send(method, host_url(url), params, headers, &block)
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def authenticate_with(login, password)
|
|
10
|
+
driver.authenticate_with login, password
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def timed_out?
|
|
14
|
+
driver.timed_out?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def host_url(url)
|
|
18
|
+
if url !~ /^http/ && Capybara.app_host
|
|
19
|
+
url = Capybara.app_host + url.to_s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if @server
|
|
23
|
+
url = "http://#{@server.host}:#{@server.port}" + url.to_s unless url =~ /^http/
|
|
24
|
+
|
|
25
|
+
if Capybara.always_include_port
|
|
26
|
+
uri = URI.parse(url)
|
|
27
|
+
uri.port = @server.port if uri.port == uri.default_port
|
|
28
|
+
url = uri.to_s
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
url
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def timeout
|
|
36
|
+
driver.options[:timeout]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def request_body
|
|
40
|
+
driver.request_body
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def request_body=(value)
|
|
44
|
+
driver.request_body = value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def as=(value)
|
|
48
|
+
driver.as = value
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require "capybara"
|
|
2
|
+
require "httpx"
|
|
3
|
+
|
|
4
|
+
module Capybara
|
|
5
|
+
module HTTPX
|
|
6
|
+
autoload :Driver, "capybara/httpx/driver"
|
|
7
|
+
autoload :Browser, "capybara/httpx/browser"
|
|
8
|
+
autoload :Session, "capybara/httpx/session"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.session_pool
|
|
12
|
+
@session_pool ||= Hash.new do |hash, name|
|
|
13
|
+
hash[name] = if current_driver==:httpx
|
|
14
|
+
::Capybara::HTTPX::Session.new(current_driver, app)
|
|
15
|
+
else
|
|
16
|
+
::Capybara::Session.new(current_driver, app)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Capybara.register_driver :httpx do |app|
|
|
23
|
+
Capybara::HTTPX::Driver.new app
|
|
24
|
+
end
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
Capybara.register_driver :httpx_with_custom_timeout do |app|
|
|
4
|
+
Capybara::HTTPX::Driver.new app, timeout: 1
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module TestSessions
|
|
8
|
+
HTTPXTest = Capybara::Session.new(:httpx, TestApp)
|
|
9
|
+
HTTPXTestCustomTimeout = Capybara::Session.new(:httpx_with_custom_timeout, TestApp)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
skipped_tests = %i[
|
|
13
|
+
js
|
|
14
|
+
modals
|
|
15
|
+
screenshot
|
|
16
|
+
frames
|
|
17
|
+
windows
|
|
18
|
+
send_keys
|
|
19
|
+
server
|
|
20
|
+
hover
|
|
21
|
+
about_scheme
|
|
22
|
+
download
|
|
23
|
+
css
|
|
24
|
+
scroll
|
|
25
|
+
active_element
|
|
26
|
+
html_validation
|
|
27
|
+
shadow_dom
|
|
28
|
+
]
|
|
29
|
+
Capybara::SpecHelper.run_specs TestSessions::HTTPXTest, 'HTTPX', capybara_skip: skipped_tests do |example|
|
|
30
|
+
case example.metadata[:full_description]
|
|
31
|
+
when /#attach_file/
|
|
32
|
+
skip "HTTPX driver doesn't support #attach_file"
|
|
33
|
+
when /#check/
|
|
34
|
+
skip "HTTPX driver doesn't support #check"
|
|
35
|
+
when /#uncheck/
|
|
36
|
+
skip "HTTPX driver doesn't support #uncheck"
|
|
37
|
+
when /#click/
|
|
38
|
+
skip "HTTPX driver doesn't support #click"
|
|
39
|
+
when /#select/
|
|
40
|
+
skip "HTTPX driver doesn't support #select"
|
|
41
|
+
when /#unselect/
|
|
42
|
+
skip "HTTPX driver doesn't support #unselect"
|
|
43
|
+
when /#has_css\? with spatial requirements/
|
|
44
|
+
skip "HTTPX driver doesn't support #has_css? with spatial requirements"
|
|
45
|
+
when /#find with spatial filters/
|
|
46
|
+
skip "HTTPX driver doesn't support #find with spatial filters"
|
|
47
|
+
when /can set cookie if a blank path is specified/
|
|
48
|
+
skip "HTTPX driver doesn't support cookie"
|
|
49
|
+
when /has_css\? should support case insensitive :class and :id options/
|
|
50
|
+
skip "Nokogiri doesn't support case insensitive CSS attribute matchers"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
RSpec.describe Capybara::HTTPX::Session do
|
|
55
|
+
|
|
56
|
+
context "with httpx driver" do
|
|
57
|
+
before { Capybara.current_driver = :httpx }
|
|
58
|
+
let(:session) { TestSessions::HTTPXTest }
|
|
59
|
+
|
|
60
|
+
describe 'Capybara#current_session' do
|
|
61
|
+
it 'should be a httpx session' do
|
|
62
|
+
expect(Capybara.current_session).to be_an_instance_of(Capybara::HTTPX::Session)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe '#driver' do
|
|
67
|
+
it 'should be a httpx driver' do
|
|
68
|
+
expect(session.driver).to be_an_instance_of(Capybara::HTTPX::Driver)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe '#mode' do
|
|
73
|
+
it 'should remember the mode' do
|
|
74
|
+
expect( session.mode ).to eq(:httpx)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context "basic authentication" do
|
|
79
|
+
subject do
|
|
80
|
+
app = Sinatra.new do
|
|
81
|
+
use Rack::Auth::Basic do |username, password|
|
|
82
|
+
username=="admin" && password=="secret"
|
|
83
|
+
end
|
|
84
|
+
get("/"){ "Success!" }
|
|
85
|
+
end
|
|
86
|
+
described_class.new :httpx, app
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "allow access with right credentials" do
|
|
90
|
+
subject.authenticate_with "admin", "secret"
|
|
91
|
+
subject.get "/"
|
|
92
|
+
expect( subject.status_code ).to be 200
|
|
93
|
+
expect( subject.source ).to eq "Success!"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "deny access with wrong credentials" do
|
|
97
|
+
subject.authenticate_with "admin", "admin"
|
|
98
|
+
subject.get "/"
|
|
99
|
+
expect( subject.status_code ).to be 401
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context "params encoding" do
|
|
104
|
+
context "setting on driver" do
|
|
105
|
+
let(:driver) { session.driver }
|
|
106
|
+
it "defaults to :httpx" do
|
|
107
|
+
expect( driver.params_encoding ).to eq :httpx
|
|
108
|
+
end
|
|
109
|
+
it "can be set to :rack, :multi or :none" do
|
|
110
|
+
driver.params_encoding = :rack
|
|
111
|
+
expect( driver.params_encoding ).to eq :rack
|
|
112
|
+
driver.params_encoding = :multi
|
|
113
|
+
expect( driver.params_encoding ).to eq :multi
|
|
114
|
+
driver.params_encoding = :none
|
|
115
|
+
expect( driver.params_encoding ).to eq :none
|
|
116
|
+
|
|
117
|
+
expect do
|
|
118
|
+
driver.params_encoding = :unknown
|
|
119
|
+
end.to raise_error ArgumentError, "Allowed values are: :httpx, :rack, :multi, :none"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
context "usage in requests" do
|
|
123
|
+
subject do
|
|
124
|
+
app = Sinatra.new do
|
|
125
|
+
get("/"){ params.inspect }
|
|
126
|
+
end
|
|
127
|
+
described_class.new :httpx, app
|
|
128
|
+
end
|
|
129
|
+
it "defaults to driver setting" do
|
|
130
|
+
subject.get "/", ids: [1, 2, 3]
|
|
131
|
+
expect( subject.body ).to eq({"ids" => ["1", "2", "3"]}.inspect)
|
|
132
|
+
|
|
133
|
+
subject.driver.params_encoding = :rack
|
|
134
|
+
subject.get "/", ids: [1, 2, 3]
|
|
135
|
+
expect( subject.body ).to eq({"ids" => ["1", "2", "3"]}.inspect)
|
|
136
|
+
|
|
137
|
+
subject.driver.params_encoding = :multi
|
|
138
|
+
subject.get "/", ids: [1, 2, 3]
|
|
139
|
+
expect( subject.body ).to eq({"ids" => "3"}.inspect)
|
|
140
|
+
|
|
141
|
+
subject.driver.params_encoding = :none
|
|
142
|
+
subject.get "/", ids: [1, 2, 3]
|
|
143
|
+
expect( subject.body ).to eq({"ids" => "[1, 2, 3]"}.inspect)
|
|
144
|
+
end
|
|
145
|
+
it "can be overriden per request" do
|
|
146
|
+
subject.driver.params_encoding = :none
|
|
147
|
+
|
|
148
|
+
subject.get "/", ids: [1, 2, 3], params_encoding: :httpx
|
|
149
|
+
expect( subject.body ).to eq({"ids" => ["1", "2", "3"]}.inspect)
|
|
150
|
+
|
|
151
|
+
subject.get "/", ids: [1, 2, 3], params_encoding: :rack
|
|
152
|
+
expect( subject.body ).to eq({"ids" => ["1", "2", "3"]}.inspect)
|
|
153
|
+
|
|
154
|
+
subject.get "/", ids: [1, 2, 3], params_encoding: :multi
|
|
155
|
+
expect( subject.body ).to eq({"ids" => "3"}.inspect)
|
|
156
|
+
|
|
157
|
+
subject.driver.params_encoding = :httpx
|
|
158
|
+
|
|
159
|
+
subject.get "/", ids: [1, 2, 3], params_encoding: :none
|
|
160
|
+
expect( subject.body ).to eq({"ids" => "[1, 2, 3]"}.inspect)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
context "timeout" do
|
|
166
|
+
subject{ described_class.new :httpx, TestApp }
|
|
167
|
+
|
|
168
|
+
context "default" do
|
|
169
|
+
it "is 3 seconds" do
|
|
170
|
+
expect( subject.timeout ).to eq 3
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "is used during request" do
|
|
174
|
+
subject.get "/slow_response"
|
|
175
|
+
expect( subject ).not_to be_timed_out
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
context "accepts custom timeout" do
|
|
180
|
+
subject{ described_class.new :httpx_with_custom_timeout, TestApp }
|
|
181
|
+
|
|
182
|
+
it "is stored in options" do
|
|
183
|
+
expect( subject.timeout ).to eq 1
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "is used during request" do
|
|
187
|
+
subject.get "/slow_response"
|
|
188
|
+
expect( subject ).to be_timed_out
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
context "#host_url returns url with scheme, host, port and path" do
|
|
194
|
+
subject{ described_class.new :httpx, TestApp }
|
|
195
|
+
|
|
196
|
+
it "with empty url" do
|
|
197
|
+
expect( subject.host_url("") ).to match /\A#{Regexp.escape("http://127.0.0.1")}:\d+\z/
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it "with relative url" do
|
|
201
|
+
expect( subject.host_url("/demo/test") ).to match /\A#{Regexp.escape("http://127.0.0.1")}:\d+\/demo\/test\z/
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "with absolute url" do
|
|
205
|
+
expect(
|
|
206
|
+
subject.host_url("http://www.example.com:443/demo/test")
|
|
207
|
+
).to eq "http://www.example.com:443/demo/test"
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
context "can upload file by setting the body per request" do
|
|
212
|
+
subject do
|
|
213
|
+
app = Sinatra.new do
|
|
214
|
+
post("/"){ request.body }
|
|
215
|
+
end
|
|
216
|
+
described_class.new :httpx, app
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it "body is nil be default" do
|
|
220
|
+
expect( subject.request_body ).to be_nil
|
|
221
|
+
subject.post "/"
|
|
222
|
+
expect( subject.status_code ).to be 200
|
|
223
|
+
expect( subject.source ).to eq ""
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it "I can send data by setting the body" do
|
|
227
|
+
body = "**raw file content**"
|
|
228
|
+
subject.request_body = body
|
|
229
|
+
expect( subject.request_body ).to eq body
|
|
230
|
+
subject.post "/"
|
|
231
|
+
expect( subject.status_code ).to be 200
|
|
232
|
+
expect( subject.source ).to eq body
|
|
233
|
+
expect( subject.request_body ).to be_nil
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: capybara-httpx
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0.preview1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Joseph HALTER
|
|
8
|
+
- Jonathan TRON
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: capybara
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: httpx
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rack
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: sinatra
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: puma
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
description: HTTPX driver for Capybara, allowing testing of REST APIs
|
|
112
|
+
email:
|
|
113
|
+
- jonathan.tron@metrilio.com
|
|
114
|
+
- joseph.halter@metrilio.com
|
|
115
|
+
executables: []
|
|
116
|
+
extensions: []
|
|
117
|
+
extra_rdoc_files:
|
|
118
|
+
- README.md
|
|
119
|
+
files:
|
|
120
|
+
- LICENSE
|
|
121
|
+
- README.md
|
|
122
|
+
- lib/capybara/httpx.rb
|
|
123
|
+
- lib/capybara/httpx/browser.rb
|
|
124
|
+
- lib/capybara/httpx/cucumber.rb
|
|
125
|
+
- lib/capybara/httpx/driver.rb
|
|
126
|
+
- lib/capybara/httpx/session.rb
|
|
127
|
+
- spec/session/httpx_spec.rb
|
|
128
|
+
- spec/spec_helper.rb
|
|
129
|
+
homepage: https://forgejo.home.tron.name/metrilio/capybara-httpx
|
|
130
|
+
licenses:
|
|
131
|
+
- MIT
|
|
132
|
+
metadata: {}
|
|
133
|
+
rdoc_options:
|
|
134
|
+
- "--main"
|
|
135
|
+
- README.md
|
|
136
|
+
- "--inline-source"
|
|
137
|
+
- "--line-numbers"
|
|
138
|
+
require_paths:
|
|
139
|
+
- lib
|
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - ">="
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '3.4'
|
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
146
|
+
requirements:
|
|
147
|
+
- - ">="
|
|
148
|
+
- !ruby/object:Gem::Version
|
|
149
|
+
version: '0'
|
|
150
|
+
requirements: []
|
|
151
|
+
rubygems_version: 3.7.2
|
|
152
|
+
specification_version: 4
|
|
153
|
+
summary: HTTPX driver for Capybara
|
|
154
|
+
test_files:
|
|
155
|
+
- spec/session/httpx_spec.rb
|
|
156
|
+
- spec/spec_helper.rb
|