capybara-dommy 0.9.0 → 0.11.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 +17 -0
- data/README.md +12 -3
- data/lib/capybara/dommy/configuration.rb +2 -1
- data/lib/capybara/dommy/driver.rb +79 -8
- data/lib/capybara/dommy/modal.rb +114 -0
- data/lib/capybara/dommy/node.rb +61 -1
- data/lib/capybara/dommy/rails.rb +7 -0
- data/lib/capybara/dommy/version.rb +1 -1
- data/lib/capybara/dommy.rb +1 -0
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6479bfca855b3003e8b6b7876981e9b59b5a2fa870b96b2528e387966986d90
|
|
4
|
+
data.tar.gz: 54d05d18a6bd90fd2a795543130c54e8bc62d438a21c8a14f0fd06f83f5918d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 588846fe1ca9d36119c237d71349d1af04fe5082fc73d64dbb48f8b7cc8b097b607d25bbb1af888ec58157547bd03793947faacf22106df80dc96d2a17a767bf
|
|
7
|
+
data.tar.gz: 48594055d5a1a63e80634fee2bc57c566ce8f33363c7163c58c6dc0b9df9aa6cfd612a9feec5321f3d326baa63a3e715072f6c50f3f75a5676f2d659d6975ce6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.0 — 2026-09-11
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Native dialog helpers in JS mode.** `accept_confirm`, `dismiss_confirm`, `accept_alert`, `accept_prompt` (with `with:`) and `dismiss_prompt` work against the page's real `confirm` / `alert` / `prompt`, including a `text:` string or Regexp to say which dialog is expected. Nested helper blocks answer the dialogs in the order the page opens them, and a dialog nobody is waiting for falls back to the headless default, so a stray `confirm` cannot silently accept. A block that opens no matching dialog raises `Capybara::ModalNotFound`, naming what turned up instead.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- Turbo-driven navigation in JS apps is followed, so `have_current_path` and the matchers after a Turbo visit see the page the app actually moved to.
|
|
10
|
+
- `current_url` advances the virtual clock like the other queries do, so a `have_current_path` poll converges when the navigation settles in a scheduled task rather than a microtask.
|
|
11
|
+
|
|
12
|
+
## 0.10.0 — 2026-07-13
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **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`.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- 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.
|
|
19
|
+
|
|
3
20
|
## 0.9.0 — 2026-06-22
|
|
4
21
|
|
|
5
22
|
Versioned in lockstep with [`dommy`](https://github.com/takahashim/dommy) 0.9.0.
|
data/README.md
CHANGED
|
@@ -19,15 +19,24 @@ speed and simplicity of a Rack-style driver.
|
|
|
19
19
|
- Preserves session state such as cookies, follows redirects by default, and
|
|
20
20
|
supports browser-like back, forward, and refresh navigation.
|
|
21
21
|
- Implements HTML-level visibility through `dommy-rack`.
|
|
22
|
+
- In JavaScript-enabled mode, supports Capybara's `accept_alert`,
|
|
23
|
+
`accept_confirm`, `dismiss_confirm`, `accept_prompt`, and `dismiss_prompt`
|
|
24
|
+
helpers with deterministic responses.
|
|
22
25
|
- Provides a Rails convenience require for `driven_by :dommy`.
|
|
23
26
|
|
|
24
27
|
## Limitations
|
|
25
28
|
|
|
26
29
|
`capybara-dommy` is intentionally not a browser automation driver.
|
|
27
30
|
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
- The default driver does not execute JavaScript. Use the JavaScript-enabled
|
|
32
|
+
variant for embedded QuickJS execution.
|
|
33
|
+
- Screenshots and browser windows are not supported. Native alerts, confirms,
|
|
34
|
+
and prompts are supported by the JavaScript-enabled variant through
|
|
35
|
+
Capybara's modal helpers.
|
|
36
|
+
- Constructable stylesheets can be built (`new CSSStyleSheet()`), but
|
|
37
|
+
`adoptedStyleSheets` is not implemented — assigning one applies no style.
|
|
38
|
+
Component libraries that feature-detect (`'adoptedStyleSheets' in
|
|
39
|
+
Document.prototype`) fall back to injecting a `<style>`, which is handled.
|
|
31
40
|
- CSS layout is not calculated. Visibility is based on HTML-level rules such as
|
|
32
41
|
`hidden`, `type="hidden"`, and inline `display: none` handling provided by
|
|
33
42
|
`dommy-rack`.
|
|
@@ -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
|
|
|
@@ -4,9 +4,10 @@ module Capybara
|
|
|
4
4
|
module Dommy
|
|
5
5
|
# A Capybara driver backed by Dommy::Rack::Session. Implements the
|
|
6
6
|
# navigation / query / reset! parts of the Capybara::Driver::Base contract;
|
|
7
|
-
# element interaction lives in Capybara::Dommy::Node.
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# element interaction lives in Capybara::Dommy::Node. The JS-enabled mode
|
|
8
|
+
# additionally supplies deterministic native dialog responses for Capybara's
|
|
9
|
+
# alert/confirm/prompt helpers. Screenshot and window methods remain with
|
|
10
|
+
# Driver::Base (which raises Capybara::NotSupportedByDriverError).
|
|
10
11
|
class Driver < Capybara::Driver::Base
|
|
11
12
|
VISIBILITY_MODES = %i[all html none].freeze
|
|
12
13
|
|
|
@@ -28,10 +29,12 @@ module Capybara
|
|
|
28
29
|
default_host: nil,
|
|
29
30
|
follow_redirects: nil,
|
|
30
31
|
max_redirects: nil,
|
|
31
|
-
visibility: nil
|
|
32
|
+
visibility: nil,
|
|
33
|
+
javascript: nil)
|
|
32
34
|
super()
|
|
33
35
|
config = Capybara::Dommy.configuration
|
|
34
36
|
@app = app
|
|
37
|
+
@javascript = javascript.nil? ? config.javascript : javascript
|
|
35
38
|
@visibility = visibility || config.visibility
|
|
36
39
|
unless VISIBILITY_MODES.include?(@visibility)
|
|
37
40
|
raise ArgumentError,
|
|
@@ -46,6 +49,24 @@ module Capybara
|
|
|
46
49
|
# hosts (e.g. app_host / multi-server specs), so don't enforce origin.
|
|
47
50
|
enforce_same_origin: false
|
|
48
51
|
}
|
|
52
|
+
@session_options[:javascript] = true if @javascript
|
|
53
|
+
# A JS session needs the virtual clock pumped inside Capybara's
|
|
54
|
+
# synchronize loop, so waiting expectations converge on timer/fetch
|
|
55
|
+
# driven updates. A host-installed pump (the documented seam) wins.
|
|
56
|
+
@time_pump ||= -> { @rack_session&.advance_time(16) } if @javascript
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Whether this driver runs page JavaScript (`javascript: true`, backed by
|
|
60
|
+
# a `Dommy::Rack::Session.new(app, javascript: true)`). Node interactions
|
|
61
|
+
# then dispatch real DOM events (Turbo/Stimulus handlers run) instead of
|
|
62
|
+
# the HTML-only fast paths.
|
|
63
|
+
def javascript? = @javascript
|
|
64
|
+
|
|
65
|
+
# Drain the JS runtime after an interaction's events (promise reactions
|
|
66
|
+
# settle before the next Capybara step). No-op without JavaScript.
|
|
67
|
+
def drain_js
|
|
68
|
+
rack_session.after_interaction if @javascript
|
|
69
|
+
nil
|
|
49
70
|
end
|
|
50
71
|
|
|
51
72
|
# The dommy-rack session. Named `rack_session` to avoid colliding with
|
|
@@ -55,6 +76,7 @@ module Capybara
|
|
|
55
76
|
def rack_session
|
|
56
77
|
host = effective_host
|
|
57
78
|
if @rack_session.nil? || @rack_session_host != host
|
|
79
|
+
@rack_session&.dispose
|
|
58
80
|
@rack_session = ::Dommy::Rack::Session.new(@app, **@session_options.merge(default_host: host))
|
|
59
81
|
@rack_session_host = host
|
|
60
82
|
end
|
|
@@ -73,6 +95,12 @@ module Capybara
|
|
|
73
95
|
end
|
|
74
96
|
|
|
75
97
|
def current_url
|
|
98
|
+
# Capybara polls current_url for have_current_path. In JS mode that poll
|
|
99
|
+
# must advance the virtual clock too: Turbo/fetch continuations often
|
|
100
|
+
# settle in a scheduled task rather than the interaction's microtask
|
|
101
|
+
# drain. The Rack session's History hook then reflects pushState in its
|
|
102
|
+
# current URL.
|
|
103
|
+
pump!
|
|
76
104
|
rack_session.current_url.to_s
|
|
77
105
|
end
|
|
78
106
|
|
|
@@ -179,6 +207,7 @@ module Capybara
|
|
|
179
207
|
# --- Lifecycle ---
|
|
180
208
|
|
|
181
209
|
def reset!
|
|
210
|
+
@rack_session&.dispose
|
|
182
211
|
@rack_session = nil
|
|
183
212
|
@frame_stack = []
|
|
184
213
|
end
|
|
@@ -200,18 +229,40 @@ module Capybara
|
|
|
200
229
|
# When raise_on_unsupported_js is false these become no-ops, so tests
|
|
201
230
|
# that incidentally call them don't fail.
|
|
202
231
|
|
|
203
|
-
def execute_script(
|
|
204
|
-
unsupported_js!("execute_script")
|
|
232
|
+
def execute_script(script, *args)
|
|
233
|
+
return unsupported_js!("execute_script") unless @javascript
|
|
234
|
+
raise ArgumentError, "script arguments are not supported" unless args.empty?
|
|
235
|
+
|
|
236
|
+
rack_session.execute_script(script)
|
|
205
237
|
end
|
|
206
238
|
|
|
207
|
-
def evaluate_script(
|
|
208
|
-
unsupported_js!("evaluate_script")
|
|
239
|
+
def evaluate_script(script, *args)
|
|
240
|
+
return unsupported_js!("evaluate_script") unless @javascript
|
|
241
|
+
raise ArgumentError, "script arguments are not supported" unless args.empty?
|
|
242
|
+
|
|
243
|
+
rack_session.evaluate_script(script)
|
|
209
244
|
end
|
|
210
245
|
|
|
211
246
|
def evaluate_async_script(_script, *_args)
|
|
212
247
|
unsupported_js!("evaluate_async_script")
|
|
213
248
|
end
|
|
214
249
|
|
|
250
|
+
# --- Native dialogs ---
|
|
251
|
+
#
|
|
252
|
+
# A native dialog is synchronous in Dommy, so installing the expected
|
|
253
|
+
# answer before the triggering block runs is sufficient. What is expected,
|
|
254
|
+
# what it answers and what it saw instead live in ModalExpectation; the
|
|
255
|
+
# stack they sit on, which is the session's dialog handler while a helper
|
|
256
|
+
# block is running, is ModalStack.
|
|
257
|
+
|
|
258
|
+
def accept_modal(type, **options, &block)
|
|
259
|
+
respond_to_modal(type, accept: true, **options, &block)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def dismiss_modal(type, **options, &block)
|
|
263
|
+
respond_to_modal(type, accept: false, **options, &block)
|
|
264
|
+
end
|
|
265
|
+
|
|
215
266
|
# Visibility decision used by Node#visible?. :all / :none treat every
|
|
216
267
|
# element as visible; :html defers to dommy-rack's HTML-level check.
|
|
217
268
|
def visible?(element)
|
|
@@ -295,6 +346,26 @@ module Capybara
|
|
|
295
346
|
raise Capybara::NotSupportedByDriverError,
|
|
296
347
|
"capybara-dommy does not support JavaScript (#{name})"
|
|
297
348
|
end
|
|
349
|
+
|
|
350
|
+
def respond_to_modal(type, accept:, text: nil, with: nil, **_options)
|
|
351
|
+
expectation = ModalExpectation.new(type: type, accept: accept, text: text, with: with)
|
|
352
|
+
modals.push(expectation)
|
|
353
|
+
rack_session.dialog_handler = modals
|
|
354
|
+
yield if block_given?
|
|
355
|
+
|
|
356
|
+
raise Capybara::ModalNotFound, expectation.not_found_message unless expectation.answered?
|
|
357
|
+
|
|
358
|
+
expectation.message
|
|
359
|
+
ensure
|
|
360
|
+
if expectation
|
|
361
|
+
modals.delete(expectation)
|
|
362
|
+
rack_session.dialog_handler = nil if modals.empty? && @rack_session
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def modals
|
|
367
|
+
@modals ||= ModalStack.new
|
|
368
|
+
end
|
|
298
369
|
end
|
|
299
370
|
end
|
|
300
371
|
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Dommy
|
|
5
|
+
# What one `accept_modal` / `dismiss_modal` block is waiting for, and what
|
|
6
|
+
# it saw. A native dialog is synchronous in Dommy, so the expectation is
|
|
7
|
+
# installed before the triggering block runs and answers the dialog the
|
|
8
|
+
# block opens.
|
|
9
|
+
class ModalExpectation
|
|
10
|
+
attr_reader :type, :text
|
|
11
|
+
# The message of the dialog this expectation answered — nil while it is
|
|
12
|
+
# still waiting, which is what makes it "not found" at the end of the
|
|
13
|
+
# block.
|
|
14
|
+
attr_reader :message
|
|
15
|
+
|
|
16
|
+
def initialize(type:, accept:, text: nil, with: nil)
|
|
17
|
+
@type = type.to_sym
|
|
18
|
+
@accept = accept
|
|
19
|
+
@text = text
|
|
20
|
+
@with = with
|
|
21
|
+
@message = nil
|
|
22
|
+
@actual = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def matches?(type, message)
|
|
26
|
+
@type == type && text_matches?(message)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def answered? = !@message.nil?
|
|
30
|
+
|
|
31
|
+
# Record that this dialog is the one, and answer it: a confirm with the
|
|
32
|
+
# accept/dismiss decision, a prompt with the text to type (`with:`, else
|
|
33
|
+
# what the page offered) or nil when dismissed. An alert has no answer.
|
|
34
|
+
def answer(message, default_value)
|
|
35
|
+
@message = message
|
|
36
|
+
case @type
|
|
37
|
+
when :confirm then @accept
|
|
38
|
+
when :prompt then @accept ? (@with || default_value) : nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# A dialog this expectation did not want. Remembered for the error at the
|
|
43
|
+
# end of the block, which says what turned up instead.
|
|
44
|
+
def saw(type, message)
|
|
45
|
+
@actual = {type: type, message: message}
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def not_found_message
|
|
50
|
+
if @actual
|
|
51
|
+
"Unable to find #{@type} dialog with #{@text.inspect} - found " \
|
|
52
|
+
"#{@actual[:type]} dialog with #{@actual[:message].inspect} instead."
|
|
53
|
+
else
|
|
54
|
+
"Unable to find #{@type} dialog#{@text ? " with #{@text.inspect}" : ""}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def text_matches?(message)
|
|
61
|
+
return true if @text.nil?
|
|
62
|
+
|
|
63
|
+
pattern = @text.is_a?(Regexp) ? @text : Regexp.new(Regexp.escape(@text.to_s))
|
|
64
|
+
pattern.match?(message)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The expectations currently in scope, innermost first, as the dialog
|
|
69
|
+
# handler a Dommy Window asks (it calls `#call`).
|
|
70
|
+
#
|
|
71
|
+
# A stack, because Capybara expresses nested confirms as nested helper
|
|
72
|
+
# blocks while the page opens them sequentially in one JS call stack:
|
|
73
|
+
# consuming the inner expectation exposes the outer answer to the next
|
|
74
|
+
# confirm immediately.
|
|
75
|
+
class ModalStack
|
|
76
|
+
def initialize
|
|
77
|
+
@expectations = []
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def empty? = @expectations.empty?
|
|
81
|
+
|
|
82
|
+
def push(expectation)
|
|
83
|
+
@expectations << expectation
|
|
84
|
+
expectation
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# By identity: two helper blocks with the same arguments build EQUAL
|
|
88
|
+
# expectations, and removing by value would drop the caller's alongside
|
|
89
|
+
# this one.
|
|
90
|
+
def delete(expectation)
|
|
91
|
+
@expectations.delete_if { |e| e.equal?(expectation) }
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# The dialog handler protocol: answer the dialog, or return
|
|
96
|
+
# DIALOG_UNANSWERED to leave it to the Window's headless default. An
|
|
97
|
+
# expectation that does not want this dialog remembers it (so the block
|
|
98
|
+
# can say what turned up instead) and declines rather than answering.
|
|
99
|
+
def call(type, message, default_value)
|
|
100
|
+
expectation = @expectations.last
|
|
101
|
+
return ::Dommy::Window::DIALOG_UNANSWERED if expectation.nil?
|
|
102
|
+
|
|
103
|
+
unless expectation.matches?(type, message)
|
|
104
|
+
expectation.saw(type, message)
|
|
105
|
+
return ::Dommy::Window::DIALOG_UNANSWERED
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
answer = expectation.answer(message, default_value)
|
|
109
|
+
delete(expectation)
|
|
110
|
+
answer
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
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
|
data/lib/capybara/dommy.rb
CHANGED
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.11.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.11.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.11.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.11.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.11.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,
|
|
@@ -69,6 +69,7 @@ files:
|
|
|
69
69
|
- lib/capybara/dommy/configuration.rb
|
|
70
70
|
- lib/capybara/dommy/driver.rb
|
|
71
71
|
- lib/capybara/dommy/errors.rb
|
|
72
|
+
- lib/capybara/dommy/modal.rb
|
|
72
73
|
- lib/capybara/dommy/node.rb
|
|
73
74
|
- lib/capybara/dommy/rails.rb
|
|
74
75
|
- lib/capybara/dommy/text_extractor.rb
|