diff --git a/rb/lib/selenium/webdriver/common/wait.rb b/rb/lib/selenium/webdriver/common/wait.rb index abe3889f8c02b..90e6df39b7320 100644 --- a/rb/lib/selenium/webdriver/common/wait.rb +++ b/rb/lib/selenium/webdriver/common/wait.rb @@ -37,7 +37,8 @@ def initialize(opts = {}) @timeout = opts.fetch(:timeout, DEFAULT_TIMEOUT) @interval = opts.fetch(:interval, DEFAULT_INTERVAL) @message = opts[:message] - @ignored = Array(opts[:ignore] || Error::NoSuchElementError) + @message_provider = opts[:message_provider] + @ignored = Array(opts[:ignore] || Error::NoSuchElementError) end # @@ -64,6 +65,8 @@ def until msg = if @message @message.dup + elsif @message_provider + @message_provider.call else "timed out after #{@timeout} seconds" end diff --git a/rb/spec/integration/selenium/webdriver/devtools_spec.rb b/rb/spec/integration/selenium/webdriver/devtools_spec.rb index 6b75778d0bb04..2427c8f71ccfb 100644 --- a/rb/spec/integration/selenium/webdriver/devtools_spec.rb +++ b/rb/spec/integration/selenium/webdriver/devtools_spec.rb @@ -100,26 +100,20 @@ module WebDriver it 'notifies about log messages' do logs = [] - driver.on_log_event(:console) { |log| logs.push(log) } + driver.on_log_event(:console) { |log| logs.push(log.args[0]) } driver.navigate.to url_for('javascriptPage.html') - driver.execute_script("console.log('I like cheese');") - sleep 0.5 driver.execute_script('console.log(true);') - sleep 0.5 driver.execute_script('console.log(null);') - sleep 0.5 driver.execute_script('console.log(undefined);') - sleep 0.5 driver.execute_script('console.log(document);') - sleep 0.5 + driver.execute_script("console.log('I like cheese');") - expect(logs).to include( - an_object_having_attributes(type: :log, args: ['I like cheese']), - an_object_having_attributes(type: :log, args: [true]), - an_object_having_attributes(type: :log, args: [nil]), - an_object_having_attributes(type: :log, args: [{'type' => 'undefined'}]) - ) + wait.until { logs.include?('I like cheese') } + + expect(logs).to include(true) + expect(logs).to include(nil) + expect(logs).to include({'type' => 'undefined'}) end it 'notifies about document log messages' do diff --git a/rb/spec/integration/selenium/webdriver/element_spec.rb b/rb/spec/integration/selenium/webdriver/element_spec.rb index 3884ca724d9d7..06d1e04a43674 100644 --- a/rb/spec/integration/selenium/webdriver/element_spec.rb +++ b/rb/spec/integration/selenium/webdriver/element_spec.rb @@ -24,25 +24,28 @@ module WebDriver describe Element, exclusive: {bidi: false, reason: 'Not yet implemented with BiDi'} do it 'clicks' do driver.navigate.to url_for('formPage.html') - expect { driver.find_element(id: 'imageButton').click }.not_to raise_error + element = wait_for_element(id: 'imageButton') + expect { element.click }.not_to raise_error reset_driver!(time: 1) if %i[safari safari_preview].include? GlobalTestEnv.browser end # Safari returns "click intercepted" error instead of "element click intercepted" it 'raises if different element receives click', except: {browser: %i[safari safari_preview]} do driver.navigate.to url_for('click_tests/overlapping_elements.html') - expect { driver.find_element(id: 'contents').click }.to raise_error(Error::ElementClickInterceptedError) + element = wait_for_element(id: 'contents', timeout: 10) + expect { element.click }.to raise_error(Error::ElementClickInterceptedError) end # Safari returns "click intercepted" error instead of "element click intercepted" it 'raises if element is partially covered', except: {browser: %i[safari safari_preview]} do driver.navigate.to url_for('click_tests/overlapping_elements.html') - expect { driver.find_element(id: 'other_contents').click }.to raise_error(Error::ElementClickInterceptedError) + element = wait_for_element(id: 'other_contents') + expect { element.click }.to raise_error(Error::ElementClickInterceptedError) end it 'raises if element stale' do driver.navigate.to url_for('formPage.html') - button = driver.find_element(id: 'imageButton') + button = wait_for_element(id: 'imageButton') driver.navigate.refresh expect { button.click }.to raise_exception(Error::StaleElementReferenceError, @@ -54,47 +57,48 @@ module WebDriver describe '#submit' do it 'valid submit button' do driver.navigate.to url_for('formPage.html') - driver.find_element(id: 'submitButton').submit + wait_for_element(id: 'submitButton').submit - sleep 0.5 + wait_for_url('resultPage.html') expect(driver.title).to eq('We Arrive Here') end it 'any input element in form' do driver.navigate.to url_for('formPage.html') - driver.find_element(id: 'checky').submit + wait_for_element(id: 'checky').submit - sleep 0.5 + wait_for_url('resultPage.html') expect(driver.title).to eq('We Arrive Here') end it 'any element in form' do driver.navigate.to url_for('formPage.html') - driver.find_element(css: 'form > p').submit + wait_for_element(css: 'form > p').submit - sleep 0.5 + wait_for_url('resultPage.html') expect(driver.title).to eq('We Arrive Here') end it 'button with id submit' do driver.navigate.to url_for('formPage.html') - driver.find_element(id: 'submit').submit + wait_for_element(id: 'submit').submit - sleep 0.5 + wait_for_url('resultPage.html') expect(driver.title).to eq('We Arrive Here') end it 'button with name submit' do driver.navigate.to url_for('formPage.html') - driver.find_element(name: 'submit').submit + wait_for_element(name: 'submit').submit - sleep 0.5 + wait_for_url('resultPage.html') expect(driver.title).to eq('We Arrive Here') end it 'errors with button outside form' do driver.navigate.to url_for('formPage.html') - expect { driver.find_element(name: 'SearchableText').submit }.to raise_error(Error::UnsupportedOperationError) + element = wait_for_element(name: 'SearchableText') + expect { element.submit }.to raise_error(Error::UnsupportedOperationError) end end @@ -113,7 +117,7 @@ module WebDriver it 'sends key presses' do driver.navigate.to url_for('javascriptPage.html') - key_reporter = driver.find_element(id: 'keyReporter') + key_reporter = wait_for_element(id: 'keyReporter') key_reporter.send_keys('Tet', :arrow_left, 's') expect(key_reporter.attribute('value')).to eq('Test') @@ -122,7 +126,7 @@ module WebDriver # https://github.com/mozilla/geckodriver/issues/245 it 'sends key presses chords', except: {browser: %i[firefox safari safari_preview]} do driver.navigate.to url_for('javascriptPage.html') - key_reporter = driver.find_element(id: 'keyReporter') + key_reporter = wait_for_element(id: 'keyReporter') key_reporter.send_keys([:shift, 'h'], 'ello') expect(key_reporter.attribute('value')).to eq('Hello') @@ -131,7 +135,7 @@ module WebDriver it 'handles file uploads' do driver.navigate.to url_for('formPage.html') - element = driver.find_element(id: 'upload') + element = wait_for_element(id: 'upload') expect(element.attribute('value')).to be_empty path = WebDriver::Platform.windows? ? WebDriver::Platform.windows_path(__FILE__) : __FILE__ @@ -145,7 +149,7 @@ module WebDriver before { driver.navigate.to url_for('formPage.html') } context 'when string type' do - let(:element) { driver.find_element(id: 'checky') } + let(:element) { wait_for_element(id: 'checky') } let(:prop_or_attr) { 'type' } it '#dom_attribute returns attribute value' do @@ -162,7 +166,7 @@ module WebDriver end context 'when numeric type' do - let(:element) { driver.find_element(id: 'withText') } + let(:element) { wait_for_element(id: 'withText') } let(:prop_or_attr) { 'rows' } it '#dom_attribute String' do @@ -179,7 +183,7 @@ module WebDriver end context 'with boolean type of true' do - let(:element) { driver.find_element(id: 'checkedchecky') } + let(:element) { wait_for_element(id: 'checkedchecky') } let(:prop_or_attr) { 'checked' } it '#dom_attribute returns String', except: {browser: :safari} do @@ -211,7 +215,7 @@ module WebDriver end context 'with boolean type of false' do - let(:element) { driver.find_element(id: 'checky') } + let(:element) { wait_for_element(id: 'checky') } let(:prop_or_attr) { 'checked' } it '#dom_attribute returns nil' do @@ -243,7 +247,7 @@ module WebDriver end context 'when property exists but attribute does not' do - let(:element) { driver.find_element(id: 'withText') } + let(:element) { wait_for_element(id: 'withText') } let(:prop_or_attr) { 'value' } it '#dom_attribute returns nil' do @@ -270,7 +274,7 @@ module WebDriver end context 'when attribute exists but property does not' do - let(:element) { driver.find_element(id: 'vsearchGadget') } + let(:element) { wait_for_element(id: 'vsearchGadget') } let(:prop_or_attr) { 'accesskey' } it '#dom_attribute returns attribute' do @@ -287,7 +291,7 @@ module WebDriver end context 'when neither attribute nor property exists' do - let(:element) { driver.find_element(id: 'checky') } + let(:element) { wait_for_element(id: 'checky') } let(:prop_or_attr) { 'nonexistent' } it '#dom_attribute returns nil' do @@ -306,7 +310,7 @@ module WebDriver describe 'style' do before { driver.navigate.to url_for('clickEventPage.html') } - let(:element) { driver.find_element(id: 'result') } + let(:element) { wait_for_element(id: 'result') } let(:prop_or_attr) { 'style' } it '#dom_attribute attribute with no formatting' do @@ -327,7 +331,7 @@ module WebDriver end describe 'incorrect casing' do - let(:element) { driver.find_element(id: 'checky') } + let(:element) { wait_for_element(id: 'checky') } let(:prop_or_attr) { 'nAme' } it '#dom_attribute returns correctly cased attribute' do @@ -344,7 +348,7 @@ module WebDriver end describe 'property attribute case difference with attribute casing' do - let(:element) { driver.find_element(name: 'readonly') } + let(:element) { wait_for_element(name: 'readonly') } let(:prop_or_attr) { 'readonly' } it '#dom_attribute returns a String', except: {browser: :safari} do @@ -361,7 +365,7 @@ module WebDriver end describe 'property attribute case difference with property casing' do - let(:element) { driver.find_element(name: 'readonly') } + let(:element) { wait_for_element(name: 'readonly') } let(:prop_or_attr) { 'readOnly' } it '#dom_attribute returns a String', @@ -381,7 +385,7 @@ module WebDriver end describe 'property attribute name difference with attribute naming' do - let(:element) { driver.find_element(id: 'wallace') } + let(:element) { wait_for_element(id: 'wallace') } let(:prop_or_attr) { 'class' } it '#dom_attribute returns attribute value' do @@ -398,7 +402,7 @@ module WebDriver end describe 'property attribute name difference with property naming' do - let(:element) { driver.find_element(id: 'wallace') } + let(:element) { wait_for_element(id: 'wallace') } let(:prop_or_attr) { 'className' } it '#dom_attribute returns nil' do @@ -415,7 +419,7 @@ module WebDriver end describe 'property attribute value difference' do - let(:element) { driver.find_element(tag_name: 'form') } + let(:element) { wait_for_element(tag_name: 'form') } let(:prop_or_attr) { 'action' } it '#dom_attribute returns attribute value' do @@ -449,14 +453,15 @@ module WebDriver it 'clears' do driver.navigate.to url_for('formPage.html') - expect { driver.find_element(id: 'withText').clear }.not_to raise_error + element = wait_for_element(id: 'withText') + expect { element.clear }.not_to raise_error end it 'gets and set selected' do driver.navigate.to url_for('formPage.html') - cheese = driver.find_element(id: 'cheese') - peas = driver.find_element(id: 'peas') + cheese = wait_for_element(id: 'cheese') + peas = wait_for_element(id: 'peas') cheese.click @@ -471,23 +476,26 @@ module WebDriver it 'gets enabled' do driver.navigate.to url_for('formPage.html') - expect(driver.find_element(id: 'notWorking')).not_to be_enabled + element = wait_for_element(id: 'notWorking') + expect(element).not_to be_enabled end it 'gets text' do driver.navigate.to url_for('xhtmlTest.html') - expect(driver.find_element(class: 'header').text).to eq('XHTML Might Be The Future') + element = wait_for_element(class: 'header') + expect(element.text).to eq('XHTML Might Be The Future') end it 'gets displayed' do driver.navigate.to url_for('xhtmlTest.html') - expect(driver.find_element(class: 'header')).to be_displayed + element = wait_for_element(class: 'header') + expect(element).to be_displayed end describe 'size and location' do it 'gets current location' do driver.navigate.to url_for('xhtmlTest.html') - loc = driver.find_element(class: 'header').location + loc = wait_for_element(class: 'header').location expect(loc.x).to be >= 1 expect(loc.y).to be >= 1 @@ -495,7 +503,7 @@ module WebDriver it 'gets location once scrolled into view' do driver.navigate.to url_for('javascriptPage.html') - loc = driver.find_element(id: 'keyUp').location_once_scrolled_into_view + loc = wait_for_element(id: 'keyUp').location_once_scrolled_into_view expect(loc.x).to be >= 1 expect(loc.y).to be >= 0 # can be 0 if scrolled to the top @@ -503,7 +511,7 @@ module WebDriver it 'gets size' do driver.navigate.to url_for('xhtmlTest.html') - size = driver.find_element(class: 'header').size + size = wait_for_element(class: 'header').size expect(size.width).to be_positive expect(size.height).to be_positive @@ -511,7 +519,7 @@ module WebDriver it 'gets rect' do driver.navigate.to url_for('xhtmlTest.html') - rect = driver.find_element(class: 'header').rect + rect = wait_for_element(class: 'header').rect expect(rect.x).to be_positive expect(rect.y).to be_positive @@ -524,8 +532,8 @@ module WebDriver it 'drags and drop', except: {browser: :ie} do driver.navigate.to url_for('dragAndDropTest.html') - img1 = driver.find_element(id: 'test1') - img2 = driver.find_element(id: 'test2') + img1 = wait_for_element(id: 'test1') + img2 = wait_for_element(id: 'test2') driver.action.drag_and_drop_by(img1, 100, 100) .drag_and_drop(img2, img1) @@ -536,7 +544,7 @@ module WebDriver it 'gets css property' do driver.navigate.to url_for('javascriptPage.html') - element = driver.find_element(id: 'green-parent') + element = wait_for_element(id: 'green-parent') style1 = element.css_value('background-color') style2 = element.style('background-color') # backwards compatibility @@ -548,8 +556,8 @@ module WebDriver it 'knows when two elements are equal' do driver.navigate.to url_for('simpleTest.html') - body = driver.find_element(tag_name: 'body') - xbody = driver.find_element(xpath: '//body') + body = wait_for_element(tag_name: 'body') + xbody = wait_for_element(xpath: '//body') jsbody = driver.execute_script('return document.getElementsByTagName("body")[0]') expect(body).to eq(xbody) diff --git a/rb/spec/integration/selenium/webdriver/spec_support/helpers.rb b/rb/spec/integration/selenium/webdriver/spec_support/helpers.rb index ac2110be4f1f9..a3a7f0ac11160 100644 --- a/rb/spec/integration/selenium/webdriver/spec_support/helpers.rb +++ b/rb/spec/integration/selenium/webdriver/spec_support/helpers.rb @@ -84,16 +84,19 @@ def wait_for_no_alert wait.until { driver.title } end - def wait_for_element(locator) - wait = Wait.new(timeout: 25, ignore: Error::NoSuchElementError) + def wait_for_element(locator, timeout = 25) + wait = Wait.new(timeout: timeout, ignore: Error::NoSuchElementError, message_provider: lambda { + url = "page url: #{driver.current_url};\n" + source = "page source: #{driver.find_element(css: 'body').attribute('innerHTML')}\n" + "could not find element #{locator} in #{timeout} seconds;\n#{url}#{source}" + }) wait.until { driver.find_element(locator) } end - def wait_for_new_url(old_url) + def wait_for_url(new_url) wait = Wait.new(timeout: 5) wait.until do - url = driver.current_url - !(url.empty? || url.include?(old_url)) + driver.current_url.include?(new_url) end end @@ -102,6 +105,11 @@ def wait_for_devtools_target(target_type:) wait.until { driver.devtools(target_type: target_type).target } end + def wait_for_title(title:) + wait = Wait.new(timeout: 5) + wait.until { driver.title == title } + end + def wait(timeout = 10) Wait.new(timeout: timeout) end