@govtechsg/oobee 0.10.84 → 0.10.85
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/dist/cli.js +7 -6
- package/dist/crawlers/custom/utils.js +81 -40
- package/dist/crawlers/runCustom.js +7 -5
- package/dist/mergeAxeResults.js +44 -25
- package/package.json +2 -2
- package/src/cli.ts +20 -15
- package/src/crawlers/custom/utils.ts +103 -48
- package/src/crawlers/runCustom.ts +10 -5
- package/src/mergeAxeResults.ts +43 -26
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import printMessage from 'print-message';
|
|
|
5
5
|
import { devices } from 'playwright';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import path from 'path';
|
|
8
|
-
import { setHeadlessMode, getVersion, getStoragePath, listenForCleanUp, cleanUpAndExit } from './utils.js';
|
|
8
|
+
import { setHeadlessMode, getVersion, getStoragePath, listenForCleanUp, cleanUpAndExit, } from './utils.js';
|
|
9
9
|
import { checkUrl, prepareData, validEmail, validName, getScreenToScan, validateDirPath, validateFilePath, validateCustomFlowLabel, } from './constants/common.js';
|
|
10
10
|
import constants, { ScannerTypes } from './constants/constants.js';
|
|
11
11
|
import { cliOptions, messageOptions } from './constants/cliFunctions.js';
|
|
@@ -146,12 +146,13 @@ Usage: npm run cli -- -c <crawler> -d <device> -w <viewport> -u <url> OPTIONS`)
|
|
|
146
146
|
return true;
|
|
147
147
|
})
|
|
148
148
|
.check(argvs => {
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
const scanner = String(argvs.scanner ?? '');
|
|
150
|
+
if (argvs.strategy && scanner !== ScannerTypes.WEBSITE && scanner !== ScannerTypes.CUSTOM) {
|
|
151
|
+
throw new Error('-s or --strategy is only available in website and custom flow scans.');
|
|
151
152
|
}
|
|
152
153
|
return true;
|
|
153
154
|
})
|
|
154
|
-
.coerce('l',
|
|
155
|
+
.coerce('l', option => {
|
|
155
156
|
const duration = Number(option);
|
|
156
157
|
if (isNaN(duration) || duration < 0) {
|
|
157
158
|
printMessage(['Invalid scan duration. Please provide a positive number of seconds.'], messageOptions);
|
|
@@ -160,8 +161,8 @@ Usage: npm run cli -- -c <crawler> -d <device> -w <viewport> -u <url> OPTIONS`)
|
|
|
160
161
|
return duration;
|
|
161
162
|
})
|
|
162
163
|
.check(argvs => {
|
|
163
|
-
if (argvs.scanner
|
|
164
|
-
throw new Error('-
|
|
164
|
+
if (argvs.scanner !== ScannerTypes.WEBSITE && argvs.strategy) {
|
|
165
|
+
throw new Error('-s or --strategy is only available in website scans.');
|
|
165
166
|
}
|
|
166
167
|
return true;
|
|
167
168
|
})
|
|
@@ -1,12 +1,44 @@
|
|
|
1
|
-
/* eslint-disable no-shadow */
|
|
2
1
|
/* eslint-disable no-alert */
|
|
3
2
|
/* eslint-disable no-param-reassign */
|
|
4
3
|
/* eslint-env browser */
|
|
5
4
|
import path from 'path';
|
|
5
|
+
import { getDomain } from 'tldts';
|
|
6
6
|
import { runAxeScript } from '../commonCrawlerFunc.js';
|
|
7
7
|
import { consoleLogger, guiInfoLog } from '../../logs.js';
|
|
8
8
|
import { guiInfoStatusTypes } from '../../constants/constants.js';
|
|
9
9
|
import { isSkippedUrl, validateCustomFlowLabel } from '../../constants/common.js';
|
|
10
|
+
const sameRegistrableDomain = (hostA, hostB) => {
|
|
11
|
+
const domainA = getDomain(hostA);
|
|
12
|
+
const domainB = getDomain(hostB);
|
|
13
|
+
if (!domainA || !domainB)
|
|
14
|
+
return hostA === hostB;
|
|
15
|
+
return domainA === domainB;
|
|
16
|
+
};
|
|
17
|
+
const parseBoolEnv = (val, defaultVal) => {
|
|
18
|
+
if (val == null)
|
|
19
|
+
return defaultVal;
|
|
20
|
+
const v = String(val).trim().toLowerCase();
|
|
21
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(v))
|
|
22
|
+
return true;
|
|
23
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(v))
|
|
24
|
+
return false;
|
|
25
|
+
return defaultVal;
|
|
26
|
+
};
|
|
27
|
+
const RESTRICT_OVERLAY_TO_ENTRY_DOMAIN = parseBoolEnv(process.env.RESTRICT_OVERLAY_TO_ENTRY_DOMAIN, false);
|
|
28
|
+
const isOverlayAllowed = (currentUrl, entryUrl) => {
|
|
29
|
+
try {
|
|
30
|
+
const cur = new URL(currentUrl);
|
|
31
|
+
if (cur.protocol !== 'http:' && cur.protocol !== 'https:')
|
|
32
|
+
return false;
|
|
33
|
+
if (!RESTRICT_OVERLAY_TO_ENTRY_DOMAIN)
|
|
34
|
+
return true;
|
|
35
|
+
const base = new URL(entryUrl);
|
|
36
|
+
return sameRegistrableDomain(cur.hostname, base.hostname);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
10
42
|
//! For Cypress Test
|
|
11
43
|
// env to check if Cypress test is running
|
|
12
44
|
const isCypressTest = process.env.IS_CYPRESS_TEST === 'true';
|
|
@@ -33,9 +65,7 @@ export const screenshotFullPage = async (page, screenshotsDir, screenshotIdx) =>
|
|
|
33
65
|
const isLoadMoreContent = async () => new Promise(resolve => {
|
|
34
66
|
setTimeout(async () => {
|
|
35
67
|
await page.waitForLoadState('domcontentloaded');
|
|
36
|
-
const newHeight = await page.evaluate(
|
|
37
|
-
// eslint-disable-next-line no-shadow
|
|
38
|
-
() => document.body.scrollHeight);
|
|
68
|
+
const newHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
39
69
|
const result = newHeight > prevHeight;
|
|
40
70
|
resolve(result);
|
|
41
71
|
}, 2500);
|
|
@@ -157,7 +187,7 @@ export const MENU_POSITION = {
|
|
|
157
187
|
export const updateMenu = async (page, urlsCrawled) => {
|
|
158
188
|
log(`Overlay menu: updating: ${page.url()}`);
|
|
159
189
|
await page.evaluate(vars => {
|
|
160
|
-
const shadowHost = document.querySelector('#
|
|
190
|
+
const shadowHost = document.querySelector('#oobeeShadowHost');
|
|
161
191
|
if (shadowHost) {
|
|
162
192
|
const p = shadowHost.shadowRoot.querySelector('#oobee-p-pages-scanned');
|
|
163
193
|
if (p) {
|
|
@@ -200,7 +230,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
200
230
|
</svg>
|
|
201
231
|
`;
|
|
202
232
|
minBtn.innerHTML = MINBTN_SVG;
|
|
203
|
-
let currentPos =
|
|
233
|
+
let currentPos = vars.menuPos || 'RIGHT';
|
|
204
234
|
const isCollapsed = () => panel.classList.contains('collapsed');
|
|
205
235
|
const setPosClass = (pos) => {
|
|
206
236
|
panel.classList.remove('pos-left', 'pos-right');
|
|
@@ -217,7 +247,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
217
247
|
setDraggableSidebarMenu();
|
|
218
248
|
};
|
|
219
249
|
const toggleCollapsed = (force) => {
|
|
220
|
-
const willCollapse =
|
|
250
|
+
const willCollapse = typeof force === 'boolean' ? force : !isCollapsed();
|
|
221
251
|
if (willCollapse) {
|
|
222
252
|
panel.classList.add('collapsed');
|
|
223
253
|
localStorage.setItem('oobee:overlay-collapsed', '1');
|
|
@@ -292,12 +322,12 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
292
322
|
}
|
|
293
323
|
const ol = document.createElement('ol');
|
|
294
324
|
ol.className = 'oobee-ol';
|
|
295
|
-
scanned.forEach(
|
|
325
|
+
scanned.forEach(item => {
|
|
296
326
|
const li = document.createElement('li');
|
|
297
327
|
li.className = 'oobee-li';
|
|
298
328
|
const title = document.createElement('div');
|
|
299
329
|
title.className = 'oobee-item-title';
|
|
300
|
-
title.textContent =
|
|
330
|
+
title.textContent = item.pageTitle && item.pageTitle.trim() ? item.pageTitle : item.url;
|
|
301
331
|
const url = document.createElement('div');
|
|
302
332
|
url.className = 'oobee-item-url';
|
|
303
333
|
url.textContent = item.url;
|
|
@@ -596,8 +626,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
596
626
|
if (!icon)
|
|
597
627
|
return;
|
|
598
628
|
const closed = isCollapsed();
|
|
599
|
-
const arrowPointsRight = (currentPos === 'RIGHT' && !closed) ||
|
|
600
|
-
(currentPos === 'LEFT' && closed);
|
|
629
|
+
const arrowPointsRight = (currentPos === 'RIGHT' && !closed) || (currentPos === 'LEFT' && closed);
|
|
601
630
|
icon.classList.toggle('is-left', !arrowPointsRight);
|
|
602
631
|
minBtn.setAttribute('aria-label', closed ? 'Expand panel' : 'Collapse panel');
|
|
603
632
|
}
|
|
@@ -652,7 +681,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
652
681
|
borderRadius: '16px',
|
|
653
682
|
overflow: 'hidden',
|
|
654
683
|
boxShadow: '0 10px 40px rgba(0,0,0,.35)',
|
|
655
|
-
fontFamily: 'system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif'
|
|
684
|
+
fontFamily: 'system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif',
|
|
656
685
|
});
|
|
657
686
|
const dialogSheet = new CSSStyleSheet();
|
|
658
687
|
dialogSheet.replaceSync(`
|
|
@@ -689,12 +718,17 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
689
718
|
display: 'flex',
|
|
690
719
|
alignItems: 'center',
|
|
691
720
|
justifyContent: 'space-between',
|
|
692
|
-
gap: '8px'
|
|
721
|
+
gap: '8px',
|
|
693
722
|
});
|
|
694
723
|
const title = document.createElement('h2');
|
|
695
724
|
title.id = 'oobee-stop-title';
|
|
696
725
|
title.textContent = 'Are you sure you want to stop this scan?';
|
|
697
|
-
Object.assign(title.style, {
|
|
726
|
+
Object.assign(title.style, {
|
|
727
|
+
margin: '0',
|
|
728
|
+
fontSize: '22px',
|
|
729
|
+
fontWeight: '700',
|
|
730
|
+
lineHeight: '1.25',
|
|
731
|
+
});
|
|
698
732
|
const closeX = document.createElement('button');
|
|
699
733
|
closeX.type = 'button';
|
|
700
734
|
closeX.setAttribute('aria-label', 'Close');
|
|
@@ -711,13 +745,13 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
711
745
|
height: '36px',
|
|
712
746
|
borderRadius: '12px',
|
|
713
747
|
display: 'grid',
|
|
714
|
-
placeItems: 'center'
|
|
748
|
+
placeItems: 'center',
|
|
715
749
|
});
|
|
716
750
|
head.appendChild(title);
|
|
717
751
|
head.appendChild(closeX);
|
|
718
752
|
const bodyWrap = document.createElement('div');
|
|
719
753
|
Object.assign(bodyWrap.style, {
|
|
720
|
-
padding: '12px 20px 20px 20px'
|
|
754
|
+
padding: '12px 20px 20px 20px',
|
|
721
755
|
});
|
|
722
756
|
const form = document.createElement('form');
|
|
723
757
|
form.noValidate = true;
|
|
@@ -725,7 +759,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
725
759
|
Object.assign(form.style, {
|
|
726
760
|
display: 'grid',
|
|
727
761
|
gridTemplateColumns: '1fr',
|
|
728
|
-
rowGap: '12px'
|
|
762
|
+
rowGap: '12px',
|
|
729
763
|
});
|
|
730
764
|
const label = document.createElement('label');
|
|
731
765
|
label.setAttribute('for', 'oobee-stop-input');
|
|
@@ -741,7 +775,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
741
775
|
padding: '12px 14px',
|
|
742
776
|
fontSize: '14px',
|
|
743
777
|
outline: 'none',
|
|
744
|
-
boxSizing: 'border-box'
|
|
778
|
+
boxSizing: 'border-box',
|
|
745
779
|
});
|
|
746
780
|
input.addEventListener('focus', () => {
|
|
747
781
|
input.style.borderColor = '#7b4dff';
|
|
@@ -765,7 +799,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
765
799
|
fontWeight: '600',
|
|
766
800
|
color: '#fff',
|
|
767
801
|
background: '#9021A6',
|
|
768
|
-
cursor: 'pointer'
|
|
802
|
+
cursor: 'pointer',
|
|
769
803
|
});
|
|
770
804
|
const cancel = document.createElement('button');
|
|
771
805
|
cancel.type = 'button';
|
|
@@ -777,7 +811,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
777
811
|
fontSize: '14px',
|
|
778
812
|
justifySelf: 'center',
|
|
779
813
|
cursor: 'pointer',
|
|
780
|
-
padding: '6px'
|
|
814
|
+
padding: '6px',
|
|
781
815
|
});
|
|
782
816
|
actions.appendChild(primary);
|
|
783
817
|
actions.appendChild(cancel);
|
|
@@ -792,10 +826,13 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
792
826
|
stopDialog.appendChild(bodyWrap);
|
|
793
827
|
shadowRoot.appendChild(stopDialog);
|
|
794
828
|
let stopResolver = null;
|
|
795
|
-
const hideStop = () => {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
829
|
+
const hideStop = () => {
|
|
830
|
+
try {
|
|
831
|
+
stopDialog.close();
|
|
832
|
+
}
|
|
833
|
+
catch { }
|
|
834
|
+
stopResolver = null;
|
|
835
|
+
};
|
|
799
836
|
const showStop = () => {
|
|
800
837
|
if (!shouldHideInput)
|
|
801
838
|
input.value = '';
|
|
@@ -813,7 +850,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
813
850
|
});
|
|
814
851
|
}
|
|
815
852
|
};
|
|
816
|
-
form.addEventListener('submit',
|
|
853
|
+
form.addEventListener('submit', e => {
|
|
817
854
|
e.preventDefault();
|
|
818
855
|
const v = (input.value || '').trim();
|
|
819
856
|
if (stopResolver)
|
|
@@ -830,13 +867,13 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
830
867
|
stopResolver({ confirmed: false, label: '' });
|
|
831
868
|
hideStop();
|
|
832
869
|
});
|
|
833
|
-
stopDialog.addEventListener('cancel',
|
|
870
|
+
stopDialog.addEventListener('cancel', e => {
|
|
834
871
|
e.preventDefault();
|
|
835
872
|
if (stopResolver)
|
|
836
873
|
stopResolver({ confirmed: false, label: '' });
|
|
837
874
|
hideStop();
|
|
838
875
|
});
|
|
839
|
-
customWindow.oobeeShowStopModal = () => new Promise(
|
|
876
|
+
customWindow.oobeeShowStopModal = () => new Promise(resolve => {
|
|
840
877
|
stopResolver = resolve;
|
|
841
878
|
showStop();
|
|
842
879
|
});
|
|
@@ -861,7 +898,7 @@ export const addOverlayMenu = async (page, urlsCrawled, menuPos, opts = {
|
|
|
861
898
|
log('Overlay menu: successfully added');
|
|
862
899
|
})
|
|
863
900
|
.catch(error => {
|
|
864
|
-
error('Overlay menu: failed to add', error);
|
|
901
|
+
consoleLogger.error('Overlay menu: failed to add', error);
|
|
865
902
|
});
|
|
866
903
|
};
|
|
867
904
|
export const removeOverlayMenu = async (page) => {
|
|
@@ -910,11 +947,17 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
910
947
|
await processPage(page, processPageParams);
|
|
911
948
|
log('Scan: success');
|
|
912
949
|
pagesDict[pageId].isScanning = false;
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
950
|
+
const allowed = isOverlayAllowed(page.url(), processPageParams.entryUrl);
|
|
951
|
+
if (allowed) {
|
|
952
|
+
await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
|
|
953
|
+
inProgress: false,
|
|
954
|
+
collapsed: !!pagesDict[pageId]?.collapsed,
|
|
955
|
+
hideStopInput: !!processPageParams.customFlowLabel,
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
else {
|
|
959
|
+
await removeOverlayMenu(page);
|
|
960
|
+
}
|
|
918
961
|
}
|
|
919
962
|
catch (error) {
|
|
920
963
|
log(`Scan failed ${error}`);
|
|
@@ -977,6 +1020,11 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
977
1020
|
};
|
|
978
1021
|
page.on('domcontentloaded', async () => {
|
|
979
1022
|
try {
|
|
1023
|
+
const allowed = isOverlayAllowed(page.url(), processPageParams.entryUrl);
|
|
1024
|
+
if (!allowed) {
|
|
1025
|
+
await removeOverlayMenu(page);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
980
1028
|
const existingOverlay = await page.evaluate(() => {
|
|
981
1029
|
return document.querySelector('#oobeeShadowHost');
|
|
982
1030
|
});
|
|
@@ -989,11 +1037,6 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
989
1037
|
hideStopInput: !!processPageParams.customFlowLabel,
|
|
990
1038
|
});
|
|
991
1039
|
}
|
|
992
|
-
setTimeout(() => {
|
|
993
|
-
// Timeout here to slow things down a little
|
|
994
|
-
}, 1000);
|
|
995
|
-
//! For Cypress Test
|
|
996
|
-
// Auto-clicks 'Scan this page' button only once
|
|
997
1040
|
if (isCypressTest) {
|
|
998
1041
|
try {
|
|
999
1042
|
await handleOnScanClick();
|
|
@@ -1003,11 +1046,9 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1003
1046
|
consoleLogger.info(`Error in calling handleOnScanClick, isCypressTest: ${isCypressTest}`);
|
|
1004
1047
|
}
|
|
1005
1048
|
}
|
|
1006
|
-
consoleLogger.info(`Overlay state: ${existingOverlay}`);
|
|
1007
1049
|
}
|
|
1008
1050
|
catch {
|
|
1009
1051
|
consoleLogger.info('Error in adding overlay menu to page');
|
|
1010
|
-
consoleLogger.info('Error in adding overlay menu to page');
|
|
1011
1052
|
}
|
|
1012
1053
|
});
|
|
1013
1054
|
await page.exposeFunction('handleOnScanClick', handleOnScanClick);
|
|
@@ -27,6 +27,7 @@ const runCustom = async (url, randomToken, viewportSettings, blacklistedPatterns
|
|
|
27
27
|
const intermediateScreenshotsPath = getIntermediateScreenshotsPath(randomToken);
|
|
28
28
|
const processPageParams = new ProcessPageParams(0, // scannedIdx
|
|
29
29
|
blacklistedPatterns, includeScreenshots, dataset, intermediateScreenshotsPath, urlsCrawled, randomToken);
|
|
30
|
+
processPageParams.entryUrl = url;
|
|
30
31
|
if (initialCustomFlowLabel && initialCustomFlowLabel.trim()) {
|
|
31
32
|
processPageParams.customFlowLabel = initialCustomFlowLabel.trim();
|
|
32
33
|
}
|
|
@@ -35,16 +36,18 @@ const runCustom = async (url, randomToken, viewportSettings, blacklistedPatterns
|
|
|
35
36
|
try {
|
|
36
37
|
const deviceConfig = viewportSettings.playwrightDeviceDetailsObject;
|
|
37
38
|
const hasCustomViewport = !!deviceConfig;
|
|
38
|
-
const baseLaunchOptions = getPlaywrightLaunchOptions(
|
|
39
|
+
const baseLaunchOptions = getPlaywrightLaunchOptions();
|
|
39
40
|
// Merge base args with custom flow specific args
|
|
40
41
|
const baseArgs = baseLaunchOptions.args || [];
|
|
41
42
|
const customArgs = hasCustomViewport ? ['--window-size=1920,1040'] : ['--start-maximized'];
|
|
42
|
-
const mergedArgs = [
|
|
43
|
+
const mergedArgs = [
|
|
44
|
+
...baseArgs.filter(a => !a.startsWith('--window-size') && a !== '--start-maximized'),
|
|
45
|
+
...customArgs,
|
|
46
|
+
];
|
|
43
47
|
const browser = await chromium.launch({
|
|
44
48
|
...baseLaunchOptions,
|
|
45
49
|
args: mergedArgs,
|
|
46
50
|
headless: false,
|
|
47
|
-
channel: 'chrome',
|
|
48
51
|
});
|
|
49
52
|
const context = await browser.newContext({
|
|
50
53
|
ignoreHTTPSErrors: true,
|
|
@@ -58,8 +61,7 @@ const runCustom = async (url, randomToken, viewportSettings, blacklistedPatterns
|
|
|
58
61
|
await context.close().catch(() => { });
|
|
59
62
|
await browser.close().catch(() => { });
|
|
60
63
|
}
|
|
61
|
-
catch {
|
|
62
|
-
}
|
|
64
|
+
catch { }
|
|
63
65
|
};
|
|
64
66
|
// For handling closing playwright browser and continue generate artifacts etc
|
|
65
67
|
registerSoftClose(processPageParams.stopAll);
|
package/dist/mergeAxeResults.js
CHANGED
|
@@ -247,35 +247,54 @@ const cleanUpJsonFiles = async (filesToDelete) => {
|
|
|
247
247
|
});
|
|
248
248
|
};
|
|
249
249
|
const writeSummaryPdf = async (storagePath, pagesScanned, filename = 'summary', browser, _userDataDirectory) => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
250
|
+
let browserInstance;
|
|
251
|
+
let context;
|
|
252
|
+
let page;
|
|
253
|
+
try {
|
|
254
|
+
const htmlFilePath = path.join(storagePath, `${filename}.html`);
|
|
255
|
+
const fileDestinationPath = path.join(storagePath, `${filename}.pdf`);
|
|
256
|
+
const htmlFileUrl = `file://${htmlFilePath}`;
|
|
257
|
+
const launchOptions = getPlaywrightLaunchOptions(browser);
|
|
258
|
+
browserInstance = await constants.launcher.launch({
|
|
259
|
+
...launchOptions,
|
|
260
|
+
headless: true,
|
|
261
|
+
});
|
|
262
|
+
register(browserInstance);
|
|
263
|
+
context = await browserInstance.newContext();
|
|
264
|
+
page = await context.newPage();
|
|
265
|
+
await page.goto(htmlFileUrl, {
|
|
266
|
+
waitUntil: 'domcontentloaded',
|
|
267
|
+
timeout: 120000,
|
|
268
|
+
});
|
|
269
|
+
await page.emulateMedia({ media: 'print' });
|
|
270
|
+
await page.pdf({
|
|
271
|
+
margin: { bottom: '32px' },
|
|
272
|
+
path: fileDestinationPath,
|
|
273
|
+
format: 'A4',
|
|
274
|
+
displayHeaderFooter: true,
|
|
275
|
+
footerTemplate: `
|
|
269
276
|
<div style="margin-top:50px;color:#26241b;font-family:Open Sans;text-align: center;width: 100%;font-weight:400">
|
|
270
277
|
<span style="color:#26241b;font-size: 14px;font-weight:400">Page <span class="pageNumber"></span> of <span class="totalPages"></span></span>
|
|
271
278
|
</div>
|
|
272
279
|
`,
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
});
|
|
281
|
+
if (pagesScanned < 2000) {
|
|
282
|
+
fs.unlinkSync(htmlFilePath);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
consoleLogger.info(`Error at writeSummaryPDF ${err instanceof Error ? err.stack : err}`);
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
await page?.close().catch(err => {
|
|
290
|
+
consoleLogger.info(`Error at page close writeSummaryPDF ${err}`);
|
|
291
|
+
});
|
|
292
|
+
await context?.close().catch(err => {
|
|
293
|
+
consoleLogger.info(`Error at context close writeSummaryPDF ${err}`);
|
|
294
|
+
});
|
|
295
|
+
await browserInstance?.close().catch(err => {
|
|
296
|
+
consoleLogger.info(`Error at browserInstance close writeSummaryPDF ${err}`);
|
|
297
|
+
});
|
|
279
298
|
}
|
|
280
299
|
};
|
|
281
300
|
// Tracking WCAG occurrences
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@govtechsg/oobee",
|
|
3
3
|
"main": "dist/npmIndex.js",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.85",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Government Technology Agency <info@tech.gov.sg>",
|
|
7
7
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"print-message": "^3.0.1",
|
|
37
37
|
"safe-regex": "^2.1.1",
|
|
38
38
|
"text-readability": "^1.1.0",
|
|
39
|
-
"tldts": "^7.0.
|
|
39
|
+
"tldts": "^7.0.27",
|
|
40
40
|
"typescript": "^5.4.5",
|
|
41
41
|
"url": "^0.11.3",
|
|
42
42
|
"uuid": "^11.0.3",
|
package/src/cli.ts
CHANGED
|
@@ -5,7 +5,13 @@ import printMessage from 'print-message';
|
|
|
5
5
|
import { devices } from 'playwright';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import path from 'path';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
setHeadlessMode,
|
|
10
|
+
getVersion,
|
|
11
|
+
getStoragePath,
|
|
12
|
+
listenForCleanUp,
|
|
13
|
+
cleanUpAndExit,
|
|
14
|
+
} from './utils.js';
|
|
9
15
|
import {
|
|
10
16
|
checkUrl,
|
|
11
17
|
prepareData,
|
|
@@ -185,12 +191,14 @@ Usage: npm run cli -- -c <crawler> -d <device> -w <viewport> -u <url> OPTIONS`,
|
|
|
185
191
|
return true;
|
|
186
192
|
})
|
|
187
193
|
.check(argvs => {
|
|
188
|
-
|
|
189
|
-
|
|
194
|
+
const scanner = String(argvs.scanner ?? '');
|
|
195
|
+
|
|
196
|
+
if (argvs.strategy && scanner !== ScannerTypes.WEBSITE && scanner !== ScannerTypes.CUSTOM) {
|
|
197
|
+
throw new Error('-s or --strategy is only available in website and custom flow scans.');
|
|
190
198
|
}
|
|
191
199
|
return true;
|
|
192
200
|
})
|
|
193
|
-
.coerce('l',
|
|
201
|
+
.coerce('l', option => {
|
|
194
202
|
const duration = Number(option);
|
|
195
203
|
if (isNaN(duration) || duration < 0) {
|
|
196
204
|
printMessage(
|
|
@@ -202,8 +210,8 @@ Usage: npm run cli -- -c <crawler> -d <device> -w <viewport> -u <url> OPTIONS`,
|
|
|
202
210
|
return duration;
|
|
203
211
|
})
|
|
204
212
|
.check(argvs => {
|
|
205
|
-
if (argvs.scanner
|
|
206
|
-
throw new Error('-
|
|
213
|
+
if (argvs.scanner !== ScannerTypes.WEBSITE && argvs.strategy) {
|
|
214
|
+
throw new Error('-s or --strategy is only available in website scans.');
|
|
207
215
|
}
|
|
208
216
|
return true;
|
|
209
217
|
})
|
|
@@ -235,10 +243,11 @@ const scanInit = async (argvs: Answers): Promise<string> => {
|
|
|
235
243
|
data.userDataDirectory,
|
|
236
244
|
data.playwrightDeviceDetailsObject,
|
|
237
245
|
data.extraHTTPHeaders,
|
|
238
|
-
data.fileTypes
|
|
246
|
+
data.fileTypes,
|
|
239
247
|
);
|
|
240
248
|
|
|
241
|
-
if (res.httpStatus)
|
|
249
|
+
if (res.httpStatus)
|
|
250
|
+
consoleLogger.info(`Connectivity Check HTTP Response Code: ${res.httpStatus}`);
|
|
242
251
|
|
|
243
252
|
if (res.status === statuses.success.code) {
|
|
244
253
|
data.url = res.url;
|
|
@@ -267,15 +276,11 @@ const scanInit = async (argvs: Answers): Promise<string> => {
|
|
|
267
276
|
}
|
|
268
277
|
}
|
|
269
278
|
|
|
270
|
-
const screenToScan = getScreenToScan(
|
|
271
|
-
data.deviceChosen,
|
|
272
|
-
data.customDevice,
|
|
273
|
-
data.viewportWidth,
|
|
274
|
-
);
|
|
279
|
+
const screenToScan = getScreenToScan(data.deviceChosen, data.customDevice, data.viewportWidth);
|
|
275
280
|
|
|
276
281
|
printMessage([`Oobee version: ${appVersion}`, 'Starting scan...'], messageOptions);
|
|
277
|
-
consoleLogger.info(`Oobee version: ${appVersion}`);
|
|
278
|
-
|
|
282
|
+
consoleLogger.info(`Oobee version: ${appVersion}`);
|
|
283
|
+
|
|
279
284
|
await combineRun(data, screenToScan);
|
|
280
285
|
|
|
281
286
|
return getStoragePath(data.randomToken);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/* eslint-disable no-shadow */
|
|
2
1
|
/* eslint-disable no-alert */
|
|
3
2
|
/* eslint-disable no-param-reassign */
|
|
4
3
|
/* eslint-env browser */
|
|
5
4
|
import path from 'path';
|
|
5
|
+
import { getDomain } from 'tldts';
|
|
6
6
|
import { runAxeScript } from '../commonCrawlerFunc.js';
|
|
7
7
|
import { consoleLogger, guiInfoLog, silentLogger } from '../../logs.js';
|
|
8
8
|
import { guiInfoStatusTypes } from '../../constants/constants.js';
|
|
@@ -19,6 +19,44 @@ declare global {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
const sameRegistrableDomain = (hostA: string, hostB: string) => {
|
|
23
|
+
const domainA = getDomain(hostA);
|
|
24
|
+
const domainB = getDomain(hostB);
|
|
25
|
+
|
|
26
|
+
if (!domainA || !domainB) return hostA === hostB;
|
|
27
|
+
|
|
28
|
+
return domainA === domainB;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const parseBoolEnv = (val: string | undefined, defaultVal: boolean) => {
|
|
32
|
+
if (val == null) return defaultVal;
|
|
33
|
+
const v = String(val).trim().toLowerCase();
|
|
34
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(v)) return true;
|
|
35
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(v)) return false;
|
|
36
|
+
return defaultVal;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const RESTRICT_OVERLAY_TO_ENTRY_DOMAIN = parseBoolEnv(
|
|
40
|
+
process.env.RESTRICT_OVERLAY_TO_ENTRY_DOMAIN,
|
|
41
|
+
false,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const isOverlayAllowed = (currentUrl: string, entryUrl: string) => {
|
|
45
|
+
try {
|
|
46
|
+
const cur = new URL(currentUrl);
|
|
47
|
+
|
|
48
|
+
if (cur.protocol !== 'http:' && cur.protocol !== 'https:') return false;
|
|
49
|
+
|
|
50
|
+
if (!RESTRICT_OVERLAY_TO_ENTRY_DOMAIN) return true;
|
|
51
|
+
|
|
52
|
+
const base = new URL(entryUrl);
|
|
53
|
+
|
|
54
|
+
return sameRegistrableDomain(cur.hostname, base.hostname);
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
22
60
|
//! For Cypress Test
|
|
23
61
|
// env to check if Cypress test is running
|
|
24
62
|
const isCypressTest = process.env.IS_CYPRESS_TEST === 'true';
|
|
@@ -67,10 +105,7 @@ export const screenshotFullPage = async (page, screenshotsDir: string, screensho
|
|
|
67
105
|
setTimeout(async () => {
|
|
68
106
|
await page.waitForLoadState('domcontentloaded');
|
|
69
107
|
|
|
70
|
-
const newHeight = await page.evaluate(
|
|
71
|
-
// eslint-disable-next-line no-shadow
|
|
72
|
-
() => document.body.scrollHeight,
|
|
73
|
-
);
|
|
108
|
+
const newHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
74
109
|
const result = newHeight > prevHeight;
|
|
75
110
|
|
|
76
111
|
resolve(result);
|
|
@@ -248,7 +283,7 @@ export const updateMenu = async (page, urlsCrawled) => {
|
|
|
248
283
|
log(`Overlay menu: updating: ${page.url()}`);
|
|
249
284
|
await page.evaluate(
|
|
250
285
|
vars => {
|
|
251
|
-
const shadowHost = document.querySelector('#
|
|
286
|
+
const shadowHost = document.querySelector('#oobeeShadowHost');
|
|
252
287
|
if (shadowHost) {
|
|
253
288
|
const p = shadowHost.shadowRoot.querySelector('#oobee-p-pages-scanned');
|
|
254
289
|
if (p) {
|
|
@@ -262,7 +297,6 @@ export const updateMenu = async (page, urlsCrawled) => {
|
|
|
262
297
|
consoleLogger.info(`Overlay menu updated`);
|
|
263
298
|
};
|
|
264
299
|
|
|
265
|
-
|
|
266
300
|
export const addOverlayMenu = async (
|
|
267
301
|
page,
|
|
268
302
|
urlsCrawled,
|
|
@@ -307,7 +341,7 @@ export const addOverlayMenu = async (
|
|
|
307
341
|
`;
|
|
308
342
|
minBtn.innerHTML = MINBTN_SVG;
|
|
309
343
|
|
|
310
|
-
let currentPos: 'LEFT' | 'RIGHT' =
|
|
344
|
+
let currentPos: 'LEFT' | 'RIGHT' = vars.menuPos || 'RIGHT';
|
|
311
345
|
const isCollapsed = () => panel.classList.contains('collapsed');
|
|
312
346
|
|
|
313
347
|
const setPosClass = (pos: 'LEFT' | 'RIGHT') => {
|
|
@@ -325,7 +359,7 @@ export const addOverlayMenu = async (
|
|
|
325
359
|
};
|
|
326
360
|
|
|
327
361
|
const toggleCollapsed = (force?: boolean) => {
|
|
328
|
-
const willCollapse =
|
|
362
|
+
const willCollapse = typeof force === 'boolean' ? force : !isCollapsed();
|
|
329
363
|
if (willCollapse) {
|
|
330
364
|
panel.classList.add('collapsed');
|
|
331
365
|
localStorage.setItem('oobee:overlay-collapsed', '1');
|
|
@@ -414,13 +448,13 @@ export const addOverlayMenu = async (
|
|
|
414
448
|
const ol = document.createElement('ol');
|
|
415
449
|
ol.className = 'oobee-ol';
|
|
416
450
|
|
|
417
|
-
scanned.forEach(
|
|
451
|
+
scanned.forEach(item => {
|
|
418
452
|
const li = document.createElement('li');
|
|
419
453
|
li.className = 'oobee-li';
|
|
420
454
|
|
|
421
455
|
const title = document.createElement('div');
|
|
422
456
|
title.className = 'oobee-item-title';
|
|
423
|
-
title.textContent =
|
|
457
|
+
title.textContent = item.pageTitle && item.pageTitle.trim() ? item.pageTitle : item.url;
|
|
424
458
|
|
|
425
459
|
const url = document.createElement('div');
|
|
426
460
|
url.className = 'oobee-item-url';
|
|
@@ -730,8 +764,7 @@ export const addOverlayMenu = async (
|
|
|
730
764
|
|
|
731
765
|
const closed = isCollapsed();
|
|
732
766
|
const arrowPointsRight =
|
|
733
|
-
(currentPos === 'RIGHT' && !closed) ||
|
|
734
|
-
(currentPos === 'LEFT' && closed);
|
|
767
|
+
(currentPos === 'RIGHT' && !closed) || (currentPos === 'LEFT' && closed);
|
|
735
768
|
|
|
736
769
|
icon.classList.toggle('is-left', !arrowPointsRight);
|
|
737
770
|
minBtn.setAttribute('aria-label', closed ? 'Expand panel' : 'Collapse panel');
|
|
@@ -761,11 +794,11 @@ export const addOverlayMenu = async (
|
|
|
761
794
|
|
|
762
795
|
grip.addEventListener('pointerdown', (e: PointerEvent) => {
|
|
763
796
|
startX = e.clientX;
|
|
764
|
-
grip.setPointerCapture(e.pointerId);
|
|
797
|
+
grip.setPointerCapture(e.pointerId); // <-- use the button
|
|
765
798
|
});
|
|
766
799
|
|
|
767
800
|
grip.addEventListener('pointermove', (e: PointerEvent) => {
|
|
768
|
-
if (!grip.hasPointerCapture?.(e.pointerId)) return;
|
|
801
|
+
if (!grip.hasPointerCapture?.(e.pointerId)) return; // <-- check the button
|
|
769
802
|
const dx = e.clientX - startX;
|
|
770
803
|
if (Math.abs(dx) >= THRESH) {
|
|
771
804
|
const nextPos: 'LEFT' | 'RIGHT' = dx < 0 ? 'LEFT' : 'RIGHT';
|
|
@@ -779,7 +812,9 @@ export const addOverlayMenu = async (
|
|
|
779
812
|
});
|
|
780
813
|
|
|
781
814
|
grip.addEventListener('pointerup', (e: PointerEvent) => {
|
|
782
|
-
try {
|
|
815
|
+
try {
|
|
816
|
+
grip.releasePointerCapture(e.pointerId);
|
|
817
|
+
} catch {}
|
|
783
818
|
});
|
|
784
819
|
|
|
785
820
|
const stopDialog = document.createElement('dialog');
|
|
@@ -791,7 +826,7 @@ export const addOverlayMenu = async (
|
|
|
791
826
|
borderRadius: '16px',
|
|
792
827
|
overflow: 'hidden',
|
|
793
828
|
boxShadow: '0 10px 40px rgba(0,0,0,.35)',
|
|
794
|
-
fontFamily: 'system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif'
|
|
829
|
+
fontFamily: 'system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif',
|
|
795
830
|
});
|
|
796
831
|
const dialogSheet = new CSSStyleSheet();
|
|
797
832
|
dialogSheet.replaceSync(`
|
|
@@ -829,13 +864,18 @@ export const addOverlayMenu = async (
|
|
|
829
864
|
display: 'flex',
|
|
830
865
|
alignItems: 'center',
|
|
831
866
|
justifyContent: 'space-between',
|
|
832
|
-
gap: '8px'
|
|
867
|
+
gap: '8px',
|
|
833
868
|
});
|
|
834
869
|
|
|
835
870
|
const title = document.createElement('h2');
|
|
836
871
|
title.id = 'oobee-stop-title';
|
|
837
872
|
title.textContent = 'Are you sure you want to stop this scan?';
|
|
838
|
-
Object.assign(title.style, {
|
|
873
|
+
Object.assign(title.style, {
|
|
874
|
+
margin: '0',
|
|
875
|
+
fontSize: '22px',
|
|
876
|
+
fontWeight: '700',
|
|
877
|
+
lineHeight: '1.25',
|
|
878
|
+
});
|
|
839
879
|
|
|
840
880
|
const closeX = document.createElement('button');
|
|
841
881
|
closeX.type = 'button';
|
|
@@ -853,14 +893,14 @@ export const addOverlayMenu = async (
|
|
|
853
893
|
height: '36px',
|
|
854
894
|
borderRadius: '12px',
|
|
855
895
|
display: 'grid',
|
|
856
|
-
placeItems: 'center'
|
|
896
|
+
placeItems: 'center',
|
|
857
897
|
});
|
|
858
898
|
head.appendChild(title);
|
|
859
899
|
head.appendChild(closeX);
|
|
860
900
|
|
|
861
901
|
const bodyWrap = document.createElement('div');
|
|
862
902
|
Object.assign(bodyWrap.style, {
|
|
863
|
-
padding: '12px 20px 20px 20px'
|
|
903
|
+
padding: '12px 20px 20px 20px',
|
|
864
904
|
});
|
|
865
905
|
|
|
866
906
|
const form = document.createElement('form');
|
|
@@ -869,7 +909,7 @@ export const addOverlayMenu = async (
|
|
|
869
909
|
Object.assign(form.style, {
|
|
870
910
|
display: 'grid',
|
|
871
911
|
gridTemplateColumns: '1fr',
|
|
872
|
-
rowGap: '12px'
|
|
912
|
+
rowGap: '12px',
|
|
873
913
|
});
|
|
874
914
|
|
|
875
915
|
const label = document.createElement('label');
|
|
@@ -887,7 +927,7 @@ export const addOverlayMenu = async (
|
|
|
887
927
|
padding: '12px 14px',
|
|
888
928
|
fontSize: '14px',
|
|
889
929
|
outline: 'none',
|
|
890
|
-
boxSizing: 'border-box'
|
|
930
|
+
boxSizing: 'border-box',
|
|
891
931
|
});
|
|
892
932
|
input.addEventListener('focus', () => {
|
|
893
933
|
input.style.borderColor = '#7b4dff';
|
|
@@ -913,7 +953,7 @@ export const addOverlayMenu = async (
|
|
|
913
953
|
fontWeight: '600',
|
|
914
954
|
color: '#fff',
|
|
915
955
|
background: '#9021A6',
|
|
916
|
-
cursor: 'pointer'
|
|
956
|
+
cursor: 'pointer',
|
|
917
957
|
});
|
|
918
958
|
|
|
919
959
|
const cancel = document.createElement('button');
|
|
@@ -926,7 +966,7 @@ export const addOverlayMenu = async (
|
|
|
926
966
|
fontSize: '14px',
|
|
927
967
|
justifySelf: 'center',
|
|
928
968
|
cursor: 'pointer',
|
|
929
|
-
padding: '6px'
|
|
969
|
+
padding: '6px',
|
|
930
970
|
});
|
|
931
971
|
|
|
932
972
|
actions.appendChild(primary);
|
|
@@ -936,7 +976,7 @@ export const addOverlayMenu = async (
|
|
|
936
976
|
form.appendChild(label);
|
|
937
977
|
form.appendChild(input);
|
|
938
978
|
}
|
|
939
|
-
|
|
979
|
+
form.appendChild(actions);
|
|
940
980
|
bodyWrap.appendChild(form);
|
|
941
981
|
|
|
942
982
|
stopDialog.appendChild(head);
|
|
@@ -944,17 +984,27 @@ export const addOverlayMenu = async (
|
|
|
944
984
|
shadowRoot.appendChild(stopDialog);
|
|
945
985
|
|
|
946
986
|
let stopResolver: null | ((v: { confirmed: boolean; label: string }) => void) = null;
|
|
947
|
-
const hideStop = () => {
|
|
987
|
+
const hideStop = () => {
|
|
988
|
+
try {
|
|
989
|
+
stopDialog.close();
|
|
990
|
+
} catch {}
|
|
991
|
+
stopResolver = null;
|
|
992
|
+
};
|
|
948
993
|
const showStop = () => {
|
|
949
994
|
if (!shouldHideInput) input.value = '';
|
|
950
|
-
try {
|
|
995
|
+
try {
|
|
996
|
+
stopDialog.showModal();
|
|
997
|
+
} catch {}
|
|
951
998
|
if (!shouldHideInput) {
|
|
952
999
|
requestAnimationFrame(() => {
|
|
953
|
-
try {
|
|
1000
|
+
try {
|
|
1001
|
+
input.focus({ preventScroll: true });
|
|
1002
|
+
input.select();
|
|
1003
|
+
} catch {}
|
|
954
1004
|
});
|
|
955
1005
|
}
|
|
956
1006
|
};
|
|
957
|
-
form.addEventListener('submit',
|
|
1007
|
+
form.addEventListener('submit', e => {
|
|
958
1008
|
e.preventDefault();
|
|
959
1009
|
const v = (input.value || '').trim();
|
|
960
1010
|
if (stopResolver) stopResolver({ confirmed: true, label: v });
|
|
@@ -968,13 +1018,13 @@ export const addOverlayMenu = async (
|
|
|
968
1018
|
if (stopResolver) stopResolver({ confirmed: false, label: '' });
|
|
969
1019
|
hideStop();
|
|
970
1020
|
});
|
|
971
|
-
stopDialog.addEventListener('cancel',
|
|
1021
|
+
stopDialog.addEventListener('cancel', e => {
|
|
972
1022
|
e.preventDefault();
|
|
973
1023
|
if (stopResolver) stopResolver({ confirmed: false, label: '' });
|
|
974
1024
|
hideStop();
|
|
975
1025
|
});
|
|
976
1026
|
(customWindow as Window).oobeeShowStopModal = () =>
|
|
977
|
-
new Promise<{ confirmed: boolean; label: string }>(
|
|
1027
|
+
new Promise<{ confirmed: boolean; label: string }>(resolve => {
|
|
978
1028
|
stopResolver = resolve;
|
|
979
1029
|
showStop();
|
|
980
1030
|
});
|
|
@@ -1000,7 +1050,7 @@ export const addOverlayMenu = async (
|
|
|
1000
1050
|
log('Overlay menu: successfully added');
|
|
1001
1051
|
})
|
|
1002
1052
|
.catch(error => {
|
|
1003
|
-
error('Overlay menu: failed to add', error);
|
|
1053
|
+
consoleLogger.error('Overlay menu: failed to add', error);
|
|
1004
1054
|
});
|
|
1005
1055
|
};
|
|
1006
1056
|
|
|
@@ -1027,7 +1077,7 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1027
1077
|
// eslint-disable-next-line no-underscore-dangle
|
|
1028
1078
|
const pageId = page._guid;
|
|
1029
1079
|
|
|
1030
|
-
page.on('dialog', () => {
|
|
1080
|
+
page.on('dialog', () => {});
|
|
1031
1081
|
|
|
1032
1082
|
const pageClosePromise = new Promise(resolve => {
|
|
1033
1083
|
page.on('close', () => {
|
|
@@ -1058,11 +1108,18 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1058
1108
|
await processPage(page, processPageParams);
|
|
1059
1109
|
log('Scan: success');
|
|
1060
1110
|
pagesDict[pageId].isScanning = false;
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1111
|
+
|
|
1112
|
+
const allowed = isOverlayAllowed(page.url(), processPageParams.entryUrl);
|
|
1113
|
+
|
|
1114
|
+
if (allowed) {
|
|
1115
|
+
await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
|
|
1116
|
+
inProgress: false,
|
|
1117
|
+
collapsed: !!pagesDict[pageId]?.collapsed,
|
|
1118
|
+
hideStopInput: !!processPageParams.customFlowLabel,
|
|
1119
|
+
});
|
|
1120
|
+
} else {
|
|
1121
|
+
await removeOverlayMenu(page);
|
|
1122
|
+
}
|
|
1066
1123
|
} catch (error) {
|
|
1067
1124
|
log(`Scan failed ${error}`);
|
|
1068
1125
|
}
|
|
@@ -1126,6 +1183,13 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1126
1183
|
|
|
1127
1184
|
page.on('domcontentloaded', async () => {
|
|
1128
1185
|
try {
|
|
1186
|
+
const allowed = isOverlayAllowed(page.url(), processPageParams.entryUrl);
|
|
1187
|
+
|
|
1188
|
+
if (!allowed) {
|
|
1189
|
+
await removeOverlayMenu(page);
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1129
1193
|
const existingOverlay = await page.evaluate(() => {
|
|
1130
1194
|
return document.querySelector('#oobeeShadowHost');
|
|
1131
1195
|
});
|
|
@@ -1141,12 +1205,6 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1141
1205
|
});
|
|
1142
1206
|
}
|
|
1143
1207
|
|
|
1144
|
-
setTimeout(() => {
|
|
1145
|
-
// Timeout here to slow things down a little
|
|
1146
|
-
}, 1000);
|
|
1147
|
-
|
|
1148
|
-
//! For Cypress Test
|
|
1149
|
-
// Auto-clicks 'Scan this page' button only once
|
|
1150
1208
|
if (isCypressTest) {
|
|
1151
1209
|
try {
|
|
1152
1210
|
await handleOnScanClick();
|
|
@@ -1155,11 +1213,8 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1155
1213
|
consoleLogger.info(`Error in calling handleOnScanClick, isCypressTest: ${isCypressTest}`);
|
|
1156
1214
|
}
|
|
1157
1215
|
}
|
|
1158
|
-
|
|
1159
|
-
consoleLogger.info(`Overlay state: ${existingOverlay}`);
|
|
1160
1216
|
} catch {
|
|
1161
1217
|
consoleLogger.info('Error in adding overlay menu to page');
|
|
1162
|
-
consoleLogger.info('Error in adding overlay menu to page');
|
|
1163
1218
|
}
|
|
1164
1219
|
});
|
|
1165
1220
|
|
|
@@ -25,6 +25,8 @@ export class ProcessPageParams {
|
|
|
25
25
|
randomToken: string;
|
|
26
26
|
customFlowLabel?: string;
|
|
27
27
|
stopAll?: () => Promise<void>;
|
|
28
|
+
entryUrl!: string;
|
|
29
|
+
strategy: string;
|
|
28
30
|
|
|
29
31
|
constructor(
|
|
30
32
|
scannedIdx: number,
|
|
@@ -69,6 +71,8 @@ const runCustom = async (
|
|
|
69
71
|
randomToken,
|
|
70
72
|
);
|
|
71
73
|
|
|
74
|
+
processPageParams.entryUrl = url;
|
|
75
|
+
|
|
72
76
|
if (initialCustomFlowLabel && initialCustomFlowLabel.trim()) {
|
|
73
77
|
processPageParams.customFlowLabel = initialCustomFlowLabel.trim();
|
|
74
78
|
}
|
|
@@ -80,18 +84,20 @@ const runCustom = async (
|
|
|
80
84
|
const deviceConfig = viewportSettings.playwrightDeviceDetailsObject;
|
|
81
85
|
const hasCustomViewport = !!deviceConfig;
|
|
82
86
|
|
|
83
|
-
const baseLaunchOptions = getPlaywrightLaunchOptions(
|
|
87
|
+
const baseLaunchOptions = getPlaywrightLaunchOptions();
|
|
84
88
|
|
|
85
89
|
// Merge base args with custom flow specific args
|
|
86
90
|
const baseArgs = baseLaunchOptions.args || [];
|
|
87
91
|
const customArgs = hasCustomViewport ? ['--window-size=1920,1040'] : ['--start-maximized'];
|
|
88
|
-
const mergedArgs = [
|
|
92
|
+
const mergedArgs = [
|
|
93
|
+
...baseArgs.filter(a => !a.startsWith('--window-size') && a !== '--start-maximized'),
|
|
94
|
+
...customArgs,
|
|
95
|
+
];
|
|
89
96
|
|
|
90
97
|
const browser = await chromium.launch({
|
|
91
98
|
...baseLaunchOptions,
|
|
92
99
|
args: mergedArgs,
|
|
93
100
|
headless: false,
|
|
94
|
-
channel: 'chrome',
|
|
95
101
|
});
|
|
96
102
|
|
|
97
103
|
const context = await browser.newContext({
|
|
@@ -107,8 +113,7 @@ const runCustom = async (
|
|
|
107
113
|
try {
|
|
108
114
|
await context.close().catch(() => {});
|
|
109
115
|
await browser.close().catch(() => {});
|
|
110
|
-
} catch {
|
|
111
|
-
}
|
|
116
|
+
} catch {}
|
|
112
117
|
};
|
|
113
118
|
|
|
114
119
|
// For handling closing playwright browser and continue generate artifacts etc
|
package/src/mergeAxeResults.ts
CHANGED
|
@@ -349,44 +349,61 @@ const writeSummaryPdf = async (
|
|
|
349
349
|
browser: string,
|
|
350
350
|
_userDataDirectory: string,
|
|
351
351
|
) => {
|
|
352
|
-
|
|
353
|
-
|
|
352
|
+
let browserInstance;
|
|
353
|
+
let context;
|
|
354
|
+
let page;
|
|
354
355
|
|
|
355
|
-
|
|
356
|
+
try {
|
|
357
|
+
const htmlFilePath = path.join(storagePath, `${filename}.html`);
|
|
358
|
+
const fileDestinationPath = path.join(storagePath, `${filename}.pdf`);
|
|
359
|
+
const htmlFileUrl = `file://${htmlFilePath}`;
|
|
356
360
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
+
const launchOptions = getPlaywrightLaunchOptions(browser);
|
|
362
|
+
|
|
363
|
+
browserInstance = await constants.launcher.launch({
|
|
364
|
+
...launchOptions,
|
|
365
|
+
headless: true,
|
|
366
|
+
});
|
|
361
367
|
|
|
362
|
-
|
|
368
|
+
register(browserInstance as unknown as { close: () => Promise<void> });
|
|
363
369
|
|
|
364
|
-
|
|
365
|
-
|
|
370
|
+
context = await browserInstance.newContext();
|
|
371
|
+
page = await context.newPage();
|
|
366
372
|
|
|
367
|
-
|
|
368
|
-
|
|
373
|
+
await page.goto(htmlFileUrl, {
|
|
374
|
+
waitUntil: 'domcontentloaded',
|
|
375
|
+
timeout: 120000,
|
|
376
|
+
});
|
|
369
377
|
|
|
370
|
-
|
|
378
|
+
await page.emulateMedia({ media: 'print' });
|
|
371
379
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
380
|
+
await page.pdf({
|
|
381
|
+
margin: { bottom: '32px' },
|
|
382
|
+
path: fileDestinationPath,
|
|
383
|
+
format: 'A4',
|
|
384
|
+
displayHeaderFooter: true,
|
|
385
|
+
footerTemplate: `
|
|
378
386
|
<div style="margin-top:50px;color:#26241b;font-family:Open Sans;text-align: center;width: 100%;font-weight:400">
|
|
379
387
|
<span style="color:#26241b;font-size: 14px;font-weight:400">Page <span class="pageNumber"></span> of <span class="totalPages"></span></span>
|
|
380
388
|
</div>
|
|
381
389
|
`,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
await page.close();
|
|
385
|
-
await context.close().catch(() => {});
|
|
386
|
-
await browserInstance.close().catch(() => {});
|
|
390
|
+
});
|
|
387
391
|
|
|
388
|
-
|
|
389
|
-
|
|
392
|
+
if (pagesScanned < 2000) {
|
|
393
|
+
fs.unlinkSync(htmlFilePath);
|
|
394
|
+
}
|
|
395
|
+
} catch (err) {
|
|
396
|
+
consoleLogger.info(`Error at writeSummaryPDF ${err instanceof Error ? err.stack : err}`);
|
|
397
|
+
} finally {
|
|
398
|
+
await page?.close().catch(err => {
|
|
399
|
+
consoleLogger.info(`Error at page close writeSummaryPDF ${err}`);
|
|
400
|
+
});
|
|
401
|
+
await context?.close().catch(err => {
|
|
402
|
+
consoleLogger.info(`Error at context close writeSummaryPDF ${err}`);
|
|
403
|
+
});
|
|
404
|
+
await browserInstance?.close().catch(err => {
|
|
405
|
+
consoleLogger.info(`Error at browserInstance close writeSummaryPDF ${err}`);
|
|
406
|
+
});
|
|
390
407
|
}
|
|
391
408
|
};
|
|
392
409
|
|