Class: Capybara::Simulated::Node

Inherits:
Driver::Node
  • Object
show all
Includes:
WhitespaceNormalizer
Defined in:
lib/capybara/simulated/node.rb

Constant Summary

Constants included from WhitespaceNormalizer

WhitespaceNormalizer::BREAKING_SPACES, WhitespaceNormalizer::EMPTY_LINES, WhitespaceNormalizer::LEADING_SPACES, WhitespaceNormalizer::LEFT_TO_RIGHT_MARK, WhitespaceNormalizer::LINE_SEPERATOR, WhitespaceNormalizer::NON_BREAKING_SPACE, WhitespaceNormalizer::PARAGRAPH_SEPERATOR, WhitespaceNormalizer::REMOVED_CHARACTERS, WhitespaceNormalizer::RIGHT_TO_LEFT_MARK, WhitespaceNormalizer::SQUEEZED_SPACES, WhitespaceNormalizer::TRAILING_SPACES, WhitespaceNormalizer::ZERO_WIDTH_SPACE

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from WhitespaceNormalizer

#normalize_spacing, #normalize_visible_spacing

Constructor Details

#initialize(driver, handle) ⇒ Node

Returns a new instance of Node.



12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/capybara/simulated/node.rb', line 12

def initialize(driver, handle)
  super(driver, self)
  @handle_id    = handle
  # Pin the Browser at construction so nodes from one window
  # stay valid even after `switch_to_window` flips to another.
  @browser      = driver.current_browser
  @initial_node = @browser.lookup_node(handle)
  @context_gen  = @browser.context_gen
  # The frame realm this handle belongs to (nil = main document). Handle
  # integers are per-realm, so a main-document node and a frame node can
  # share an id — `==` includes the realm to keep them distinct.
  @realm_id     = @browser.current_realm_id
end

Instance Attribute Details

#context_genObject (readonly)

Returns the value of attribute context_gen.



26
27
28
# File 'lib/capybara/simulated/node.rb', line 26

def context_gen
  @context_gen
end

#handle_idObject (readonly)

Returns the value of attribute handle_id.



26
27
28
# File 'lib/capybara/simulated/node.rb', line 26

def handle_id
  @handle_id
end

#realm_idObject (readonly)

Returns the value of attribute realm_id.



26
27
28
# File 'lib/capybara/simulated/node.rb', line 26

def realm_id
  @realm_id
end

Instance Method Details

#==(other) ⇒ Object

Both handle_id and context_gen are part of identity: after a cross-page reload the new element can land on the same handle id (JS-side counter resets per ctx) but a different generation. Capybara's synchronize uses old_base == @base to decide whether reload made progress, so id-only equality would mark a successful reload as a no-op and raise the original error.



241
242
243
244
245
246
# File 'lib/capybara/simulated/node.rb', line 241

def ==(other)
  other.is_a?(Node) &&
    other.handle_id == @handle_id &&
    other.context_gen == @context_gen &&
    other.realm_id == @realm_id
end

#[](name) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/capybara/simulated/node.rb', line 77

def [](name)
  # Tick the virtual clock so Capybara's helpers that poll an
  # attribute in a tight `sleep(0.1) until` loop (e.g. Avo's
  # `wait_for_body_class_missing`) actually see the JS chain
  # progress. Without this the body class never transitions
  # because we only advance time during `find`, and the helper
  # caches the body node before its poll loop.
  #
  # Gated behind the same wall-clock throttle `find_css` uses
  # (`timer_wait_elapsed?`): on framework-runloop pages that keep
  # `@timers_active` permanently true, an ungated tick here would
  # drain timers on every per-result attribute filter / poll
  # iteration. The throttle still ticks the first time and once
  # per ~50 ms window thereafter, so poll loops that wait for a
  # timer to fire between reads still make progress.
  #
  # `tick_real_time` also drains the Worker / EventSource / hijacked
  # -fetch outboxes, so an attribute poll whose value is delivered
  # only by one of those channels (with no active timer) would
  # otherwise never see it. `async_io_pending?` is an O(1) gate
  # (three empty? checks) that lets those cases drain without paying
  # an unconditional tick on timer-driven runloop pages.
  browser.tick_real_time if browser.timer_wait_elapsed? || browser.async_io_pending?
  check_stale
  browser.attr(handle_id, name.to_s)
end

#all_textObject

Tick the virtual clock unconditionally on the text path. Unlike Node#[] (attribute reads), the text readers are NOT preceded by a find_css that ticks under the wall throttle: have_text / assert_text polls .text directly against an already-found, cached scope node, so the find loop short-circuits without re-ticking. Gating these behind timer_wait_elapsed? therefore stalls a pure text-poll loop's virtual clock and scheduled setTimeouts never fire (smoke_spec virtual-clock contract). They run ~once per poll-scope (not once per matched result like the attribute filters the audit targeted), so they are not the O(N) hot path and are safe to tick every call.



39
40
41
42
43
# File 'lib/capybara/simulated/node.rb', line 39

def all_text
  browser.tick_real_time
  check_stale
  normalize_spacing(browser.all_text(handle_id))
end

#checked?Boolean

Returns:

  • (Boolean)


219
# File 'lib/capybara/simulated/node.rb', line 219

def checked?         = !!self['checked']

#click(keys = [], **opts) ⇒ Object



104
105
106
107
# File 'lib/capybara/simulated/node.rb', line 104

def click(keys = [], **opts)
  check_stale
  browser.click(handle_id, keys, **opts)
end

#disabled?Boolean

Returns:

  • (Boolean)


217
# File 'lib/capybara/simulated/node.rb', line 217

def disabled?        = browser.disabled?(handle_id)

#double_click(keys = [], **opts) ⇒ Object



114
115
116
117
# File 'lib/capybara/simulated/node.rb', line 114

def double_click(keys = [], **opts)
  check_stale
  browser.double_click(handle_id, keys, **opts)
end

#drag_to(target_node, **opts) ⇒ Object



177
178
179
180
181
182
183
# File 'lib/capybara/simulated/node.rb', line 177

def drag_to(target_node, **opts)
  check_stale
  target_node.check_stale if target_node.respond_to?(:check_stale)
  target_handle = target_node.respond_to?(:handle_id) ? target_node.handle_id : target_node.native
  browser.drag_to(handle_id, target_handle, **opts)
  self
end

#drop(*args) ⇒ Object



171
172
173
174
175
# File 'lib/capybara/simulated/node.rb', line 171

def drop(*args)
  check_stale
  browser.drop(handle_id, args)
  true
end

#find_css(query) ⇒ Object



208
209
210
# File 'lib/capybara/simulated/node.rb', line 208

def find_css(query)
  browser.find_css(query, handle_id).map {|id| self.class.new(driver, id) }
end

#find_xpath(query) ⇒ Object



204
205
206
# File 'lib/capybara/simulated/node.rb', line 204

def find_xpath(query)
  browser.find_xpath(query, handle_id).map {|id| self.class.new(driver, id) }
end

#hover(**_opts) ⇒ Object



119
120
121
122
123
# File 'lib/capybara/simulated/node.rb', line 119

def hover(**_opts)
  check_stale
  browser.hover(handle_id)
  self
end

#inner_htmlObject

Convenience accessors mirroring real browser nodes — Discourse tests reach for .native.inner_html (e.g. reviewables XSS checks); without this method .native (= self) raised NoMethodError.



67
68
69
70
# File 'lib/capybara/simulated/node.rb', line 67

def inner_html
  check_stale
  browser.inner_html(handle_id)
end

#obscured?Boolean

Returns:

  • (Boolean)


221
222
223
224
# File 'lib/capybara/simulated/node.rb', line 221

def obscured?(*)
  check_stale
  browser.obscured?(handle_id)
end

#outer_htmlObject



72
73
74
75
# File 'lib/capybara/simulated/node.rb', line 72

def outer_html
  check_stale
  browser.outer_html(handle_id)
end

#pathObject



230
231
232
233
# File 'lib/capybara/simulated/node.rb', line 230

def path
  check_stale
  browser.node_path(handle_id)
end

#readonly?Boolean

Returns:

  • (Boolean)


220
# File 'lib/capybara/simulated/node.rb', line 220

def readonly?        = !!self['readonly']

#rectObject

Capybara's standard rect API. No layout engine — but The element's coarse border-box (viewport-relative), from the layout engine — backs the spatial selectors (:above/:below/:near) and coordinate drag. Deterministic per layout generation (memoised; only a DOM/style mutation changes it), so Discourse's wait_for_animation — which polls element.rect[:x] twice and waits for it to stabilise — still settles immediately when nothing is animating.



155
156
157
158
# File 'lib/capybara/simulated/node.rb', line 155

def rect
  check_stale
  browser.rect(handle_id)
end

#right_click(keys = [], **opts) ⇒ Object



109
110
111
112
# File 'lib/capybara/simulated/node.rb', line 109

def right_click(keys = [], **opts)
  check_stale
  browser.right_click(handle_id, keys, **opts)
end

#scroll_by(dx, dy) ⇒ Object

Capybara's Element#scroll_to(:current, offset: [dx, dy]) calls this after the position handling above — a relative scroll from wherever the element is now.



143
144
145
146
147
# File 'lib/capybara/simulated/node.rb', line 143

def scroll_by(dx, dy)
  check_stale
  browser.scroll_by(handle_id, dx, dy)
  self
end

#scroll_to(target = nil, arg = nil, coords = nil) ⇒ Object

Capybara's driver scroll_to contract (from Element#scroll_to): a target element + align symbol, a position symbol (:top/:bottom/:center), or an [x, y] coordinate pair. self is the element it was called on — the document root for a session-level scroll (routed via find('/html')), else an element. Drives a real scroll offset in the layout engine.



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/capybara/simulated/node.rb', line 129

def scroll_to(target = nil, arg = nil, coords = nil)
  check_stale
  if target
    browser.scroll_to(handle_id, target.handle_id, arg)
  elsif coords
    browser.scroll_to(handle_id, nil, nil, coords[0], coords[1])
  elsif arg
    browser.scroll_to(handle_id, nil, arg)
  end
  self
end

#select_optionObject



189
190
191
192
# File 'lib/capybara/simulated/node.rb', line 189

def select_option
  check_stale
  browser.select_option(handle_id)
end

#selected?Boolean

Returns:

  • (Boolean)


218
# File 'lib/capybara/simulated/node.rb', line 218

def selected?        = browser.option_selected?(handle_id)

#send_keys(*keys) ⇒ Object



160
161
162
163
164
# File 'lib/capybara/simulated/node.rb', line 160

def send_keys(*keys)
  check_stale
  browser.send_keys(handle_id, keys)
  true
end

#set(value, **_) ⇒ Object



184
185
186
187
# File 'lib/capybara/simulated/node.rb', line 184

def set(value, **_)
  check_stale
  browser.set_value_with_events(handle_id, value)
end

#shadow_rootObject



212
213
214
215
216
# File 'lib/capybara/simulated/node.rb', line 212

def shadow_root
  check_stale
  h = browser.shadow_root_handle(handle_id)
  h && self.class.new(driver, h)
end

#style(names = []) ⇒ Object



226
227
228
229
# File 'lib/capybara/simulated/node.rb', line 226

def style(names = [])
  check_stale
  browser.computed_style(handle_id, Array(names))
end

#submit(*_) ⇒ Object



199
200
201
202
# File 'lib/capybara/simulated/node.rb', line 199

def submit(*_)
  check_stale
  browser.submit_form(handle_id)
end

#synchronizeObject



225
# File 'lib/capybara/simulated/node.rb', line 225

def synchronize(*)   = yield

#tag_nameObject



61
# File 'lib/capybara/simulated/node.rb', line 61

def tag_name = browser.tag_name(handle_id)

#trigger(event) ⇒ Object



166
167
168
169
170
# File 'lib/capybara/simulated/node.rb', line 166

def trigger(event)
  check_stale
  browser.dispatch_event(handle_id, event.to_s)
  true
end

#unselect_optionObject



194
195
196
197
# File 'lib/capybara/simulated/node.rb', line 194

def unselect_option
  check_stale
  browser.unselect_option(handle_id)
end

#valueObject



51
52
53
54
# File 'lib/capybara/simulated/node.rb', line 51

def value
  check_stale
  browser.value(handle_id)
end

#visible?Boolean

Returns:

  • (Boolean)


56
57
58
59
# File 'lib/capybara/simulated/node.rb', line 56

def visible?
  check_stale
  browser.visible?(handle_id)
end

#visible_textObject



45
46
47
48
49
# File 'lib/capybara/simulated/node.rb', line 45

def visible_text
  browser.tick_real_time
  check_stale
  normalize_visible_spacing(browser.visible_text(handle_id))
end