capybara-dommy 0.8.0
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/CHANGELOG.md +16 -0
- data/LICENSE.txt +21 -0
- data/README.md +190 -0
- data/Rakefile +21 -0
- data/lib/capybara/dommy/configuration.rb +34 -0
- data/lib/capybara/dommy/driver.rb +191 -0
- data/lib/capybara/dommy/errors.rb +15 -0
- data/lib/capybara/dommy/node.rb +334 -0
- data/lib/capybara/dommy/rails.rb +10 -0
- data/lib/capybara/dommy/text_extractor.rb +68 -0
- data/lib/capybara/dommy/version.rb +7 -0
- data/lib/capybara/dommy.rb +17 -0
- data/sig/capybara/dommy.rbs +6 -0
- metadata +100 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5c7a62f13890b2fa82ad1270440924e9a1723a20975d71d91aa1bf24938922ae
|
|
4
|
+
data.tar.gz: 8045f008aaca496cab19488fc3b551cc2a955880d08bdc24034e60981d791ffe
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e337cfa551b1a24a3f06554d5638260f5b2c2c19c09cad1b90aa3bfe55bd7363b1671f329070eabe32ab489ebcf035b06240cf79d1dea6ce364ff1541b2b9b03
|
|
7
|
+
data.tar.gz: 05ac1cd5fdda2ba28b907adbac630d3d139e2e33dfaf26f06478bd4fb7a7ca668b662fb4b6726da46818cae9aa09b5f95dc7a0390d81e9915bf64f1ad3ace1a0
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.8.0 — 2026-05-31
|
|
4
|
+
|
|
5
|
+
Versioned in lockstep with [`dommy`](https://github.com/takahashim/dommy) 0.8.0.
|
|
6
|
+
No functional changes to capybara-dommy itself.
|
|
7
|
+
|
|
8
|
+
## 0.7.0 — 2026-05-30
|
|
9
|
+
|
|
10
|
+
Initial release.
|
|
11
|
+
|
|
12
|
+
Versioned in lockstep with the [`dommy`](https://github.com/takahashim/dommy)
|
|
13
|
+
gem. capybara-dommy is a Capybara driver backed by `dommy` and `dommy-rack`. It
|
|
14
|
+
drives Rack/Rails apps through the Capybara DSL without a real browser or
|
|
15
|
+
JavaScript, keeping the page as a `Dommy::Document` (RackTest-like, with
|
|
16
|
+
HTML-level visibility).
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Masayoshi Takahashi
|
|
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,190 @@
|
|
|
1
|
+
# Capybara::Dommy
|
|
2
|
+
|
|
3
|
+
`capybara-dommy` is a [Capybara](https://github.com/teamcapybara/capybara) driver backed by
|
|
4
|
+
[Dommy](https://github.com/takahashim/dommy) and `dommy-rack`.
|
|
5
|
+
|
|
6
|
+
It drives Rack and Rails applications through the normal Capybara DSL without
|
|
7
|
+
starting a real browser. The current page is kept as a `Dommy::Document`, so
|
|
8
|
+
tests can inspect and interact with parsed HTML while staying close to the
|
|
9
|
+
speed and simplicity of a Rack-style driver.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Visits Rack endpoints without a browser process or JavaScript runtime.
|
|
14
|
+
- Supports Capybara navigation, CSS/XPath queries, scoped `within` queries,
|
|
15
|
+
status codes, response headers, page title, and serialized HTML.
|
|
16
|
+
- Supports common HTML interactions: links, buttons, form submission, text
|
|
17
|
+
fields, textareas, checkboxes, radios, selects, ranges, labels, details, and
|
|
18
|
+
file uploads.
|
|
19
|
+
- Preserves session state such as cookies, follows redirects by default, and
|
|
20
|
+
supports browser-like back, forward, and refresh navigation.
|
|
21
|
+
- Implements HTML-level visibility through `dommy-rack`.
|
|
22
|
+
- Provides a Rails convenience require for `driven_by :dommy`.
|
|
23
|
+
|
|
24
|
+
## Limitations
|
|
25
|
+
|
|
26
|
+
`capybara-dommy` is intentionally not a browser automation driver.
|
|
27
|
+
|
|
28
|
+
- JavaScript is not executed.
|
|
29
|
+
- Screenshots, browser windows, alerts, confirms, prompts, and other real
|
|
30
|
+
browser features are not supported.
|
|
31
|
+
- CSS layout is not calculated. Visibility is based on HTML-level rules such as
|
|
32
|
+
`hidden`, `type="hidden"`, and inline `display: none` handling provided by
|
|
33
|
+
`dommy-rack`.
|
|
34
|
+
|
|
35
|
+
By default, `execute_script`, `evaluate_script`, and
|
|
36
|
+
`evaluate_async_script` raise `Capybara::NotSupportedByDriverError`. You can
|
|
37
|
+
turn those calls into no-ops with configuration when migrating tests that call
|
|
38
|
+
JavaScript helpers incidentally.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Add the gem to your application's Gemfile:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
gem "capybara-dommy"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then run:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
bundle install
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Until the gem is available from RubyGems, install it from GitHub:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
gem "capybara-dommy", github: "takahashim/capybara-dommy"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`capybara-dommy` requires Ruby 3.2 or newer.
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
For a plain Rack app, require the gem and register a Capybara driver:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
require "capybara/dommy"
|
|
68
|
+
|
|
69
|
+
Capybara.register_driver(:dommy) do |app|
|
|
70
|
+
Capybara::Dommy::Driver.new(app)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
Capybara.default_driver = :dommy
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
You can then use the normal Capybara DSL:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
visit "/"
|
|
80
|
+
click_link "New post"
|
|
81
|
+
fill_in "Title", with: "Hello"
|
|
82
|
+
click_button "Create"
|
|
83
|
+
|
|
84
|
+
expect(page).to have_text("Created")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Rails System Tests
|
|
88
|
+
|
|
89
|
+
For Rails system tests, require the Rails integration and use
|
|
90
|
+
`driven_by :dommy`:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
# test/application_system_test_case.rb or spec/rails_helper.rb
|
|
94
|
+
require "capybara/dommy/rails"
|
|
95
|
+
|
|
96
|
+
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
|
|
97
|
+
driven_by :dommy
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The Rails integration only registers the driver. Driver defaults still come from
|
|
102
|
+
`Capybara::Dommy.configuration`.
|
|
103
|
+
|
|
104
|
+
## Configuration
|
|
105
|
+
|
|
106
|
+
Configure process-wide defaults before creating sessions:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
Capybara::Dommy.configure do |config|
|
|
110
|
+
config.default_host = "http://example.org"
|
|
111
|
+
config.follow_redirects = true
|
|
112
|
+
config.max_redirects = 5
|
|
113
|
+
config.visibility = :html
|
|
114
|
+
config.raise_on_unsupported_js = true
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Available options:
|
|
119
|
+
|
|
120
|
+
- `default_host`: host used for relative visits. Defaults to
|
|
121
|
+
`"http://example.org"`.
|
|
122
|
+
- `follow_redirects`: whether Rack redirects are followed automatically.
|
|
123
|
+
Defaults to `true`.
|
|
124
|
+
- `max_redirects`: maximum redirect count. Defaults to `5`.
|
|
125
|
+
- `visibility`: one of `:html`, `:all`, or `:none`. `:html` uses
|
|
126
|
+
`dommy-rack` visibility checks. `:all` and `:none` treat every element as
|
|
127
|
+
visible.
|
|
128
|
+
- `raise_on_unsupported_js`: when `true`, JavaScript methods raise
|
|
129
|
+
`Capybara::NotSupportedByDriverError`; when `false`, they return `nil`.
|
|
130
|
+
|
|
131
|
+
You can also override driver options per registration:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
Capybara.register_driver(:dommy) do |app|
|
|
135
|
+
Capybara::Dommy::Driver.new(
|
|
136
|
+
app,
|
|
137
|
+
default_host: "http://test.example",
|
|
138
|
+
follow_redirects: true,
|
|
139
|
+
max_redirects: 10,
|
|
140
|
+
visibility: :html
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
After checking out the repository, install dependencies:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
bin/setup
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Run the full test suite:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
bundle exec rake spec
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Run only the fast unit specs:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
bundle exec rake spec:unit
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Run only Capybara's shared driver compliance suite:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
bundle exec rake spec:compliance
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Open an interactive console:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
bin/console
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Install the gem locally:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
bundle exec rake install
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Contributing
|
|
184
|
+
|
|
185
|
+
Bug reports and pull requests are welcome on GitHub:
|
|
186
|
+
<https://github.com/takahashim/capybara-dommy>.
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
The gem is available as open source under the terms of the MIT License.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
|
|
6
|
+
# Everything (fast unit specs + Capybara compliance suite).
|
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
8
|
+
|
|
9
|
+
namespace :spec do
|
|
10
|
+
desc "Run only the fast unit specs (no compliance harness / TestApp)"
|
|
11
|
+
RSpec::Core::RakeTask.new(:unit) do |t|
|
|
12
|
+
t.pattern = "spec/unit/**/*_spec.rb"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc "Run only the Capybara driver compliance suite"
|
|
16
|
+
RSpec::Core::RakeTask.new(:compliance) do |t|
|
|
17
|
+
t.pattern = "spec/compliance/**/*_spec.rb"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
task default: :spec
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Dommy
|
|
5
|
+
# Process-wide defaults for new drivers. `Driver.new` falls back to these
|
|
6
|
+
# when a keyword argument is omitted.
|
|
7
|
+
class Configuration
|
|
8
|
+
attr_accessor :default_host, :follow_redirects, :max_redirects, :visibility,
|
|
9
|
+
:raise_on_unsupported_js
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@default_host = "http://example.org"
|
|
13
|
+
@follow_redirects = true
|
|
14
|
+
@max_redirects = 5
|
|
15
|
+
@visibility = :html
|
|
16
|
+
@raise_on_unsupported_js = true
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def configuration
|
|
22
|
+
@configuration ||= Configuration.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def configure
|
|
26
|
+
yield configuration
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def reset_configuration!
|
|
30
|
+
@configuration = Configuration.new
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Dommy
|
|
5
|
+
# A Capybara driver backed by Dommy::Rack::Session. Implements the
|
|
6
|
+
# navigation / query / reset! parts of the Capybara::Driver::Base contract;
|
|
7
|
+
# element interaction lives in Capybara::Dommy::Node. JavaScript, screenshot,
|
|
8
|
+
# window, and modal methods are left to Driver::Base (which raises
|
|
9
|
+
# Capybara::NotSupportedByDriverError).
|
|
10
|
+
class Driver < Capybara::Driver::Base
|
|
11
|
+
VISIBILITY_MODES = %i[all html none].freeze
|
|
12
|
+
|
|
13
|
+
attr_reader :app, :visibility
|
|
14
|
+
|
|
15
|
+
def initialize(app,
|
|
16
|
+
default_host: nil,
|
|
17
|
+
follow_redirects: nil,
|
|
18
|
+
max_redirects: nil,
|
|
19
|
+
visibility: nil)
|
|
20
|
+
super()
|
|
21
|
+
config = Capybara::Dommy.configuration
|
|
22
|
+
@app = app
|
|
23
|
+
@visibility = visibility || config.visibility
|
|
24
|
+
unless VISIBILITY_MODES.include?(@visibility)
|
|
25
|
+
raise ArgumentError,
|
|
26
|
+
"unknown visibility mode #{@visibility.inspect} (expected one of #{VISIBILITY_MODES.join(", ")})"
|
|
27
|
+
end
|
|
28
|
+
@raise_on_unsupported_js = config.raise_on_unsupported_js
|
|
29
|
+
@session_options = {
|
|
30
|
+
default_host: default_host || config.default_host,
|
|
31
|
+
follow_redirects: follow_redirects.nil? ? config.follow_redirects : follow_redirects,
|
|
32
|
+
max_redirects: max_redirects || config.max_redirects,
|
|
33
|
+
# Capybara drives a trusted app and legitimately visits multiple
|
|
34
|
+
# hosts (e.g. app_host / multi-server specs), so don't enforce origin.
|
|
35
|
+
enforce_same_origin: false
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The dommy-rack session. Named `rack_session` to avoid colliding with
|
|
40
|
+
# Capybara::Driver::Base#session (the owning Capybara::Session). Rebuilt
|
|
41
|
+
# when the effective host (Capybara app_host / default_host) changes so
|
|
42
|
+
# current_url reflects it and same-origin checks pass.
|
|
43
|
+
def rack_session
|
|
44
|
+
host = effective_host
|
|
45
|
+
if @rack_session.nil? || @rack_session_host != host
|
|
46
|
+
@rack_session = ::Dommy::Rack::Session.new(@app, **@session_options.merge(default_host: host))
|
|
47
|
+
@rack_session_host = host
|
|
48
|
+
end
|
|
49
|
+
@rack_session
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# --- Navigation ---
|
|
53
|
+
|
|
54
|
+
def visit(path)
|
|
55
|
+
# A fresh visit resolves a relative path against the host root (not the
|
|
56
|
+
# current page's directory), matching browser address-bar semantics.
|
|
57
|
+
rack_session.visit(::URI.join("#{effective_host}/", path.to_s).to_s)
|
|
58
|
+
rescue URI::InvalidURIError
|
|
59
|
+
rack_session.visit(path)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def current_url
|
|
63
|
+
rack_session.current_url.to_s
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def refresh
|
|
67
|
+
rack_session.reload
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def go_back
|
|
71
|
+
rack_session.back
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def go_forward
|
|
75
|
+
rack_session.forward
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# --- Page state ---
|
|
79
|
+
|
|
80
|
+
def html
|
|
81
|
+
rack_session.html
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def title
|
|
85
|
+
document&.title
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def status_code
|
|
89
|
+
rack_session.status
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def response_headers
|
|
93
|
+
rack_session.headers || {}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# --- Query (returns Capybara::Dommy::Node arrays) ---
|
|
97
|
+
|
|
98
|
+
def find_css(query, **_options)
|
|
99
|
+
wrap(document&.query_selector_all(query))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def find_xpath(query, **_options)
|
|
103
|
+
wrap(document&.xpath(query))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# --- Node-facing seam (keeps the dommy-rack Session API in one place) ---
|
|
107
|
+
|
|
108
|
+
def document
|
|
109
|
+
rack_session.document
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def follow_link(element)
|
|
113
|
+
rack_session.click_link_element(element)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def submit_form(form, submitter:)
|
|
117
|
+
rack_session.submit_form(form, submitter: submitter)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# --- Lifecycle ---
|
|
121
|
+
|
|
122
|
+
def reset!
|
|
123
|
+
@rack_session = nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def wait?
|
|
127
|
+
false
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def needs_server?
|
|
131
|
+
false
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Lets Capybara reload a node when it goes stale (after navigation).
|
|
135
|
+
def invalid_element_errors
|
|
136
|
+
[Capybara::Dommy::StaleElementReferenceError]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# --- JavaScript (unsupported) ---
|
|
140
|
+
# When raise_on_unsupported_js is false these become no-ops, so tests
|
|
141
|
+
# that incidentally call them don't fail.
|
|
142
|
+
|
|
143
|
+
def execute_script(_script, *_args)
|
|
144
|
+
unsupported_js!("execute_script")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def evaluate_script(_script, *_args)
|
|
148
|
+
unsupported_js!("evaluate_script")
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def evaluate_async_script(_script, *_args)
|
|
152
|
+
unsupported_js!("evaluate_async_script")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Visibility decision used by Node#visible?. :all / :none treat every
|
|
156
|
+
# element as visible; :html defers to dommy-rack's HTML-level check.
|
|
157
|
+
def visible?(element)
|
|
158
|
+
return true if @visibility == :all || @visibility == :none
|
|
159
|
+
|
|
160
|
+
::Dommy::Rack.visible?(element)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
# Capybara's app_host (set per-example) wins over default_host; falls
|
|
166
|
+
# back to the host this driver was configured with. Guarded so a
|
|
167
|
+
# standalone driver (no owning Capybara session) still works.
|
|
168
|
+
def effective_host
|
|
169
|
+
options = owning_session_options
|
|
170
|
+
(options && (options.app_host || options.default_host)) || @session_options[:default_host]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def owning_session_options
|
|
174
|
+
session_options if session
|
|
175
|
+
rescue StandardError
|
|
176
|
+
nil
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def wrap(elements)
|
|
180
|
+
(elements || []).map { |element| Node.new(self, element) }
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def unsupported_js!(name)
|
|
184
|
+
return nil unless @raise_on_unsupported_js
|
|
185
|
+
|
|
186
|
+
raise Capybara::NotSupportedByDriverError,
|
|
187
|
+
"capybara-dommy does not support JavaScript (#{name})"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Dommy
|
|
5
|
+
# Base error for capybara-dommy. Element lookup failures surface as
|
|
6
|
+
# Capybara's own ElementNotFound / Ambiguous from the finder layer, and
|
|
7
|
+
# unsupported-URL / cross-origin errors propagate from dommy-rack.
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
|
|
10
|
+
# Raised when a node is used after its element left the current document
|
|
11
|
+
# (e.g. after navigation). Listed in Driver#invalid_element_errors so
|
|
12
|
+
# Capybara reloads and retries.
|
|
13
|
+
class StaleElementReferenceError < Error; end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Dommy
|
|
5
|
+
# Wraps a Dommy::Element as a Capybara driver node. Modeled on
|
|
6
|
+
# Capybara::RackTest::Node: field setting mutates the Dommy::Element in the
|
|
7
|
+
# live document, while link clicks and form submission delegate to the
|
|
8
|
+
# dommy-rack session (which re-reads the same document at submit time).
|
|
9
|
+
class Node < Capybara::Driver::Node
|
|
10
|
+
OPTION_OWNER_XPATH = "./parent::*[self::optgroup | self::select | self::datalist]"
|
|
11
|
+
DISABLED_BY_FIELDSET_XPATH =
|
|
12
|
+
"./parent::fieldset[./@disabled] | " \
|
|
13
|
+
"./ancestor::*[(not(./self::legend) or ./preceding-sibling::legend)][./parent::fieldset[./@disabled]]"
|
|
14
|
+
|
|
15
|
+
def tag_name
|
|
16
|
+
native.tag_name.downcase
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def [](name)
|
|
20
|
+
native.get_attribute(name.to_s)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def value
|
|
24
|
+
if select?
|
|
25
|
+
native.multiple ? native.selected_options.map(&:value) : native.value
|
|
26
|
+
elsif checkable?
|
|
27
|
+
native.has_attribute?("value") ? native.get_attribute("value") : "on"
|
|
28
|
+
elsif native.respond_to?(:value)
|
|
29
|
+
native.value
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def all_text
|
|
34
|
+
text_extractor.all_text(native)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def visible_text
|
|
38
|
+
text_extractor.visible_text(native)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def visible?
|
|
42
|
+
driver.visible?(native)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def checked?
|
|
46
|
+
native.respond_to?(:checked) && native.checked
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def selected?
|
|
50
|
+
native.respond_to?(:selected) && native.selected
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# `disabled` only applies to form-associated elements; on anything else
|
|
54
|
+
# (e.g. a link that incorrectly carries the attribute) it has no effect.
|
|
55
|
+
DISABLEABLE_ELEMENTS = %w[button fieldset input optgroup option select textarea].freeze
|
|
56
|
+
|
|
57
|
+
def disabled?
|
|
58
|
+
return false unless DISABLEABLE_ELEMENTS.include?(tag_name)
|
|
59
|
+
return true if native.has_attribute?("disabled")
|
|
60
|
+
|
|
61
|
+
if %w[option optgroup].include?(tag_name)
|
|
62
|
+
owner = native.xpath(OPTION_OWNER_XPATH).first
|
|
63
|
+
owner ? self.class.new(driver, owner).disabled? : false
|
|
64
|
+
else
|
|
65
|
+
!native.xpath(DISABLED_BY_FIELDSET_XPATH).empty?
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# readonly does not apply to these input types, so they are never
|
|
70
|
+
# readonly even if the attribute is present (matches RackTest).
|
|
71
|
+
NON_READONLY_TYPES = %w[hidden range color checkbox radio file submit image reset button].freeze
|
|
72
|
+
|
|
73
|
+
def readonly?
|
|
74
|
+
return false if input_field? && NON_READONLY_TYPES.include?(field_type)
|
|
75
|
+
|
|
76
|
+
native.has_attribute?("readonly")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def path
|
|
80
|
+
native.path
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def style(_styles)
|
|
84
|
+
{}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# --- Interaction ---
|
|
88
|
+
|
|
89
|
+
def click(_keys = [], **_options)
|
|
90
|
+
if link?
|
|
91
|
+
click_link_node
|
|
92
|
+
elsif submits?
|
|
93
|
+
submit_owning_form
|
|
94
|
+
elsif checkable?
|
|
95
|
+
set(!checked?)
|
|
96
|
+
elsif tag_name == "label"
|
|
97
|
+
click_label
|
|
98
|
+
elsif (details = native.closest("details"))
|
|
99
|
+
toggle_details(details)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def set(value, **_options)
|
|
104
|
+
return if disabled? || readonly?
|
|
105
|
+
|
|
106
|
+
if radio?
|
|
107
|
+
set_radio
|
|
108
|
+
elsif checkbox?
|
|
109
|
+
set_checkbox(value)
|
|
110
|
+
elsif range?
|
|
111
|
+
set_range(value)
|
|
112
|
+
elsif file?
|
|
113
|
+
set_file(value)
|
|
114
|
+
elsif input_field? || textarea?
|
|
115
|
+
set_text_value(value)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def select_option
|
|
120
|
+
return if disabled?
|
|
121
|
+
|
|
122
|
+
select_el = select_node
|
|
123
|
+
deselect_all(select_el) unless select_el&.multiple
|
|
124
|
+
native.selected = true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def unselect_option
|
|
128
|
+
unless select_node&.multiple
|
|
129
|
+
raise Capybara::UnselectNotAllowed, "Cannot unselect option from a non-multiple select box"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
native.selected = false
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# --- Scoped queries (for `within`) ---
|
|
136
|
+
|
|
137
|
+
def find_css(locator, **_options)
|
|
138
|
+
native.query_selector_all(locator).map { |element| self.class.new(driver, element) }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def find_xpath(locator, **_options)
|
|
142
|
+
native.xpath(locator).map { |element| self.class.new(driver, element) }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Guard every public method with a staleness check so Capybara can
|
|
146
|
+
# reload a node whose element left the current document (after
|
|
147
|
+
# navigation). Mirrors Capybara::RackTest::Node.
|
|
148
|
+
public_instance_methods(false).each do |meth_name|
|
|
149
|
+
alias_method "unchecked_#{meth_name}", meth_name
|
|
150
|
+
private "unchecked_#{meth_name}"
|
|
151
|
+
|
|
152
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
153
|
+
def #{meth_name}(...)
|
|
154
|
+
stale_check
|
|
155
|
+
send(:"unchecked_#{meth_name}", ...)
|
|
156
|
+
end
|
|
157
|
+
RUBY
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def stale_check
|
|
163
|
+
return if native.document.equal?(driver.document)
|
|
164
|
+
|
|
165
|
+
raise StaleElementReferenceError, "element is no longer attached to the document"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def submit_owning_form
|
|
169
|
+
form = form_for(native)
|
|
170
|
+
driver.submit_form(form, submitter: native) if form
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# javascript: links are no-ops in Capybara (a policy decision); every
|
|
174
|
+
# other link delegates to dommy-rack, which handles fragment / same-page
|
|
175
|
+
# / blank-href semantics and raises on genuinely unsupported schemes.
|
|
176
|
+
def click_link_node
|
|
177
|
+
scheme = native.get_attribute("href").to_s.split(":", 2).first.to_s.downcase
|
|
178
|
+
return if scheme == "javascript"
|
|
179
|
+
|
|
180
|
+
driver.follow_link(native)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def text_extractor
|
|
184
|
+
TextExtractor.new(driver)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def toggle_details(details)
|
|
188
|
+
if details.has_attribute?("open")
|
|
189
|
+
details.remove_attribute("open")
|
|
190
|
+
else
|
|
191
|
+
details.set_attribute("open", "open")
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def click_label
|
|
196
|
+
control = labelled_control
|
|
197
|
+
return unless control
|
|
198
|
+
|
|
199
|
+
node = self.class.new(driver, control)
|
|
200
|
+
node.set(!node.checked?) if node.send(:checkable?)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def labelled_control
|
|
204
|
+
for_id = native.get_attribute("for")
|
|
205
|
+
if for_id && !for_id.empty?
|
|
206
|
+
document.get_element_by_id(for_id)
|
|
207
|
+
else
|
|
208
|
+
native.query_selector("input, textarea, select")
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def set_text_value(value)
|
|
213
|
+
string = value.to_s
|
|
214
|
+
if text_or_password? && attribute_present?("maxlength")
|
|
215
|
+
string = string[0, native.get_attribute("maxlength").to_i].to_s
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# An <input> value ending in a newline submits a single-field form.
|
|
219
|
+
# There is no submitter button in this case.
|
|
220
|
+
form = single_field_form
|
|
221
|
+
if input_field? && string.end_with?("\n") && form
|
|
222
|
+
native.value = string.chomp
|
|
223
|
+
driver.submit_form(form, submitter: nil)
|
|
224
|
+
else
|
|
225
|
+
native.value = string
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def single_field_form
|
|
230
|
+
form = form_for(native)
|
|
231
|
+
return nil unless form && form.query_selector_all("input, textarea").length == 1
|
|
232
|
+
|
|
233
|
+
form
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def set_range(value)
|
|
237
|
+
min = (native.get_attribute("min") || 0).to_f
|
|
238
|
+
max = (native.get_attribute("max") || 100).to_f
|
|
239
|
+
step = (native.get_attribute("step") || 1).to_f
|
|
240
|
+
v = value.to_f.clamp(min, max)
|
|
241
|
+
v = (((v - min) / step).round * step) + min
|
|
242
|
+
v = v.clamp(min, max)
|
|
243
|
+
native.value = (v == v.to_i ? v.to_i : v).to_s
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def attribute_present?(name)
|
|
247
|
+
value = native.get_attribute(name)
|
|
248
|
+
value && !value.empty?
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Reflect checked state on the attribute so node[:checked] and form
|
|
252
|
+
# submission both observe it (Dommy's `checked=` only sets the property).
|
|
253
|
+
def set_checkbox(value)
|
|
254
|
+
if value
|
|
255
|
+
native.set_attribute("checked", "checked")
|
|
256
|
+
else
|
|
257
|
+
native.remove_attribute("checked")
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def set_radio
|
|
262
|
+
name = native.get_attribute("name")
|
|
263
|
+
scope = native.closest("form") || document
|
|
264
|
+
if name && scope
|
|
265
|
+
scope.query_selector_all("input[type='radio']").each do |radio|
|
|
266
|
+
radio.remove_attribute("checked") if radio.get_attribute("name") == name
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
native.set_attribute("checked", "checked")
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def set_file(value)
|
|
273
|
+
files = Array(value).map do |path|
|
|
274
|
+
path = path.to_s
|
|
275
|
+
raise Capybara::FileNotFound, "cannot attach file, #{path} does not exist" unless ::File.exist?(path)
|
|
276
|
+
|
|
277
|
+
::Dommy::File.new(
|
|
278
|
+
[::File.binread(path)], ::File.basename(path),
|
|
279
|
+
"type" => ::Dommy::Rack::FileUpload.mime_type_for(path)
|
|
280
|
+
)
|
|
281
|
+
end
|
|
282
|
+
native.__driver_set_files__(files)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def deselect_all(select_el)
|
|
286
|
+
return unless select_el
|
|
287
|
+
|
|
288
|
+
select_el.options.each { |option| option.selected = false }
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def form_for(element)
|
|
292
|
+
form_id = element.get_attribute("form")
|
|
293
|
+
if form_id && !form_id.empty?
|
|
294
|
+
document.get_element_by_id(form_id)
|
|
295
|
+
else
|
|
296
|
+
element.closest("form")
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def select_node
|
|
301
|
+
native.closest("select")
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def document
|
|
305
|
+
driver.document
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def field_type
|
|
309
|
+
native.respond_to?(:type) ? native.type : nil
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def input_field? = tag_name == "input"
|
|
313
|
+
def textarea? = tag_name == "textarea"
|
|
314
|
+
def select? = tag_name == "select"
|
|
315
|
+
def radio? = input_field? && field_type == "radio"
|
|
316
|
+
def checkbox? = input_field? && field_type == "checkbox"
|
|
317
|
+
def range? = input_field? && field_type == "range"
|
|
318
|
+
def file? = input_field? && field_type == "file"
|
|
319
|
+
def text_or_password? = input_field? && %w[text password].include?(field_type)
|
|
320
|
+
def checkable? = radio? || checkbox?
|
|
321
|
+
def link? = tag_name == "a" && !native.get_attribute("href").nil?
|
|
322
|
+
|
|
323
|
+
def submits?
|
|
324
|
+
if input_field?
|
|
325
|
+
%w[submit image].include?(field_type)
|
|
326
|
+
elsif tag_name == "button"
|
|
327
|
+
field_type == "submit"
|
|
328
|
+
else
|
|
329
|
+
false
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "capybara/dommy"
|
|
4
|
+
|
|
5
|
+
# Thin convenience layer: registers the :dommy driver so Rails system tests
|
|
6
|
+
# can use `driven_by :dommy`. Driver defaults come from
|
|
7
|
+
# Capybara::Dommy.configuration.
|
|
8
|
+
Capybara.register_driver(:dommy) do |app|
|
|
9
|
+
Capybara::Dommy::Driver.new(app)
|
|
10
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Dommy
|
|
5
|
+
# Extracts text from a Dommy subtree, reusing Capybara's own whitespace
|
|
6
|
+
# normalization so results match the matchers' expectations. `all_text` is
|
|
7
|
+
# the raw textContent; `visible_text` excludes element subtrees that are
|
|
8
|
+
# hidden under the driver's visibility mode or are non-rendered
|
|
9
|
+
# (script/style/head), and inserts breaks at block boundaries.
|
|
10
|
+
class TextExtractor
|
|
11
|
+
include Capybara::Node::WhitespaceNormalizer
|
|
12
|
+
|
|
13
|
+
BLOCK_ELEMENTS = %w[
|
|
14
|
+
p h1 h2 h3 h4 h5 h6 ol ul pre address blockquote dl div fieldset form hr noscript table
|
|
15
|
+
].freeze
|
|
16
|
+
NON_DISPLAYED_ELEMENTS = %w[script style head title].freeze
|
|
17
|
+
|
|
18
|
+
def initialize(driver)
|
|
19
|
+
@driver = driver
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def all_text(element)
|
|
23
|
+
normalize_spacing(element.text_content)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def visible_text(element)
|
|
27
|
+
return "" unless @driver.visible?(element)
|
|
28
|
+
|
|
29
|
+
normalize_visible_spacing(displayed_text(element))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def displayed_text(element)
|
|
35
|
+
element.child_nodes.map do |child|
|
|
36
|
+
if text_node?(child)
|
|
37
|
+
# Whitespace inside a text node (incl. newlines) collapses to
|
|
38
|
+
# spaces; only block boundaries introduce line breaks.
|
|
39
|
+
child.text_content.tr(SQUEEZED_SPACES, " ")
|
|
40
|
+
elsif element_node?(child)
|
|
41
|
+
next "" if non_displayed?(child) || !@driver.visible?(child)
|
|
42
|
+
|
|
43
|
+
inner = displayed_text(child)
|
|
44
|
+
block_element?(child) ? "\n#{inner}\n" : inner
|
|
45
|
+
else
|
|
46
|
+
""
|
|
47
|
+
end
|
|
48
|
+
end.join
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def non_displayed?(node)
|
|
52
|
+
NON_DISPLAYED_ELEMENTS.include?(node.tag_name.downcase)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def block_element?(node)
|
|
56
|
+
BLOCK_ELEMENTS.include?(node.tag_name.downcase)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def text_node?(node)
|
|
60
|
+
node.is_a?(::Dommy::TextNode)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def element_node?(node)
|
|
64
|
+
node.is_a?(::Dommy::Element)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "capybara"
|
|
4
|
+
require "dommy"
|
|
5
|
+
require "dommy/rack"
|
|
6
|
+
|
|
7
|
+
require_relative "dommy/version"
|
|
8
|
+
require_relative "dommy/errors"
|
|
9
|
+
require_relative "dommy/configuration"
|
|
10
|
+
require_relative "dommy/text_extractor"
|
|
11
|
+
require_relative "dommy/node"
|
|
12
|
+
require_relative "dommy/driver"
|
|
13
|
+
|
|
14
|
+
module Capybara
|
|
15
|
+
module Dommy
|
|
16
|
+
end
|
|
17
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: capybara-dommy
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.8.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- takahashim
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-05-31 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: capybara
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.40'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '3.40'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: dommy
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.8.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.8.0
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: dommy-rack
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 0.8.0
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 0.8.0
|
|
54
|
+
description: |
|
|
55
|
+
capybara-dommy is a Capybara driver backed by Dommy and dommy-rack. It drives
|
|
56
|
+
Rack/Rails apps through the Capybara DSL without a real browser or JavaScript,
|
|
57
|
+
keeping the page as a Dommy::Document. RackTest-like, with HTML-level visibility.
|
|
58
|
+
email:
|
|
59
|
+
- takahashimm@gmail.com
|
|
60
|
+
executables: []
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files: []
|
|
63
|
+
files:
|
|
64
|
+
- CHANGELOG.md
|
|
65
|
+
- LICENSE.txt
|
|
66
|
+
- README.md
|
|
67
|
+
- Rakefile
|
|
68
|
+
- lib/capybara/dommy.rb
|
|
69
|
+
- lib/capybara/dommy/configuration.rb
|
|
70
|
+
- lib/capybara/dommy/driver.rb
|
|
71
|
+
- lib/capybara/dommy/errors.rb
|
|
72
|
+
- lib/capybara/dommy/node.rb
|
|
73
|
+
- lib/capybara/dommy/rails.rb
|
|
74
|
+
- lib/capybara/dommy/text_extractor.rb
|
|
75
|
+
- lib/capybara/dommy/version.rb
|
|
76
|
+
- sig/capybara/dommy.rbs
|
|
77
|
+
homepage: https://github.com/takahashim/dommy
|
|
78
|
+
licenses:
|
|
79
|
+
- MIT
|
|
80
|
+
metadata:
|
|
81
|
+
homepage_uri: https://github.com/takahashim/dommy
|
|
82
|
+
source_code_uri: https://github.com/takahashim/dommy/tree/main/gems/capybara-dommy
|
|
83
|
+
rdoc_options: []
|
|
84
|
+
require_paths:
|
|
85
|
+
- lib
|
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: 3.2.0
|
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
requirements: []
|
|
97
|
+
rubygems_version: 3.6.2
|
|
98
|
+
specification_version: 4
|
|
99
|
+
summary: A Dommy-backed Capybara driver
|
|
100
|
+
test_files: []
|