From ca8aa315251ae206d02843b125ee0e652258d186 Mon Sep 17 00:00:00 2001 From: Gabe <41127686+Zidious@users.noreply.github.com> Date: Fri, 17 Dec 2021 18:35:58 +0000 Subject: [PATCH] fix(webdriverio): include/exclude chaining and iframe selectors (#409) --- packages/webdriverio/package-lock.json | 2 +- packages/webdriverio/src/index.ts | 13 ++- packages/webdriverio/src/test.ts | 141 +++++++++++++++++++------ packages/webdriverio/src/types.ts | 4 +- packages/webdriverio/src/utils.ts | 6 +- 5 files changed, 126 insertions(+), 40 deletions(-) diff --git a/packages/webdriverio/package-lock.json b/packages/webdriverio/package-lock.json index db04f909..cdfe6ced 100644 --- a/packages/webdriverio/package-lock.json +++ b/packages/webdriverio/package-lock.json @@ -891,7 +891,7 @@ "integrity": "sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA==" }, "axe-test-fixtures": { - "version": "github:dequelabs/axe-test-fixtures#fdf716904202205c300f24c87c91643ccb6ee19a", + "version": "github:dequelabs/axe-test-fixtures#ecbc704faae02752354aae318f71078efc96ddb6", "from": "github:dequelabs/axe-test-fixtures#v1", "dev": true }, diff --git a/packages/webdriverio/src/index.ts b/packages/webdriverio/src/index.ts index 1224f856..0f5c9f05 100644 --- a/packages/webdriverio/src/index.ts +++ b/packages/webdriverio/src/index.ts @@ -19,14 +19,15 @@ import type { CallbackFunction, WdioBrowser, WdioElement, - PartialResults + PartialResults, + Selector } from './types'; export default class AxeBuilder { private client: Browser<'async'>; private axeSource: string; - private includes: string[] = []; - private excludes: string[] = []; + private includes: Selector[] = []; + private excludes: Selector[] = []; private option: RunOptions = {}; private disableFrameSelectors: string[] = []; private legacyMode = false; @@ -68,7 +69,8 @@ export default class AxeBuilder { * Selector to include in analysis. * This may be called any number of times. */ - public include(selector: string): this { + public include(selector: Selector): this { + selector = Array.isArray(selector) ? selector : [selector]; this.includes.push(selector); return this; } @@ -77,7 +79,8 @@ export default class AxeBuilder { * Selector to exclude in analysis. * This may be called any number of times. */ - public exclude(selector: string): this { + public exclude(selector: Selector): this { + selector = Array.isArray(selector) ? selector : [selector]; this.excludes.push(selector); return this; } diff --git a/packages/webdriverio/src/test.ts b/packages/webdriverio/src/test.ts index 236b00d8..29a0361f 100644 --- a/packages/webdriverio/src/test.ts +++ b/packages/webdriverio/src/test.ts @@ -13,6 +13,7 @@ import delay from 'delay'; import AxeBuilder from '.'; import { logOrRethrowError } from './utils'; import { WdioBrowser } from './types'; +import type { AxeResults } from 'axe-core'; const connectToChromeDriver = (port: number): Promise => { let socket: net.Socket; @@ -853,48 +854,128 @@ describe('@axe-core/webdriverio', () => { }); describe('include/exclude', () => { + const flatPassesTargets = (results: AxeResults): string[] => { + return results.passes + .reduce((acc, pass) => { + return acc.concat(pass.nodes as any); + }, []) + .reduce((acc, node: any) => { + return acc.concat(node.target); + }, []); + }; + it('with include and exclude', async () => { - let error: unknown = null; - await client.url(`${addr}/nested-iframes.html`); - const builder = new AxeBuilder({ client }) - .include('#ifr-foo') - .exclude('#ifr-bar'); + await client.url(`${addr}/context-include-exclude.html`); - try { - await builder.analyze(); - } catch (e) { - error = e; - } + const builder = new AxeBuilder({ client }) + .include('.include') + .exclude('.exclude'); + const results = await builder.analyze(); - assert.strictEqual(error, null); + assert.isTrue(flatPassesTargets(results).includes('.include')); + assert.isFalse(flatPassesTargets(results).includes('.exclude')); }); it('with only include', async () => { - let error: unknown = null; - await client.url(`${addr}/nested-iframes.html`); - const builder = new AxeBuilder({ client }).include('#ifr-foo'); + await client.url(`${addr}/context-include-exclude.html`); - try { - await builder.analyze(); - } catch (e) { - error = e; - } + const builder = new AxeBuilder({ client }).include('.include'); + const results = await builder.analyze(); - assert.strictEqual(error, null); + assert.isTrue(flatPassesTargets(results).includes('.include')); }); - it('wth only exclude', async () => { - let error: unknown = null; - await client.url(`${addr}/nested-iframes.html`); - const builder = new AxeBuilder({ client }).exclude('#ifr-bar'); + it('with only exclude', async () => { + await client.url(`${addr}/context-include-exclude.html`); - try { - await builder.analyze(); - } catch (e) { - error = e; - } + const builder = new AxeBuilder({ client }).exclude('.exclude'); + const results = await builder.analyze(); + + assert.isFalse(flatPassesTargets(results).includes('.exclude')); + }); + + it('with only chaining include', async () => { + await client.url(`${addr}/context-include-exclude.html`); - assert.strictEqual(error, null); + const builder = new AxeBuilder({ client }) + .include('.include') + .include('.include2'); + + const results = await builder.analyze(); + + assert.isTrue(flatPassesTargets(results).includes('.include')); + assert.isTrue(flatPassesTargets(results).includes('.include2')); + }); + + it('with only chaining exclude', async () => { + await client.url(`${addr}/context-include-exclude.html`); + + const builder = new AxeBuilder({ client }) + .exclude('.exclude') + .exclude('.exclude2'); + + const results = await builder.analyze(); + + assert.isFalse(flatPassesTargets(results).includes('.exclude')); + assert.isFalse(flatPassesTargets(results).includes('.exclude2')); + }); + + it('with chaining include and exclude', async () => { + await client.url(`${addr}/context-include-exclude.html`); + + const builder = new AxeBuilder({ client }) + .include('.include') + .include('.include2') + .exclude('.exclude') + .exclude('.exclude2'); + + const results = await builder.analyze(); + + assert.isTrue(flatPassesTargets(results).includes('.include')); + assert.isTrue(flatPassesTargets(results).includes('.include2')); + assert.isFalse(flatPassesTargets(results).includes('.exclude')); + assert.isFalse(flatPassesTargets(results).includes('.exclude2')); + }); + + it('with include and exclude iframes', async () => { + await client.url(`${addr}/context-include-exclude.html`); + + const builder = new AxeBuilder({ client }) + .include(['#ifr-inc-excl', 'html']) + .exclude(['#ifr-inc-excl', '#foo-bar']) + .include(['#ifr-inc-excl', '#foo-baz', 'html']) + .exclude(['#ifr-inc-excl', '#foo-baz', 'input']); + + const results = await builder.analyze(); + const labelResult = results.incomplete.find( + ({ id }) => id === 'label' + ); + + assert.isFalse(flatPassesTargets(results).includes('#foo-bar')); + assert.isFalse(flatPassesTargets(results).includes('input')); + assert.isUndefined(labelResult); + }); + + it('with include and exclude iframes', async () => { + await client.url(`${addr}/context-include-exclude.html`); + + const builder = new AxeBuilder({ client }) + .include(['#ifr-inc-excl', '#foo-baz', 'html']) + .include(['#ifr-inc-excl', '#foo-baz', 'input']) + // does not exist + .include(['#hazaar', 'html']); + + const results = await builder.analyze(); + const labelResult = results.violations.find( + ({ id }) => id === 'label' + ); + assert.isTrue(flatPassesTargets(results).includes('#ifr-inc-excl')); + assert.isTrue(flatPassesTargets(results).includes('#foo-baz')); + assert.isTrue(flatPassesTargets(results).includes('input')); + assert.isFalse(flatPassesTargets(results).includes('#foo-bar')); + // does not exist + assert.isFalse(flatPassesTargets(results).includes('#hazaar')); + assert.isDefined(labelResult); }); }); diff --git a/packages/webdriverio/src/types.ts b/packages/webdriverio/src/types.ts index ce0a3c16..02fc0234 100644 --- a/packages/webdriverio/src/types.ts +++ b/packages/webdriverio/src/types.ts @@ -1,5 +1,5 @@ import type { Browser, MultiRemoteBrowser, Element } from 'webdriverio'; -import type { AxeResults, ElementContext, RunOptions, Spec } from 'axe-core'; +import type { AxeResults, BaseSelector } from 'axe-core'; import * as axe from 'axe-core'; export type WdioBrowser = @@ -32,3 +32,5 @@ declare global { } export type PartialResults = Parameters[0]; + +export type Selector = BaseSelector | BaseSelector[]; diff --git a/packages/webdriverio/src/utils.ts b/packages/webdriverio/src/utils.ts index e2e247b2..371023bf 100644 --- a/packages/webdriverio/src/utils.ts +++ b/packages/webdriverio/src/utils.ts @@ -8,7 +8,7 @@ import type { Spec, PartialResults } from 'axe-core'; -import type { WdioBrowser } from './types'; +import type { Selector, WdioBrowser } from './types'; /** * Validates that the client provided is WebdriverIO v5 or v6. @@ -33,8 +33,8 @@ export const isWebdriverClient = (client: WdioBrowser): boolean => { * Get running context */ export const normalizeContext = ( - includes: string[], - excludes: string[], + includes: Selector[], + excludes: Selector[], disabledFrameSelectors: string[] ): ContextObject => { const base: ContextObject = {