capybara-dommy 0.8.0 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c7a62f13890b2fa82ad1270440924e9a1723a20975d71d91aa1bf24938922ae
4
- data.tar.gz: 8045f008aaca496cab19488fc3b551cc2a955880d08bdc24034e60981d791ffe
3
+ metadata.gz: ce693099e9f737e726914acb5cf9129ad87a96978209f5b1d9cc27132925b652
4
+ data.tar.gz: a69e70e2f0878025fd8d8c5a9411aaa990d6ebd7f812bd623cd3f27c4614e3bd
5
5
  SHA512:
6
- metadata.gz: e337cfa551b1a24a3f06554d5638260f5b2c2c19c09cad1b90aa3bfe55bd7363b1671f329070eabe32ab489ebcf035b06240cf79d1dea6ce364ff1541b2b9b03
7
- data.tar.gz: 05ac1cd5fdda2ba28b907adbac630d3d139e2e33dfaf26f06478bd4fb7a7ca668b662fb4b6726da46818cae9aa09b5f95dc7a0390d81e9915bf64f1ad3ace1a0
6
+ metadata.gz: 1f351cf3f7989d3bfa1a984dc425cf895790350ed699431f1e6fb8a7d6708c8b1abc05ab20df20f129839a0c926074d6b64774b9885a97b593ad41b8354b0b2a
7
+ data.tar.gz: 4246e91a681b995f8cc2fb2633eac92a1d6e5b1fc8dad67954d099124cc999055575597a59a94965ea6d37b201ed04d9ec2271a6dbd0146d7eede6b634a416cc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0 — 2026-07-13
4
+
5
+ ### Added
6
+ - **JavaScript-enabled driver variant** (`:dommy_js`, or `Driver.new(app, javascript: true)` / `config.javascript`): pages run their real Turbo/Stimulus/React bundles in the embedded QuickJS runtime, no browser process. Node interactions behave like a browser instead of the HTML-only fast paths — `click` dispatches the full pointer/mouse/click sequence before the default action (Turbo can `preventDefault` and take over), `set` types with focus + `input` / `change` events, `select_option` fires `input` / `change`, and `send_keys` dispatches real keyboard events. `execute_script` / `evaluate_script` run for real, and a time pump advances the virtual clock inside Capybara's synchronize loop so waiting matchers converge. Requires `dommy-js-quickjs`.
7
+
8
+ ### Fixed
9
+ - The dommy-rack session is fully disposed on `reset!` and when the effective host changes, so JS runtimes and open WebSocket transports no longer leak across tests.
10
+
11
+ ## 0.9.0 — 2026-06-22
12
+
13
+ Versioned in lockstep with [`dommy`](https://github.com/takahashim/dommy) 0.9.0.
14
+ No functional changes to capybara-dommy itself; it inherits dommy 0.9.0's CSS
15
+ cascade, computed styles, and accessibility tree, and now drives check/choose
16
+ through native click activation.
17
+
3
18
  ## 0.8.0 — 2026-05-31
4
19
 
5
20
  Versioned in lockstep with [`dommy`](https://github.com/takahashim/dommy) 0.8.0.
@@ -6,7 +6,7 @@ module Capybara
6
6
  # when a keyword argument is omitted.
7
7
  class Configuration
8
8
  attr_accessor :default_host, :follow_redirects, :max_redirects, :visibility,
9
- :raise_on_unsupported_js
9
+ :raise_on_unsupported_js, :javascript
10
10
 
11
11
  def initialize
12
12
  @default_host = "http://example.org"
@@ -14,6 +14,7 @@ module Capybara
14
14
  @max_redirects = 5
15
15
  @visibility = :html
16
16
  @raise_on_unsupported_js = true
17
+ @javascript = false
17
18
  end
18
19
  end
19
20
 
@@ -12,14 +12,28 @@ module Capybara
12
12
 
13
13
  attr_reader :app, :visibility
14
14
 
15
+ # --- Deterministic-time seam (used by JS runtimes) ---
16
+ #
17
+ # A JS runtime assigns a callable here; the driver invokes it before
18
+ # each DOM read Capybara polls in its synchronize loop (find_css /
19
+ # find_xpath / html / title). The pump is expected to advance Dommy's
20
+ # virtual scheduler a small slice and drain microtasks, so "content
21
+ # appears after a timeout" specs converge without wall-clock sleeps.
22
+ # Installing a pump also flips `wait?` to true, making Capybara retry
23
+ # failed expectations instead of raising immediately. Survives
24
+ # `reset!` (it belongs to the runtime, not to one page session).
25
+ attr_accessor :time_pump
26
+
15
27
  def initialize(app,
16
28
  default_host: nil,
17
29
  follow_redirects: nil,
18
30
  max_redirects: nil,
19
- visibility: nil)
31
+ visibility: nil,
32
+ javascript: nil)
20
33
  super()
21
34
  config = Capybara::Dommy.configuration
22
35
  @app = app
36
+ @javascript = javascript.nil? ? config.javascript : javascript
23
37
  @visibility = visibility || config.visibility
24
38
  unless VISIBILITY_MODES.include?(@visibility)
25
39
  raise ArgumentError,
@@ -34,6 +48,24 @@ module Capybara
34
48
  # hosts (e.g. app_host / multi-server specs), so don't enforce origin.
35
49
  enforce_same_origin: false
36
50
  }
51
+ @session_options[:javascript] = true if @javascript
52
+ # A JS session needs the virtual clock pumped inside Capybara's
53
+ # synchronize loop, so waiting expectations converge on timer/fetch
54
+ # driven updates. A host-installed pump (the documented seam) wins.
55
+ @time_pump ||= -> { @rack_session&.advance_time(16) } if @javascript
56
+ end
57
+
58
+ # Whether this driver runs page JavaScript (`javascript: true`, backed by
59
+ # a `Dommy::Rack::Session.new(app, javascript: true)`). Node interactions
60
+ # then dispatch real DOM events (Turbo/Stimulus handlers run) instead of
61
+ # the HTML-only fast paths.
62
+ def javascript? = @javascript
63
+
64
+ # Drain the JS runtime after an interaction's events (promise reactions
65
+ # settle before the next Capybara step). No-op without JavaScript.
66
+ def drain_js
67
+ rack_session.after_interaction if @javascript
68
+ nil
37
69
  end
38
70
 
39
71
  # The dommy-rack session. Named `rack_session` to avoid colliding with
@@ -43,6 +75,7 @@ module Capybara
43
75
  def rack_session
44
76
  host = effective_host
45
77
  if @rack_session.nil? || @rack_session_host != host
78
+ @rack_session&.dispose
46
79
  @rack_session = ::Dommy::Rack::Session.new(@app, **@session_options.merge(default_host: host))
47
80
  @rack_session_host = host
48
81
  end
@@ -52,6 +85,7 @@ module Capybara
52
85
  # --- Navigation ---
53
86
 
54
87
  def visit(path)
88
+ @frame_stack = []
55
89
  # A fresh visit resolves a relative path against the host root (not the
56
90
  # current page's directory), matching browser address-bar semantics.
57
91
  rack_session.visit(::URI.join("#{effective_host}/", path.to_s).to_s)
@@ -78,11 +112,15 @@ module Capybara
78
112
  # --- Page state ---
79
113
 
80
114
  def html
115
+ pump!
81
116
  rack_session.html
82
117
  end
83
118
 
119
+ # The title of the top-level browsing context, even inside a frame
120
+ # (Capybara's #title contract); the current frame's title is #frame_title.
84
121
  def title
85
- document&.title
122
+ pump!
123
+ rack_session.document&.title
86
124
  end
87
125
 
88
126
  def status_code
@@ -96,17 +134,59 @@ module Capybara
96
134
  # --- Query (returns Capybara::Dommy::Node arrays) ---
97
135
 
98
136
  def find_css(query, **_options)
137
+ pump!
99
138
  wrap(document&.query_selector_all(query))
100
139
  end
101
140
 
102
141
  def find_xpath(query, **_options)
142
+ pump!
103
143
  wrap(document&.xpath(query))
104
144
  end
105
145
 
106
146
  # --- Node-facing seam (keeps the dommy-rack Session API in one place) ---
107
147
 
148
+ # The document queries run against: the innermost switched-to frame's
149
+ # document, or the top-level page when no frame is active.
108
150
  def document
109
- rack_session.document
151
+ frame_stack.empty? ? rack_session.document : frame_stack.last[:document]
152
+ end
153
+
154
+ # --- Frames ---
155
+ # Capybara::Session#switch_to_frame drives these with an iframe element
156
+ # node, :parent, or :top. Frame documents are fetched through the
157
+ # dommy-rack session (sharing cookies); nothing here touches the
158
+ # top-level page state, so current_url / title stay top-level.
159
+
160
+ def switch_to_frame(frame)
161
+ case frame
162
+ when :top
163
+ @frame_stack = []
164
+ when :parent
165
+ frame_stack.pop
166
+ else
167
+ frame_stack.push(load_frame(frame.native))
168
+ end
169
+ end
170
+
171
+ def frame_url
172
+ frame_stack.empty? ? rack_session.current_url.to_s : frame_stack.last[:url]
173
+ end
174
+
175
+ def frame_title
176
+ document&.title
177
+ end
178
+
179
+ # --- Focus / keyboard ---
180
+
181
+ def active_element
182
+ Node.new(self, document.active_element)
183
+ end
184
+
185
+ # Session-level send_keys. Without JavaScript only focus navigation is
186
+ # meaningful, so :tab (the key Capybara's focused: specs use) moves
187
+ # focus through the tab order; other keys are ignored.
188
+ def send_keys(*keys)
189
+ keys.each { |key| focus_next_tabbable if key == :tab }
110
190
  end
111
191
 
112
192
  def follow_link(element)
@@ -120,11 +200,13 @@ module Capybara
120
200
  # --- Lifecycle ---
121
201
 
122
202
  def reset!
203
+ @rack_session&.dispose
123
204
  @rack_session = nil
205
+ @frame_stack = []
124
206
  end
125
207
 
126
208
  def wait?
127
- false
209
+ !@time_pump.nil?
128
210
  end
129
211
 
130
212
  def needs_server?
@@ -140,12 +222,18 @@ module Capybara
140
222
  # When raise_on_unsupported_js is false these become no-ops, so tests
141
223
  # that incidentally call them don't fail.
142
224
 
143
- def execute_script(_script, *_args)
144
- unsupported_js!("execute_script")
225
+ def execute_script(script, *args)
226
+ return unsupported_js!("execute_script") unless @javascript
227
+ raise ArgumentError, "script arguments are not supported" unless args.empty?
228
+
229
+ rack_session.execute_script(script)
145
230
  end
146
231
 
147
- def evaluate_script(_script, *_args)
148
- unsupported_js!("evaluate_script")
232
+ def evaluate_script(script, *args)
233
+ return unsupported_js!("evaluate_script") unless @javascript
234
+ raise ArgumentError, "script arguments are not supported" unless args.empty?
235
+
236
+ rack_session.evaluate_script(script)
149
237
  end
150
238
 
151
239
  def evaluate_async_script(_script, *_args)
@@ -162,6 +250,55 @@ module Capybara
162
250
 
163
251
  private
164
252
 
253
+ def pump!
254
+ @time_pump&.call
255
+ end
256
+
257
+ def frame_stack
258
+ @frame_stack ||= []
259
+ end
260
+
261
+ # Fetch an iframe's document, resolving its src against the enclosing
262
+ # frame's URL so nested frames with relative srcs load correctly.
263
+ def load_frame(iframe_element)
264
+ src = iframe_element.get_attribute("src").to_s
265
+ raise Capybara::Dommy::Error, "iframe has no src" if src.empty?
266
+
267
+ url = ::URI.join(frame_url, src).to_s
268
+ response = rack_session.fetch(url, headers: {"Referer" => frame_url})
269
+ doc = response.document
270
+ raise Capybara::Dommy::Error, "iframe did not return an HTML document" unless doc
271
+
272
+ {document: doc, url: url}
273
+ end
274
+
275
+ # Sequential focus navigation: elements with a positive tabindex first
276
+ # (ascending, document order within a value), then the remaining
277
+ # focusables in document order. The page's tab cycle starts over when
278
+ # the current active element is not in the order (e.g. body).
279
+ FOCUSABLE_SELECTOR = "a[href], button, input, select, textarea, [tabindex]"
280
+
281
+ def focus_next_tabbable
282
+ ordered = tab_order
283
+ return if ordered.empty?
284
+
285
+ current = document.active_element
286
+ index = ordered.index { |el| el == current }
287
+ target = ordered[index ? index + 1 : 0]
288
+ target&.focus
289
+ end
290
+
291
+ def tab_order
292
+ candidates = document.query_selector_all(FOCUSABLE_SELECTOR).to_a.reject do |el|
293
+ el.get_attribute("tabindex").to_s.start_with?("-") ||
294
+ el.has_attribute?("disabled") ||
295
+ el.get_attribute("type").to_s.downcase == "hidden" ||
296
+ !visible?(el)
297
+ end
298
+ positive, natural = candidates.each_with_index.partition { |el, _i| el.get_attribute("tabindex").to_i.positive? }
299
+ positive.sort_by { |el, i| [el.get_attribute("tabindex").to_i, i] }.map(&:first) + natural.map(&:first)
300
+ end
301
+
165
302
  # Capybara's app_host (set per-example) wins over default_host; falls
166
303
  # back to the host this driver was configured with. Guarded so a
167
304
  # standalone driver (no owning Capybara session) still works.
@@ -17,7 +17,24 @@ module Capybara
17
17
  end
18
18
 
19
19
  def [](name)
20
- native.get_attribute(name.to_s)
20
+ key = name.to_s
21
+ # Capybara's validation_message filter reads the constraint-validation
22
+ # DOM property, which never appears as a markup attribute.
23
+ if key == "validationMessage" && native.respond_to?(:validation_message)
24
+ return native.validation_message
25
+ end
26
+
27
+ # `checked` / `selected` reflect the live IDL property (current state),
28
+ # not the content attribute (which is the *default*) — as WebDriver's
29
+ # getAttribute does for these boolean attributes.
30
+ if key == "checked" && native.respond_to?(:checked)
31
+ return native.checked ? "true" : nil
32
+ end
33
+ if key == "selected" && native.respond_to?(:selected)
34
+ return native.selected ? "true" : nil
35
+ end
36
+
37
+ native.get_attribute(key)
21
38
  end
22
39
 
23
40
  def value
@@ -77,16 +94,56 @@ module Capybara
77
94
  end
78
95
 
79
96
  def path
97
+ # Capybara's documented placeholder: a shadow tree has no XPath.
98
+ if native.respond_to?(:get_root_node) && native.get_root_node.is_a?(::Dommy::ShadowRoot)
99
+ return "(: Shadow DOM element - no XPath :)"
100
+ end
101
+
80
102
  native.path
81
103
  end
82
104
 
83
- def style(_styles)
84
- {}
105
+ # Keyboard input without a JS engine: maintains a caret over the field's
106
+ # value and applies printable keys plus the position/modifier keys
107
+ # Capybara's non-JS send_keys specs use. Key *events* are not dispatched
108
+ # (nothing here can observe them without JavaScript).
109
+ def send_keys(*args)
110
+ # Under JavaScript, dispatch real keyboard events (keydown/keypress/
111
+ # input/keyup with browser default actions) through the dommy driver,
112
+ # so keyboard handlers (arrow navigation, Enter selection) run.
113
+ # Key chords ([:shift, "o"]) are not supported on this path.
114
+ return driver.rack_session.send_keys_to(native, *args) if driver.javascript?
115
+
116
+ return unless native.respond_to?(:value=)
117
+
118
+ state = {chars: native.value.to_s.chars, caret: native.value.to_s.length, shift: false}
119
+ args.each do |arg|
120
+ if arg.is_a?(Array)
121
+ # A chord like [:shift, 'o'] holds its modifiers only for the
122
+ # duration of the array.
123
+ held = state[:shift]
124
+ arg.each { |key| apply_key(state, key) }
125
+ state[:shift] = held
126
+ else
127
+ apply_key(state, arg)
128
+ end
129
+ end
130
+ native.focus if native.respond_to?(:focus)
131
+ native.value = state[:chars].join
132
+ end
133
+
134
+ # Computed styles for Capybara's matches_style? / style: filters,
135
+ # served by Dommy's CSS cascade (values come back in the computed
136
+ # serialization, e.g. colors as rgb()).
137
+ def style(styles)
138
+ computed = ::Dommy::Internal::CSS::Cascade.computed_style(native)
139
+ Array(styles).flatten.to_h { |name| [name.to_s, computed[name.to_s].to_s] }
85
140
  end
86
141
 
87
142
  # --- Interaction ---
88
143
 
89
144
  def click(_keys = [], **_options)
145
+ return js_click if driver.javascript?
146
+
90
147
  if link?
91
148
  click_link_node
92
149
  elsif submits?
@@ -122,6 +179,7 @@ module Capybara
122
179
  select_el = select_node
123
180
  deselect_all(select_el) unless select_el&.multiple
124
181
  native.selected = true
182
+ notify_select_changed(select_el)
125
183
  end
126
184
 
127
185
  def unselect_option
@@ -132,6 +190,15 @@ module Capybara
132
190
  native.selected = false
133
191
  end
134
192
 
193
+ # Move the (virtual) pointer over this element: :hover rules and
194
+ # `matches(":hover")` then apply to it and its ancestors. No
195
+ # mouseover/mouseout events are dispatched (nothing observes them
196
+ # without JavaScript).
197
+ def hover
198
+ native.owner_document.__internal_set_hovered_element__(native)
199
+ nil
200
+ end
201
+
135
202
  # --- Scoped queries (for `within`) ---
136
203
 
137
204
  def find_css(locator, **_options)
@@ -157,8 +224,34 @@ module Capybara
157
224
  RUBY
158
225
  end
159
226
 
227
+ # Identity matters to Capybara (e.g. the focused: filter compares a
228
+ # candidate against session.active_element). Dommy's wrapper cache hands
229
+ # out one wrapper per DOM node, so native equality is node identity.
230
+ def ==(other)
231
+ other.is_a?(Node) && native == other.native
232
+ end
233
+
160
234
  private
161
235
 
236
+ def apply_key(state, key)
237
+ case key
238
+ when String
239
+ key.each_char do |ch|
240
+ state[:chars].insert(state[:caret], state[:shift] ? ch.upcase : ch)
241
+ state[:caret] += 1
242
+ end
243
+ when :space
244
+ state[:chars].insert(state[:caret], " ")
245
+ state[:caret] += 1
246
+ when :left
247
+ state[:caret] = [state[:caret] - 1, 0].max
248
+ when :right
249
+ state[:caret] = [state[:caret] + 1, state[:chars].length].min
250
+ when :shift
251
+ state[:shift] = true
252
+ end
253
+ end
254
+
162
255
  def stale_check
163
256
  return if native.document.equal?(driver.document)
164
257
 
@@ -173,6 +266,40 @@ module Capybara
173
266
  # javascript: links are no-ops in Capybara (a policy decision); every
174
267
  # other link delegates to dommy-rack, which handles fragment / same-page
175
268
  # / blank-href semantics and raises on genuinely unsupported schemes.
269
+ # A JavaScript click: dispatch the full pointer/mouse/click sequence
270
+ # (Stimulus actions, Turbo's link/submit interception run like in a
271
+ # browser), then perform the un-prevented default action ourselves —
272
+ # link navigation, or the form submission algorithm (a cancelable
273
+ # submit event, then the real submission if nothing canceled it;
274
+ # Turbo cancels and takes over). Checkbox/radio toggling already ran
275
+ # as the click event's activation behavior.
276
+ def js_click
277
+ prevented = ::Dommy::Interaction::EventSynthesis.click(native)
278
+ unless prevented
279
+ # Link navigation and form submission are the elements' own activation
280
+ # behavior — run inside EventSynthesis.click above, routing through the
281
+ # session's navigation delegate (performed when driver.drain_js drains).
282
+ # Only the non-navigating default actions remain here.
283
+ if tag_name == "label"
284
+ click_label
285
+ elsif (details = native.closest("details"))
286
+ toggle_details(details)
287
+ end
288
+ end
289
+ driver.drain_js
290
+ nil
291
+ end
292
+
293
+ # Selecting an option through the UI fires input + change on the select
294
+ # (under JavaScript; nothing listens without it).
295
+ def notify_select_changed(select_el)
296
+ return unless driver.javascript? && select_el
297
+
298
+ ::Dommy::Interaction::EventSynthesis.input(select_el)
299
+ ::Dommy::Interaction::EventSynthesis.change(select_el)
300
+ driver.drain_js
301
+ end
302
+
176
303
  def click_link_node
177
304
  scheme = native.get_attribute("href").to_s.split(":", 2).first.to_s.downcase
178
305
  return if scheme == "javascript"
@@ -219,11 +346,28 @@ module Capybara
219
346
  # There is no submitter button in this case.
220
347
  form = single_field_form
221
348
  if input_field? && string.end_with?("\n") && form
222
- native.value = string.chomp
349
+ write_text(string.chomp)
223
350
  driver.submit_form(form, submitter: nil)
224
351
  else
352
+ write_text(string)
353
+ end
354
+ end
355
+
356
+ # Set a text field's value. Under JavaScript this types like a user:
357
+ # focus, value, then input + change events, so Stimulus/React handlers
358
+ # observe the edit; the JS-less driver keeps the bare value write
359
+ # (nothing listens).
360
+ def write_text(string)
361
+ unless driver.javascript?
225
362
  native.value = string
363
+ return
226
364
  end
365
+
366
+ ::Dommy::Interaction::EventSynthesis.focus(native)
367
+ native.value = string
368
+ ::Dommy::Interaction::EventSynthesis.input(native, string)
369
+ ::Dommy::Interaction::EventSynthesis.change(native)
370
+ driver.drain_js
227
371
  end
228
372
 
229
373
  def single_field_form
@@ -248,25 +392,15 @@ module Capybara
248
392
  value && !value.empty?
249
393
  end
250
394
 
251
- # Reflect checked state on the attribute so node[:checked] and form
252
- # submission both observe it (Dommy's `checked=` only sets the property).
395
+ # Toggle through the control's native activation behavior (which fires
396
+ # input/change and, for a radio, maintains its group) — only when it
397
+ # isn't already in the requested state, matching a real click.
253
398
  def set_checkbox(value)
254
- if value
255
- native.set_attribute("checked", "checked")
256
- else
257
- native.remove_attribute("checked")
258
- end
399
+ native.click if checked? != !!value
259
400
  end
260
401
 
261
402
  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")
403
+ native.click unless checked?
270
404
  end
271
405
 
272
406
  def set_file(value)
@@ -8,3 +8,10 @@ require "capybara/dommy"
8
8
  Capybara.register_driver(:dommy) do |app|
9
9
  Capybara::Dommy::Driver.new(app)
10
10
  end
11
+
12
+ # The JavaScript-enabled variant (`driven_by :dommy_js`): pages run their real
13
+ # Turbo/Stimulus/React bundles in the embedded QuickJS runtime, no browser
14
+ # process. Requires dommy-js-quickjs.
15
+ Capybara.register_driver(:dommy_js) do |app|
16
+ Capybara::Dommy::Driver.new(app, javascript: true)
17
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Dommy
5
- VERSION = "0.8.0"
5
+ VERSION = "0.10.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capybara-dommy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - takahashim
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-05-31 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: capybara
@@ -29,28 +29,28 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: 0.8.0
32
+ version: 0.10.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.8.0
39
+ version: 0.10.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: dommy-rack
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 0.8.0
46
+ version: 0.10.0
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: 0.8.0
53
+ version: 0.10.0
54
54
  description: |
55
55
  capybara-dommy is a Capybara driver backed by Dommy and dommy-rack. It drives
56
56
  Rack/Rails apps through the Capybara DSL without a real browser or JavaScript,
@@ -94,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  requirements: []
97
- rubygems_version: 3.6.2
97
+ rubygems_version: 4.0.10
98
98
  specification_version: 4
99
99
  summary: A Dommy-backed Capybara driver
100
100
  test_files: []