200 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			200 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
/**
 | 
						|
 * WCAG 2.1 AAA compliance test using Playwright and axe-core
 | 
						|
 * 
 | 
						|
 * This script uses Playwright to load pages and axe-core to test them for accessibility issues.
 | 
						|
 * It's an alternative to the axe-test.js script that properly injects axe-core into the page.
 | 
						|
 */
 | 
						|
 | 
						|
const { chromium } = require('playwright');
 | 
						|
const fs = require('fs');
 | 
						|
const path = require('path');
 | 
						|
const axeCore = require('axe-core');
 | 
						|
 | 
						|
// Base URL to test
 | 
						|
const BASE_URL = process.argv[2] || 'http://localhost:8080';
 | 
						|
 | 
						|
// Create reports directory if it doesn't exist
 | 
						|
const reportsDir = path.join(__dirname, '../reports');
 | 
						|
if (!fs.existsSync(reportsDir)) {
 | 
						|
  fs.mkdirSync(reportsDir, { recursive: true });
 | 
						|
}
 | 
						|
 | 
						|
async function getPagesFromSitemap(sitemapUrl) {
 | 
						|
  try {
 | 
						|
    const response = await fetch(sitemapUrl);
 | 
						|
    if (!response.ok) {
 | 
						|
      throw new Error(`Failed to fetch sitemap: ${response.statusText}`);
 | 
						|
    }
 | 
						|
    const sitemapText = await response.text();
 | 
						|
    const urls = sitemapText.match(/<loc>(.*?)<\/loc>/g) || [];
 | 
						|
    
 | 
						|
    return urls.map(url => {
 | 
						|
      const urlContent = url.replace(/<\/?loc>/g, '');
 | 
						|
      const urlObject = new URL(urlContent);
 | 
						|
      return urlObject.pathname;
 | 
						|
    });
 | 
						|
  } catch (error) {
 | 
						|
    console.error(`Error reading sitemap: ${error}`);
 | 
						|
    // Fallback to a default list if sitemap fails
 | 
						|
    return [
 | 
						|
      '/',
 | 
						|
      '/stories/',
 | 
						|
      '/stories/open-source-success.html',
 | 
						|
      '/stories/viperwire.html',
 | 
						|
      '/one-pager-tools/csv-tool.html'
 | 
						|
    ];
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
async function runAxe(page, pageUrl) {
 | 
						|
  try {
 | 
						|
    // Use a file URL to load axe-core from a local file to avoid CSP issues
 | 
						|
    const axeScriptPath = path.join(__dirname, 'axe-core.js');
 | 
						|
    fs.writeFileSync(axeScriptPath, axeCore.source);
 | 
						|
    
 | 
						|
    // Add the script as a file
 | 
						|
    await page.addScriptTag({ path: axeScriptPath, type: 'text/javascript' });
 | 
						|
    
 | 
						|
    // Run axe with WCAG 2.1 AAA rules
 | 
						|
    const results = await page.evaluate(() => {
 | 
						|
      if (typeof axe === 'undefined') {
 | 
						|
        return { error: 'axe-core not loaded' };
 | 
						|
      }
 | 
						|
      
 | 
						|
      return new Promise(resolve => {
 | 
						|
        axe.run(document, { 
 | 
						|
          runOnly: { 
 | 
						|
            type: 'tag', 
 | 
						|
            values: ['wcag2aaa'] 
 | 
						|
          },
 | 
						|
          resultTypes: ['violations', 'incomplete', 'inapplicable'],
 | 
						|
          rules: {
 | 
						|
            'color-contrast': { enabled: true, options: { noScroll: true } }
 | 
						|
          }
 | 
						|
        })
 | 
						|
        .then(results => resolve(results))
 | 
						|
        .catch(err => resolve({ error: err.toString() }));
 | 
						|
      });
 | 
						|
    });
 | 
						|
    
 | 
						|
    // Clean up temporary file
 | 
						|
    fs.unlinkSync(axeScriptPath);
 | 
						|
    
 | 
						|
    return results;
 | 
						|
  } catch (error) {
 | 
						|
    console.error('Error running axe:', error);
 | 
						|
    return { error: error.toString() };
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
async function testPage(browser, pageUrl) {
 | 
						|
  const context = await browser.newContext({
 | 
						|
    bypassCSP: true
 | 
						|
  });
 | 
						|
  const page = await context.newPage();
 | 
						|
  console.log(`Testing ${pageUrl}...`);
 | 
						|
  
 | 
						|
  try {
 | 
						|
    // Navigate to the page
 | 
						|
    await page.goto(pageUrl, { waitUntil: 'networkidle' });
 | 
						|
    
 | 
						|
    // Run axe-core tests
 | 
						|
    const results = await runAxe(page, pageUrl);
 | 
						|
    
 | 
						|
    // Save results to file
 | 
						|
    const fileName = pageUrl === BASE_URL ? 'index' : pageUrl.replace(BASE_URL, '').replace(/\//g, '-').replace(/^-/, '');
 | 
						|
    const reportPath = path.join(reportsDir, `axe-${fileName || 'index'}.json`);
 | 
						|
    fs.writeFileSync(reportPath, JSON.stringify(results, null, 2));
 | 
						|
    
 | 
						|
    // Log results summary
 | 
						|
    if (results.error) {
 | 
						|
      console.error(`Error running axe-core on ${pageUrl}:`, results.error);
 | 
						|
      return { url: pageUrl, success: false, error: results.error };
 | 
						|
    }
 | 
						|
    
 | 
						|
    const { violations, incomplete, passes, inapplicable } = results;
 | 
						|
    console.log(`Results for ${pageUrl}:`);
 | 
						|
    console.log(`- Violations: ${violations.length}`);
 | 
						|
    console.log(`- Incomplete: ${incomplete.length}`);
 | 
						|
    console.log(`- Passes: ${passes.length}`);
 | 
						|
    console.log(`- Inapplicable: ${inapplicable.length}`);
 | 
						|
    
 | 
						|
    // Print violations
 | 
						|
    if (violations.length > 0) {
 | 
						|
      console.log('\nViolations:');
 | 
						|
      violations.forEach((violation, i) => {
 | 
						|
        console.log(`${i + 1}. ${violation.id} - ${violation.help} (Impact: ${violation.impact})`);
 | 
						|
        console.log(`   Description: ${violation.description}`);
 | 
						|
        console.log(`   Help: ${violation.helpUrl}`);
 | 
						|
        violation.nodes.forEach(node => {
 | 
						|
          console.log(`   - Element: ${node.html}`);
 | 
						|
          console.log(`     Selector: ${node.target.join(', ')}`);
 | 
						|
        });
 | 
						|
      });
 | 
						|
    }
 | 
						|
    
 | 
						|
    return { 
 | 
						|
      url: pageUrl, 
 | 
						|
      success: violations.length === 0, 
 | 
						|
      violations: violations.length,
 | 
						|
      incomplete: incomplete.length,
 | 
						|
      passes: passes.length
 | 
						|
    };
 | 
						|
  } catch (error) {
 | 
						|
    console.error(`Error testing ${pageUrl}:`, error);
 | 
						|
    return { url: pageUrl, success: false, error: error.toString() };
 | 
						|
  } finally {
 | 
						|
    await page.close();
 | 
						|
    await context.close();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
async function runTests() {
 | 
						|
  const browser = await chromium.launch();
 | 
						|
  const results = [];
 | 
						|
  
 | 
						|
  const sitemapUrl = `${BASE_URL}/sitemap.xml`;
 | 
						|
  console.log(`Fetching pages from sitemap: ${sitemapUrl}`);
 | 
						|
  const pagesToTest = await getPagesFromSitemap(sitemapUrl);
 | 
						|
  console.log(`Found ${pagesToTest.length} pages to test.`);
 | 
						|
 | 
						|
  try {
 | 
						|
    // Test each page
 | 
						|
    for (const pagePath of pagesToTest) {
 | 
						|
      const pageUrl = `${BASE_URL}${pagePath.startsWith('/') ? '' : '/'}${pagePath}`;
 | 
						|
      const result = await testPage(browser, pageUrl);
 | 
						|
      results.push(result);
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Save overall results
 | 
						|
    const overallReport = {
 | 
						|
      timestamp: new Date().toISOString(),
 | 
						|
      baseUrl: BASE_URL,
 | 
						|
      pages: results,
 | 
						|
      summary: {
 | 
						|
        total: results.length,
 | 
						|
        passed: results.filter(r => r.success).length,
 | 
						|
        failed: results.filter(r => !r.success).length
 | 
						|
      }
 | 
						|
    };
 | 
						|
    
 | 
						|
    fs.writeFileSync(path.join(reportsDir, 'axe-summary.json'), JSON.stringify(overallReport, null, 2));
 | 
						|
    
 | 
						|
    // Print overall summary
 | 
						|
    console.log('\n=== Overall Summary ===');
 | 
						|
    console.log(`Total pages tested: ${overallReport.summary.total}`);
 | 
						|
    console.log(`Pages passed: ${overallReport.summary.passed}`);
 | 
						|
    console.log(`Pages failed: ${overallReport.summary.failed}`);
 | 
						|
    
 | 
						|
    // Exit with appropriate code
 | 
						|
    process.exit(overallReport.summary.failed > 0 ? 1 : 0);
 | 
						|
  } catch (error) {
 | 
						|
    console.error('Error running tests:', error);
 | 
						|
    process.exit(1);
 | 
						|
  } finally {
 | 
						|
    await browser.close();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
runTests();
 |