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.



215
216
217
218
219
220
# File 'lib/capybara/simulated/node.rb', line 215

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 ‘setTimeout`s 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)


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

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)


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

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



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

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



148
149
150
151
152
# File 'lib/capybara/simulated/node.rb', line 148

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

#find_css(query) ⇒ Object



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

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

#find_xpath(query) ⇒ Object



181
182
183
# File 'lib/capybara/simulated/node.rb', line 181

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)


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

def obscured?(*)     = !visible?

#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



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

def path
  check_stale
  browser.node_path(handle_id)
end

#readonly?Boolean

Returns:

  • (Boolean)


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

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

#rectObject

Capybara’s standard rect API. No layout engine — but Discourse’s ‘wait_for_animation` helper polls `element.rect` twice and waits for the values to stabilise, which they immediately do here (constant zeros) since we never animate. That unblocks every test guarded by an animation settle.



132
133
134
135
# File 'lib/capybara/simulated/node.rb', line 132

def rect
  check_stale
  {x: 0, y: 0, width: 0, height: 0, top: 0, left: 0, bottom: 0, right: 0}
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_toObject



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

def scroll_to(*, **)       ; self ; end

#select_optionObject



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

def select_option
  check_stale
  browser.select_option(handle_id)
end

#selected?Boolean

Returns:

  • (Boolean)


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

def selected?        = browser.option_selected?(handle_id)

#send_keys(*keys) ⇒ Object



137
138
139
140
141
# File 'lib/capybara/simulated/node.rb', line 137

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

#set(value, **_) ⇒ Object



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

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

#shadow_rootObject



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

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

#style(names = []) ⇒ Object



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

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

#submit(*_) ⇒ Object



176
177
178
179
# File 'lib/capybara/simulated/node.rb', line 176

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

#synchronizeObject



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

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



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

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

#unselect_optionObject



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

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