/***************************************************************************** * Open MCT, Copyright (c) 2014-2024, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * * Open MCT is licensed under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * * Open MCT includes source code licensed under additional open source * licenses. See the Open Source Licenses file (LICENSES.md) included with * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ /** * avpFixtures.js * * @file This module provides custom fixtures specifically tailored for Accessibility, Visual, and Performance (AVP) tests. * These fixtures extend the base functionality of the Playwright fixtures and appActions, and are designed to be * generalized across all plugins. They offer functionalities like scanning for accessibility violations, integrating * with axe-core, and more. * * IMPORTANT NOTE: This fixture file is not intended to be extended further by other fixtures. If you find yourself * needing to do so, please consult the documentation and consider creating a specialized fixture or modifying the * existing ones. */ import AxeBuilder from '@axe-core/playwright'; import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import { expect, test } from './pluginFixtures.js'; // Constants for repeated values const __dirname = path.dirname(fileURLToPath(import.meta.url)); const TEST_RESULTS_DIR = path.join(__dirname, './test-results'); const extendedTest = test.extend({ /** * Overrides the default screenshot function to apply default options that should apply to all * screenshots taken in the AVP tests. * * @param {import('@playwright/test').PlaywrightTestArgs} args - The Playwright test arguments. * @param {Function} use - The function to use the page object. * Defaults: * - Disables animations * - Masks the clock indicator * - Masks the time conductor last update time in realtime mode * - Masks the time conductor start bounds in fixed mode * - Masks the time conductor end bounds in fixed mode */ page: async ({ page }, use) => { const playwrightScreenshot = page.screenshot; /** * Override the screenshot function to always mask a given set of locators which will always * show variance across screenshots. Defaults may be overridden by passing in options to the * screenshot function. * @param {import('@playwright/test').PageScreenshotOptions} options - The options for the screenshot. * @returns {Promise} Returns the screenshot as a buffer. */ page.screenshot = async function (options = {}) { const mask = [ this.getByLabel('Clock Indicator'), // Mask the clock indicator this.getByLabel('Last update'), // Mask the time conductor last update time in realtime mode this.getByLabel('Start bounds'), // Mask the time conductor start bounds in fixed mode this.getByLabel('End bounds') // Mask the time conductor end bounds in fixed mode ]; const result = await playwrightScreenshot.call(this, { animations: 'disabled', mask, ...options // Pass through or override any options }); return result; }; await use(page); } }); /** * Writes the accessibility report to the specified path. * * @param {string} reportPath - The path to write the report to. * @param {Object} accessibilityScanResults - The results of the accessibility scan. * @returns {Promise} The accessibility scan results. * @throws Will throw an error if writing the report fails. */ async function writeAccessibilityReport(reportPath, accessibilityScanResults) { try { await fs.mkdir(path.dirname(reportPath), { recursive: true }); const data = JSON.stringify(accessibilityScanResults, null, 2); await fs.writeFile(reportPath, data); console.log(`Accessibility report with violations saved successfully as ${reportPath}`); return accessibilityScanResults; } catch (err) { console.error(`Error writing the accessibility report to file ${reportPath}:`, err); throw err; } } /** * Scans for accessibility violations on a page and writes a report to disk if violations are found. * Automatically asserts that no violations should be present. * * @param {import('playwright').Page} page - The page object from Playwright. * @param {string} testCaseName - The name of the test case. * @param {{ reportName?: string }} [options={}] - The options for the report generation. * @returns {Promise} Returns the accessibility scan results if violations are found, otherwise returns null. */ export async function scanForA11yViolations(page, testCaseName, options = {}) { const builder = new AxeBuilder({ page }); builder.withTags(['wcag2aa']); // https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md const accessibilityScanResults = await builder.analyze(); // Assert that no violations should be present expect .soft( accessibilityScanResults.violations, `Accessibility violations found in test case: ${testCaseName}` ) .toEqual([]); // Check if there are any violations if (accessibilityScanResults.violations.length > 0) { const reportName = options.reportName || testCaseName; const sanitizedReportName = reportName.replace(/\//g, '_'); const reportPath = path.join( TEST_RESULTS_DIR, 'a11y-json-reports', `${sanitizedReportName}.json` ); try { await page.screenshot({ path: path.join(TEST_RESULTS_DIR, 'a11y-screenshots', `${sanitizedReportName}.png`) }); return await writeAccessibilityReport(reportPath, accessibilityScanResults); } catch (err) { console.error(`Error writing the accessibility report to file ${reportPath}:`, err); throw err; } } else { console.log('No accessibility violations found, no report generated.'); return null; } } export { expect, extendedTest as test };