Skip to content

Commit

Permalink
fix: gracefully handle unloaded iframes (#315)
Browse files Browse the repository at this point in the history
  • Loading branch information
AdnoC authored Aug 24, 2023
1 parent 3fef592 commit bef7d97
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 30 deletions.
36 changes: 35 additions & 1 deletion packages/axe-core-api/e2e/selenium/spec/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,16 @@ 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"
with_js($axe_post_43x + $crasher_js) {
expect { run_axe }.to raise_error /Boom!/
expect { run_axe }.to raise_error /Boom!/
}
end

Expand All @@ -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 Expand Up @@ -253,6 +278,15 @@ def recursive_compact(thing)

expect(res.results.url).to eq(fixture "/index.html")
end

it "keeps selenium page-load to user-set" do
my_page_load = 3.0
$driver.manage.timeouts.page_load = my_page_load
$driver.get fixture "/index.html"
res = run_axe

expect($driver.manage.timeouts.page_load).to be my_page_load
end
end

describe "axe.finishRun" do
Expand Down
72 changes: 43 additions & 29 deletions packages/axe-core-api/lib/axe/api/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,34 @@ def call(page)
end

def analyze_post_43x(page, lib)
@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")
results = within_about_blank_context(page) { |page|
partial_res_str = partial_results.to_json
size_limit = 10_000_000
while not partial_res_str.empty? do
chunk_size = size_limit
chunk_size = partial_res_str.length if chunk_size > partial_res_str.length
chunk = partial_res_str[0..chunk_size-1]
partial_res_str = partial_res_str[chunk_size..-1]
store_chunk page, chunk
end

Common::Loader.new(page, lib).load_top_level Axe::Configuration.instance.jslib
begin
axe_finish_run page
rescue
raise StandardError.new "axe.finishRun failed. Please check out https://github.com/dequelabs/axe-core-gems/blob/develop/error-handling.md"
end
}
user_page_load = (get_selenium page).manage.timeouts.page_load
(get_selenium page).manage.timeouts.page_load = 1
begin
@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")
results = within_about_blank_context(page) { |page|
partial_res_str = partial_results.to_json
size_limit = 10_000_000
while not partial_res_str.empty? do
chunk_size = size_limit
chunk_size = partial_res_str.length if chunk_size > partial_res_str.length
chunk = partial_res_str[0..chunk_size-1]
partial_res_str = partial_res_str[chunk_size..-1]
store_chunk page, chunk
end

Common::Loader.new(page, lib).load_top_level Axe::Configuration.instance.jslib
begin
axe_finish_run page
rescue
raise StandardError.new "axe.finishRun failed. Please check out https://github.com/dequelabs/axe-core-gems/blob/develop/error-handling.md"
end

}
ensure
(get_selenium page).manage.timeouts.page_load = user_page_load
end
Audit.new to_js, Results.new(results)
end

Expand Down Expand Up @@ -108,15 +115,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 +141,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

0 comments on commit bef7d97

Please sign in to comment.