Skip to content

Commit a447977

Browse files
committed
spec: refactor implicit waiting, fixes SD-1939
[ci rspec profile] basically never use built-in implicit waiting, since it makes everything wait (not just find_element, but attribute lookups, etc.). that means that StaleElementProtection would automatically always take the full 15 seconds, because that's how long we'd wait for the StaleElementReferenceError to come back instead roll our own, which allows those to recover almost immediately, and prevents other unnecessary waiting. disable_implicit_wait is also now much cheaper since it doesn't mean two ruby <-> firefox roundtrips test plan: 1. specs should be faster, e.g. ./spec/selenium/quizzes/quizzes_unpublish_quiz_teacher_spec.rb:20 now takes 10 sec instead of 25, since it always has a StaleElementReferenceError Change-Id: Ice31e64a274930cdf8af4407b5632e961683cdf6 Reviewed-on: https://gerrit.instructure.com/98295 Tested-by: Jenkins Reviewed-by: Landon Wilkins <lwilkins@instructure.com> Product-Review: Landon Wilkins <lwilkins@instructure.com> QA-Review: Landon Wilkins <lwilkins@instructure.com>
1 parent 1df96a5 commit a447977

11 files changed

Lines changed: 101 additions & 104 deletions

spec/selenium/assignments/assignments_external_tool_spec.rb

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@
4949
f('.add_item_button.ui-button').click
5050

5151
expect(f('#assignment_external_tool_tag_attributes_url')).to have_attribute('value', @t2.url)
52-
disable_implicit_wait do # this action results in an expected 404, so don't wait
53-
f("#edit_assignment_form button[type='submit']").click
54-
end
52+
f("#edit_assignment_form button[type='submit']").click
5553

5654
keep_trying_until do # timing issues require waiting
5755
expect(@course.assignments(true).last).to be_present
@@ -78,9 +76,7 @@
7876
expect(f('#context_external_tools_select input#external_tool_create_url')).to have_attribute('value', @t1.url)
7977
f('.add_item_button.ui-button').click
8078
expect(f('#assignment_external_tool_tag_attributes_url')).to have_attribute('value', @t1.url)
81-
disable_implicit_wait do # this action results in an expected 404, so don't wait
82-
f("#edit_assignment_form button[type='submit']").click
83-
end
79+
f("#edit_assignment_form button[type='submit']").click
8480

8581
keep_trying_until do # timing issues require waiting
8682
a.reload

spec/selenium/context_modules_teacher_spec.rb

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -135,19 +135,19 @@ def module_with_two_items
135135

136136
f('.ig-header-admin .al-trigger').click
137137
hover_and_click('#context_modules .edit_module_link')
138-
wait_for_ajax_requests
138+
wait_for_animations
139139
expect(f('#add_context_module_form')).to be_displayed
140140
f('.add_completion_criterion_link').click
141-
assignment_picker = fj('.assignment_picker:visible')
142-
143-
assignment_picker.find_element(:css, "option[value='#{content_tag_1.id}']").click
144-
requirement_picker = fj('.assignment_requirement_picker:visible')
145-
requirement_picker.find_element(:css, 'option[value="min_score"]').click
146-
expect(driver.execute_script('return $(".points_possible_parent:visible").length')).to be > 0
147-
148-
assignment_picker.find_element(:css, "option[value='#{content_tag_2.id}']").click
149-
requirement_picker.find_element(:css, 'option[value="min_score"]').click
150-
expect(driver.execute_script('return $(".points_possible_parent:visible").length')).to eq 0
141+
wait_for_animations
142+
fj(".assignment_picker:visible option[value='#{content_tag_1.id}']").click
143+
wait_for_js # the action above might be changing the element underneath between finding and clicking
144+
fj('.assignment_requirement_picker:visible option[value="min_score"]').click
145+
expect(f("body")).to contain_jqcss(".points_possible_parent:visible")
146+
147+
fj(".assignment_picker:visible option[value='#{content_tag_2.id}']").click
148+
wait_for_js # the action above might be changing the element underneath between finding and clicking
149+
fj('.assignment_requirement_picker:visible option[value="min_score"]').click
150+
expect(f("body")).not_to contain_jqcss(".points_possible_parent:visible")
151151
end
152152

153153
it "should add and remove completion criteria" do

spec/selenium/eportfolios_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@
173173
f("#right-side .edit_content_link").click
174174
wait_for_ajaximations
175175
f('.add_file_link').click
176+
wait_for_animations
176177
fj(".file_upload:visible").send_keys(fullpath)
177178
wait_for_ajaximations
178179
f(".upload_file_button").click

spec/selenium/grades/page_objects/srgb_page.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def submission_details_button
2121
end
2222

2323
def notes_field
24-
f('#student_information textarea')
24+
fj('#student_information textarea:visible:not([disabled])')
2525
end
2626

2727
def final_grade

spec/selenium/helpers/wiki_and_tiny_common.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ def add_image_to_rce
148148
clear_wiki_rce
149149
f('#editor_tabs .ui-tabs-nav li:nth-child(3) a').click
150150
f('.upload_new_image_link').click
151+
wait_for_animations
151152
wiki_page_tools_upload_file('#sidebar_upload_image_form', :image)
152153
in_frame wiki_page_body_ifr_id do
153154
expect(f('#tinymce img')).to be_displayed

spec/selenium/test_setup/common_helper_methods/custom_selenium_actions.rb

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def fln(link_text, scope = nil)
6262
# expect(f('#content')).not_to contain_jqcss('.gone:visible')
6363
def fj(selector, scope = nil)
6464
stale_element_protection do
65-
wait_for(method: :fj, timeout: driver.manage.timeouts.implicit_wait) do
65+
wait_for(method: :fj) do
6666
find_with_jquery selector, scope
6767
end or raise Selenium::WebDriver::Error::NoSuchElementError
6868
end
@@ -73,11 +73,14 @@ def fj(selector, scope = nil)
7373
# like other selenium methods, this will wait until it finds elements on
7474
# the page, and will eventually raise if none are found
7575
def ff(selector, scope = nil)
76-
result = reloadable_collection do
77-
(scope || driver).find_elements :css, selector
76+
reloadable_collection do
77+
result = nil
78+
wait_for(method: :ff) do
79+
result = disable_implicit_wait { (scope || driver).find_elements(:css, selector) }
80+
result.present?
81+
end or raise Selenium::WebDriver::Error::NoSuchElementError
82+
result
7883
end
79-
raise Selenium::WebDriver::Error::NoSuchElementError unless result.present?
80-
result
8184
end
8285

8386
# same as `fj`, but returns all matching elements
@@ -87,7 +90,7 @@ def ff(selector, scope = nil)
8790
def ffj(selector, scope = nil)
8891
reloadable_collection do
8992
result = nil
90-
wait_for(method: :ffj, timeout: driver.manage.timeouts.implicit_wait) do
93+
wait_for(method: :ffj) do
9194
result = find_all_with_jquery(selector, scope)
9295
result.present?
9396
end or raise Selenium::WebDriver::Error::NoSuchElementError

spec/selenium/test_setup/common_helper_methods/custom_validators.rb

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,24 +49,20 @@ def check_element_attrs(element, attrs)
4949

5050
def expect_flash_message(type = :warning, message = nil)
5151
message = Regexp.new(Regexp.escape(message)) if message.is_a?(String)
52-
disable_implicit_wait do
53-
wait_for method: :expect_flash_message, ignore: [Selenium::WebDriver::Error::StaleElementReferenceError] do
54-
messages = driver.find_elements :css, "#flash_message_holder .ic-flash-#{type}"
55-
text = messages.map(&:text).join('\n')
56-
message ? !!text.match(message) : messages.present?
57-
end or raise(RSpec::Expectations::ExpectationNotMetError, "expected flash #{type} message#{message ? " " + message.inspect : ""}, none found")
58-
end
52+
wait_for method: :expect_flash_message, ignore: [Selenium::WebDriver::Error::StaleElementReferenceError] do
53+
messages = disable_implicit_wait { driver.find_elements :css, "#flash_message_holder .ic-flash-#{type}" }
54+
text = messages.map(&:text).join('\n')
55+
message ? !!text.match(message) : messages.present?
56+
end or raise(RSpec::Expectations::ExpectationNotMetError, "expected flash #{type} message#{message ? " " + message.inspect : ""}, none found")
5957
end
6058

6159
def expect_no_flash_message(type = :warning, message = nil)
6260
message = Regexp.new(Regexp.escape(message)) if message.is_a?(String)
63-
disable_implicit_wait do
64-
wait_for method: :expect_no_flash_message, ignore: [Selenium::WebDriver::Error::StaleElementReferenceError] do
65-
messages = driver.find_elements :css, "#flash_message_holder .ic-flash-#{type}"
66-
text = messages.map(&:text).join('\n')
67-
message ? !text.match(message) : messages.empty?
68-
end or raise(RSpec::Expectations::ExpectationNotMetError, "expected no flash #{type} message#{message ? " " + message.inspect : ""}, one was found")
69-
end
61+
wait_for method: :expect_no_flash_message, ignore: [Selenium::WebDriver::Error::StaleElementReferenceError] do
62+
messages = disable_implicit_wait { driver.find_elements :css, "#flash_message_holder .ic-flash-#{type}" }
63+
text = messages.map(&:text).join('\n')
64+
message ? !text.match(message) : messages.empty?
65+
end or raise(RSpec::Expectations::ExpectationNotMetError, "expected no flash #{type} message#{message ? " " + message.inspect : ""}, one was found")
7066
end
7167

7268
def assert_flash_notice_message(okay_message_regex)
@@ -93,7 +89,7 @@ def assert_error_box(selector)
9389
def expect_new_page_load(accept_alert = false)
9490
driver.execute_script("window.INST = window.INST || {}; INST.still_on_old_page = true;")
9591
yield
96-
wait_for do
92+
wait_for method: :expect_new_page_load do
9793
begin
9894
driver.execute_script("return window.INST && INST.still_on_old_page !== true;")
9995
rescue Selenium::WebDriver::Error::UnhandledAlertError, Selenium::WebDriver::Error::UnknownError

spec/selenium/test_setup/common_helper_methods/custom_wait_methods.rb

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def wait_for_tiny(element)
142142
tiny_frame = nil
143143
keep_trying_until {
144144
begin
145-
tiny_frame = parent.find_element(:css, 'iframe')
145+
tiny_frame = disable_implicit_wait { parent.find_element(:css, 'iframe') }
146146
rescue => e
147147
puts "#{e.inspect}"
148148
false
@@ -152,32 +152,27 @@ def wait_for_tiny(element)
152152
end
153153

154154
def disable_implicit_wait
155-
driver.manage.timeouts.implicit_wait = 0
156-
yield
157-
ensure
158-
driver.manage.timeouts.implicit_wait = SeleniumDriverSetup::IMPLICIT_WAIT_TIMEOUT
155+
::SeleniumExtensions::FinderWaiting.disable do
156+
yield
157+
end
159158
end
160159

161160
# little wrapper around Selenium::WebDriver::Wait, notably it:
162161
# * is less verbose
163162
# * returns false (rather than raising) if the block never returns true
164163
# * doesn't rescue :allthethings: like keep_trying_until
165164
# * prevents nested waiting, cuz that's terrible
166-
def wait_for(timeout: SeleniumDriverSetup::IMPLICIT_WAIT_TIMEOUT, method: nil, ignore: nil)
167-
return yield if timeout == 0
168-
driver.prevent_nested_waiting(method) do
169-
Selenium::WebDriver::Wait.new(timeout: timeout, ignore: ignore).until do
170-
yield
171-
end
172-
end
173-
rescue Selenium::WebDriver::Error::TimeOutError
174-
false
165+
def wait_for(*args, &block)
166+
::SeleniumExtensions::FinderWaiting.wait_for(*args, &block)
175167
end
176168

177169
def wait_for_no_such_element(method: nil)
178170
wait_for(method: method, ignore: []) do
179-
yield
180-
false
171+
# so find_element calls return ASAP
172+
disable_implicit_wait do
173+
yield
174+
false
175+
end
181176
end
182177
rescue Selenium::WebDriver::Error::NoSuchElementError
183178
true

spec/selenium/test_setup/custom_selenium_rspec_matchers.rb

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,7 @@
150150
end
151151

152152
match_when_negated do |element|
153-
disable_implicit_wait do # so find_element calls return ASAP
154-
wait_for_no_such_element(method: :contain_css) { f(selector, element) }
155-
end
153+
wait_for_no_such_element(method: :contain_css) { f(selector, element) }
156154
end
157155
end
158156

@@ -193,9 +191,7 @@
193191
end
194192

195193
match_when_negated do |element|
196-
disable_implicit_wait do # so find_element calls return ASAP
197-
wait_for_no_such_element(method: :contain_link) { fln(text, element) }
198-
end
194+
wait_for_no_such_element(method: :contain_link) { fln(text, element) }
199195
end
200196
end
201197

@@ -242,10 +238,8 @@ def supports_block_expectations?
242238

243239
match do |actual|
244240
raise "The `become` matcher expects a block, e.g. `expect { actual }.to become(value)`, NOT `expect(actual).to become(value)`" unless actual.is_a? Proc
245-
disable_implicit_wait do
246-
wait_for(method: :become) do
247-
actual.call == expected
248-
end
241+
wait_for(method: :become) do
242+
disable_implicit_wait { actual.call == expected }
249243
end
250244
end
251245
end

spec/selenium/test_setup/selenium_driver_setup.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ def start_driver
118118

119119
focus_viewport if run_headless?
120120

121-
@driver.manage.timeouts.implicit_wait = IMPLICIT_WAIT_TIMEOUT
121+
@driver.manage.timeouts.implicit_wait = 0 # nothing should wait by default
122+
SeleniumExtensions::FinderWaiting.timeout = IMPLICIT_WAIT_TIMEOUT # except finding elements
122123
@driver.manage.timeouts.script_timeout = 60
123124

124125
puts "Browser: #{browser_name} - #{browser_version}"

0 commit comments

Comments
 (0)