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.



238
239
240
241
242
243
# File 'lib/capybara/simulated/node.rb', line 238

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

#[](name) ⇒ Object



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

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

The text readers advance the virtual clock, unlike Node#[] (attribute reads) which sit behind a find_css that already ticked: have_text / assert_text polls .text directly against an already-found, cached scope node, so the find loop short-circuits without re-ticking and a page waiting on a timer would never make progress. tick_for_read decides WHEN: a read leaves the clock where it is and the next query pays the step, so a walk over one query's results can't be stranded mid-way.



36
37
38
39
40
# File 'lib/capybara/simulated/node.rb', line 36

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

#checked?Boolean

Returns:

  • (Boolean)


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

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

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



101
102
103
104
# File 'lib/capybara/simulated/node.rb', line 101

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

#disabled?Boolean

Returns:

  • (Boolean)


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

def disabled?        = browser.disabled?(handle_id)

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



111
112
113
114
# File 'lib/capybara/simulated/node.rb', line 111

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

#drag_to(target_node, **opts) ⇒ Object



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

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



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

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

#find_css(query) ⇒ Object



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

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

#find_xpath(query) ⇒ Object



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

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

#hover(**_opts) ⇒ Object



116
117
118
119
120
# File 'lib/capybara/simulated/node.rb', line 116

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.



64
65
66
67
# File 'lib/capybara/simulated/node.rb', line 64

def inner_html
  check_stale
  browser.inner_html(handle_id)
end

#obscured?Boolean

Returns:

  • (Boolean)


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

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

#outer_htmlObject



69
70
71
72
# File 'lib/capybara/simulated/node.rb', line 69

def outer_html
  check_stale
  browser.outer_html(handle_id)
end

#pathObject



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

def path
  check_stale
  browser.node_path(handle_id)
end

#readonly?Boolean

Returns:

  • (Boolean)


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

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.



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

def rect
  check_stale
  browser.rect(handle_id)
end

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



106
107
108
109
# File 'lib/capybara/simulated/node.rb', line 106

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.



140
141
142
143
144
# File 'lib/capybara/simulated/node.rb', line 140

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.



126
127
128
129
130
131
132
133
134
135
136
# File 'lib/capybara/simulated/node.rb', line 126

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



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

def select_option
  check_stale
  browser.select_option(handle_id)
end

#selected?Boolean

Returns:

  • (Boolean)


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

def selected?        = browser.option_selected?(handle_id)

#send_keys(*keys) ⇒ Object



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

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

#set(value, **_) ⇒ Object



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

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

#shadow_rootObject



209
210
211
212
213
# File 'lib/capybara/simulated/node.rb', line 209

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

#style(names = []) ⇒ Object



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

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

#submit(*_) ⇒ Object



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

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

#synchronizeObject



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

def synchronize(*)   = yield

#tag_nameObject



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

def tag_name = browser.tag_name(handle_id)

#trigger(event) ⇒ Object



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

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

#unselect_optionObject



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

def unselect_option
  check_stale
  browser.unselect_option(handle_id)
end

#valueObject



48
49
50
51
# File 'lib/capybara/simulated/node.rb', line 48

def value
  check_stale
  browser.value(handle_id)
end

#visible?Boolean

Returns:

  • (Boolean)


53
54
55
56
# File 'lib/capybara/simulated/node.rb', line 53

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

#visible_textObject



42
43
44
45
46
# File 'lib/capybara/simulated/node.rb', line 42

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