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.



233
234
235
236
237
238
# File 'lib/capybara/simulated/node.rb', line 233

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)


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

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)


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

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



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

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



163
164
165
166
167
# File 'lib/capybara/simulated/node.rb', line 163

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

#find_css(query) ⇒ Object



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

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

#find_xpath(query) ⇒ Object



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

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)


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

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



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

def path
  check_stale
  browser.node_path(handle_id)
end

#readonly?Boolean

Returns:

  • (Boolean)


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

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.



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

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_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



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

def select_option
  check_stale
  browser.select_option(handle_id)
end

#selected?Boolean

Returns:

  • (Boolean)


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

def selected?        = browser.option_selected?(handle_id)

#send_keys(*keys) ⇒ Object



152
153
154
155
156
# File 'lib/capybara/simulated/node.rb', line 152

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

#set(value, **_) ⇒ Object



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

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

#shadow_rootObject



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

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

#style(names = []) ⇒ Object



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

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

#submit(*_) ⇒ Object



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

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

#synchronizeObject



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

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



158
159
160
161
162
# File 'lib/capybara/simulated/node.rb', line 158

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

#unselect_optionObject



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

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