@govtechsg/oobee 0.10.85 → 0.10.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +10 -0
- package/DETAILS.md +29 -0
- package/dist/cli.js +18 -5
- package/dist/combine.js +3 -1
- package/dist/constants/cliFunctions.js +2 -2
- package/dist/constants/common.js +70 -17
- package/dist/constants/constants.js +604 -1
- package/dist/crawlers/commonCrawlerFunc.js +3 -2
- package/dist/crawlers/crawlDomain.js +38 -13
- package/dist/crawlers/crawlIntelligentSitemap.js +62 -30
- package/dist/crawlers/crawlSitemap.js +141 -84
- package/dist/crawlers/custom/utils.js +218 -71
- package/dist/crawlers/guards/urlGuard.js +8 -15
- package/dist/crawlers/runCustom.js +18 -11
- package/dist/generateHtmlReport.js +18 -11
- package/dist/generateOobeeClientScanner.js +570 -0
- package/dist/mergeAxeResults/itemReferences.js +60 -25
- package/dist/mergeAxeResults/sentryTelemetry.js +4 -1
- package/dist/mergeAxeResults.js +23 -13
- package/dist/npmIndex.js +10 -2
- package/dist/proxyService.js +18 -3
- package/dist/services/s3Uploader.js +21 -10
- package/dist/static/ejs/partials/scripts/decodeUnzipParse.ejs +6 -3
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +2 -2
- package/dist/static/ejs/partials/scripts/ruleModal/constants.ejs +1 -761
- package/dist/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +38 -2
- package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +1 -1
- package/dist/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +4 -4
- package/dist/static/ejs/summary.ejs +19 -8
- package/dist/utils.js +4 -3
- package/fix-summary-html-oom-pr.md +62 -0
- package/oobee-client-scanner.js +34992 -0
- package/package.json +5 -5
- package/src/cli.ts +19 -5
- package/src/combine.ts +5 -1
- package/src/constants/cliFunctions.ts +2 -2
- package/src/constants/common.ts +87 -22
- package/src/constants/constants.ts +602 -1
- package/src/crawlers/commonCrawlerFunc.ts +4 -3
- package/src/crawlers/crawlDomain.ts +39 -13
- package/src/crawlers/crawlIntelligentSitemap.ts +63 -30
- package/src/crawlers/crawlSitemap.ts +165 -100
- package/src/crawlers/custom/utils.ts +241 -80
- package/src/crawlers/guards/urlGuard.ts +24 -31
- package/src/crawlers/runCustom.ts +29 -11
- package/src/generateHtmlReport.ts +21 -11
- package/src/generateOobeeClientScanner.ts +591 -0
- package/src/mergeAxeResults/itemReferences.ts +70 -26
- package/src/mergeAxeResults/sentryTelemetry.ts +4 -1
- package/src/mergeAxeResults.ts +26 -14
- package/src/npmIndex.ts +12 -2
- package/src/proxyService.ts +25 -4
- package/src/services/s3Uploader.ts +23 -11
- package/src/static/ejs/partials/scripts/decodeUnzipParse.ejs +6 -3
- package/src/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +2 -2
- package/src/static/ejs/partials/scripts/ruleModal/constants.ejs +1 -761
- package/src/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +38 -2
- package/src/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +1 -1
- package/src/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +4 -4
- package/src/static/ejs/summary.ejs +19 -8
- package/src/utils.ts +4 -3
- package/testStaticJSScanner.html +534 -0
|
@@ -25,6 +25,7 @@ const parseBoolEnv = (val, defaultVal) => {
|
|
|
25
25
|
return defaultVal;
|
|
26
26
|
};
|
|
27
27
|
const RESTRICT_OVERLAY_TO_ENTRY_DOMAIN = parseBoolEnv(process.env.RESTRICT_OVERLAY_TO_ENTRY_DOMAIN, false);
|
|
28
|
+
const OVERLAY_OPERATION_TIMEOUT_MS = 5000;
|
|
28
29
|
const isOverlayAllowed = (currentUrl, entryUrl) => {
|
|
29
30
|
try {
|
|
30
31
|
const cur = new URL(currentUrl);
|
|
@@ -62,14 +63,19 @@ export const screenshotFullPage = async (page, screenshotsDir, screenshotIdx) =>
|
|
|
62
63
|
await page.evaluate(() => {
|
|
63
64
|
window.scrollTo(0, document.body.scrollHeight);
|
|
64
65
|
});
|
|
65
|
-
const isLoadMoreContent = async () =>
|
|
66
|
-
|
|
66
|
+
const isLoadMoreContent = async () => {
|
|
67
|
+
await new Promise(resolve => setTimeout(resolve, 2500));
|
|
68
|
+
if (page.isClosed())
|
|
69
|
+
return false;
|
|
70
|
+
try {
|
|
67
71
|
await page.waitForLoadState('domcontentloaded');
|
|
68
72
|
const newHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
return newHeight > prevHeight;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
73
79
|
const result = await isLoadMoreContent();
|
|
74
80
|
return result;
|
|
75
81
|
};
|
|
@@ -201,7 +207,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
201
207
|
inProgress: false,
|
|
202
208
|
collapsed: false,
|
|
203
209
|
}) => {
|
|
204
|
-
await page.waitForLoadState('domcontentloaded');
|
|
210
|
+
await page.waitForLoadState('domcontentloaded', { timeout: OVERLAY_OPERATION_TIMEOUT_MS });
|
|
205
211
|
consoleLogger.info(`Overlay menu: adding to ${menuPos}...`);
|
|
206
212
|
// Add the overlay menu with initial styling
|
|
207
213
|
return page
|
|
@@ -291,22 +297,60 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
291
297
|
const h2 = document.createElement('h2');
|
|
292
298
|
h2.id = 'oobeeHPagesScanned';
|
|
293
299
|
h2.className = 'oobee-section-title';
|
|
294
|
-
h2.textContent =
|
|
300
|
+
h2.textContent = `Pages Scanned (${vars.urlsCrawled.scanned.length || 0})`;
|
|
301
|
+
const scanIcon = document.createElement('span');
|
|
302
|
+
scanIcon.className = 'oobee-btn-icon';
|
|
303
|
+
const SCAN_SVG = `
|
|
304
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
305
|
+
<g clip-path="url(#clip0_1421_431)">
|
|
306
|
+
<path d="M12.5763 11.5472L12.2958 11.2857L12.1037 11.1005C12.776 10.3183 12.9194 9.56432 12.9194 8.45969C12.9194 5.99657 10.9228 4 8.45969 4C5.99657 4 4 5.99657 4 8.45969C4 10.9228 5.99657 12.9194 8.45969 12.9194C9.56432 12.9194 10.3183 12.776 11.1005 12.1037L11.2857 12.2958L11.5472 12.5763L14.9777 16L16 14.9777L12.5763 11.5472ZM8.45969 11.5472C6.75129 11.5472 5.37221 10.1681 5.37221 8.45969C5.37221 6.75129 6.75129 5.37221 8.45969 5.37221C10.1681 5.37221 11.5472 6.75129 11.5472 8.45969C11.5472 10.1681 10.1681 11.5472 8.45969 11.5472Z" fill="white"/>
|
|
307
|
+
<path d="M18.5 0H19.5C19.7761 0 20 0.223858 20 0.5V5H18.5V0Z" fill="white"/>
|
|
308
|
+
<path d="M19.5 2.18552e-08L19.5 1.5L15 1.5L15 -2.18556e-07L19.5 2.18552e-08Z" fill="white"/>
|
|
309
|
+
<path d="M1.5 0H0.5C0.223858 0 0 0.223858 0 0.5V5H1.5V0Z" fill="white"/>
|
|
310
|
+
<path d="M0.5 2.18552e-08L0.5 1.5L5 1.5L5 -2.18556e-07L0.5 2.18552e-08Z" fill="white"/>
|
|
311
|
+
<path d="M1.5 20H0.5C0.223858 20 0 19.7761 0 19.5V15H1.5V20Z" fill="white"/>
|
|
312
|
+
<path d="M0.5 20L0.5 18.5L5 18.5L5 20L0.5 20Z" fill="white"/>
|
|
313
|
+
<path d="M18.5 20H19.5C19.7761 20 20 19.7761 20 19.5V15H18.5V20Z" fill="white"/>
|
|
314
|
+
<path d="M19.5 20L19.5 18.5L15 18.5L15 20L19.5 20Z" fill="white"/>
|
|
315
|
+
</g>
|
|
316
|
+
<defs>
|
|
317
|
+
<clipPath id="clip0_1421_431">
|
|
318
|
+
<rect width="20" height="20" fill="white"/>
|
|
319
|
+
</clipPath>
|
|
320
|
+
</defs>
|
|
321
|
+
</svg>
|
|
322
|
+
`;
|
|
323
|
+
scanIcon.innerHTML = SCAN_SVG;
|
|
295
324
|
const scanBtn = document.createElement('button');
|
|
296
325
|
scanBtn.id = 'oobeeBtnScan';
|
|
297
326
|
scanBtn.className = 'oobee-btn oobee-btn-primary';
|
|
298
|
-
scanBtn.innerText = 'Scan this page';
|
|
299
327
|
scanBtn.disabled = inProgress;
|
|
328
|
+
scanBtn.appendChild(scanIcon);
|
|
329
|
+
const scanText = document.createElement('span');
|
|
330
|
+
scanText.className = 'oobee-btn-text';
|
|
331
|
+
scanText.innerText = 'Scan page';
|
|
332
|
+
scanBtn.appendChild(scanText);
|
|
300
333
|
scanBtn.addEventListener('click', async () => customWindow.handleOnScanClick?.());
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
334
|
+
const endScanIcon = document.createElement('span');
|
|
335
|
+
endScanIcon.className = 'oobee-btn-icon';
|
|
336
|
+
const ENDSCAN_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
337
|
+
<path d="M10 0C4.47 0 0 4.47 0 10C0 15.53 4.47 20 10 20C15.53 20 20 15.53 20 10C20 4.47 15.53 0 10 0ZM10 18C5.59 18 2 14.41 2 10C2 5.59 5.59 2 10 2C14.41 2 18 5.59 18 10C18 14.41 14.41 18 10 18ZM13.59 5L10 8.59L6.41 5L5 6.41L8.59 10L5 13.59L6.41 15L10 11.41L13.59 15L15 13.59L11.41 10L15 6.41L13.59 5Z" fill="#9021A6"/>
|
|
338
|
+
</svg>
|
|
339
|
+
`;
|
|
340
|
+
endScanIcon.innerHTML = ENDSCAN_SVG;
|
|
341
|
+
const endScanBtn = document.createElement('button');
|
|
342
|
+
endScanBtn.id = 'oobeeBtnEndScan';
|
|
343
|
+
endScanBtn.className = 'oobee-btn oobee-btn-secondary';
|
|
344
|
+
endScanBtn.appendChild(endScanIcon);
|
|
345
|
+
const endScanText = document.createElement('span');
|
|
346
|
+
endScanText.className = 'oobee-btn-text';
|
|
347
|
+
endScanText.innerText = 'End scan';
|
|
348
|
+
endScanBtn.appendChild(endScanText);
|
|
349
|
+
endScanBtn.addEventListener('click', async () => customWindow.handleOnStopClick?.());
|
|
306
350
|
const btnGroup = document.createElement('div');
|
|
307
351
|
btnGroup.className = 'oobee-actions';
|
|
308
352
|
btnGroup.appendChild(scanBtn);
|
|
309
|
-
btnGroup.appendChild(
|
|
353
|
+
btnGroup.appendChild(endScanBtn);
|
|
310
354
|
const listWrap = document.createElement('div');
|
|
311
355
|
listWrap.id = 'oobeeList';
|
|
312
356
|
listWrap.className = 'oobee-list';
|
|
@@ -370,7 +414,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
370
414
|
border-right: 1px solid rgba(0,0,0,.08)
|
|
371
415
|
}
|
|
372
416
|
.oobee-panel.collapsed {
|
|
373
|
-
width:
|
|
417
|
+
width: 58px;
|
|
374
418
|
overflow: hidden
|
|
375
419
|
}
|
|
376
420
|
|
|
@@ -447,6 +491,12 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
447
491
|
padding: 1rem;
|
|
448
492
|
}
|
|
449
493
|
|
|
494
|
+
.oobee-panel.collapsed .oobee-actions {
|
|
495
|
+
display: flex;
|
|
496
|
+
justify-content: center;
|
|
497
|
+
padding: 1rem 0.7rem;
|
|
498
|
+
}
|
|
499
|
+
|
|
450
500
|
/* Base button */
|
|
451
501
|
.oobee-btn {
|
|
452
502
|
width: 100%;
|
|
@@ -457,6 +507,10 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
457
507
|
line-height: 1.2;
|
|
458
508
|
font-weight: 400;
|
|
459
509
|
cursor: pointer;
|
|
510
|
+
display: flex;
|
|
511
|
+
align-items: center;
|
|
512
|
+
justify-content: center;
|
|
513
|
+
gap: 10px;
|
|
460
514
|
transition: {
|
|
461
515
|
box-shadow .12s ease,
|
|
462
516
|
transform .02s ease,
|
|
@@ -470,6 +524,19 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
470
524
|
cursor:not-allowed
|
|
471
525
|
}
|
|
472
526
|
|
|
527
|
+
.oobee-panel.collapsed .oobee-btn {
|
|
528
|
+
width: 44px !important;
|
|
529
|
+
height: 44px !important;
|
|
530
|
+
min-width: 44px !important;
|
|
531
|
+
min-height: 44px !important;
|
|
532
|
+
max-width: 44px !important;
|
|
533
|
+
max-height: 44px !important;
|
|
534
|
+
border-radius: 50% !important;
|
|
535
|
+
padding: 0 !important;
|
|
536
|
+
justify-content: center;
|
|
537
|
+
gap: 0;
|
|
538
|
+
}
|
|
539
|
+
|
|
473
540
|
/* Primary (filled) */
|
|
474
541
|
.oobee-btn-primary {
|
|
475
542
|
background: #9021a6;
|
|
@@ -525,6 +592,25 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
525
592
|
display: none;
|
|
526
593
|
}
|
|
527
594
|
|
|
595
|
+
.oobee-btn-icon {
|
|
596
|
+
display: inline-flex;
|
|
597
|
+
align-items: center;
|
|
598
|
+
justify-content: center;
|
|
599
|
+
width: 20px;
|
|
600
|
+
height: 20px;
|
|
601
|
+
vertical-align: middle;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.oobee-btn-text {
|
|
605
|
+
display: inline;
|
|
606
|
+
white-space: nowrap;
|
|
607
|
+
vertical-align: middle;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.oobee-panel.collapsed .oobee-btn-text {
|
|
611
|
+
display: none;
|
|
612
|
+
}
|
|
613
|
+
|
|
528
614
|
#oobeeStopOverlay[hidden] {
|
|
529
615
|
display:none !important;
|
|
530
616
|
}
|
|
@@ -542,7 +628,10 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
542
628
|
}
|
|
543
629
|
|
|
544
630
|
.oobee-panel.collapsed .oobee-section-title {
|
|
545
|
-
|
|
631
|
+
font-size: 14px;
|
|
632
|
+
display: flex;
|
|
633
|
+
justify-content: center;
|
|
634
|
+
text-align: center;
|
|
546
635
|
}
|
|
547
636
|
|
|
548
637
|
.oobee-ol {
|
|
@@ -899,6 +988,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
899
988
|
})
|
|
900
989
|
.catch(error => {
|
|
901
990
|
consoleLogger.error('Overlay menu: failed to add', error);
|
|
991
|
+
throw error;
|
|
902
992
|
});
|
|
903
993
|
};
|
|
904
994
|
export const removeOverlayMenu = async (page) => {
|
|
@@ -919,9 +1009,18 @@ export const removeOverlayMenu = async (page) => {
|
|
|
919
1009
|
};
|
|
920
1010
|
export const initNewPage = async (page, pageClosePromises, processPageParams, pagesDict) => {
|
|
921
1011
|
let menuPos = MENU_POSITION.right;
|
|
1012
|
+
let overlayRefreshSeq = 0;
|
|
1013
|
+
let overlayRefreshChain = Promise.resolve();
|
|
922
1014
|
// eslint-disable-next-line no-underscore-dangle
|
|
923
1015
|
const pageId = page._guid;
|
|
924
|
-
page.on('dialog', () => {
|
|
1016
|
+
page.on('dialog', async (dialog) => {
|
|
1017
|
+
try {
|
|
1018
|
+
await dialog.dismiss();
|
|
1019
|
+
}
|
|
1020
|
+
catch {
|
|
1021
|
+
// dialog may already be closed
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
925
1024
|
const pageClosePromise = new Promise(resolve => {
|
|
926
1025
|
page.on('close', () => {
|
|
927
1026
|
log(`Page: close detected: ${page.url()}`);
|
|
@@ -937,6 +1036,68 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
937
1036
|
collapsed: false,
|
|
938
1037
|
};
|
|
939
1038
|
}
|
|
1039
|
+
const reconcileOverlayMenu = async (trigger) => {
|
|
1040
|
+
// Mark this as the latest refresh so older ones can stop.
|
|
1041
|
+
const refreshSeq = ++overlayRefreshSeq;
|
|
1042
|
+
// Serialize overlay updates so multiple navigation events do not add/remove concurrently.
|
|
1043
|
+
overlayRefreshChain = overlayRefreshChain
|
|
1044
|
+
.catch(() => { })
|
|
1045
|
+
.then(async () => {
|
|
1046
|
+
if (refreshSeq !== overlayRefreshSeq || page.isClosed())
|
|
1047
|
+
return;
|
|
1048
|
+
try {
|
|
1049
|
+
// `framenavigated` can fire before the new document is ready for DOM inspection/injection.
|
|
1050
|
+
await page.waitForLoadState('domcontentloaded', { timeout: 5000 });
|
|
1051
|
+
}
|
|
1052
|
+
catch {
|
|
1053
|
+
// Best effort only. The page may still be mid-navigation.
|
|
1054
|
+
}
|
|
1055
|
+
try {
|
|
1056
|
+
// Give fast redirect chains a brief chance to advance before we inject/remove the overlay.
|
|
1057
|
+
await page.waitForTimeout(300);
|
|
1058
|
+
}
|
|
1059
|
+
catch {
|
|
1060
|
+
// Best effort only. The page may already be closing.
|
|
1061
|
+
}
|
|
1062
|
+
// Re-check staleness after waiting because a newer navigation may have happened meanwhile.
|
|
1063
|
+
if (refreshSeq !== overlayRefreshSeq || page.isClosed())
|
|
1064
|
+
return;
|
|
1065
|
+
const allowed = isOverlayAllowed(page.url(), processPageParams.entryUrl);
|
|
1066
|
+
if (!allowed) {
|
|
1067
|
+
await Promise.race([
|
|
1068
|
+
removeOverlayMenu(page),
|
|
1069
|
+
new Promise((_, reject) => {
|
|
1070
|
+
setTimeout(() => {
|
|
1071
|
+
reject(new Error(`removeOverlayMenu timed out after ${OVERLAY_OPERATION_TIMEOUT_MS}ms`));
|
|
1072
|
+
}, OVERLAY_OPERATION_TIMEOUT_MS);
|
|
1073
|
+
}),
|
|
1074
|
+
]);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
const hasOverlay = await page.evaluate(() => Boolean(document.querySelector('#oobeeShadowHost')));
|
|
1078
|
+
consoleLogger.info(`Overlay state (${trigger}): ${hasOverlay}`);
|
|
1079
|
+
if (!hasOverlay) {
|
|
1080
|
+
// Recreate the overlay after allowed redirects while preserving current UI state.
|
|
1081
|
+
consoleLogger.info(`Adding overlay menu to page (${trigger}): ${page.url()}`);
|
|
1082
|
+
await Promise.race([
|
|
1083
|
+
addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
|
|
1084
|
+
inProgress: !!pagesDict[pageId]?.isScanning,
|
|
1085
|
+
collapsed: !!pagesDict[pageId]?.collapsed,
|
|
1086
|
+
hideStopInput: !!processPageParams.customFlowLabel,
|
|
1087
|
+
}),
|
|
1088
|
+
new Promise((_, reject) => {
|
|
1089
|
+
setTimeout(() => {
|
|
1090
|
+
reject(new Error(`addOverlayMenu timed out after ${OVERLAY_OPERATION_TIMEOUT_MS}ms`));
|
|
1091
|
+
}, OVERLAY_OPERATION_TIMEOUT_MS);
|
|
1092
|
+
}),
|
|
1093
|
+
]);
|
|
1094
|
+
}
|
|
1095
|
+
})
|
|
1096
|
+
.catch(() => {
|
|
1097
|
+
consoleLogger.info('Error in adding overlay menu to page');
|
|
1098
|
+
});
|
|
1099
|
+
await overlayRefreshChain;
|
|
1100
|
+
};
|
|
940
1101
|
// Window functions exposed in browser
|
|
941
1102
|
const handleOnScanClick = async () => {
|
|
942
1103
|
consoleLogger.info('Scan: click detected');
|
|
@@ -947,17 +1108,9 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
947
1108
|
await processPage(page, processPageParams);
|
|
948
1109
|
log('Scan: success');
|
|
949
1110
|
pagesDict[pageId].isScanning = false;
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
inProgress: false,
|
|
954
|
-
collapsed: !!pagesDict[pageId]?.collapsed,
|
|
955
|
-
hideStopInput: !!processPageParams.customFlowLabel,
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
else {
|
|
959
|
-
await removeOverlayMenu(page);
|
|
960
|
-
}
|
|
1111
|
+
if (page.isClosed())
|
|
1112
|
+
return;
|
|
1113
|
+
await reconcileOverlayMenu('scan-click');
|
|
961
1114
|
}
|
|
962
1115
|
catch (error) {
|
|
963
1116
|
log(`Scan failed ${error}`);
|
|
@@ -987,10 +1140,10 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
987
1140
|
});
|
|
988
1141
|
if (!inputValue?.confirmed) {
|
|
989
1142
|
await page.evaluate(() => {
|
|
990
|
-
const
|
|
991
|
-
if (
|
|
992
|
-
|
|
993
|
-
|
|
1143
|
+
const endScanBtn = document.getElementById('oobeeBtnEndScan');
|
|
1144
|
+
if (endScanBtn) {
|
|
1145
|
+
endScanBtn.disabled = false;
|
|
1146
|
+
endScanBtn.textContent = 'Stop';
|
|
994
1147
|
}
|
|
995
1148
|
});
|
|
996
1149
|
return;
|
|
@@ -1019,47 +1172,41 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1019
1172
|
}
|
|
1020
1173
|
};
|
|
1021
1174
|
page.on('domcontentloaded', async () => {
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
return document.querySelector('#oobeeShadowHost');
|
|
1030
|
-
});
|
|
1031
|
-
consoleLogger.info(`Overlay state: ${existingOverlay}`);
|
|
1032
|
-
if (!existingOverlay) {
|
|
1033
|
-
consoleLogger.info(`Adding overlay menu to page: ${page.url()}`);
|
|
1034
|
-
await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
|
|
1035
|
-
inProgress: !!pagesDict[pageId]?.isScanning,
|
|
1036
|
-
collapsed: !!pagesDict[pageId]?.collapsed,
|
|
1037
|
-
hideStopInput: !!processPageParams.customFlowLabel,
|
|
1038
|
-
});
|
|
1175
|
+
if (page.isClosed())
|
|
1176
|
+
return;
|
|
1177
|
+
await reconcileOverlayMenu('domcontentloaded');
|
|
1178
|
+
if (isCypressTest) {
|
|
1179
|
+
try {
|
|
1180
|
+
await handleOnScanClick();
|
|
1181
|
+
page.close();
|
|
1039
1182
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
await handleOnScanClick();
|
|
1043
|
-
page.close();
|
|
1044
|
-
}
|
|
1045
|
-
catch {
|
|
1046
|
-
consoleLogger.info(`Error in calling handleOnScanClick, isCypressTest: ${isCypressTest}`);
|
|
1047
|
-
}
|
|
1183
|
+
catch {
|
|
1184
|
+
consoleLogger.info(`Error in calling handleOnScanClick, isCypressTest: ${isCypressTest}`);
|
|
1048
1185
|
}
|
|
1049
1186
|
}
|
|
1050
|
-
catch {
|
|
1051
|
-
consoleLogger.info('Error in adding overlay menu to page');
|
|
1052
|
-
}
|
|
1053
1187
|
});
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1188
|
+
page.on('framenavigated', async (frame) => {
|
|
1189
|
+
if (frame !== page.mainFrame() || page.isClosed())
|
|
1190
|
+
return;
|
|
1191
|
+
await reconcileOverlayMenu('framenavigated');
|
|
1192
|
+
});
|
|
1193
|
+
try {
|
|
1194
|
+
if (page.isClosed())
|
|
1195
|
+
return page;
|
|
1196
|
+
await page.exposeFunction('handleOnScanClick', handleOnScanClick);
|
|
1197
|
+
await page.exposeFunction('handleOnStopClick', handleOnStopClick);
|
|
1198
|
+
// Define the updateMenuPos function
|
|
1199
|
+
const updateMenuPos = newPos => {
|
|
1200
|
+
const prevPos = menuPos;
|
|
1201
|
+
if (prevPos !== newPos) {
|
|
1202
|
+
menuPos = newPos;
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
await page.exposeFunction('updateMenuPos', updateMenuPos);
|
|
1206
|
+
}
|
|
1207
|
+
catch (e) {
|
|
1208
|
+
log(`Error exposing functions on page: ${e}`);
|
|
1209
|
+
}
|
|
1210
|
+
await reconcileOverlayMenu('init');
|
|
1064
1211
|
return page;
|
|
1065
1212
|
};
|
|
@@ -2,15 +2,16 @@ const ALLOWED_PROTOCOLS = new Set(['http:', 'https:']);
|
|
|
2
2
|
export function addUrlGuardScript(context, opts = {}) {
|
|
3
3
|
const { fallbackUrl } = opts;
|
|
4
4
|
const lastAllowedUrlByPage = new WeakMap();
|
|
5
|
-
const attachGuardsToPage =
|
|
5
|
+
const attachGuardsToPage = page => {
|
|
6
6
|
if (!lastAllowedUrlByPage.has(page) && fallbackUrl) {
|
|
7
7
|
lastAllowedUrlByPage.set(page, String(fallbackUrl));
|
|
8
8
|
}
|
|
9
|
-
page
|
|
10
|
-
|
|
9
|
+
page
|
|
10
|
+
.addInitScript(() => {
|
|
11
|
+
const isAllowedProtocol = value => {
|
|
11
12
|
try {
|
|
12
13
|
const s = value instanceof URL ? value.toString() : String(value);
|
|
13
|
-
const protocol = new URL(s, window.location.href)
|
|
14
|
+
const { protocol } = new URL(s, window.location.href);
|
|
14
15
|
return protocol === 'http:' || protocol === 'https:';
|
|
15
16
|
}
|
|
16
17
|
catch {
|
|
@@ -24,17 +25,9 @@ export function addUrlGuardScript(context, opts = {}) {
|
|
|
24
25
|
return null;
|
|
25
26
|
return openOriginal.call(this, targetUrl, ...args);
|
|
26
27
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
assignOriginal(nextUrl); };
|
|
31
|
-
win.location.replace = (nextUrl) => { if (isAllowedProtocol(nextUrl))
|
|
32
|
-
replaceOriginal(nextUrl); };
|
|
33
|
-
Object.defineProperty(win.location, 'href', {
|
|
34
|
-
get() { return String(win.location.toString()); },
|
|
35
|
-
set(nextUrl) { if (isAllowedProtocol(nextUrl))
|
|
36
|
-
assignOriginal(nextUrl); },
|
|
37
|
-
});
|
|
28
|
+
})
|
|
29
|
+
.catch(() => {
|
|
30
|
+
// page may have closed before addInitScript completed; safe to ignore
|
|
38
31
|
});
|
|
39
32
|
const restoreToSafeUrl = async (page, attemptedUrl) => {
|
|
40
33
|
try {
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/* eslint-env browser */
|
|
2
|
-
import { chromium } from 'playwright';
|
|
3
2
|
import { createCrawleeSubFolders } from './commonCrawlerFunc.js';
|
|
4
3
|
import { cleanUpAndExit, register, registerSoftClose } from '../utils.js';
|
|
5
4
|
import constants, { getIntermediateScreenshotsPath, guiInfoStatusTypes, } from '../constants/constants.js';
|
|
6
5
|
import { initNewPage, log } from './custom/utils.js';
|
|
7
6
|
import { guiInfoLog } from '../logs.js';
|
|
8
7
|
import { addUrlGuardScript } from './guards/urlGuard.js';
|
|
9
|
-
import { getPlaywrightLaunchOptions } from '../constants/common.js';
|
|
8
|
+
import { getBrowserToRun, getPlaywrightLaunchOptions, initModifiedUserAgent, } from '../constants/common.js';
|
|
10
9
|
// Export of classes
|
|
11
10
|
export class ProcessPageParams {
|
|
12
11
|
constructor(scannedIdx, blacklistedPatterns, includeScreenshots, dataset, intermediateScreenshotsPath, urlsCrawled, randomToken) {
|
|
@@ -19,7 +18,7 @@ export class ProcessPageParams {
|
|
|
19
18
|
this.randomToken = randomToken;
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
|
-
const runCustom = async (url, randomToken, viewportSettings, blacklistedPatterns, includeScreenshots, initialCustomFlowLabel) => {
|
|
21
|
+
const runCustom = async (url, randomToken, browserToRun, userDataDirectory, viewportSettings, blacklistedPatterns, includeScreenshots, initialCustomFlowLabel) => {
|
|
23
22
|
// checks and delete datasets path if it already exists
|
|
24
23
|
process.env.CRAWLEE_STORAGE_DIR = randomToken;
|
|
25
24
|
const urlsCrawled = { ...constants.urlsCrawledObj };
|
|
@@ -34,9 +33,13 @@ const runCustom = async (url, randomToken, viewportSettings, blacklistedPatterns
|
|
|
34
33
|
const pagesDict = {};
|
|
35
34
|
const pageClosePromises = [];
|
|
36
35
|
try {
|
|
36
|
+
const { browserToRun: resolvedBrowserToRun } = getBrowserToRun(randomToken, browserToRun, false);
|
|
37
37
|
const deviceConfig = viewportSettings.playwrightDeviceDetailsObject;
|
|
38
38
|
const hasCustomViewport = !!deviceConfig;
|
|
39
|
-
const
|
|
39
|
+
const rawDevice = (deviceConfig || {});
|
|
40
|
+
const { userAgent: deviceUserAgent, ...contextDeviceOptions } = rawDevice;
|
|
41
|
+
await initModifiedUserAgent(resolvedBrowserToRun, viewportSettings.playwrightDeviceDetailsObject);
|
|
42
|
+
const baseLaunchOptions = getPlaywrightLaunchOptions(resolvedBrowserToRun);
|
|
40
43
|
// Merge base args with custom flow specific args
|
|
41
44
|
const baseArgs = baseLaunchOptions.args || [];
|
|
42
45
|
const customArgs = hasCustomViewport ? ['--window-size=1920,1040'] : ['--start-maximized'];
|
|
@@ -44,33 +47,37 @@ const runCustom = async (url, randomToken, viewportSettings, blacklistedPatterns
|
|
|
44
47
|
...baseArgs.filter(a => !a.startsWith('--window-size') && a !== '--start-maximized'),
|
|
45
48
|
...customArgs,
|
|
46
49
|
];
|
|
47
|
-
const
|
|
50
|
+
const context = await constants.launcher.launchPersistentContext(userDataDirectory, {
|
|
48
51
|
...baseLaunchOptions,
|
|
49
52
|
args: mergedArgs,
|
|
50
53
|
headless: false,
|
|
51
|
-
});
|
|
52
|
-
const context = await browser.newContext({
|
|
53
54
|
ignoreHTTPSErrors: true,
|
|
54
55
|
serviceWorkers: 'block',
|
|
55
56
|
viewport: null,
|
|
56
|
-
...(hasCustomViewport ?
|
|
57
|
+
...(hasCustomViewport ? contextDeviceOptions : {}),
|
|
58
|
+
userAgent: process.env.OOBEE_USER_AGENT || deviceUserAgent,
|
|
57
59
|
});
|
|
58
60
|
register(context);
|
|
59
61
|
processPageParams.stopAll = async () => {
|
|
60
62
|
try {
|
|
61
63
|
await context.close().catch(() => { });
|
|
62
|
-
await browser.close().catch(() => { });
|
|
63
64
|
}
|
|
64
65
|
catch { }
|
|
65
66
|
};
|
|
66
67
|
// For handling closing playwright browser and continue generate artifacts etc
|
|
67
68
|
registerSoftClose(processPageParams.stopAll);
|
|
68
69
|
addUrlGuardScript(context, { fallbackUrl: url });
|
|
70
|
+
const page = context.pages().find(existingPage => !existingPage.isClosed()) || (await context.newPage());
|
|
71
|
+
await initNewPage(page, pageClosePromises, processPageParams, pagesDict);
|
|
69
72
|
// Detection of new page
|
|
70
73
|
context.on('page', async (newPage) => {
|
|
71
|
-
|
|
74
|
+
try {
|
|
75
|
+
await initNewPage(newPage, pageClosePromises, processPageParams, pagesDict);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
log(`Error initializing new page: ${e}`);
|
|
79
|
+
}
|
|
72
80
|
});
|
|
73
|
-
const page = await context.newPage();
|
|
74
81
|
await page.goto(url, { timeout: 0 });
|
|
75
82
|
// to execute and wait for all pages to close
|
|
76
83
|
// idea is for promise to be pending until page.on('close') detected
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { compressJsonFileStreaming, writeHTML, flattenAndSortResults, populateScanPagesDetail, getWcagPassPercentage, getProgressPercentage, getIssuesPercentage, itemTypeDescription, oobeeAiHtmlETL, oobeeAiRules, formatAboutStartTime, convertItemsToReferences, } from './mergeAxeResults.js';
|
|
4
|
-
import constants, { ScannerTypes, WCAGclauses, a11yRuleShortDescriptionMap, disabilityBadgesMap, a11yRuleLongDescriptionMap, } from './constants/constants.js';
|
|
4
|
+
import constants, { ScannerTypes, WCAGclauses, a11yRuleShortDescriptionMap, disabilityBadgesMap, a11yRuleLongDescriptionMap, a11yRuleStepByStepGuide, } from './constants/constants.js';
|
|
5
5
|
import { consoleLogger } from './logs.js';
|
|
6
6
|
const ensureCategory = (categoryObj, categoryName) => {
|
|
7
7
|
const rulesRaw = categoryObj?.rules ?? [];
|
|
@@ -23,7 +23,12 @@ const ensureCategory = (categoryObj, categoryName) => {
|
|
|
23
23
|
rule.pagesAffected = [];
|
|
24
24
|
}
|
|
25
25
|
if (typeof rule.totalItems !== 'number') {
|
|
26
|
-
rule.totalItems = rule.pagesAffected.reduce((accumulate, page) => accumulate +
|
|
26
|
+
rule.totalItems = rule.pagesAffected.reduce((accumulate, page) => accumulate +
|
|
27
|
+
(Array.isArray(page.items)
|
|
28
|
+
? page.items.length
|
|
29
|
+
: typeof page.itemsCount === 'number'
|
|
30
|
+
? page.itemsCount
|
|
31
|
+
: 0), 0);
|
|
27
32
|
}
|
|
28
33
|
});
|
|
29
34
|
const totals = {
|
|
@@ -38,7 +43,7 @@ const ensureCategory = (categoryObj, categoryName) => {
|
|
|
38
43
|
rules,
|
|
39
44
|
};
|
|
40
45
|
};
|
|
41
|
-
export const generateHtmlReport = async (resultDir) => {
|
|
46
|
+
export const generateHtmlReport = async (resultDir, htmlFilename = 'report') => {
|
|
42
47
|
try {
|
|
43
48
|
const storagePath = path.resolve(resultDir);
|
|
44
49
|
const scanDataJsonPath = path.join(storagePath, 'scanData.json');
|
|
@@ -61,17 +66,16 @@ export const generateHtmlReport = async (resultDir) => {
|
|
|
61
66
|
}
|
|
62
67
|
const scanData = JSON.parse(await fs.readFile(scanDataJsonPath, 'utf8'));
|
|
63
68
|
const scanItemsAll = JSON.parse(await fs.readFile(scanItemsJsonPath, 'utf8'));
|
|
64
|
-
//
|
|
65
|
-
const
|
|
69
|
+
// Build the lighter scanItems payload used by the HTML report.
|
|
70
|
+
const lightScanItemsPayload = convertItemsToReferences({
|
|
66
71
|
items: scanItemsAll,
|
|
67
|
-
...scanData
|
|
68
72
|
});
|
|
69
|
-
const { mustFix = {}, goodToFix = {}, needsReview = {},
|
|
73
|
+
const { mustFix = {}, goodToFix = {}, needsReview = {}, } = lightScanItemsPayload;
|
|
70
74
|
const items = {
|
|
71
75
|
mustFix: ensureCategory(mustFix, 'mustFix'),
|
|
72
76
|
goodToFix: ensureCategory(goodToFix, 'goodToFix'),
|
|
73
77
|
needsReview: ensureCategory(needsReview, 'needsReview'),
|
|
74
|
-
passed: ensureCategory(passed, 'passed'),
|
|
78
|
+
passed: ensureCategory(scanItemsAll.passed || {}, 'passed'),
|
|
75
79
|
};
|
|
76
80
|
const pagesScanned = Array.isArray(scanData.pagesScanned) ? scanData.pagesScanned : [];
|
|
77
81
|
const pagesNotScanned = Array.isArray(scanData.pagesNotScanned) ? scanData.pagesNotScanned : [];
|
|
@@ -116,6 +120,8 @@ export const generateHtmlReport = async (resultDir) => {
|
|
|
116
120
|
a11yRuleShortDescriptionMap,
|
|
117
121
|
disabilityBadgesMap,
|
|
118
122
|
a11yRuleLongDescriptionMap,
|
|
123
|
+
a11yRuleStepByStepGuide,
|
|
124
|
+
wcagCriteriaLabels: constants.wcagCriteriaLabels,
|
|
119
125
|
advancedScanOptionsSummaryItems: {
|
|
120
126
|
showIncludeScreenshots: !!scanData.advancedScanOptionsSummaryItems?.showIncludeScreenshots,
|
|
121
127
|
showAllowSubdomains: !!scanData.advancedScanOptionsSummaryItems?.showAllowSubdomains,
|
|
@@ -137,9 +143,10 @@ export const generateHtmlReport = async (resultDir) => {
|
|
|
137
143
|
allIssues.wcagPassPercentage = getWcagPassPercentage(allIssues.wcagViolations, allIssues.advancedScanOptionsSummaryItems.showEnableWcagAaa);
|
|
138
144
|
allIssues.progressPercentage = getProgressPercentage(allIssues.scanPagesDetail, allIssues.advancedScanOptionsSummaryItems.showEnableWcagAaa);
|
|
139
145
|
allIssues.issuesPercentage = await getIssuesPercentage(allIssues.scanPagesDetail, allIssues.advancedScanOptionsSummaryItems.showEnableWcagAaa, allIssues.advancedScanOptionsSummaryItems?.disableOobee);
|
|
140
|
-
await writeHTML(allIssues, storagePath,
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
await writeHTML(allIssues, storagePath, htmlFilename, scanDataB64Path, scanItemsB64Path);
|
|
147
|
+
const outputPath = path.join(storagePath, `${htmlFilename}.html`);
|
|
148
|
+
consoleLogger.info(`Report generated at: ${outputPath}`);
|
|
149
|
+
return outputPath;
|
|
143
150
|
}
|
|
144
151
|
catch (err) {
|
|
145
152
|
consoleLogger.error(`generateHtmlReport failed: ${err?.message || err}`);
|