Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/rb/spec/integration/selenium/webdriver/action_builder_spec.rb
1864 views
1
# frozen_string_literal: true
2
3
# Licensed to the Software Freedom Conservancy (SFC) under one
4
# or more contributor license agreements. See the NOTICE file
5
# distributed with this work for additional information
6
# regarding copyright ownership. The SFC licenses this file
7
# to you under the Apache License, Version 2.0 (the
8
# "License"); you may not use this file except in compliance
9
# with the License. You may obtain a copy of the License at
10
#
11
# http://www.apache.org/licenses/LICENSE-2.0
12
#
13
# Unless required by applicable law or agreed to in writing,
14
# software distributed under the License is distributed on an
15
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
# KIND, either express or implied. See the License for the
17
# specific language governing permissions and limitations
18
# under the License.
19
20
require_relative 'spec_helper'
21
22
module Selenium
23
module WebDriver
24
describe ActionBuilder, exclusive: {bidi: false, reason: 'Not yet implemented with BiDi'} do
25
after { driver.action.clear_all_actions }
26
27
describe '#send_keys' do
28
it 'sends keys to the active element', except: {browser: %i[safari safari_preview]} do
29
driver.navigate.to url_for('bodyTypingTest.html')
30
keylogger = driver.find_element(id: 'body_result')
31
32
driver.find_element(css: 'body').click
33
driver.action.send_keys('ab').perform
34
35
wait.until { keylogger.text.split.length == 2 }
36
expect(keylogger.text.strip).to eq('keypress keypress')
37
38
expect(driver.find_element(id: 'result').text.strip).to be_empty
39
end
40
41
it 'sends keys to element' do
42
driver.navigate.to url_for('formPage.html')
43
44
input = driver.find_element(css: '#working')
45
46
driver.action.send_keys(input, 'abcd').perform
47
wait.until { input.property(:value).length == 4 }
48
expect(input.property(:value)).to eq('abcd')
49
end
50
51
it 'sends keys with multiple arguments' do
52
driver.navigate.to url_for('formPage.html')
53
54
input = driver.find_element(css: '#working')
55
input.click
56
57
driver.action.send_keys('abcd', 'dcba').perform
58
wait.until { input.property(:value).length == 8 }
59
expect(input.property(:value)).to eq('abcddcba')
60
end
61
62
it 'sends non-ASCII keys' do
63
driver.navigate.to url_for('formPage.html')
64
65
input = driver.find_element(css: '#working')
66
input.click
67
68
driver.action.send_keys('abcd', :left, 'a').perform
69
wait.until { input.property(:value).length == 5 }
70
expect(input.property(:value)).to eq('abcad')
71
end
72
end
73
74
describe 'multiple key presses' do
75
it 'sends keys with shift pressed', except: {browser: %i[safari safari_preview]} do
76
driver.navigate.to url_for('javascriptPage.html')
77
78
event_input = driver.find_element(id: 'theworks')
79
keylogger = driver.find_element(id: 'result')
80
81
event_input.click
82
83
driver.action.key_down(:shift).send_keys('ab').key_up(:shift).perform
84
wait.until { event_input.property(:value).length == 2 }
85
86
expect(event_input.property(:value)).to eq('AB')
87
expected = keylogger.text.strip
88
expect(expected).to match(/^(focus )?keydown keydown keypress keyup keydown keypress keyup keyup$/)
89
end
90
91
it 'press and release modifier keys' do
92
driver.navigate.to url_for('javascriptPage.html')
93
94
event_input = driver.find_element(id: 'theworks')
95
keylogger = driver.find_element(id: 'result')
96
97
event_input.click
98
99
driver.action.key_down(:shift).perform
100
wait.until { keylogger.text.include? 'down' }
101
expect(keylogger.text).to match(/keydown *$/)
102
103
driver.action.key_up(:shift).perform
104
wait.until { keylogger.text.include? 'up' }
105
expect(keylogger.text).to match(/keyup *$/)
106
end
107
end
108
109
describe '#release_actions' do
110
it 'releases pressed keys' do
111
driver.navigate.to url_for('javascriptPage.html')
112
113
event_input = driver.find_element(id: 'theworks')
114
keylogger = driver.find_element(id: 'result')
115
116
event_input.click
117
118
driver.action.key_down(:shift).perform
119
wait.until { keylogger.text.include? 'down' }
120
expect(keylogger.text).to match(/keydown *$/)
121
122
driver.action.release_actions
123
wait.until { keylogger.text.include? 'up' }
124
expect(keylogger.text).to match(/keyup *$/)
125
end
126
127
it 'releases pressed buttons', except: [{browser: %i[safari safari_preview]},
128
{driver: :remote, browser: :ie}] do
129
driver.navigate.to url_for('javascriptPage.html')
130
131
event_input = driver.find_element(id: 'clickField')
132
133
driver.action.click_and_hold(event_input).perform
134
expect(event_input.property(:value)).to eq('Hello')
135
136
driver.action.release_actions
137
expect(event_input.property(:value)).to eq('Clicked')
138
end
139
end
140
141
describe '#click' do
142
it 'clicks provided element' do
143
driver.navigate.to url_for('javascriptPage.html')
144
element = driver.find_element(id: 'clickField')
145
driver.action.click(element).perform
146
expect(element.property(:value)).to eq('Clicked')
147
end
148
149
it 'executes with equivalent pointer methods' do
150
driver.navigate.to url_for('javascriptPage.html')
151
element = driver.find_element(id: 'clickField')
152
driver.action.move_to(element).pointer_down(:left).pointer_up(:left).perform
153
expect(element.property(:value)).to eq('Clicked')
154
end
155
end
156
157
describe '#double_click' do
158
# https://issues.chromium.org/issues/400087471
159
before { reset_driver! if GlobalTestEnv.rbe? && GlobalTestEnv.browser == :chrome }
160
161
it 'presses pointer twice', except: {browser: %i[safari safari_preview]} do
162
driver.navigate.to url_for('javascriptPage.html')
163
element = driver.find_element(id: 'doubleClickField')
164
165
driver.action.double_click(element).perform
166
expect(element.property(:value)).to eq('DoubleClicked')
167
end
168
169
it 'executes with equivalent pointer methods', except: {browser: %i[safari safari_preview]} do
170
driver.navigate.to url_for('javascriptPage.html')
171
element = driver.find_element(id: 'doubleClickField')
172
173
driver.action.move_to(element)
174
.pointer_down(:left).pointer_up(:left)
175
.pointer_down(:left).pointer_up(:left)
176
.perform
177
expect(element.property(:value)).to eq('DoubleClicked')
178
end
179
end
180
181
describe '#context_click' do
182
it 'right clicks an element' do
183
driver.navigate.to url_for('javascriptPage.html')
184
element = driver.find_element(id: 'doubleClickField')
185
186
driver.action.context_click(element).perform
187
expect(element.property(:value)).to eq('ContextClicked')
188
end
189
190
it 'executes with equivalent pointer methods' do
191
driver.navigate.to url_for('javascriptPage.html')
192
element = driver.find_element(id: 'doubleClickField')
193
194
driver.action.move_to(element).pointer_down(:right).pointer_up(:right).perform
195
expect(element.property(:value)).to eq('ContextClicked')
196
end
197
end
198
199
describe '#move_to' do
200
it 'moves to element' do
201
driver.navigate.to url_for('javascriptPage.html')
202
element = driver.find_element(id: 'clickField')
203
driver.action.move_to(element).click.perform
204
205
expect(element.property(:value)).to eq('Clicked')
206
end
207
208
it 'moves to element with offset' do
209
driver.navigate.to url_for('javascriptPage.html')
210
origin = driver.find_element(id: 'keyUpArea')
211
destination = driver.find_element(id: 'clickField')
212
origin_rect = origin.rect
213
destination_rect = destination.rect
214
x_offset = (destination_rect.x - origin_rect.x).ceil
215
y_offset = (destination_rect.y - origin_rect.y).ceil
216
217
driver.action.move_to(origin, x_offset, y_offset).click.perform
218
expect(destination.property(:value)).to eq('Clicked')
219
end
220
end
221
222
describe '#drag_and_drop' do
223
it 'moves one element to another' do
224
driver.navigate.to url_for('droppableItems.html')
225
226
draggable = long_wait.until do
227
driver.find_element(id: 'draggable')
228
end
229
230
droppable = driver.find_element(id: 'droppable')
231
232
driver.action.drag_and_drop(draggable, droppable).perform
233
234
text = droppable.find_element(tag_name: 'p').text
235
expect(text).to eq('Dropped!')
236
end
237
end
238
239
describe '#drag_and_drop_by' do
240
it 'moves one element a provided distance' do
241
driver.navigate.to url_for('droppableItems.html')
242
243
draggable = long_wait.until do
244
driver.find_element(id: 'draggable')
245
end
246
247
driver.action.drag_and_drop_by(draggable, 138, 50).perform
248
249
droppable = driver.find_element(id: 'droppable')
250
text = droppable.find_element(tag_name: 'p').text
251
expect(text).to eq('Dropped!')
252
end
253
end
254
255
describe '#move_to_location' do
256
it 'moves pointer to specified coordinates' do
257
driver.navigate.to url_for('javascriptPage.html')
258
element = driver.find_element(id: 'clickField')
259
rect = element.rect
260
driver.action.move_to_location(rect.x.ceil, rect.y.ceil).click.perform
261
262
expect(element.property(:value)).to eq('Clicked')
263
end
264
end
265
266
describe 'pen stylus', except: [{browser: :firefox, reason: 'Unknown pointerType'},
267
{browser: :safari, reason: 'Some issues with resolution?'}] do
268
it 'sets pointer event properties' do
269
driver.navigate.to url_for('pointerActionsPage.html')
270
pointer_area = driver.find_element(id: 'pointerArea')
271
rect = pointer_area.rect
272
x_val = rect.x
273
y_val = rect.y
274
275
pointer_options = {pressure: 0.8, tilt_x: -40, tilt_y: -10, twist: 177}
276
actions = driver.action(devices: :pen)
277
.move_by(x_val + 5, y_val + 5)
278
.pointer_down
279
.move_by(2, 2, duration: 0.8, **pointer_options)
280
.pointer_up
281
282
start = Time.now
283
actions.perform
284
expect(Time.now - start).to be > 0.8
285
286
moves = driver.find_elements(class: 'pointermove')
287
move_to = properties(moves[0])
288
down = properties(driver.find_element(class: 'pointerdown'))
289
move_by = properties(moves[1])
290
up = properties(driver.find_element(class: 'pointerup'))
291
292
expect(move_to).to include('button' => '-1',
293
'pageX' => (x_val + 5).to_s,
294
'pageY' => (y_val + 5).floor.to_s)
295
expect(down).to include('button' => '0')
296
expect(move_by).to include('button' => '-1',
297
'pageX' => (x_val + 5 + 2).to_s,
298
'pageY' => (y_val + 5 + 2).floor.to_s,
299
'tiltX' => '-40',
300
'tiltY' => '-10',
301
'twist' => '177')
302
expect(up).to include('button' => '0',
303
'pageX' => (x_val + 5 + 2).to_s,
304
'pageY' => (y_val + 5 + 2).floor.to_s)
305
end
306
end
307
308
describe '#scroll_to', only: {browser: %i[chrome edge firefox]} do
309
it 'scrolls to element',
310
except: {browser: :firefox, reason: 'incorrect MoveTargetOutOfBoundsError'} do
311
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')
312
iframe = driver.find_element(tag_name: 'iframe')
313
314
expect(in_viewport?(iframe)).to be false
315
316
driver.action.scroll_to(iframe).perform
317
318
expect(in_viewport?(iframe)).to be true
319
end
320
end
321
322
describe '#scroll_by' do
323
it 'scrolls by given amount',
324
exclude: {driver: :firefox, reason: 'inconsistent behavior between versions'} do
325
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')
326
footer = driver.find_element(tag_name: 'footer')
327
delta_y = footer.rect.y.round
328
329
driver.action.scroll_by(0, delta_y).perform
330
sleep 0.5
331
332
expect(in_viewport?(footer)).to be true
333
end
334
end
335
336
describe '#scroll_from' do
337
it 'scrolls from element by given amount',
338
except: {browser: %i[firefox safari], reason: 'incorrect MoveTargetOutOfBoundsError'} do
339
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')
340
iframe = driver.find_element(tag_name: 'iframe')
341
scroll_origin = WheelActions::ScrollOrigin.element(iframe)
342
343
driver.action.scroll_from(scroll_origin, 0, 200).perform
344
345
driver.switch_to.frame(iframe)
346
sleep 0.5
347
checkbox = driver.find_element(name: 'scroll_checkbox')
348
expect(in_viewport?(checkbox)).to be true
349
end
350
351
it 'scrolls from element by given amount with offset',
352
except: {browser: %i[firefox safari], reason: 'incorrect MoveTargetOutOfBoundsError'} do
353
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')
354
footer = driver.find_element(tag_name: 'footer')
355
scroll_origin = WheelActions::ScrollOrigin.element(footer, 0, -50)
356
357
driver.action.scroll_from(scroll_origin, 0, 200).perform
358
sleep 0.5
359
360
iframe = driver.find_element(tag_name: 'iframe')
361
driver.switch_to.frame(iframe)
362
checkbox = driver.find_element(name: 'scroll_checkbox')
363
expect(in_viewport?(checkbox)).to be true
364
end
365
366
it 'raises MoveTargetOutOfBoundsError when origin offset from element is out of viewport' do
367
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')
368
footer = driver.find_element(tag_name: 'footer')
369
scroll_origin = WheelActions::ScrollOrigin.element(footer, 0, 50)
370
371
expect {
372
driver.action.scroll_from(scroll_origin, 0, 200).perform
373
}.to raise_error(Error::MoveTargetOutOfBoundsError)
374
end
375
376
it 'scrolls by given amount with offset' do
377
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame.html')
378
scroll_origin = WheelActions::ScrollOrigin.viewport(10, 10)
379
380
driver.action.scroll_from(scroll_origin, 0, 200).perform
381
382
iframe = driver.find_element(tag_name: 'iframe')
383
driver.switch_to.frame(iframe)
384
checkbox = driver.find_element(name: 'scroll_checkbox')
385
386
expect {
387
wait.until { in_viewport?(checkbox) }
388
}.not_to raise_error
389
end
390
391
it 'raises MoveTargetOutOfBoundsError when origin offset is out of viewport',
392
only: {browser: %i[chrome edge firefox]} do
393
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame.html')
394
scroll_origin = WheelActions::ScrollOrigin.viewport(-10, -10)
395
396
expect {
397
driver.action.scroll_from(scroll_origin, 0, 200).perform
398
}.to raise_error(Error::MoveTargetOutOfBoundsError)
399
end
400
end
401
402
def properties(element)
403
element.text.sub(/.*?\s/, '').split(',').to_h { |item| item.lstrip.split(/\s*:\s*/) }
404
end
405
406
def in_viewport?(element)
407
in_viewport = <<~IN_VIEWPORT
408
for(var e=arguments[0],f=e.offsetTop,t=e.offsetLeft,o=e.offsetWidth,n=e.offsetHeight;
409
e.offsetParent;)f+=(e=e.offsetParent).offsetTop,t+=e.offsetLeft;
410
return f<window.pageYOffset+window.innerHeight&&t<window.pageXOffset+window.innerWidth&&f+n>
411
window.pageYOffset&&t+o>window.pageXOffset
412
IN_VIEWPORT
413
414
driver.execute_script(in_viewport, element)
415
end
416
end # ActionBuilder
417
end # WebDriver
418
end # Selenium
419
420