Skip to content

Commit

Permalink
fix: Safari noSuchWindowException and Page/Frame not ready (#385)
Browse files Browse the repository at this point in the history
QA Notes:

The following code occasionally caused Safari to throw a
`noSuchWindowException` around 1 in 10 times, and I expect it never to
do so now.

```
require "selenium-webdriver"
require_relative "../../lib/axe/api/run"
require "json"


driver = Selenium::WebDriver.for :safari
# driver.navigate.to "http://google.com"
driver.navigate.to "https://dequeuniversity.com/demo/mars/"

res = Axe::Core.new(driver).call Axe::API::Run.new.with_options

puts JSON.pretty_generate res.results.to_h

driver.quit
```

Note the commented `# driver.navigate to "http://google.com"`. Issue
#353 notes that attempting to navigate to `http://google.com` would also
occasionally cause an exception "Page/Frame not ready" to be thrown; it
should no longer do so.

Please uncomment this line, comment `driver.navigate.to
"https://dequeuniversity.com/demo/mars/"` and test again. This fix is
included here since they are closely related.

Closes: #352
Closes: #353
  • Loading branch information
scottmries authored Apr 23, 2024
1 parent 02e93ef commit a02013a
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 23 deletions.
45 changes: 45 additions & 0 deletions packages/axe-core-api/e2e/selenium/spec/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,19 @@ def get_check_by_id(check_list, id)
}
end

it "throws an error if the window handle can't be found" do
# this is to cause execute_script to run twice, thus creating one extra window when it's called with `window.open` and making the handle undeterminable
allow($driver).to receive(:execute_script).and_wrap_original do |original_method, *args, &block|
original_method.call(*args, &block)
original_method.call(*args, &block)
end

$driver.get fixture "/index.html"
with_js($axe_post_43x) {
expect { run_axe }.to raise_error /Unable to determine window handle/
}
end

it "works with large results", :nt => true do
$driver.get fixture "/index.html"
res = with_js($axe_post_43x + $large_partial_js) { run_axe }
Expand All @@ -317,6 +330,38 @@ def get_check_by_id(check_list, id)
end
end

describe "does not throw when given" do
it "chromedriver" do
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
driver = Selenium::WebDriver.for :chrome, options: options
expect do
driver.get fixture "/index.html"
driver.get "about:blank"
end.not_to raise_error
end

it "geckodriver" do
options = Selenium::WebDriver::Firefox::Options.new
options.add_argument('--headless')
driver = Selenium::WebDriver.for :firefox, options: options
expect do
driver.get fixture "/index.html"
driver.get "about:blank"
end.not_to raise_error
end

if Object::RUBY_PLATFORM =~ /darwin/i
it "safaridriver" do
driver = Selenium::WebDriver.for :safari
expect do
driver.get fixture "/index.html"
driver.get "about:blank"
end.not_to raise_error
end
end
end

describe "run vs runPartial" do
it "should return the same results" do
Expand Down
20 changes: 13 additions & 7 deletions packages/axe-core-api/lib/axe/api/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,28 @@ def switch_to_parent_frame(page)

def within_about_blank_context(page)
driver = get_selenium page
# This is a workaround to maintain Selenium 3 support
# Likely driver.switch_to.new_window(:tab) should be used instead, should we drop support, as per
# https://github.com/dequelabs/axe-core-gems/issues/352

num_handles = page.window_handles.length
before_handles = page.window_handles
begin
driver.execute_script("window.open('about:blank'), '_blank'")
if num_handles == page.window_handles.length
raise StandardError.new "Could not open new window. Please make sure that you have popup blockers disabled."
end
driver.switch_to.window page.window_handles[-1]
rescue
raise StandardError.new "switchToWindow failed. Are you using updated browser drivers? Please check out https://github.com/dequelabs/axe-core-gems/blob/develop/error-handling.md"
raise StandardError.new "switchToWindow failed. Are you using updated browser drivers? Please check out https://github.com/dequelabs/axe-core-gems/blob/develop/error-handling.md"
end
after_handles = page.window_handles
new_handles = after_handles.difference(before_handles)
if new_handles.length != 1
raise StandardError.new "Unable to determine window handle"
end
new_handle = new_handles[0]
driver.switch_to.window new_handle
driver.get "about:blank"

ret = yield page

driver.switch_to.window page.window_handles[-1]
driver.switch_to.window new_handle
driver.close
driver.switch_to.window @original_window

Expand Down
14 changes: 0 additions & 14 deletions packages/axe-core-api/lib/axe/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,8 @@ def use_run_partial
Core.has_run_partial?(@page) and not Axe::Configuration.instance.legacy_mode
end

def assert_frame_ready
begin
ready = Timeout.timeout(10) {
@page.evaluate_script <<-JS
document.readyState === 'complete'
JS
}
rescue Timeout::Error
ready = false
end
raise Exception.new "Page/frame not ready" if not ready
end

def load_axe_core(source)
return if already_loaded?
assert_frame_ready
loader = Common::Loader.new(@page, self)
loader.load_top_level source
return if use_run_partial
Expand Down
11 changes: 9 additions & 2 deletions packages/axe-core-api/spec/axe/core_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ module Axe
let(:page) {
spy("page", evaluate_script: false)
}
before { allow(page).to receive(:evaluate_script).and_return(false, true, false) }

before { allow(page).to receive(:evaluate_script).and_return(false, 'complete', false) }
describe "initialize" do
# We have removed comments from `axe.min.js`, so excluding this test
# Hence cannot do start_with("/*! aXe"), instead do a function we know should exist check
Expand All @@ -25,6 +24,14 @@ module Axe
expect(page).not_to have_received(:execute_script)
end
end

context "when document.readyState is interactive" do
before { allow(page).to receive(:evaluate_script).and_return(false, 'interactive', 'interactive', 'complete') }
it "should check ready frame until complete, then proceed" do
described_class.new(page)
expect(page).to have_received(:execute_script).with(a_string_including ("axe.run="))
end
end
end

describe "call" do
Expand Down

0 comments on commit a02013a

Please sign in to comment.