From 38a15d46bf21d4ba9855b6595a28c1e4483f0202 Mon Sep 17 00:00:00 2001 From: Dan Bjorge Date: Fri, 28 Jun 2019 13:50:44 -0700 Subject: [PATCH] fix: populate axe-core version based on input rather than hardcoding version 3.2.2 (#68) * Adds `axeVersion` to `EnvironmentData` * Populates it appropriately in `environment-data-provider` * Uses it in `axe-tool-property-provider` in place of hardcoded version * Updates converters to pass environment data to the provider * Updates all tests accordingly --- src/__snapshots__/index.test.ts.snap | 10 +++--- src/artifact-property-provider.test.ts | 2 ++ src/axe-raw-sarif-converter.test.ts | 7 ++-- src/axe-raw-sarif-converter.ts | 6 ++-- src/axe-tool-property-provider.test.ts | 17 ++++++--- src/axe-tool-property-provider.ts | 14 +++++--- src/environment-data-provider.test.ts | 31 ++++++++++++++-- src/environment-data-provider.ts | 14 ++++++++ src/environment-data.ts | 1 + src/index.test.ts | 10 ++++-- src/invocation-provider.test.ts | 1 + src/sarif-converter.test.ts | 49 ++++++++++++++++++++------ src/sarif-converter.ts | 6 ++-- 13 files changed, 132 insertions(+), 36 deletions(-) diff --git a/src/__snapshots__/index.test.ts.snap b/src/__snapshots__/index.test.ts.snap index 2b821933..aab995e4 100644 --- a/src/__snapshots__/index.test.ts.snap +++ b/src/__snapshots__/index.test.ts.snap @@ -14881,7 +14881,7 @@ Fix any of the following: } `; -exports[`public convertAxeToSarif API converts empty/minimal axe v2 input with no results to the pinned minimal SARIF output 1`] = ` +exports[`public convertAxeToSarif API converts minimal axe v2 input with no results to the pinned minimal SARIF output 1`] = ` Object { "runs": Array [ Object { @@ -15560,15 +15560,15 @@ Object { ], "tool": Object { "driver": Object { - "downloadUri": "https://www.npmjs.com/package/axe-core/v/3.2.2", - "fullName": "axe for Web v3.2.2", + "downloadUri": "https://www.npmjs.com/package/axe-core/v/1.2.3", + "fullName": "axe for Web v1.2.3", "informationUri": "https://www.deque.com/axe/axe-for-web/", "name": "axe-core", "properties": Object { "microsoft/qualityDomain": "Accessibility", }, "rules": Array [], - "semanticVersion": "3.2.2", + "semanticVersion": "1.2.3", "shortDescription": Object { "text": "An open source accessibility rules library for automated testing.", }, @@ -15579,7 +15579,7 @@ Object { "name": "WCAG", }, ], - "version": "3.2.2", + "version": "1.2.3", }, }, }, diff --git a/src/artifact-property-provider.test.ts b/src/artifact-property-provider.test.ts index 010a9207..3ba9c17d 100644 --- a/src/artifact-property-provider.test.ts +++ b/src/artifact-property-provider.test.ts @@ -9,12 +9,14 @@ describe('artifact-property-provider', () => { const targetPageUrl: string = 'target_page_url_stub'; const targetPageTitle: string = 'target_page_title_stub'; const timestamp: string = 'timestamp_stub'; + const axeVersion: string = 'axe_version_stub'; it('returns artifact object with the provided environment data', () => { const environmentData: EnvironmentData = { targetPageUrl: targetPageUrl, targetPageTitle: targetPageTitle, timestamp: timestamp, + axeVersion: axeVersion, }; const expectedResults: Sarif.Artifact = { diff --git a/src/axe-raw-sarif-converter.test.ts b/src/axe-raw-sarif-converter.test.ts index d3327f3e..0ba8a31b 100644 --- a/src/axe-raw-sarif-converter.test.ts +++ b/src/axe-raw-sarif-converter.test.ts @@ -48,6 +48,7 @@ describe('AxeRawSarifConverter', () => { const environmentDataStub: EnvironmentData = { timestamp: axeResult.timestamp, targetPageUrl: axeResult.url, + axeVersion: axeResult.testEngine.version, }; const axeRawToSarifOutput = testSubject.convert( @@ -127,10 +128,10 @@ describe('AxeRawSarifConverter', () => { it('outputs a sarif log whose run uses the axeToolPropertyProvider to populate the tool property', () => { const axeToolPropertyProviderMock: IMock< - () => Sarif.ToolComponent + (environmentData: EnvironmentData) => Sarif.ToolComponent > = Mock.ofInstance(getAxeToolProperties); axeToolPropertyProviderMock - .setup(ap => ap()) + .setup(ap => ap(stubEnvironmentData)) .returns(() => stubToolProperties['driver']) .verifiable(Times.once()); @@ -229,7 +230,7 @@ describe('AxeRawSarifConverter', () => { (environmentData: EnvironmentData) => Sarif.Artifact > = Mock.ofInstance(getArtifactProperties); artifactPropertyProviderMock - .setup(ap => ap(It.isAny())) + .setup(ap => ap(stubEnvironmentData)) .returns(() => stubArtifactProperties) .verifiable(Times.once()); diff --git a/src/axe-raw-sarif-converter.ts b/src/axe-raw-sarif-converter.ts index 459a03f1..ecdd7c4a 100644 --- a/src/axe-raw-sarif-converter.ts +++ b/src/axe-raw-sarif-converter.ts @@ -37,7 +37,9 @@ export class AxeRawSarifConverter { ); public constructor( private getConverterToolProperties: () => Sarif.Run['conversion'], - private getAxeProperties: () => Sarif.ToolComponent, + private getAxeProperties: ( + environmentData: EnvironmentData, + ) => Sarif.ToolComponent, private invocationConverter: ( environmentData: EnvironmentData, ) => Sarif.Invocation[], @@ -71,7 +73,7 @@ export class AxeRawSarifConverter { conversion: this.getConverterToolProperties(), tool: { driver: { - ...this.getAxeProperties(), + ...this.getAxeProperties(environmentData), rules: resultToRuleConverter.getRulePropertiesFromResults(), }, }, diff --git a/src/axe-tool-property-provider.test.ts b/src/axe-tool-property-provider.test.ts index 8c9b66c3..c4560cc1 100644 --- a/src/axe-tool-property-provider.test.ts +++ b/src/axe-tool-property-provider.test.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import * as Sarif from 'sarif'; import { getAxeToolProperties } from './axe-tool-property-provider'; +import { EnvironmentData } from './environment-data'; import { getWcagTaxonomyReference } from './wcag-taxonomy-provider'; describe('axe-tool-property-provider', () => { @@ -9,22 +10,28 @@ describe('axe-tool-property-provider', () => { it('returns the axe properties as a Sarif.ToolComponent', () => { const expectedResults: Sarif.ToolComponent = { name: 'axe-core', - fullName: 'axe for Web v3.2.2', + fullName: 'axe for Web v1.2.3', shortDescription: { text: 'An open source accessibility rules library for automated testing.', }, - version: '3.2.2', - semanticVersion: '3.2.2', + version: '1.2.3', + semanticVersion: '1.2.3', informationUri: 'https://www.deque.com/axe/axe-for-web/', - downloadUri: 'https://www.npmjs.com/package/axe-core/v/3.2.2', + downloadUri: 'https://www.npmjs.com/package/axe-core/v/1.2.3', properties: { 'microsoft/qualityDomain': 'Accessibility', }, supportedTaxonomies: [getWcagTaxonomyReference()], }; - const actualResults: Sarif.ToolComponent = getAxeToolProperties(); + const stubEnvironmentData = { + axeVersion: '1.2.3', + } as EnvironmentData; + + const actualResults: Sarif.ToolComponent = getAxeToolProperties( + stubEnvironmentData, + ); expect(actualResults).toEqual(expectedResults); }); }); diff --git a/src/axe-tool-property-provider.ts b/src/axe-tool-property-provider.ts index 1ec3b052..aeb08514 100644 --- a/src/axe-tool-property-provider.ts +++ b/src/axe-tool-property-provider.ts @@ -1,20 +1,24 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import * as Sarif from 'sarif'; +import { EnvironmentData } from './environment-data'; import { getWcagTaxonomyReference } from './wcag-taxonomy-provider'; -export function getAxeToolProperties(): Sarif.ToolComponent { +export function getAxeToolProperties( + environmentData: EnvironmentData, +): Sarif.ToolComponent { + const version = environmentData.axeVersion; return { name: 'axe-core', - fullName: 'axe for Web v3.2.2', + fullName: `axe for Web v${version}`, shortDescription: { text: 'An open source accessibility rules library for automated testing.', }, - version: '3.2.2', - semanticVersion: '3.2.2', + version: version, + semanticVersion: version, informationUri: 'https://www.deque.com/axe/axe-for-web/', - downloadUri: 'https://www.npmjs.com/package/axe-core/v/3.2.2', + downloadUri: `https://www.npmjs.com/package/axe-core/v/${version}`, properties: { 'microsoft/qualityDomain': 'Accessibility', }, diff --git a/src/environment-data-provider.test.ts b/src/environment-data-provider.test.ts index 5b56dc48..1095963c 100644 --- a/src/environment-data-provider.test.ts +++ b/src/environment-data-provider.test.ts @@ -12,16 +12,20 @@ describe('environment-data-provider', () => { it('returns an EnvironmentData object from environment data extracted from AxeResults', () => { const stubTimestamp: string = 'stub_timestamp'; const stubTargetPageUrl: string = 'https://example.com'; + const stubAxeVersion: string = 'stub_axe_version'; const stubAxeResults: Axe.AxeResults = { url: stubTargetPageUrl, timestamp: stubTimestamp, + testEngine: { + name: 'stub_axe_name', + version: stubAxeVersion, + }, passes: [], incomplete: [], violations: [], inapplicable: [], toolOptions: {} as Axe.RunOptions, - testEngine: {} as Axe.TestEngine, testRunner: {} as Axe.TestRunner, testEnvironment: {} as Axe.TestEnvironment, }; @@ -29,6 +33,7 @@ describe('environment-data-provider', () => { const expectedResults: EnvironmentData = { timestamp: stubTimestamp, targetPageUrl: stubTargetPageUrl, + axeVersion: stubAxeVersion, }; const actualResults = getEnvironmentDataFromResults(stubAxeResults); @@ -36,12 +41,24 @@ describe('environment-data-provider', () => { }); }); - describe('getEnvironmentDataFromEnvironment', () => { + describe('getEnvironmentDataFromEnvironment in axe-like environment', () => { + beforeAll(() => { + (global as any).axe = { version: 'stub_axe_version' }; + }); + + afterAll(() => { + delete (global as any).axe; + }); + it('returns an EnvironmentData object from environment data extracted from the environment', () => { const actualResults = getEnvironmentDataFromEnvironment(); expect(actualResults).toHaveProperty('timestamp'); expect(actualResults).toHaveProperty('targetPageUrl'); expect(actualResults).toHaveProperty('targetPageTitle'); + expect(actualResults).toHaveProperty( + 'axeVersion', + 'stub_axe_version', + ); }); it('contains a timestamp with a valid ISO format', () => { @@ -57,4 +74,14 @@ describe('environment-data-provider', () => { expect(actualTimestamp).toBe(expectedTimestamp); }); }); + + describe('getEnvironmentDataFromEnvironment outside of axe-like environment', () => { + it('throws a descriptive error when it cannot infer axe version', () => { + expect( + getEnvironmentDataFromEnvironment, + ).toThrowErrorMatchingInlineSnapshot( + `"Could not infer axe version from global axe object. Are you running from the context of an axe reporter?"`, + ); + }); + }); }); diff --git a/src/environment-data-provider.ts b/src/environment-data-provider.ts index 73690e59..85e27f29 100644 --- a/src/environment-data-provider.ts +++ b/src/environment-data-provider.ts @@ -9,13 +9,27 @@ export function getEnvironmentDataFromResults( return { timestamp: axeResults.timestamp, targetPageUrl: axeResults.url, + axeVersion: axeResults.testEngine.version, }; } export function getEnvironmentDataFromEnvironment(): EnvironmentData { + // We use the global "axe" object to detect this rather than saying + // "import { version } from 'axe-core'"" because we want to pick up the + // version of axe that is invoking us, which will usually be a *peer* + // dependency, rather than using the version that axe-sarif-converter built + // against as a child dependency. + const globalAxeVersion = (global as any).axe && (global as any).axe.version; + if (!globalAxeVersion) { + throw Error( + 'Could not infer axe version from global axe object. Are you running from the context of an axe reporter?', + ); + } + return { timestamp: new Date().toISOString(), targetPageUrl: window.location.href, targetPageTitle: document.title, + axeVersion: globalAxeVersion, }; } diff --git a/src/environment-data.ts b/src/environment-data.ts index 0df42dd4..ad6fcfc2 100644 --- a/src/environment-data.ts +++ b/src/environment-data.ts @@ -4,4 +4,5 @@ export interface EnvironmentData { targetPageUrl: string; targetPageTitle?: string; timestamp: string; + axeVersion: string; } diff --git a/src/index.test.ts b/src/index.test.ts index 271d9a2a..e29e18e8 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -24,10 +24,13 @@ function readTestResourceJSON(testResourceFileName: string): any { } describe('public convertAxeToSarif API', () => { - it('converts empty/minimal axe v2 input with no results to the pinned minimal SARIF output', () => { + it('converts minimal axe v2 input with no results to the pinned minimal SARIF output', () => { + // "Minimal" means "just enough for the converter to infer required EnvironmentData" const minimalAxeV2Input: AxeResults = { toolOptions: {} as RunOptions, - testEngine: {} as TestEngine, + testEngine: { + version: '1.2.3', + } as TestEngine, testRunner: {} as TestRunner, testEnvironment: {} as TestEnvironment, url: 'https://example.com', @@ -63,6 +66,9 @@ describe('public convertAxeToSarif API', () => { }); }); +// This initializes global state that the reporter API assumes is available +require('axe-core'); + describe('public sarifReporter API', () => { const emptyAxeRunOptions = {}; diff --git a/src/invocation-provider.test.ts b/src/invocation-provider.test.ts index 79243b9b..a2460d6c 100644 --- a/src/invocation-provider.test.ts +++ b/src/invocation-provider.test.ts @@ -11,6 +11,7 @@ describe('invocation-provider', () => { targetPageUrl: 'https://example.com', targetPageTitle: 'Environment Data Stub', timestamp: '2018-03-23T21:36:58.321Z', + axeVersion: 'stub_version', }; const invocationStub: Sarif.Invocation[] = [ { diff --git a/src/sarif-converter.test.ts b/src/sarif-converter.test.ts index 72e65717..4df12c5f 100644 --- a/src/sarif-converter.test.ts +++ b/src/sarif-converter.test.ts @@ -34,9 +34,11 @@ describe('SarifConverter', () => { ]; const stubTimestamp: string = 'stub_timestamp'; const stubTargetPageUrl: string = 'stub_url'; + const stubAxeVersion: string = 'stub_axe_version'; const stubEnvironmentData: EnvironmentData = { timestamp: stubTimestamp, targetPageUrl: stubTargetPageUrl, + axeVersion: stubAxeVersion, }; const converterPropertyProviderStub: () => Sarif.Run['conversion'] = () => { @@ -54,14 +56,24 @@ describe('SarifConverter', () => { it('outputs a sarif log whose run uses the axeToolPropertyProvider to populate the tool property', () => { const axeToolPropertyProviderMock: IMock< - () => Sarif.ToolComponent + (environmentData: EnvironmentData) => Sarif.ToolComponent > = Mock.ofInstance(getAxeToolProperties); axeToolPropertyProviderMock - .setup(ap => ap()) + .setup(ap => + ap( + It.isObjectWith({ + axeVersion: stubAxeVersion, + } as EnvironmentData), + ), + ) .returns(() => stubToolProperties['driver']) .verifiable(Times.once()); - const irrelevantResults: Axe.AxeResults = {} as Axe.AxeResults; + const stubAxeResults: Axe.AxeResults = { + testEngine: { + version: stubAxeVersion, + }, + } as Axe.AxeResults; const irrelevantOptions: ConverterOptions = {}; const testSubject = new SarifConverter( @@ -72,7 +84,7 @@ describe('SarifConverter', () => { ); const actualResults = testSubject.convert( - irrelevantResults, + stubAxeResults, irrelevantOptions, ); @@ -93,7 +105,9 @@ describe('SarifConverter', () => { inapplicable: [], incomplete: [], toolOptions: {} as Axe.RunOptions, - testEngine: {} as Axe.TestEngine, + testEngine: { + version: stubAxeVersion, + } as Axe.TestEngine, testRunner: {} as Axe.TestRunner, testEnvironment: {} as Axe.TestEnvironment, }; @@ -138,7 +152,11 @@ describe('SarifConverter', () => { .returns(() => stubConverterProperties) .verifiable(Times.once()); - const irrelevantResults: Axe.AxeResults = {} as Axe.AxeResults; + const stubAxeResults: Axe.AxeResults = { + testEngine: { + version: stubAxeVersion, + }, + } as Axe.AxeResults; const irrelevantOptions: ConverterOptions = {}; const testSubject = new SarifConverter( @@ -149,7 +167,7 @@ describe('SarifConverter', () => { ); const actualResults = testSubject.convert( - irrelevantResults, + stubAxeResults, irrelevantOptions, ); @@ -166,11 +184,22 @@ describe('SarifConverter', () => { (environmentData: EnvironmentData) => Sarif.Artifact > = Mock.ofInstance(getArtifactProperties); artifactPropertyProviderMock - .setup(ap => ap(It.isAny())) + .setup(ap => + ap( + It.isObjectWith({ + targetPageUrl: stubTargetPageUrl, + } as EnvironmentData), + ), + ) .returns(() => stubArtifactProperties) .verifiable(Times.once()); - const irrelevantResults: Axe.AxeResults = {} as Axe.AxeResults; + const stubAxeResults: Axe.AxeResults = { + testEngine: { + version: stubAxeVersion, + }, + url: stubTargetPageUrl, + } as Axe.AxeResults; const irrelevantOptions: ConverterOptions = {}; const testSubject = new SarifConverter( @@ -181,7 +210,7 @@ describe('SarifConverter', () => { ); const actualResults = testSubject.convert( - irrelevantResults, + stubAxeResults, irrelevantOptions, ); diff --git a/src/sarif-converter.ts b/src/sarif-converter.ts index fef61a58..2118d3ef 100644 --- a/src/sarif-converter.ts +++ b/src/sarif-converter.ts @@ -37,7 +37,9 @@ export class SarifConverter { public constructor( private getConverterToolProperties: () => Sarif.Run['conversion'], - private getAxeProperties: () => Sarif.ToolComponent, + private getAxeProperties: ( + environmentData: EnvironmentData, + ) => Sarif.ToolComponent, private invocationConverter: ( environmentData: EnvironmentData, ) => Sarif.Invocation[], @@ -74,7 +76,7 @@ export class SarifConverter { conversion: this.getConverterToolProperties(), tool: { driver: { - ...this.getAxeProperties(), + ...this.getAxeProperties(environmentData), rules: resultToRuleConverter.getRulePropertiesFromResults(), }, },