diff --git a/README.md b/README.md index 5facc37..474e7be 100644 --- a/README.md +++ b/README.md @@ -314,8 +314,27 @@ await checkA11y( } ) ``` +#### JUnit Report - +``` +await checkA11y( + page, + 'form', + { + axeOptions: { + runOnly: { + type: 'tag', + values: ['wcag2a'], + }, + }, + }, + true, + 'junit', + { + outputFilename: 'junit.xml' + } + ) +``` ## Before you Go If it works for you , leave a [Star](https://github.com/abhinaba-ghosh/axe-playwright)! :star: diff --git a/package.json b/package.json index 846f68b..b034320 100644 --- a/package.json +++ b/package.json @@ -40,9 +40,11 @@ "typescript": "^4.8.4" }, "dependencies": { - "picocolors": "^1.0.0", + "@types/junit-report-builder": "^3.0.0", "axe-core": "^4.5.1", - "axe-html-reporter": "2.2.3" + "axe-html-reporter": "2.2.3", + "junit-report-builder": "^3.0.1", + "picocolors": "^1.0.0" }, "repository": { "type": "git", diff --git a/src/index.ts b/src/index.ts index fd07420..1af4a81 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,12 @@ import { Page } from 'playwright' import * as fs from 'fs' -import { AxeResults, ElementContext, Result, RunOptions, Spec } from 'axe-core' +import axe, { AxeResults, ElementContext, Result, RunOptions, Spec } from 'axe-core' import { getImpactedViolations, testResultDependsOnViolations } from './utils' import DefaultTerminalReporter from './reporter/defaultTerminalReporter' import TerminalReporterV2 from './reporter/terminalReporterV2' import Reporter, { ConfigOptions, AxeOptions } from './types' import { CreateReport, createHtmlReport, Options } from 'axe-html-reporter' +import JUnitReporter from './reporter/junitReporter' declare global { interface Window { @@ -103,7 +104,7 @@ export const checkA11y = async ( context: ElementContext | undefined = undefined, axeOptions: AxeOptions | undefined = undefined, skipFailures: boolean = false, - reporter: Reporter | 'default' | 'html' | 'v2' = 'default', + reporter: Reporter | 'default' | 'html' | 'junit' | 'v2' = 'default', options: Options | undefined = undefined ): Promise => { const violations = await getViolations(page, context, axeOptions?.axeOptions) @@ -127,6 +128,12 @@ export const checkA11y = async ( if (violations.length > 0) { await createHtmlReport({ results: { violations }, options } as CreateReport) } else console.log("There were no violations to save in report"); + } else if (reporter === 'junit') { + reporterWithOptions = new JUnitReporter( + axeOptions?.detailedReport, + page, + axeOptions?.detailedReportOptions?.outputFilename + ) } else { reporterWithOptions = reporter } diff --git a/src/reporter/junitReporter.ts b/src/reporter/junitReporter.ts new file mode 100644 index 0000000..e95054f --- /dev/null +++ b/src/reporter/junitReporter.ts @@ -0,0 +1,69 @@ +import Reporter from '../types' +import { Result } from 'axe-core' +import { Page } from 'playwright' +import builder from 'junit-report-builder'; +import pc from 'picocolors' +import assert from 'assert' + +export default class JUnitReporter implements Reporter { + constructor( + protected verbose: boolean | undefined, + protected page: Page | undefined, + protected outputFilename: string | undefined + ) { + + } + + async report(violations: Result[]) : Promise { + let lineBreak = '\n'; + let pageUrl = this.page?.url() || 'Page'; + let suite = builder.testSuite().name(pageUrl); + + const message = + violations.length === 0 + ? 'No accessibility violations detected!' + : `Found ${violations.length} accessibility violations` + + violations + .map((violation) => { + + const errorBody = violation.nodes + .map((node) => { + const selector = node.target.join(', ') + const expectedText = + `Expected the HTML found at $('${selector}') to have no violations:` + + '\n' + return ( + expectedText + + node.html + + lineBreak + + `Received:\n` + + `${violation.help} (${violation.id})` + + lineBreak + + node.failureSummary + + lineBreak + + (violation.helpUrl + ? `You can find more information on this issue here: \n${violation.helpUrl}` + : '') + + '\n' + ) + }) + .join(lineBreak) + + suite.testCase().className(violation.id).name(violation.description).failure(errorBody) + }) + + const pass = violations.length === 0 + + if (pass) { + builder.testCase().name("Accesibility testing - A11Y"); + this.verbose && console.log(`No accessibility violations detected!`) + } + let location = this.outputFilename || 'a11y-tests.xml'; + builder.writeTo(location); + + if (!pass) { + assert.fail(message) + } + } +} diff --git a/src/types.ts b/src/types.ts index 8341072..c4e9604 100644 --- a/src/types.ts +++ b/src/types.ts @@ -45,6 +45,6 @@ export default interface Reporter { export type AxeOptions = { includedImpacts?: ImpactValue[] detailedReport?: boolean - detailedReportOptions?: { html?: boolean } + detailedReportOptions?: { html?: boolean, outputFilename?: string } verbose?: boolean } & axeOptionsConfig diff --git a/test/a11y.spec.ts b/test/a11y.spec.ts index b97cbb1..a88fb8b 100644 --- a/test/a11y.spec.ts +++ b/test/a11y.spec.ts @@ -1,6 +1,7 @@ import { Browser, chromium, Page } from 'playwright' import { checkA11y, injectAxe } from '../src' import each from 'jest-each' +import fs from 'fs'; let browser: Browser let page: Page @@ -204,4 +205,48 @@ describe('Playwright web page accessibility test using generated html report wit afterEach(async () => { await browser.close() }) +}) + +describe('Playwright web page accessibility test using junit reporter', () => { + each([ + [ + 'on page with no detectable accessibility issues', + `file://${process.cwd()}/test/site-no-accessibility-issues.html`, + ], + ]).it('check a11y %s', async (description, site) => { + const log = jest.spyOn(global.console, 'log') + + browser = await chromium.launch({ args: ['--no-sandbox'] }) + page = await browser.newPage() + await page.goto(site) + await injectAxe(page) + await checkA11y( + page, + 'form', + { + axeOptions: { + runOnly: { + type: 'tag', + values: ['wcag2a'], + }, + }, + }, + false, 'junit', + { + outputDirPath: 'results', + outputDir: 'accessibility', + reportFileName: 'accessibility-audit.html' + } + ) + description === 'on page with detectable accessibility issues' + ? expect.assertions(1) + : expect.assertions(0) + + expect(fs.existsSync("a1y-tests.xml")) + }) + + afterEach(async () => { + await browser.close() + //fs.unlinkSync('a11y-tests.xml') + }) }) \ No newline at end of file