Path: blob/trunk/rb/spec/integration/selenium/webdriver/action_builder_spec.rb
1864 views
# frozen_string_literal: true12# Licensed to the Software Freedom Conservancy (SFC) under one3# or more contributor license agreements. See the NOTICE file4# distributed with this work for additional information5# regarding copyright ownership. The SFC licenses this file6# to you under the Apache License, Version 2.0 (the7# "License"); you may not use this file except in compliance8# with the License. You may obtain a copy of the License at9#10# http://www.apache.org/licenses/LICENSE-2.011#12# Unless required by applicable law or agreed to in writing,13# software distributed under the License is distributed on an14# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY15# KIND, either express or implied. See the License for the16# specific language governing permissions and limitations17# under the License.1819require_relative 'spec_helper'2021module Selenium22module WebDriver23describe ActionBuilder, exclusive: {bidi: false, reason: 'Not yet implemented with BiDi'} do24after { driver.action.clear_all_actions }2526describe '#send_keys' do27it 'sends keys to the active element', except: {browser: %i[safari safari_preview]} do28driver.navigate.to url_for('bodyTypingTest.html')29keylogger = driver.find_element(id: 'body_result')3031driver.find_element(css: 'body').click32driver.action.send_keys('ab').perform3334wait.until { keylogger.text.split.length == 2 }35expect(keylogger.text.strip).to eq('keypress keypress')3637expect(driver.find_element(id: 'result').text.strip).to be_empty38end3940it 'sends keys to element' do41driver.navigate.to url_for('formPage.html')4243input = driver.find_element(css: '#working')4445driver.action.send_keys(input, 'abcd').perform46wait.until { input.property(:value).length == 4 }47expect(input.property(:value)).to eq('abcd')48end4950it 'sends keys with multiple arguments' do51driver.navigate.to url_for('formPage.html')5253input = driver.find_element(css: '#working')54input.click5556driver.action.send_keys('abcd', 'dcba').perform57wait.until { input.property(:value).length == 8 }58expect(input.property(:value)).to eq('abcddcba')59end6061it 'sends non-ASCII keys' do62driver.navigate.to url_for('formPage.html')6364input = driver.find_element(css: '#working')65input.click6667driver.action.send_keys('abcd', :left, 'a').perform68wait.until { input.property(:value).length == 5 }69expect(input.property(:value)).to eq('abcad')70end71end7273describe 'multiple key presses' do74it 'sends keys with shift pressed', except: {browser: %i[safari safari_preview]} do75driver.navigate.to url_for('javascriptPage.html')7677event_input = driver.find_element(id: 'theworks')78keylogger = driver.find_element(id: 'result')7980event_input.click8182driver.action.key_down(:shift).send_keys('ab').key_up(:shift).perform83wait.until { event_input.property(:value).length == 2 }8485expect(event_input.property(:value)).to eq('AB')86expected = keylogger.text.strip87expect(expected).to match(/^(focus )?keydown keydown keypress keyup keydown keypress keyup keyup$/)88end8990it 'press and release modifier keys' do91driver.navigate.to url_for('javascriptPage.html')9293event_input = driver.find_element(id: 'theworks')94keylogger = driver.find_element(id: 'result')9596event_input.click9798driver.action.key_down(:shift).perform99wait.until { keylogger.text.include? 'down' }100expect(keylogger.text).to match(/keydown *$/)101102driver.action.key_up(:shift).perform103wait.until { keylogger.text.include? 'up' }104expect(keylogger.text).to match(/keyup *$/)105end106end107108describe '#release_actions' do109it 'releases pressed keys' do110driver.navigate.to url_for('javascriptPage.html')111112event_input = driver.find_element(id: 'theworks')113keylogger = driver.find_element(id: 'result')114115event_input.click116117driver.action.key_down(:shift).perform118wait.until { keylogger.text.include? 'down' }119expect(keylogger.text).to match(/keydown *$/)120121driver.action.release_actions122wait.until { keylogger.text.include? 'up' }123expect(keylogger.text).to match(/keyup *$/)124end125126it 'releases pressed buttons', except: [{browser: %i[safari safari_preview]},127{driver: :remote, browser: :ie}] do128driver.navigate.to url_for('javascriptPage.html')129130event_input = driver.find_element(id: 'clickField')131132driver.action.click_and_hold(event_input).perform133expect(event_input.property(:value)).to eq('Hello')134135driver.action.release_actions136expect(event_input.property(:value)).to eq('Clicked')137end138end139140describe '#click' do141it 'clicks provided element' do142driver.navigate.to url_for('javascriptPage.html')143element = driver.find_element(id: 'clickField')144driver.action.click(element).perform145expect(element.property(:value)).to eq('Clicked')146end147148it 'executes with equivalent pointer methods' do149driver.navigate.to url_for('javascriptPage.html')150element = driver.find_element(id: 'clickField')151driver.action.move_to(element).pointer_down(:left).pointer_up(:left).perform152expect(element.property(:value)).to eq('Clicked')153end154end155156describe '#double_click' do157# https://issues.chromium.org/issues/400087471158before { reset_driver! if GlobalTestEnv.rbe? && GlobalTestEnv.browser == :chrome }159160it 'presses pointer twice', except: {browser: %i[safari safari_preview]} do161driver.navigate.to url_for('javascriptPage.html')162element = driver.find_element(id: 'doubleClickField')163164driver.action.double_click(element).perform165expect(element.property(:value)).to eq('DoubleClicked')166end167168it 'executes with equivalent pointer methods', except: {browser: %i[safari safari_preview]} do169driver.navigate.to url_for('javascriptPage.html')170element = driver.find_element(id: 'doubleClickField')171172driver.action.move_to(element)173.pointer_down(:left).pointer_up(:left)174.pointer_down(:left).pointer_up(:left)175.perform176expect(element.property(:value)).to eq('DoubleClicked')177end178end179180describe '#context_click' do181it 'right clicks an element' do182driver.navigate.to url_for('javascriptPage.html')183element = driver.find_element(id: 'doubleClickField')184185driver.action.context_click(element).perform186expect(element.property(:value)).to eq('ContextClicked')187end188189it 'executes with equivalent pointer methods' do190driver.navigate.to url_for('javascriptPage.html')191element = driver.find_element(id: 'doubleClickField')192193driver.action.move_to(element).pointer_down(:right).pointer_up(:right).perform194expect(element.property(:value)).to eq('ContextClicked')195end196end197198describe '#move_to' do199it 'moves to element' do200driver.navigate.to url_for('javascriptPage.html')201element = driver.find_element(id: 'clickField')202driver.action.move_to(element).click.perform203204expect(element.property(:value)).to eq('Clicked')205end206207it 'moves to element with offset' do208driver.navigate.to url_for('javascriptPage.html')209origin = driver.find_element(id: 'keyUpArea')210destination = driver.find_element(id: 'clickField')211origin_rect = origin.rect212destination_rect = destination.rect213x_offset = (destination_rect.x - origin_rect.x).ceil214y_offset = (destination_rect.y - origin_rect.y).ceil215216driver.action.move_to(origin, x_offset, y_offset).click.perform217expect(destination.property(:value)).to eq('Clicked')218end219end220221describe '#drag_and_drop' do222it 'moves one element to another' do223driver.navigate.to url_for('droppableItems.html')224225draggable = long_wait.until do226driver.find_element(id: 'draggable')227end228229droppable = driver.find_element(id: 'droppable')230231driver.action.drag_and_drop(draggable, droppable).perform232233text = droppable.find_element(tag_name: 'p').text234expect(text).to eq('Dropped!')235end236end237238describe '#drag_and_drop_by' do239it 'moves one element a provided distance' do240driver.navigate.to url_for('droppableItems.html')241242draggable = long_wait.until do243driver.find_element(id: 'draggable')244end245246driver.action.drag_and_drop_by(draggable, 138, 50).perform247248droppable = driver.find_element(id: 'droppable')249text = droppable.find_element(tag_name: 'p').text250expect(text).to eq('Dropped!')251end252end253254describe '#move_to_location' do255it 'moves pointer to specified coordinates' do256driver.navigate.to url_for('javascriptPage.html')257element = driver.find_element(id: 'clickField')258rect = element.rect259driver.action.move_to_location(rect.x.ceil, rect.y.ceil).click.perform260261expect(element.property(:value)).to eq('Clicked')262end263end264265describe 'pen stylus', except: [{browser: :firefox, reason: 'Unknown pointerType'},266{browser: :safari, reason: 'Some issues with resolution?'}] do267it 'sets pointer event properties' do268driver.navigate.to url_for('pointerActionsPage.html')269pointer_area = driver.find_element(id: 'pointerArea')270rect = pointer_area.rect271x_val = rect.x272y_val = rect.y273274pointer_options = {pressure: 0.8, tilt_x: -40, tilt_y: -10, twist: 177}275actions = driver.action(devices: :pen)276.move_by(x_val + 5, y_val + 5)277.pointer_down278.move_by(2, 2, duration: 0.8, **pointer_options)279.pointer_up280281start = Time.now282actions.perform283expect(Time.now - start).to be > 0.8284285moves = driver.find_elements(class: 'pointermove')286move_to = properties(moves[0])287down = properties(driver.find_element(class: 'pointerdown'))288move_by = properties(moves[1])289up = properties(driver.find_element(class: 'pointerup'))290291expect(move_to).to include('button' => '-1',292'pageX' => (x_val + 5).to_s,293'pageY' => (y_val + 5).floor.to_s)294expect(down).to include('button' => '0')295expect(move_by).to include('button' => '-1',296'pageX' => (x_val + 5 + 2).to_s,297'pageY' => (y_val + 5 + 2).floor.to_s,298'tiltX' => '-40',299'tiltY' => '-10',300'twist' => '177')301expect(up).to include('button' => '0',302'pageX' => (x_val + 5 + 2).to_s,303'pageY' => (y_val + 5 + 2).floor.to_s)304end305end306307describe '#scroll_to', only: {browser: %i[chrome edge firefox]} do308it 'scrolls to element',309except: {browser: :firefox, reason: 'incorrect MoveTargetOutOfBoundsError'} do310driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')311iframe = driver.find_element(tag_name: 'iframe')312313expect(in_viewport?(iframe)).to be false314315driver.action.scroll_to(iframe).perform316317expect(in_viewport?(iframe)).to be true318end319end320321describe '#scroll_by' do322it 'scrolls by given amount',323exclude: {driver: :firefox, reason: 'inconsistent behavior between versions'} do324driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')325footer = driver.find_element(tag_name: 'footer')326delta_y = footer.rect.y.round327328driver.action.scroll_by(0, delta_y).perform329sleep 0.5330331expect(in_viewport?(footer)).to be true332end333end334335describe '#scroll_from' do336it 'scrolls from element by given amount',337except: {browser: %i[firefox safari], reason: 'incorrect MoveTargetOutOfBoundsError'} do338driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')339iframe = driver.find_element(tag_name: 'iframe')340scroll_origin = WheelActions::ScrollOrigin.element(iframe)341342driver.action.scroll_from(scroll_origin, 0, 200).perform343344driver.switch_to.frame(iframe)345sleep 0.5346checkbox = driver.find_element(name: 'scroll_checkbox')347expect(in_viewport?(checkbox)).to be true348end349350it 'scrolls from element by given amount with offset',351except: {browser: %i[firefox safari], reason: 'incorrect MoveTargetOutOfBoundsError'} do352driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')353footer = driver.find_element(tag_name: 'footer')354scroll_origin = WheelActions::ScrollOrigin.element(footer, 0, -50)355356driver.action.scroll_from(scroll_origin, 0, 200).perform357sleep 0.5358359iframe = driver.find_element(tag_name: 'iframe')360driver.switch_to.frame(iframe)361checkbox = driver.find_element(name: 'scroll_checkbox')362expect(in_viewport?(checkbox)).to be true363end364365it 'raises MoveTargetOutOfBoundsError when origin offset from element is out of viewport' do366driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')367footer = driver.find_element(tag_name: 'footer')368scroll_origin = WheelActions::ScrollOrigin.element(footer, 0, 50)369370expect {371driver.action.scroll_from(scroll_origin, 0, 200).perform372}.to raise_error(Error::MoveTargetOutOfBoundsError)373end374375it 'scrolls by given amount with offset' do376driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame.html')377scroll_origin = WheelActions::ScrollOrigin.viewport(10, 10)378379driver.action.scroll_from(scroll_origin, 0, 200).perform380381iframe = driver.find_element(tag_name: 'iframe')382driver.switch_to.frame(iframe)383checkbox = driver.find_element(name: 'scroll_checkbox')384385expect {386wait.until { in_viewport?(checkbox) }387}.not_to raise_error388end389390it 'raises MoveTargetOutOfBoundsError when origin offset is out of viewport',391only: {browser: %i[chrome edge firefox]} do392driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame.html')393scroll_origin = WheelActions::ScrollOrigin.viewport(-10, -10)394395expect {396driver.action.scroll_from(scroll_origin, 0, 200).perform397}.to raise_error(Error::MoveTargetOutOfBoundsError)398end399end400401def properties(element)402element.text.sub(/.*?\s/, '').split(',').to_h { |item| item.lstrip.split(/\s*:\s*/) }403end404405def in_viewport?(element)406in_viewport = <<~IN_VIEWPORT407for(var e=arguments[0],f=e.offsetTop,t=e.offsetLeft,o=e.offsetWidth,n=e.offsetHeight;408e.offsetParent;)f+=(e=e.offsetParent).offsetTop,t+=e.offsetLeft;409return f<window.pageYOffset+window.innerHeight&&t<window.pageXOffset+window.innerWidth&&f+n>410window.pageYOffset&&t+o>window.pageXOffset411IN_VIEWPORT412413driver.execute_script(in_viewport, element)414end415end # ActionBuilder416end # WebDriver417end # Selenium418419420