Skip to content

Commit

Permalink
Added new summary report in HTML and PDF (#134)
Browse files Browse the repository at this point in the history
* Added new summary report in HTML and PDF 
* Update version number
  • Loading branch information
MagdelineNg authored Jul 4, 2023
1 parent b772a49 commit 01173ea
Show file tree
Hide file tree
Showing 14 changed files with 13,279 additions and 135 deletions.
58 changes: 56 additions & 2 deletions mergeAxeResults.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import constants from './constants/constants.js';
import { getCurrentTime, getStoragePath } from './utils.js';
import { consoleLogger, silentLogger } from './logs.js';
import itemTypeDescription from './constants/itemTypeDescription.js';
import { chromium } from 'playwright';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand Down Expand Up @@ -65,11 +66,60 @@ const writeResults = async (allissues, storagePath, jsonFilename = 'compiledResu

const writeHTML = async (allIssues, storagePath, htmlFilename = 'report') => {
const ejsString = fs.readFileSync(path.join(__dirname, './static/ejs/report.ejs'), 'utf-8');
const template = ejs.compile(ejsString, { filename: path.join(__dirname, './static/ejs/report.ejs') });
const template = ejs.compile(ejsString, {
filename: path.join(__dirname, './static/ejs/report.ejs'),
});
const html = template(allIssues);
fs.writeFileSync(`${storagePath}/reports/${htmlFilename}.html`, html);
};

const writeSummaryHTML = async (allIssues, storagePath, htmlFilename = 'summary') => {
const ejsString = fs.readFileSync(path.join(__dirname, './static/ejs/summary.ejs'), 'utf-8');
const template = ejs.compile(ejsString, {
filename: path.join(__dirname, './static/ejs/summary.ejs'),
});
const html = template(allIssues);
fs.writeFileSync(`${storagePath}/reports/${htmlFilename}.html`, html);
};

const writeSummaryPdf = async (htmlFilePath, fileDestinationPath) => {
const browser = await chromium.launch({
headless: true,
});

const context = await browser.newContext({
ignoreHTTPSErrors: true,
serviceWorkers: 'block',
});

const page = await context.newPage();

fs.readFile(htmlFilePath, 'utf8', async (err, data) => {
await page.setContent(data);
});

await page.waitForLoadState('networkidle', {'timeout': 10000 });

await page.emulateMedia({ media: 'print' });

await page.pdf({
margin: { bottom: '32px' },
path: fileDestinationPath,
format: 'A4',
displayHeaderFooter: true,
footerTemplate: `
<div style="margin-top:50px;color:#333333;font-family:Open Sans;text-align: center;width: 100%;font-weight:400">
<span style="color:#333333;font-size: 14px;font-weight:400">Page <span class="pageNumber"></span> of <span class="totalPages"></span></span>
</div>
`,
});

await page.close();

await context.close();
await browser.close();
};

const pushResults = async (rPath, allIssues) => {
const pageResults = await parseContentToJson(rPath);
const { url, pageTitle } = pageResults;
Expand Down Expand Up @@ -181,8 +231,12 @@ export const generateArtifacts = async (randomToken, urlScanned, scanType, viewp
`Must Fix: ${allIssues.items.mustFix.rules.length} issues / ${allIssues.items.mustFix.totalItems} occurrences`,
`Good to Fix: ${allIssues.items.goodToFix.rules.length} issues / ${allIssues.items.goodToFix.totalItems} occurrences`,
`Passed: ${allIssues.items.passed.totalItems} occurrences`,
])
]);

const htmlFilename = `${storagePath}/reports/summary.html`;
const fileDestinationPath = `${storagePath}/reports/summary.pdf`;
await writeResults(allIssues, storagePath);
await writeHTML(allIssues, storagePath);
await writeSummaryHTML(allIssues, storagePath);
await writeSummaryPdf(htmlFilename, fileDestinationPath);
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@govtechsg/purple-hats",
"main": "npmIndex.js",
"version": "0.9.0",
"version": "0.9.1",
"type": "module",
"imports": {
"#root/*.js": "./*.js"
Expand Down
258 changes: 129 additions & 129 deletions static/ejs/partials/components/scanAbout.ejs

Large diffs are not rendered by default.

142 changes: 142 additions & 0 deletions static/ejs/partials/components/summaryScanAbout.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<h2>About this scan</h2>
<ul class="p-0" style="list-style: none">
<li class="svg-container">
<svg
aria-label="Start time"
width="18px"
viewBox="0 0 18 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="svg-aboutscan"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.69863 0.927936C3.69863 0.41626 4.11262 0.00146484 4.62329 0.00146484C5.13396 0.00146484 5.54795 0.41626 5.54795 0.927936V1.85441H9.24658V0.927936C9.24658 0.41626 9.66056 0.00146484 10.1712 0.00146484C10.6819 0.00146484 11.0959 0.41626 11.0959 0.927936V1.85441H12.0205C13.5526 1.85441 14.7945 3.09879 14.7945 4.63382V8.51434C14.6723 8.5058 14.549 8.50146 14.4247 8.50146C14.0768 8.50146 13.7367 8.53544 13.4075 8.60029V7.41323H1.38699V12.9721C1.38699 13.7396 2.00795 14.3618 2.77397 14.3618H9.1446C9.18737 14.8464 9.29359 15.3125 9.45476 15.7515H2.77397C1.24195 15.7515 0 14.5071 0 12.9721V4.63382C0 3.09879 1.24195 1.85441 2.77397 1.85441H3.69863V0.927936ZM9.24658 3.24411V3.70735C9.24658 4.21902 9.66056 4.63382 10.1712 4.63382C10.6819 4.63382 11.0959 4.21902 11.0959 3.70735V3.24411H12.0205C12.7866 3.24411 13.4075 3.8663 13.4075 4.63382V6.48676H1.38699V4.63382C1.38699 3.8663 2.00795 3.24411 2.77397 3.24411H3.69863V3.70735C3.69863 4.21902 4.11262 4.63382 4.62329 4.63382C5.13396 4.63382 5.54795 4.21902 5.54795 3.70735V3.24411H9.24658ZM17.0959 14.0015C17.0959 15.7043 15.7343 17.0848 14.0548 17.0848C12.3752 17.0848 11.0137 15.7043 11.0137 14.0015C11.0137 12.2986 12.3752 10.9181 14.0548 10.9181C15.7343 10.9181 17.0959 12.2986 17.0959 14.0015ZM18 14.0015C18 16.2106 16.2337 18.0015 14.0548 18.0015C11.8759 18.0015 10.1096 16.2106 10.1096 14.0015C10.1096 11.7923 11.8759 10.0015 14.0548 10.0015C16.2337 10.0015 18 11.7923 18 14.0015ZM14.0548 11.5015C13.8732 11.5015 13.726 11.6507 13.726 11.8348V13.9181C13.726 14.0241 13.7748 14.1185 13.8507 14.1795C13.8597 14.191 13.8696 14.2021 13.8803 14.2125L14.8486 15.161C14.9791 15.2889 15.1873 15.2853 15.3134 15.1529C15.4396 15.0205 15.436 14.8095 15.3054 14.6816L14.3836 13.7785V11.8348C14.3836 11.6507 14.2364 11.5015 14.0548 11.5015Z"
fill="#CED4DA"
/>
</svg>
<span><%= startTime %></span>
</li>
<li class="svg-container" style="word-break: break-all;">
<svg
aria-label="URL scanned"
width="18px"
viewBox="0 0 18 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="svg-aboutscan"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M16.5789 2.80146H1.42105V12.6015H16.5789V2.80146ZM18 0.934798V2.80146V13.0681C18 13.5836 17.5758 14.0015 17.0526 14.0015H0.947369C0.42415 14.0015 0 13.5836 0 13.0681V2.80146V0.934798C0 0.419332 0.42415 0.00146484 0.947369 0.00146484H17.0526C17.5758 0.00146484 18 0.419332 18 0.934798ZM3.07895 2.3348C2.68653 2.3348 2.36842 2.0214 2.36842 1.6348C2.36842 1.2482 2.68653 0.934798 3.07895 0.934798C3.47136 0.934798 3.78947 1.2482 3.78947 1.6348C3.78947 2.0214 3.47136 2.3348 3.07895 2.3348ZM4.26316 1.6348C4.26316 2.0214 4.58127 2.3348 4.97368 2.3348C5.3661 2.3348 5.68421 2.0214 5.68421 1.6348C5.68421 1.2482 5.3661 0.934798 4.97368 0.934798C4.58127 0.934798 4.26316 1.2482 4.26316 1.6348ZM6.86842 2.3348C6.47601 2.3348 6.15789 2.0214 6.15789 1.6348C6.15789 1.2482 6.47601 0.934798 6.86842 0.934798C7.26083 0.934798 7.57895 1.2482 7.57895 1.6348C7.57895 2.0214 7.26083 2.3348 6.86842 2.3348Z"
fill="#CED4DA"
/>
</svg>
<a aria-label="URL scanned" href="<%= urlScanned %>" target="_blank"><%= urlScanned %></a>
</li>
<% if (viewport !== null) { %>
<li class="svg-container">
<% if (viewport.startsWith('Desktop')) { %>
<svg
aria-label="Viewport"
width="18px"
viewBox="0 0 18 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="svg-aboutscan"
>
<path
d="M16.56 0.19H1.44A1.26 1.26 0 0 0 0.18 1.452v10.092a1.26 1.26 0 0 0 1.26 1.262h5.67v1.242H5.85c-0.347 0 -0.63 0.282 -0.63 0.63 0 0.349 0.282 0.63 0.63 0.63h6.3c0.347 0 0.63 -0.282 0.63 -0.63 0 -0.349 -0.282 -0.63 -0.63 -0.63h-1.26v-1.242h5.67c0.697 0 1.26 -0.565 1.26 -1.262V1.452A1.26 1.26 0 0 0 16.56 0.19Zm0 10.98c0 0.218 -0.176 0.394 -0.394 0.394H1.834A0.394 0.394 0 0 1 1.44 11.17V1.866c0 -0.218 0.176 -0.394 0.394 -0.394H16.168c0.218 0 0.394 0.176 0.394 0.394L16.56 11.17Z"
fill="#CED4DA"
/>
</svg>
<% } else if (viewport.startsWith('CustomWidth')) { %>
<svg
aria-label="Viewport"
width="18px"
viewBox="0 0 18 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="svg-aboutscan"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M0 3.6a3.6 3.6 0 0 1 3.6 -3.6h10.8a3.6 3.6 0 0 1 3.6 3.6v10.8a3.6 3.6 0 0 1 -3.6 3.6H3.6a3.6 3.6 0 0 1 -3.6 -3.6V3.6Zm3.329 6.263a0.863 0.863 0 0 0 -0.863 0.863v3.945a0.863 0.863 0 0 0 0.986 0.854c0.041 0.006 0.081 0.009 0.123 0.009h3.945a0.863 0.863 0 1 0 0 -1.726H4.192v-3.083a0.863 0.863 0 0 0 -0.863 -0.863Zm12.452 -2.588a0.863 0.863 0 1 1 -1.726 0V4.192h-3.328a0.863 0.863 0 0 1 0 -1.726h3.945c0.042 0 0.083 0.004 0.123 0.009a0.863 0.863 0 0 1 0.986 0.854v3.946Z"
fill="#CED4DA"
/>
</svg>
<% } else { %>
<svg
aria-label="Viewport"
width="18px"
viewBox="0 0 18 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="svg-aboutscan"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1.94595 0.00146484C0.87123 0.00146484 0 0.856193 0 1.91056V19.0924C0 20.1467 0.87123 21.0015 1.94595 21.0015H10.0541C11.1288 21.0015 12 20.1467 12 19.0924V1.91056C12 0.856194 11.1288 0.00146484 10.0541 0.00146484H1.94595ZM10.7027 2.54692H1.2973V17.8196H10.7027V2.54692ZM4.21622 1.1151C4.21622 1.02724 4.28882 0.95601 4.37838 0.95601H7.94595C8.03551 0.95601 8.10811 1.02724 8.10811 1.1151C8.10811 1.20296 8.03551 1.27419 7.94595 1.27419H4.37838C4.28882 1.27419 4.21622 1.20296 4.21622 1.1151ZM6.16216 20.3651C6.69952 20.3651 7.13514 19.9377 7.13514 19.4106C7.13514 18.8834 6.69952 18.456 6.16216 18.456C5.6248 18.456 5.18919 18.8834 5.18919 19.4106C5.18919 19.9377 5.6248 20.3651 6.16216 20.3651Z"
fill="#CED4DA"
/>
</svg>
<% } %>
<span>
<%= viewport.startsWith("CustomWidth") ? `${viewport.split("_")[1]} width` : viewport %>
viewport <%= scanType === 'Customized' ? '(customized scan)' : '' %>
</span>
</li>
<% } %>
<li class="svg-container">
<svg
aria-label="Pages scanned"
width="18px"
viewBox="0 0 18 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="svg-aboutscan"
>
<g clip-path="url(#clip0_1630_1547)">
<path
d="M14.0299 1.09229H2.97102C2.59546 1.09229 2.29102 1.39673 2.29102 1.77228V16.231C2.29102 16.6065 2.59546 16.911 2.97102 16.911H14.0299C14.4054 16.911 14.7099 16.6065 14.7099 16.231V1.77228C14.7099 1.39673 14.4054 1.09229 14.0299 1.09229Z"
stroke="#CED4DA"
stroke-width="1.7369"
/>
<path
d="M5.03369 4.92139H11.9663"
stroke="#CED4DA"
stroke-width="1.189"
stroke-linecap="round"
/>
<path
d="M5.03369 7.64136H11.9663"
stroke="#CED4DA"
stroke-width="1.189"
stroke-linecap="round"
/>
<path
d="M5.03369 10.3613H11.9663"
stroke="#CED4DA"
stroke-width="1.189"
stroke-linecap="round"
/>
<path
d="M5.03369 13.0813H11.9663"
stroke="#CED4DA"
stroke-width="1.189"
stroke-linecap="round"
/>
</g>
<defs>
<clipPath id="clip0_1630_1547">
<rect width="17" height="17" fill="white" transform="translate(0 0.501465)" />
</clipPath>
</defs>
</svg>
<span><%= totalPagesScanned %> <%= totalPagesScanned > 1 ? 'pages' : 'page'%></span>
</li>
</ul>
15 changes: 15 additions & 0 deletions static/ejs/partials/components/summaryScanResults.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<% const formattedCategoryTitles = {
mustFix: "Must Fix",
goodToFix: "Good to Fix",
passed: "Passed"
} %>
<li class="d-flex align-items-center mb-3 justify-content-between">
<span class="d-flex align-items-center d-inline mb-0"
><img id="summary<%= category %>Icon"><%= formattedCategoryTitles[category] %></span
>
<span><%= items[category].rules.length %></span>
</li>
<% if (category !== 'passed') { %>
<hr/>
<% } %>

20 changes: 20 additions & 0 deletions static/ejs/partials/components/summaryTable.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<div class="pb-4 mx-3">
<table id="summary-table" style="table-layout:fixed;width:100%;">
<col style="width:10%">
<col style="width:30%">
<col style="width:20%">
<col style="width:20%">
<col style="width:20%">
<thead id="summary-table-header">
<tr id="summary-table-row" style="font-weight: 600">
<td id="summary-table-icon">Icon</td>
<td id="summary-table-issue-description" class="py-4">Issue description</td>
<td id="summary-table-occurrences">Occurrences</td>
<td id="summary-table-pages">Pages</td>
<td id="summary-table-helpUrl">Help URL</td>
</tr>
</thead>
<tbody id="summary-table-contents">
</tbody>
</table>
</div>
124 changes: 124 additions & 0 deletions static/ejs/partials/components/summaryWcagCompliance.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<% wcagLinks = { 'WCAG 1.1.1': 'https://www.w3.org/TR/WCAG21/#non-text-content', 'WCAG 1.2.2':
'https://www.w3.org/TR/WCAG21/#captions-prerecorded', 'WCAG 1.3.1':
'https://www.w3.org/TR/WCAG21/#info-and-relationships', 'WCAG 1.3.5':
'https://www.w3.org/TR/WCAG21/#use-of-color', 'WCAG 1.4.2':
'https://www.w3.org/TR/WCAG21/#audio-control', 'WCAG 1.4.3':
'https://www.w3.org/TR/WCAG21/#contrast-minimum', 'WCAG 1.4.4':
'https://www.w3.org/TR/WCAG21/#resize-text', 'WCAG 1.4.12':
'https://www.w3.org/TR/WCAG21/#text-spacing', 'WCAG 2.1.1':
'https://www.w3.org/TR/WCAG21/#pause-stop-hide', 'WCAG 2.4.1':
'https://www.w3.org/TR/WCAG21/#bypass-blocks', 'WCAG 2.4.2':
'https://www.w3.org/TR/WCAG21/#page-titled', 'WCAG 2.4.4':
'https://www.w3.org/TR/WCAG21/#link-purpose-in-context', 'WCAG 3.1.1':
'https://www.w3.org/TR/WCAG21/#language-of-page', 'WCAG 3.1.2':
'https://www.w3.org/TR/WCAG21/#labels-or-instructions', 'WCAG 4.1.1':
'https://www.w3.org/TR/WCAG21/#parsing', 'WCAG 4.1.2':
'https://www.w3.org/TR/WCAG21/#name-role-value', }; wcagPassPercentage =
parseFloat((Object.keys(wcagLinks).length - wcagViolations.length) / Object.keys(wcagLinks).length *
100).toFixed(2); %>

<div id="wcag-compliance-card" class="card h-100 p-3">
<h2 class="mb-0">WCAG Compliance</h2>
<div class="wcag-compliance-passes-panel">
<div class="d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<span class="fw-bold">Automated: WCAG (A & AA) Passes</span>
<button
aria-label="More on automated testing WCAG coverage"
id="wcagModalToggle"
class="ms-2"
data-bs-toggle="modal"
data-bs-target="#wcagModal"
>
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.9528 2.05329C9.22079 -0.68205 4.78867 -0.68477 2.0533 2.04718C-0.682042 4.7792 -0.684795 9.21135 2.04722 11.9467C4.77917 14.6821 9.21135 14.6848 11.9467 11.9528C14.682 9.22085 14.6848 4.78863 11.9528 2.05329ZM7.00216 11.2406C6.6319 11.2406 6.33174 10.9405 6.33174 10.5702C6.33174 10.1999 6.63187 9.89976 7.00216 9.89976C7.37243 9.89976 7.67259 10.1999 7.67259 10.5702C7.67255 10.9405 7.37243 11.2406 7.00216 11.2406ZM8.09214 7.20401C7.70918 7.42788 7.67645 7.7033 7.6748 8.49691C7.6747 8.54938 7.67453 8.60233 7.67423 8.65558C7.67185 9.03068 7.36712 9.33312 6.99253 9.33312C6.99105 9.33312 6.98957 9.33312 6.98813 9.33312C6.61159 9.33074 6.3083 9.02356 6.31066 8.64699C6.31099 8.59568 6.31109 8.54468 6.31119 8.49415C6.31287 7.67852 6.31492 6.66352 7.40395 6.02694C8.27777 5.51613 8.3879 5.18059 8.28543 4.74029C8.16503 4.2231 7.69273 4.08907 7.32418 4.1312C7.20042 4.14541 6.58322 4.24724 6.58322 4.85783C6.58322 5.23431 6.27795 5.5396 5.90141 5.5396C5.52487 5.5396 5.21964 5.23431 5.21964 4.85783C5.21964 3.76416 6.02107 2.90831 7.16859 2.77656C8.35043 2.64099 9.35515 3.32135 9.61338 4.4312C9.99939 6.08915 8.61379 6.89911 8.09214 7.20401Z"
fill="#B5C5CA"
/>
</svg>
</button>
</div>
<span aria-label="Pass percentage" class="ms-2"><%= wcagPassPercentage %>%</span>
</div>
<div class="wcag-compliance-passes-bar">
<div
class="wcag-compliance-passes-bar-progress"
style="width: <%= wcagPassPercentage %>%"
></div>
</div>
</div>
<p>
Only a subset of
<a
href="https://www.w3.org/WAI/WCAG21/quickref/?currentsidebar=%23col_customize&versions=2.1&levels=aaa"
target="_blank"
>WCAG 2.1</a
>
(Conformance Level A & AA) Success Criteria can be automatically checked so
<a aria-label="Manual testing guide" href="http://go.gov.sg/a11y-manual-testing" target="_blank">manual testing</a>
is still required.
</p>
</div>
<div
id="wcagModal"
class="modal fade"
tabindex="-1"
aria-labelledby="wcagModalLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title fw-bold" id="wcagModalLabel">Automated Testing WCAG Coverage</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div>
Only 16 WCAG 2.1 Success Criteria can be checked reasonably through automated testing:
</div>
<div class="accordion my-3" id="wcagLinksAccordion">
<div class="accordion-item">
<div class="accordion-header" id="wcagLinksAccordionTitle">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#wcagLinksAccordionContent"
aria-expanded="false"
aria-controls="wcagLinksAccordionContent"
>
16 WCAG Success Criteria (A & AA)
</button>
</div>
<div
id="wcagLinksAccordionContent"
class="accordion-collapse collapse"
aria-labelledby="wcagLinksAccordionTitle"
data-bs-parent="#accordionExample"
>
<div class="accordion-body">
<ul id="wcagLinksList">
<% Object.entries(wcagLinks).forEach(link => { %>
<li>
<a href="<%= link[1] %>" target="_blank"><%= link[0] %></a>
</li>
<% }) %>
</ul>
</div>
</div>
</div>
</div>
<div>
<strong>Manual testing is still recommended</strong> as they involve subjective judgements
and human interpretation, which cannot be fully automated.
</div>
</div>
</div>
</div>
</div>
Loading

0 comments on commit 01173ea

Please sign in to comment.