Skip to content

Commit

Permalink
fix(webdriverjs): skip unloaded iframes (#743)
Browse files Browse the repository at this point in the history
* fix(webdriverjs): skip unloaded iframes

* suggestions

* remove By

* Update packages/webdriverjs/src/index.ts

Co-authored-by: Gabe <41127686+Zidious@users.noreply.github.com>

---------

Co-authored-by: Gabe <41127686+Zidious@users.noreply.github.com>
  • Loading branch information
straker and Zidious committed Jun 8, 2023
1 parent 48e222e commit 9cb50eb
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 9 deletions.
37 changes: 28 additions & 9 deletions packages/webdriverjs/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { WebDriver } from 'selenium-webdriver';
import type { WebDriver, WebElement } from 'selenium-webdriver';
import axe, {
RunOptions,
Spec,
Expand Down Expand Up @@ -174,7 +174,17 @@ export default class AxeBuilder {
return this.runLegacy(context);
}

const partials = await this.runPartialRecursive(context, true);
// ensure we fail quickly if an iframe cannot be loaded (instead of waiting
// the default length of 30 seconds)
const { pageLoad } = await this.driver.manage().getTimeouts();
this.driver.manage().setTimeouts({ pageLoad: 1000 });

let partials: string[]
try {
partials = await this.runPartialRecursive(context);
} finally {
this.driver.manage().setTimeouts({ pageLoad });
}

try {
return await this.finishRun(partials);
Expand Down Expand Up @@ -212,9 +222,9 @@ export default class AxeBuilder {
*/
private async runPartialRecursive(
context: SerialContextObject,
initiator = false
frameStack: WebElement[] = []
): Promise<string[]> {
if (!initiator) {
if (frameStack.length) {
await axeSourceInject(this.driver, this.axeSource, this.config);
}
// IMPORTANT: axeGetFrameContext MUST be called before axeRunPartial
Expand All @@ -224,17 +234,25 @@ export default class AxeBuilder {
];

for (const { frameContext, frameSelector, frame } of frameContexts) {
let switchedFrame = false;
try {
assert(frame, `Expect frame of "${frameSelector}" to be defined`);
await this.driver.switchTo().frame(frame);
switchedFrame = true;
partials.push(...(await this.runPartialRecursive(frameContext)));
partials.push(...(await this.runPartialRecursive(frameContext, [...frameStack, frame])));
await this.driver.switchTo().parentFrame();
} catch {
if (switchedFrame) {
await this.driver.switchTo().parentFrame();
/*
When switchTo().frame() fails using chromedriver (but not geckodriver)
it puts the driver into a really bad state that is impossible to
recover from. So we need to switch back to the main window and then
go back to the desired iframe context
*/
const win = await this.driver.getWindowHandle();
await this.driver.switchTo().window(win);

for (const frameElm of frameStack) {
await this.driver.switchTo().frame(frameElm);
}

partials.push('null');
}
}
Expand All @@ -248,6 +266,7 @@ export default class AxeBuilder {
const { driver, axeSource, config, option } = this;

const win = await driver.getWindowHandle();
await driver.switchTo().window(win);

try {
await driver.executeScript(`window.open('about:blank')`);
Expand Down
34 changes: 34 additions & 0 deletions packages/webdriverjs/test/axe-webdriverjs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,40 @@ describe('@axe-core/webdriverjs', () => {
normalResults.testEngine.name = legacyResults.testEngine.name;
assert.deepEqual(normalResults, legacyResults);
});

it('skips unloaded iframes (e.g. loading=lazy)', async () => {
await driver.get(`${addr}/lazy-loaded-iframe.html`);
const title = await driver.getTitle();

const results = await new AxeBuilder(driver)
.options({ runOnly: ['label', 'frame-tested'] })
.analyze();

assert.notEqual(title, 'Error');
assert.equal(results.incomplete[0].id, 'frame-tested');
assert.lengthOf(results.incomplete[0].nodes, 1);
assert.deepEqual(results.incomplete[0].nodes[0].target, ['#parent', '#lazy-iframe']);
assert.equal(results.violations[0].id, 'label');
assert.lengthOf(results.violations[0].nodes, 1);
assert.deepEqual(results.violations[0].nodes[0].target, [
'#parent',
'#child',
'input'
]);
})

it('resets pageLoad timeout to user setting', async () => {
await driver.get(`${addr}/lazy-loaded-iframe.html`);
driver.manage().setTimeouts({ pageLoad: 500 })
const title = await driver.getTitle();

const results = await new AxeBuilder(driver)
.options({ runOnly: ['label', 'frame-tested'] })
.analyze();

const timeout = await driver.manage().getTimeouts();
assert.equal(timeout.pageLoad, 500);
})
});

describe('withRules', () => {
Expand Down
15 changes: 15 additions & 0 deletions packages/webdriverjs/test/fixtures/iframe-lazy-1.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en" id="lazy-loading-iframe-parent">
<head>
<title>Lazy Loading Iframe Parent</title>
</head>
<body>
<main>
<h1>iframe context test</h1>
<div style="margin-top: 300vh">
<iframe id="lazy-iframe" src="https://www.youtube.com/embed/REDRiwmNunA" title="MONTHLY SPOTLIGHT" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" loading="lazy">
</iframe><iframe id="child" title="child" src="iframe-one.html"></iframe>
</div>
</main>
</body>
</html>
14 changes: 14 additions & 0 deletions packages/webdriverjs/test/fixtures/lazy-loaded-iframe.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en" id="lazy-loading-iframe-root">
<head>
<title>Lazy Loading Iframe Root</title>
</head>
<body>
<main>
<h1>iframe context test</h1>
<div style="margin-top: 300vh">
<iframe title="parent" id="parent" src="iframe-lazy-1.html" loading="lazy"></iframe>
</div>
</main>
</body>
</html>

0 comments on commit 9cb50eb

Please sign in to comment.