accessibility-checker
Version:
An automated testing tools for accessibility testing using Puppeteer, Selenium, or Zombie
405 lines (402 loc) • 21.3 kB
JavaScript
/******************************************************************************
Copyright:: 2020- IBM, Inc
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.
*****************************************************************************/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ACReporterXLSX = void 0;
// Load all the modules that are needed
var pathLib = require("path");
var fs = require("fs");
var ACEngineManager_1 = require("../ACEngineManager");
var ACConfigManager_1 = require("../ACConfigManager");
var multiScanData_1 = require("./multiScanData");
var multiScanReport_1 = require("./multiScanReport");
var toCSV = function (str) {
if (str === null) {
return '"null"';
}
else if (str.length == 0) {
return '""';
}
else {
str = str.replace(/"/g, '""');
return "\"".concat(str, "\"");
}
};
/**
* This function is responsible for constructing the aChecker Reporter which will be used to, report
* the scan results, such as writing the page results and the summary to a JSON file. This reporter function
* is registered with Karma server and triggered based on events that are triggered by the karma communication.
*
* @param {Object} baseReporterDecorator - the base karma reporter, which had the base functions which we override
* @param {Object} this.Config - All the Karma this.Configuration, we will extract what we need from this over
* all object, we need the entire object so that we detect any changes in the object
* as other plugins are loaded and the object is updated dynamically.
* @param {Object} logger - logger object which is used to log debug/error/info messages
* @param {Object} emitter - emitter object which allows to listem on any event that is triggered and allow to execute
* custom code, to handle the event that was triggered.
*
* @return - N/A
*
* @memberOf this
*/
var ACReporterXLSX = /** @class */ (function () {
// helpPath:string = ""
function ACReporterXLSX(config, scanSummary) {
this.storedScans = [];
this.resultStr = "Label,Level,RuleId,Message,Xpath,Help\n";
this.deploymentDate = "latest";
this.accessibilityGuidelines = "IBM_Accessibility";
this.Config = config;
this.Config.DEBUG && console.log("START ACReporter Constructor");
var myThis = this;
if (typeof (after) !== "undefined") {
after(function (done) {
myThis.onRunComplete();
done && done();
});
}
else {
process.on('beforeExit', function () {
myThis.onRunComplete();
});
}
this.Config.DEBUG && console.log("END ACReporter Constructor");
}
ACReporterXLSX.prototype.preprocessReport = function (report, filter, scroll) {
var valueMap = {
"VIOLATION": {
"POTENTIAL": "Needs review",
"FAIL": "Violation",
"PASS": "Pass",
"MANUAL": "Needs review"
},
"RECOMMENDATION": {
"POTENTIAL": "Recommendation",
"FAIL": "Recommendation",
"PASS": "Pass",
"MANUAL": "Recommendation"
},
"INFORMATION": {
"POTENTIAL": "Needs review",
"FAIL": "Violation",
"PASS": "Pass",
"MANUAL": "Recommendation"
}
};
if (scroll === true) {
}
report.counts = {
"total": {},
"filtered": {}
};
report.counts.total["All"] = 0;
for (var _i = 0, _a = report.results; _i < _a.length; _i++) {
var item = _a[_i];
var filtVal = "";
item.selected = false;
item.selectedChild = false;
item.scrollTo = false;
if (!filter) {
filtVal = "X";
}
else {
var xpath = item.path.dom;
if (xpath === filter) {
filtVal = "=";
item.selected = true;
if (item.value[1] !== "PASS") {
item.scrollTo = false;
}
}
else if (xpath.startsWith(filter)) {
item.selectedChild = true;
filtVal = "^";
if (item.value[1] !== "PASS") {
item.scrollTo = false;
}
}
}
// let val = valueMap[item.value[0]][item.value[1]] || item.value[0] + "_" + item.value[1];
var val = valueMap[item.value[0]][item.value[1]] || item.value[0] + "_" + item.value[1];
// report.counts.total[val] = (report.counts.total[val] || 0) + 1;
report.counts.total[val] = (report.counts.total[val] || 0) + 1;
report.counts.total["All"] = report.counts.total["All"] + 1;
item.help = ACEngineManager_1.ACEngineManager.getHelpURL(item);
if (filtVal !== "") {
report.counts.filtered[val] = (report.counts.filtered[val] || 0) + 1;
}
}
return report;
};
// This emitter function is responsible for calling this function when the info event is detected
ACReporterXLSX.prototype.report = function (report1) {
return __awaiter(this, void 0, void 0, function () {
var report, myConfig, ruleArchiveSet, myRulesets, helpPath, helpPathLatest, xlsx_props, myScanData, scanData, violation, needsReview, recommendation, all, element_no_failures, element_no_violations, scanLabel, currentScan;
var _a;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
this.Config.DEBUG && console.log("START 'info' emitter function");
// get values from user config
if (this.Config.hasOwnProperty('ruleArchive')) {
this.deploymentDate = this.Config.ruleArchive;
}
if (this.Config.hasOwnProperty('policies')) { // TODO should this always be 0?
this.accessibilityGuidelines = this.Config.policies[0];
}
report = this.preprocessReport(report1, null, false);
report.timestamp = new Date().getTime();
return [4 /*yield*/, ACConfigManager_1.ACConfigManager.getConfig()];
case 1:
myConfig = (_b.sent());
ruleArchiveSet = myConfig.ruleArchiveSet;
this.ruleArchiveSet = ruleArchiveSet;
return [4 /*yield*/, ACEngineManager_1.ACEngineManager.getRuleset(this.accessibilityGuidelines)];
case 2:
myRulesets = _b.sent();
report.ruleset = myRulesets;
helpPath = "";
helpPathLatest = "";
ruleArchiveSet.forEach(function (element) {
if (element.id === _this.deploymentDate) {
if (element.hasOwnProperty('helpPath')) { // The preview rules do no have a helpPath associated with them so we need to check
helpPath = element.helpPath;
}
}
if (element.id === "latest") { // This is to default to a known helpPath when we cant find them othere wise. Like in the case of preview rules.
if (element.hasOwnProperty('helpPath')) {
helpPathLatest = element.helpPath;
}
}
});
helpPath = (helpPath !== "") ? helpPath : helpPathLatest;
_a = {
report: report
};
return [4 /*yield*/, ACEngineManager_1.ACEngineManager.getRulesets()];
case 3:
xlsx_props = (_a.rulesets = _b.sent(),
_a.tabTitle = "",
_a.tabURL = report.summary.URL,
_a.helpPath = helpPath + "en-US/",
_a);
myScanData = new multiScanData_1.MultiScanData(this.Config);
scanData = myScanData.issues_sheet_rows(xlsx_props);
violation = (report === null || report === void 0 ? void 0 : report.counts.total["Violation"]) ? report === null || report === void 0 ? void 0 : report.counts.total["Violation"] : 0;
needsReview = (report === null || report === void 0 ? void 0 : report.counts.total["Needs review"]) ? report === null || report === void 0 ? void 0 : report.counts.total["Needs review"] : 0;
recommendation = (report === null || report === void 0 ? void 0 : report.counts.total["Recommendation"]) ? report === null || report === void 0 ? void 0 : report.counts.total["Recommendation"] : 0;
all = violation + needsReview + recommendation // not using report?.counts.total["All"] here on purpose. This calculation matches the excel sheet produced by the extension.
;
if (all !== 0) {
element_no_failures = parseInt((((all - recommendation) / all) * 100).toFixed(0));
element_no_violations = parseInt((((all - violation) / all) * 100).toFixed(0));
}
else {
element_no_failures = 0;
element_no_violations = 0;
}
scanLabel = report.label;
currentScan = {
actualStoredScan: true,
isSelected: false,
url: report.summary.URL,
pageTitle: "",
dateTime: Date.now(),
scanLabel: scanLabel,
userScanLabel: scanLabel,
ruleSet: this.deploymentDate,
guidelines: this.accessibilityGuidelines,
reportDate: new Date().toJSON(),
violations: violation,
needsReviews: needsReview,
recommendations: recommendation,
elementsNoViolations: element_no_violations,
elementsNoFailures: element_no_failures,
storedScan: scanLabel,
screenShot: null,
storedScanData: scanData,
};
this.toolID = report.toolID;
this.storedScans = __spreadArray(__spreadArray([], this.storedScans, true), [currentScan], false);
// Save the results of a single scan to a JSON file based on the label provided
this.savePageResults(report);
// Update the overall summary object count object to include the new scan that was performed
this.addToSummaryCount(report.summary.counts);
// Save the summary of this scan into global space of this reporter, to be logged
// once the whole scan is done.
this.addResultsToGlobal(report);
this.Config.DEBUG && console.log("END 'info' emitter function");
return [2 /*return*/];
}
});
});
};
;
/**
* This function is responsible for performing any action when the entire karma run is done.
* Overrides onRunComplete function from baseReporterDecorator
*
* @override
*
* @memberOf this
*/
ACReporterXLSX.prototype.onRunComplete = function () {
this.Config.DEBUG && console.log("START 'ACReporterCSV::onRunComplete' function");
// Save summary object to a JSON file.
this.saveSummary();
this.Config.DEBUG && console.log("END 'ACReporterCSV::onRunComplete' function");
};
;
/**
* This function is responsible for saving a single scans results to a file as JSON. On a side note
* this function will also extract the label which will be the file names where the results will be
* saved.
*
* @param {Object} this.Config - Karma this.Config object, used to extrat the outputFolder from the ACthis.Config.
* @param {Object} results - Provide the scan results for a single page that should be saved.
*
* @memberOf this
*/
ACReporterXLSX.prototype.savePageResults = function (report) {
this.Config.DEBUG && console.log("START 'savePageResults' function");
for (var _i = 0, _a = report.results; _i < _a.length; _i++) {
var result = _a[_i];
this.resultStr += "".concat(toCSV(report.label), ",").concat(toCSV(result.level), ",").concat(toCSV(result.ruleId), ",").concat(toCSV(result.message), ",").concat(toCSV(result.path.dom), ",").concat(toCSV(ACEngineManager_1.ACEngineManager.getHelpURL(result)), "\n");
}
this.Config.DEBUG && console.log("END 'savePageResults' function");
};
/**
* This function is responsible for converting a javascript object into JSON and then writing that to a
* json file.
*
* @param {String} fileName - Full path of file where the JSON object should be stored
* @param {String} content - The javascript object which should be converted and saved to file as JSON.
*
* @memberOf this
*/
ACReporterXLSX.prototype.writeObjectToFile = function (fileName, content) {
this.Config.DEBUG && console.log("START 'writeObjectToFileAsCSV' function");
fileName = pathLib.join(this.Config.outputFolder, fileName);
// Extract the parent directory of the file name that is provided
var parentDir = pathLib.dirname(fileName);
this.Config.DEBUG && console.log("Parent Directoy: \"" + parentDir + "\"");
// In the case that the parent directoy does not exist, create the directories
if (!fs.existsSync(parentDir)) {
// Create the parent directory recerseivly if it does not exist.
fs.mkdirSync(parentDir, { recursive: true });
}
this.Config.DEBUG && console.log("Object will be written to file: \"" + fileName + "\"");
// Convert the Object into JSON string and write that to the file
// Make sure to use utf-8 encoding to avoid an issues specify to OS.
// In terms of the JSON string that is constructed use 4 spaces to format the JSON object, before
// writing it to the file.
fs.writeFileSync(fileName, content, { encoding: 'utf-8' });
this.Config.DEBUG && console.log("END 'writeObjectToFileAsCSV' function");
};
/**
* This function is responsible for saving the summary object of the while scan to a summary file.
*
* @param {Object} summary - The summary object that needs to be written to the summary file.
*
* @memberOf this
*/
ACReporterXLSX.prototype.saveSummary = function () {
this.Config.DEBUG && console.log("START 'saveSummary' function");
if (this.Config.outputFormat.indexOf("xlsx") === -1) {
return;
}
// this.writeObjectToFile("results.xlsx", this.resultStr);
var fileName = pathLib.join(this.Config.outputFolder, "results.xlsx");
multiScanReport_1.default.multiScanXlsxDownload(this.storedScans, "all", this.storedScans.length, this.ruleArchiveSet, this.toolID, fileName);
this.Config.DEBUG && console.log("END 'saveSummary' function");
};
/**
* This function is responsible for indexing the results into global spaces based on label.
*
* @param {Object} results - Results object which will be provided to the user/wroten to the file.
* Refer to aChecker.buildReport function's return to figure out what the object
* will look like.
*
* @return - N/A - Global object is updated with the results
*
* @memberOf this
*/
ACReporterXLSX.prototype.addResultsToGlobal = function (results) {
this.Config.DEBUG && console.log("START 'addResultsToGlobal' function");
this.Config.DEBUG && console.log("END 'addResultsToGlobal' function");
};
/**
* This function is responsible for updating/creating the global violation summary for the engine karma run
* for browser that it is running on. Will take the pageCount object which is part of the page object and
* add extract the values for each of the levels and add them to the global object. This will provide an overall
* summary of violations for all testcases run and all scans done.
*
* @param {Object} pageCount - Provide the page count object, in the following format:
*
* @return N/A - Global summary object is updated with the counts
*
* @memberOf this
*/
ACReporterXLSX.prototype.addToSummaryCount = function (pageCount) {
};
return ACReporterXLSX;
}());
exports.ACReporterXLSX = ACReporterXLSX;
;
//# sourceMappingURL=ACReporterXLSX.js.map
;