Class: Capybara::Playwright::Node
- Inherits:
-
Driver::Node
- Object
- Driver::Node
- Capybara::Playwright::Node
show all
- Defined in:
- lib/capybara/playwright/node.rb
Overview
Selector and checking methods are derived from twapole/apparition
Action methods (click, select_option, ...) uses playwright.
ref:
selenium: https://github.com/teamcapybara/capybara/blob/master/lib/capybara/selenium/node.rb
apparition: https://github.com/twalpole/apparition/blob/master/lib/capybara/apparition/node.rb
Defined Under Namespace
Modules: UpdateValueJS
Classes: Checkbox, ClickOptions, DateInput, DateTimeInput, DragTo, FileUpload, JSValueInput, NotActionableError, RadioButton, SendKeys, Settable, StaleReferenceError, TextInput, TimeInput
Constant Summary
collapse
- ATTACH_FILE =
<<~JAVASCRIPT
() => {
const input = document.createElement('INPUT');
input.type = 'file';
input.multiple = true;
input.style.display = 'none';
document.body.appendChild(input);
return input;
}
JAVASCRIPT
- DROP_FILE =
<<~JAVASCRIPT
(el, input) => {
const dt = new DataTransfer();
for (const file of input.files) { dt.items.add(file); }
input.remove();
el.dispatchEvent(new DragEvent('drop', {
cancelable: true, bubbles: true, dataTransfer: dt
}));
}
JAVASCRIPT
- DROP_STRING =
<<~JAVASCRIPT
(el, items) => {
const dt = new DataTransfer();
for (const item of items) { dt.items.add(item.data, item.type); }
el.dispatchEvent(new DragEvent('drop', {
cancelable: true, bubbles: true, dataTransfer: dt
}));
}
JAVASCRIPT
- SCROLL_POSITIONS =
{
top: '0',
bottom: 'el.scrollHeight',
center: '(el.scrollHeight - el.clientHeight)/2'
}.freeze
Instance Method Summary
collapse
-
#==(other) ⇒ Object
-
#[](name) ⇒ Object
-
#all_text ⇒ Object
-
#checked? ⇒ Boolean
-
#click(keys = [], **options) ⇒ Object
-
#disabled? ⇒ Boolean
-
#double_click(keys = [], **options) ⇒ Object
-
#drag_to(element, **options) ⇒ Object
-
#drop(*args) ⇒ Object
-
#find_css(query, **options) ⇒ Object
-
#find_xpath(query, **options) ⇒ Object
-
#hover ⇒ Object
-
#initialize(driver, internal_logger, page, element) ⇒ Node
constructor
-
#inspect ⇒ Object
-
#multiple? ⇒ Boolean
-
#obscured? ⇒ Boolean
-
#path ⇒ Object
-
#readonly? ⇒ Boolean
-
#rect ⇒ Object
-
#right_click(keys = [], **options) ⇒ Object
-
#scroll_by(x, y) ⇒ Object
-
#scroll_to(element, location, position = nil) ⇒ Object
-
#select_option ⇒ Object
-
#selected? ⇒ Boolean
-
#send_keys(*args) ⇒ Object
-
#set(value, **options) ⇒ Object
-
#shadow_root ⇒ Object
-
#style(styles) ⇒ Object
-
#tag_name ⇒ Object
-
#trigger(event) ⇒ Object
-
#unselect_option ⇒ Object
-
#value ⇒ Object
-
#visible? ⇒ Boolean
-
#visible_text ⇒ Object
Constructor Details
#initialize(driver, internal_logger, page, element) ⇒ Node
Returns a new instance of Node.
223
224
225
226
227
228
|
# File 'lib/capybara/playwright/node.rb', line 223
def initialize(driver, internal_logger, page, element)
super(driver, element)
@internal_logger = internal_logger
@page = page
@element = element
end
|
Instance Method Details
#==(other) ⇒ Object
1184
1185
1186
1187
1188
|
# File 'lib/capybara/playwright/node.rb', line 1184
def ==(other)
return false unless other.is_a?(Node)
@element.evaluate('(self, other) => self == other', arg: other.element)
end
|
#[](name) ⇒ Object
300
301
302
303
304
|
# File 'lib/capybara/playwright/node.rb', line 300
def [](name)
assert_element_not_stale {
property(name) || attribute(name)
}
end
|
#all_text ⇒ Object
267
268
269
270
271
272
273
274
275
276
|
# File 'lib/capybara/playwright/node.rb', line 267
def all_text
assert_element_not_stale {
text = @element.text_content
text.to_s.gsub(/[\u200b\u200e\u200f]/, '')
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
.gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
.tr("\u00a0", ' ')
}
end
|
#checked? ⇒ Boolean
1084
1085
1086
1087
1088
|
# File 'lib/capybara/playwright/node.rb', line 1084
def checked?
assert_element_not_stale {
@element.evaluate('el => !!el.checked')
}
end
|
#click(keys = [], **options) ⇒ Object
572
573
574
575
|
# File 'lib/capybara/playwright/node.rb', line 572
def click(keys = [], **options)
click_options = ClickOptions.new(@element, keys, options, capybara_default_wait_time)
@element.click(**click_options.as_params)
end
|
#disabled? ⇒ Boolean
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
|
# File 'lib/capybara/playwright/node.rb', line 1096
def disabled?
@element.evaluate(<<~JAVASCRIPT)
function(el) {
const xpath = 'parent::optgroup[@disabled] | \
ancestor::select[@disabled] | \
parent::fieldset[@disabled] | \
ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]';
return el.disabled || document.evaluate(xpath, el, null, XPathResult.BOOLEAN_TYPE, null).booleanValue
}
JAVASCRIPT
end
|
#double_click(keys = [], **options) ⇒ Object
584
585
586
587
|
# File 'lib/capybara/playwright/node.rb', line 584
def double_click(keys = [], **options)
click_options = ClickOptions.new(@element, keys, options, capybara_default_wait_time)
@element.dblclick(**click_options.as_params)
end
|
#drag_to(element, **options) ⇒ Object
846
847
848
|
# File 'lib/capybara/playwright/node.rb', line 846
def drag_to(element, **options)
DragTo.new(@page, @element, element.element, options).execute
end
|
#drop(*args) ⇒ Object
952
953
954
955
956
957
958
959
960
961
|
# File 'lib/capybara/playwright/node.rb', line 952
def drop(*args)
if args.first.is_a?(String) || args.first.is_a?(Pathname)
input = @page.evaluate_handle(ATTACH_FILE)
input.as_element.set_input_files(args.map(&:to_s))
@element.evaluate(DROP_FILE, arg: input)
else
items = args.flat_map { |arg| arg.map { |(type, data)| { type: type, data: data } } }
@element.evaluate(DROP_STRING, arg: items)
end
end
|
#find_css(query, **options) ⇒ Object
1198
1199
1200
1201
1202
1203
1204
|
# File 'lib/capybara/playwright/node.rb', line 1198
def find_css(query, **options)
assert_element_not_stale {
@element.query_selector_all(query).map do |el|
Node.new(@driver, @internal_logger, @page, el)
end
}
end
|
#find_xpath(query, **options) ⇒ Object
1190
1191
1192
1193
1194
1195
1196
|
# File 'lib/capybara/playwright/node.rb', line 1190
def find_xpath(query, **options)
assert_element_not_stale {
@element.query_selector_all("xpath=#{query}").map do |el|
Node.new(@driver, @internal_logger, @page, el)
end
}
end
|
#hover ⇒ Object
842
843
844
|
# File 'lib/capybara/playwright/node.rb', line 842
def hover
@element.hover(timeout: capybara_default_wait_time)
end
|
#inspect ⇒ Object
1180
1181
1182
|
# File 'lib/capybara/playwright/node.rb', line 1180
def inspect
%(#<#{self.class} tag="#{tag_name}" path="#{path}">)
end
|
#multiple? ⇒ Boolean
1112
1113
1114
|
# File 'lib/capybara/playwright/node.rb', line 1112
def multiple?
@element.evaluate('el => el.multiple')
end
|
#obscured? ⇒ Boolean
1080
1081
1082
|
# File 'lib/capybara/playwright/node.rb', line 1080
def obscured?
@element.capybara_obscured?
end
|
#path ⇒ Object
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
|
# File 'lib/capybara/playwright/node.rb', line 1128
def path
assert_element_not_stale {
@element.evaluate(<<~JAVASCRIPT)
(el) => {
var xml = document;
var xpath = '';
var pos, tempitem2;
if (el.getRootNode && el.getRootNode() instanceof ShadowRoot) {
return "(: Shadow DOM element - no XPath :)";
};
while(el !== xml.documentElement) {
pos = 0;
tempitem2 = el;
while(tempitem2) {
if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) { // If it is ELEMENT_NODE of the same name
pos += 1;
}
tempitem2 = tempitem2.previousSibling;
}
if (el.namespaceURI != xml.documentElement.namespaceURI) {
xpath = "*[local-name()='"+el.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']["+pos+']'+'/'+xpath;
} else {
xpath = el.nodeName.toUpperCase()+"["+pos+"]/"+xpath;
}
el = el.parentNode;
if (!el) {
throw "(: Shadow DOM element - no XPath :)";
}
}
xpath = '/'+xml.documentElement.nodeName.toUpperCase()+'/'+xpath;
xpath = xpath.replace(/\\/$/, '');
return xpath;
}
JAVASCRIPT
}
end
|
#readonly? ⇒ Boolean
1108
1109
1110
|
# File 'lib/capybara/playwright/node.rb', line 1108
def readonly?
!@element.editable?
end
|
#rect ⇒ Object
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
|
# File 'lib/capybara/playwright/node.rb', line 1116
def rect
assert_element_not_stale {
@element.evaluate(<<~JAVASCRIPT)
function(el){
const rects = [...el.getClientRects()]
const rect = rects.find(r => (r.height && r.width)) || el.getBoundingClientRect();
return rect.toJSON();
}
JAVASCRIPT
}
end
|
#right_click(keys = [], **options) ⇒ Object
577
578
579
580
581
582
|
# File 'lib/capybara/playwright/node.rb', line 577
def right_click(keys = [], **options)
click_options = ClickOptions.new(@element, keys, options, capybara_default_wait_time)
params = click_options.as_params
params[:button] = 'right'
@element.click(**params)
end
|
963
964
965
966
967
968
969
970
971
972
973
974
975
976
|
# File 'lib/capybara/playwright/node.rb', line 963
def scroll_by(x, y)
js = <<~JAVASCRIPT
(el, [x, y]) => {
if (el.scrollBy){
el.scrollBy(x, y);
} else {
el.scrollTop = el.scrollTop + y;
el.scrollLeft = el.scrollLeft + x;
}
}
JAVASCRIPT
@element.evaluate(js, arg: [x, y])
end
|
978
979
980
981
982
983
984
985
986
987
988
989
|
# File 'lib/capybara/playwright/node.rb', line 978
def scroll_to(element, location, position = nil)
if element.is_a? Capybara::Playwright::Node
scroll_element_to_location(element, location)
elsif location.is_a? Symbol
scroll_to_location(location)
else
scroll_to_coords(*position)
end
self
end
|
#select_option ⇒ Object
545
546
547
548
549
550
551
552
553
554
555
556
|
# File 'lib/capybara/playwright/node.rb', line 545
def select_option
return false if disabled?
select_element = parent_select_element
if select_element.evaluate('el => el.multiple')
selected_options = select_element.query_selector_all('option:checked')
selected_options << @element
select_element.select_option(element: selected_options, timeout: capybara_default_wait_time)
else
select_element.select_option(element: @element, timeout: capybara_default_wait_time)
end
end
|
#selected? ⇒ Boolean
1090
1091
1092
1093
1094
|
# File 'lib/capybara/playwright/node.rb', line 1090
def selected?
assert_element_not_stale {
@element.evaluate('el => !!el.selected')
}
end
|
#send_keys(*args) ⇒ Object
668
669
670
|
# File 'lib/capybara/playwright/node.rb', line 668
def send_keys(*args)
SendKeys.new(@element, args).execute
end
|
#set(value, **options) ⇒ Object
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
|
# File 'lib/capybara/playwright/node.rb', line 337
def set(value, **options)
settable_class =
case tag_name
when 'input'
case attribute('type')
when 'radio'
RadioButton
when 'checkbox'
Checkbox
when 'file'
FileUpload
when 'date'
DateInput
when 'time'
TimeInput
when 'datetime-local'
DateTimeInput
when 'color'
JSValueInput
when 'range'
JSValueInput
else
TextInput
end
when 'textarea'
TextInput
else
if @element.editable?
TextInput
else
raise NotSupportedByDriverError
end
end
settable_class.new(@element, capybara_default_wait_time, @internal_logger).set(value, **options)
rescue ::Playwright::TimeoutError => err
raise NotActionableError.new(err)
end
|
#shadow_root ⇒ Object
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
|
# File 'lib/capybara/playwright/node.rb', line 1169
def shadow_root
ShadowRootNode.new(@driver, @internal_logger, @page, @element)
end
|
#style(styles) ⇒ Object
329
330
331
332
333
|
# File 'lib/capybara/playwright/node.rb', line 329
def style(styles)
raise NotImplementedError
end
|
#tag_name ⇒ Object
1042
1043
1044
|
# File 'lib/capybara/playwright/node.rb', line 1042
def tag_name
@tag_name ||= @element.evaluate('e => e.tagName.toLowerCase()')
end
|
#trigger(event) ⇒ Object
1165
1166
1167
|
# File 'lib/capybara/playwright/node.rb', line 1165
def trigger(event)
@element.dispatch_event(event)
end
|
#unselect_option ⇒ Object
558
559
560
561
562
563
564
565
566
|
# File 'lib/capybara/playwright/node.rb', line 558
def unselect_option
if parent_select_element.evaluate('el => el.multiple')
return false if disabled?
@element.evaluate('el => el.selected = false')
else
raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.'
end
end
|
#value ⇒ Object
315
316
317
318
319
320
321
322
323
324
325
326
327
|
# File 'lib/capybara/playwright/node.rb', line 315
def value
assert_element_not_stale {
if tag_name == 'select' && @element.evaluate('el => el.multiple')
@element.query_selector_all('option:checked').map do |option|
option.evaluate('el => el.value')
end
else
@element.evaluate('el => el.value')
end
}
end
|
#visible? ⇒ Boolean
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
|
# File 'lib/capybara/playwright/node.rb', line 1046
def visible?
assert_element_not_stale {
@element.evaluate(<<~JAVASCRIPT)
function(el) {
if (el.tagName == 'AREA'){
const map_name = document.evaluate('./ancestor::map/@name', el, null, XPathResult.STRING_TYPE, null).stringValue;
el = document.querySelector(`img[usemap='#${map_name}']`);
if (!el){
return false;
}
}
var forced_visible = false;
while (el) {
const style = window.getComputedStyle(el);
if (style.visibility == 'visible')
forced_visible = true;
if ((style.display == 'none') ||
((style.visibility == 'hidden') && !forced_visible) ||
(parseFloat(style.opacity) == 0)) {
return false;
}
var parent = el.parentElement;
if (parent && (parent.tagName == 'DETAILS') && !parent.open && (el.tagName != 'SUMMARY')) {
return false;
}
el = parent;
}
return true;
}
JAVASCRIPT
}
end
|
#visible_text ⇒ Object
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
|
# File 'lib/capybara/playwright/node.rb', line 278
def visible_text
assert_element_not_stale {
return '' unless visible?
text = @element.evaluate(<<~JAVASCRIPT)
function(el){
if (el.nodeName == 'TEXTAREA'){
return el.textContent;
} else if (el instanceof SVGElement) {
return el.textContent;
} else {
return el.innerText;
}
}
JAVASCRIPT
text.to_s.scrub.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
.gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
.gsub(/\n+/, "\n")
.tr("\u00a0", ' ')
}
end
|