import * as Axe from 'axe-core' ; import { Browser , Frame , JSONObject , Page } from 'puppeteer' ; import { pageIsLoaded , runAxe } from './browser' ; import { AnalyzeCB } from './types' ; function arrayify < T > ( src ) { if ( ! Array . isArray ( src ) ) { return [ src ] ; } return src ; } function injectAxeModule ( frame ) { return frame . addScriptTag ( { path : require . resolve ( 'axe-core' ) } ) ; } function injectAxeString ( frame , source ) { return frame . evaluate ( source ) ; } function injectAxeChild ( frame , source ? ) { const injections = frame . childFrames ( ) . map ( subFrame => injectAxeChild ( subFrame , source ) ) . reduce ( ( acc , arr ) => acc . concat ( arr ) , [ ] ) ; const reportError = ( ) => { console . error ( `Failed to inject axe-core into frame (${ frame . url ( ) })` ) ; } ; let injectP ; if ( ! source ) { injectP = injectAxeModule ( frame ) ; } else { injectP = injectAxeString ( frame , source ) ; } injections . push ( injectP . catch ( reportError ) ) ; return injections ; } function injectAxe ( frame , source ? ) { const injections = frame . childFrames ( ) . map ( subFrame => injectAxeChild ( subFrame , source ) ) . reduce ( ( acc , arr ) => acc . concat ( arr ) , [ ] ) ; let injectP ; if ( ! source ) { injectP = injectAxeModule ( frame ) ; } else { injectP = injectAxeString ( frame , source ) ; } injections . push ( injectP ) ; return Promise . all ( injections ) . then ( ( ) => undefined ) ; } function isPage ( pageFrame ) { return ( pageFrame as any ) . mainFrame !== undefined ; } function getFrame ( pageFrame ) { if ( isPage ( pageFrame ) ) { return pageFrame . mainFrame ( ) ; } return pageFrame ; } async function ensureFrameReady ( frame ) { await frame . waitForSelector ( 'html' ) ; const pageReady = await frame . evaluate ( pageIsLoaded ) ; if ( ! pageReady ) { throw new Error ( 'Page/Frame is not ready' ) ; } } function normalizeContext ( includes , excludes ) { if ( ! excludes . length && ! includes . length ) { return null ; } const ctx = { } ; if ( excludes . length ) { ctx . exclude = excludes ; } if ( includes . length ) { ctx . include = includes ; } return ctx ; } export class AxePuppeteer { private frame : Frame ; private source ? : string ; private includes : string [ ] [ ] ; private excludes : string [ ] [ ] ; private axeOptions : Axe . RunOptions | null ; private config : Axe . Spec | null ; constructor ( pageFrame , source ? ) { this . frame = getFrame ( pageFrame ) ; this . source = source ; this . includes = [ ] ; this . excludes = [ ] ; this . axeOptions = null ; this . config = null ; } public include ( selector ) { selector = arrayify ( selector ) ; this . includes . push ( selector ) ; return this ; } public exclude ( selector ) { selector = arrayify ( selector ) ; this . excludes . push ( selector ) ; return this ; } public options ( options ) { this . axeOptions = options ; return this ; } public withRules ( rules ) { rules = arrayify ( rules ) ; if ( ! this . axeOptions ) { this . axeOptions = { } ; } this . axeOptions . runOnly = { type : 'rule' , values : rules } ; return this ; } public withTags ( tags ) { tags = arrayify ( tags ) ; if ( ! this . axeOptions ) { this . axeOptions = { } ; } this . axeOptions . runOnly = { type : 'tag' , values : tags } ; return this ; } public disableRules ( rules ) { rules = arrayify ( rules ) ; if ( ! this . axeOptions ) { this . axeOptions = { } ; } interface IRulesObj { [ id ] : { enabled : boolean ; } ; } const newRules = { } ; for ( const rule of rules ) { newRules [ rule ] = { enabled : false } ; } this . axeOptions . rules = newRules ; return this ; } public configure ( config ) { if ( typeof ( config as any ) !== 'object' ) { throw new Error ( 'AxePuppeteer needs an object to configure. See axe-core configure API.' ) ; } this . config = config ; return this ; } public async analyze ( ) ; public async analyze < T extends AnalyzeCB > ( callback ? ) ; public async analyze < T extends AnalyzeCB > ( callback ? ) { try { await ensureFrameReady ( this . frame ) ; await injectAxe ( this . frame , this . source ) ; const context = normalizeContext ( this . includes , this . excludes ) ; const axeResults = await this . frame . evaluate ( runAxe , this . config as JSONObject , context as JSONObject , this . axeOptions as JSONObject ) ; if ( callback ) { callback ( null , axeResults ) ; } return axeResults ; } catch ( err ) { if ( callback ) { callback ( err ) ; return null ; } throw err ; } } }