capybara-dommy 0.9.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 +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/capybara/dommy/configuration.rb +2 -1
- data/lib/capybara/dommy/driver.rb +33 -5
- data/lib/capybara/dommy/node.rb +61 -1
- data/lib/capybara/dommy/rails.rb +7 -0
- data/lib/capybara/dommy/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ce693099e9f737e726914acb5cf9129ad87a96978209f5b1d9cc27132925b652
|
|
4
|
+
data.tar.gz: a69e70e2f0878025fd8d8c5a9411aaa990d6ebd7f812bd623cd3f27c4614e3bd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f351cf3f7989d3bfa1a984dc425cf895790350ed699431f1e6fb8a7d6708c8b1abc05ab20df20f129839a0c926074d6b64774b9885a97b593ad41b8354b0b2a
|
|
7
|
+
data.tar.gz: 4246e91a681b995f8cc2fb2633eac92a1d6e5b1fc8dad67954d099124cc999055575597a59a94965ea6d37b201ed04d9ec2271a6dbd0146d7eede6b634a416cc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
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
|
+
|
|
3
11
|
## 0.9.0 — 2026-06-22
|
|
4
12
|
|
|
5
13
|
Versioned in lockstep with [`dommy`](https://github.com/takahashim/dommy) 0.9.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
|
|
|
@@ -28,10 +28,12 @@ module Capybara
|
|
|
28
28
|
default_host: nil,
|
|
29
29
|
follow_redirects: nil,
|
|
30
30
|
max_redirects: nil,
|
|
31
|
-
visibility: nil
|
|
31
|
+
visibility: nil,
|
|
32
|
+
javascript: nil)
|
|
32
33
|
super()
|
|
33
34
|
config = Capybara::Dommy.configuration
|
|
34
35
|
@app = app
|
|
36
|
+
@javascript = javascript.nil? ? config.javascript : javascript
|
|
35
37
|
@visibility = visibility || config.visibility
|
|
36
38
|
unless VISIBILITY_MODES.include?(@visibility)
|
|
37
39
|
raise ArgumentError,
|
|
@@ -46,6 +48,24 @@ module Capybara
|
|
|
46
48
|
# hosts (e.g. app_host / multi-server specs), so don't enforce origin.
|
|
47
49
|
enforce_same_origin: false
|
|
48
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
|
|
49
69
|
end
|
|
50
70
|
|
|
51
71
|
# The dommy-rack session. Named `rack_session` to avoid colliding with
|
|
@@ -55,6 +75,7 @@ module Capybara
|
|
|
55
75
|
def rack_session
|
|
56
76
|
host = effective_host
|
|
57
77
|
if @rack_session.nil? || @rack_session_host != host
|
|
78
|
+
@rack_session&.dispose
|
|
58
79
|
@rack_session = ::Dommy::Rack::Session.new(@app, **@session_options.merge(default_host: host))
|
|
59
80
|
@rack_session_host = host
|
|
60
81
|
end
|
|
@@ -179,6 +200,7 @@ module Capybara
|
|
|
179
200
|
# --- Lifecycle ---
|
|
180
201
|
|
|
181
202
|
def reset!
|
|
203
|
+
@rack_session&.dispose
|
|
182
204
|
@rack_session = nil
|
|
183
205
|
@frame_stack = []
|
|
184
206
|
end
|
|
@@ -200,12 +222,18 @@ module Capybara
|
|
|
200
222
|
# When raise_on_unsupported_js is false these become no-ops, so tests
|
|
201
223
|
# that incidentally call them don't fail.
|
|
202
224
|
|
|
203
|
-
def execute_script(
|
|
204
|
-
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)
|
|
205
230
|
end
|
|
206
231
|
|
|
207
|
-
def evaluate_script(
|
|
208
|
-
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)
|
|
209
237
|
end
|
|
210
238
|
|
|
211
239
|
def evaluate_async_script(_script, *_args)
|
data/lib/capybara/dommy/node.rb
CHANGED
|
@@ -107,6 +107,12 @@ module Capybara
|
|
|
107
107
|
# Capybara's non-JS send_keys specs use. Key *events* are not dispatched
|
|
108
108
|
# (nothing here can observe them without JavaScript).
|
|
109
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
|
+
|
|
110
116
|
return unless native.respond_to?(:value=)
|
|
111
117
|
|
|
112
118
|
state = {chars: native.value.to_s.chars, caret: native.value.to_s.length, shift: false}
|
|
@@ -136,6 +142,8 @@ module Capybara
|
|
|
136
142
|
# --- Interaction ---
|
|
137
143
|
|
|
138
144
|
def click(_keys = [], **_options)
|
|
145
|
+
return js_click if driver.javascript?
|
|
146
|
+
|
|
139
147
|
if link?
|
|
140
148
|
click_link_node
|
|
141
149
|
elsif submits?
|
|
@@ -171,6 +179,7 @@ module Capybara
|
|
|
171
179
|
select_el = select_node
|
|
172
180
|
deselect_all(select_el) unless select_el&.multiple
|
|
173
181
|
native.selected = true
|
|
182
|
+
notify_select_changed(select_el)
|
|
174
183
|
end
|
|
175
184
|
|
|
176
185
|
def unselect_option
|
|
@@ -257,6 +266,40 @@ module Capybara
|
|
|
257
266
|
# javascript: links are no-ops in Capybara (a policy decision); every
|
|
258
267
|
# other link delegates to dommy-rack, which handles fragment / same-page
|
|
259
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
|
+
|
|
260
303
|
def click_link_node
|
|
261
304
|
scheme = native.get_attribute("href").to_s.split(":", 2).first.to_s.downcase
|
|
262
305
|
return if scheme == "javascript"
|
|
@@ -303,11 +346,28 @@ module Capybara
|
|
|
303
346
|
# There is no submitter button in this case.
|
|
304
347
|
form = single_field_form
|
|
305
348
|
if input_field? && string.end_with?("\n") && form
|
|
306
|
-
|
|
349
|
+
write_text(string.chomp)
|
|
307
350
|
driver.submit_form(form, submitter: nil)
|
|
308
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?
|
|
309
362
|
native.value = string
|
|
363
|
+
return
|
|
310
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
|
|
311
371
|
end
|
|
312
372
|
|
|
313
373
|
def single_field_form
|
data/lib/capybara/dommy/rails.rb
CHANGED
|
@@ -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
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: capybara-dommy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- takahashim
|
|
@@ -29,28 +29,28 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 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.
|
|
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.
|
|
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.
|
|
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:
|
|
97
|
+
rubygems_version: 4.0.10
|
|
98
98
|
specification_version: 4
|
|
99
99
|
summary: A Dommy-backed Capybara driver
|
|
100
100
|
test_files: []
|