Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: gracefully handle unloaded iframes #315

Merged
merged 7 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 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 @@ -66,6 +66,11 @@ def recursive_compact(thing)
end
end


def get_check_by_id(check_list, id)
return check_list.find { |check| check.id == id }
end

describe "Crashes" do
it "throws if axe errors out on the top window" do
$driver.get fixture "/crash.html"
Expand Down Expand Up @@ -95,6 +100,26 @@ def recursive_compact(thing)
end

describe "frame tests" do
it "works on pages with unloaded frames" do
$driver.get fixture "/lazy-loaded-iframe.html"
title = $driver.title
res = run_axe
expect(title).not_to eq "Error"
frame_tested = get_check_by_id res.results.incomplete, :'frame-tested'
expect(frame_tested.nodes.length).to be 1
expect(frame_tested.nodes[0].target).to contain_exactly(
"#ifr-lazy",
"#lazy-iframe"
)

label = get_check_by_id res.results.violations, :label
expect(label.nodes.length).to be 1
expect(label.nodes[0].target).to contain_exactly(
"#ifr-lazy",
"#lazy-baz",
"input"
)
end
it "injects into nested iframes", :fo => true do
$driver.get fixture "/nested-iframes.html"
res = run_axe
Expand Down
24 changes: 16 additions & 8 deletions packages/axe-core-api/lib/axe/api/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def call(page)
end

def analyze_post_43x(page, lib)
(get_selenium page).manage.timeouts.page_load = 1
straker marked this conversation as resolved.
Show resolved Hide resolved
@original_window = window_handle page
partial_results = run_partial_recursive(page, @context, lib, true)
throw partial_results if partial_results.respond_to?("key?") and partial_results.key?("errorMessage")
Expand Down Expand Up @@ -108,15 +109,15 @@ def window_handle(page)
page.current_window_handle
end

def run_partial_recursive(page, context, lib, top_level = false)
def run_partial_recursive(page, context, lib, top_level = false, frame_stack = [])
begin
current_window_handle = window_handle page
if not top_level
begin
Common::Loader.new(page, lib).load_top_level Axe::Configuration.instance.jslib
rescue
return [nil]
end

end

frame_contexts = get_frame_context_script page
Expand All @@ -134,12 +135,19 @@ def run_partial_recursive(page, context, lib, top_level = false)
end

for frame_context in frame_contexts
frame_selector = frame_context["frameSelector"]
frame_context = frame_context["frameContext"]
frame = axe_shadow_select page, frame_selector
switch_to_frame_by_handle page, frame
res = run_partial_recursive page, frame_context, lib
results += res
begin
frame_selector = frame_context["frameSelector"]
frame_context = frame_context["frameContext"]
frame = axe_shadow_select page, frame_selector
switch_to_frame_by_handle page, frame
res = run_partial_recursive page, frame_context, lib, false, [*frame_stack, frame]
results += res
rescue Selenium::WebDriver::Error::TimeoutError
page = get_selenium page
page.switch_to.window current_window_handle
frame_stack.each {|frame| page.switch_to.frame frame }
results.push nil
end
end

ensure
Expand Down
15 changes: 15 additions & 0 deletions packages/axe-core-api/lib/axe/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require_relative "../webdriver_script_adapter/query_selector_adapter"
require_relative "../loader"
require_relative "./configuration"
require 'timeout'

module Axe
class Core
Expand Down Expand Up @@ -37,8 +38,22 @@ 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
1 change: 1 addition & 0 deletions packages/axe-core-api/spec/axe/core_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Axe
let(:page) {
spy("page", evaluate_script: false)
}
before { allow(page).to receive(:evaluate_script).and_return(false, true, false) }

describe "initialize" do
# We have removed comments from `axe.min.js`, so excluding this test
Expand Down