@govtechsg/oobee 0.10.83 → 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/README.md +6 -1
- package/dist/cli.js +7 -6
- package/dist/constants/common.js +13 -1
- package/dist/crawlers/crawlDomain.js +220 -120
- package/dist/crawlers/crawlIntelligentSitemap.js +22 -7
- package/dist/crawlers/custom/utils.js +81 -40
- package/dist/crawlers/runCustom.js +13 -5
- package/dist/mergeAxeResults/itemReferences.js +55 -0
- package/dist/mergeAxeResults/jsonArtifacts.js +335 -0
- package/dist/mergeAxeResults/scanPages.js +159 -0
- package/dist/mergeAxeResults/sentryTelemetry.js +152 -0
- package/dist/mergeAxeResults/types.js +1 -0
- package/dist/mergeAxeResults/writeCsv.js +125 -0
- package/dist/mergeAxeResults/writeScanDetailsCsv.js +35 -0
- package/dist/mergeAxeResults/writeSitemap.js +10 -0
- package/dist/mergeAxeResults.js +64 -950
- package/dist/proxyService.js +90 -5
- package/dist/utils.js +20 -7
- package/package.json +6 -6
- package/src/cli.ts +20 -15
- package/src/constants/common.ts +13 -1
- package/src/crawlers/crawlDomain.ts +248 -137
- package/src/crawlers/crawlIntelligentSitemap.ts +22 -8
- package/src/crawlers/custom/utils.ts +103 -48
- package/src/crawlers/runCustom.ts +18 -5
- package/src/mergeAxeResults/itemReferences.ts +62 -0
- package/src/mergeAxeResults/jsonArtifacts.ts +451 -0
- package/src/mergeAxeResults/scanPages.ts +207 -0
- package/src/mergeAxeResults/sentryTelemetry.ts +183 -0
- package/src/mergeAxeResults/types.ts +99 -0
- package/src/mergeAxeResults/writeCsv.ts +145 -0
- package/src/mergeAxeResults/writeScanDetailsCsv.ts +51 -0
- package/src/mergeAxeResults/writeSitemap.ts +13 -0
- package/src/mergeAxeResults.ts +125 -1344
- package/src/proxyService.ts +96 -4
- package/src/utils.ts +19 -7
|
@@ -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
|
|
|
@@ -11,6 +11,7 @@ import { DEBUG, initNewPage, log } from './custom/utils.js';
|
|
|
11
11
|
import { guiInfoLog } from '../logs.js';
|
|
12
12
|
import { ViewportSettingsClass } from '../combine.js';
|
|
13
13
|
import { addUrlGuardScript } from './guards/urlGuard.js';
|
|
14
|
+
import { getPlaywrightLaunchOptions } from '../constants/common.js';
|
|
14
15
|
|
|
15
16
|
// Export of classes
|
|
16
17
|
|
|
@@ -24,6 +25,8 @@ export class ProcessPageParams {
|
|
|
24
25
|
randomToken: string;
|
|
25
26
|
customFlowLabel?: string;
|
|
26
27
|
stopAll?: () => Promise<void>;
|
|
28
|
+
entryUrl!: string;
|
|
29
|
+
strategy: string;
|
|
27
30
|
|
|
28
31
|
constructor(
|
|
29
32
|
scannedIdx: number,
|
|
@@ -68,6 +71,8 @@ const runCustom = async (
|
|
|
68
71
|
randomToken,
|
|
69
72
|
);
|
|
70
73
|
|
|
74
|
+
processPageParams.entryUrl = url;
|
|
75
|
+
|
|
71
76
|
if (initialCustomFlowLabel && initialCustomFlowLabel.trim()) {
|
|
72
77
|
processPageParams.customFlowLabel = initialCustomFlowLabel.trim();
|
|
73
78
|
}
|
|
@@ -79,11 +84,20 @@ const runCustom = async (
|
|
|
79
84
|
const deviceConfig = viewportSettings.playwrightDeviceDetailsObject;
|
|
80
85
|
const hasCustomViewport = !!deviceConfig;
|
|
81
86
|
|
|
87
|
+
const baseLaunchOptions = getPlaywrightLaunchOptions();
|
|
88
|
+
|
|
89
|
+
// Merge base args with custom flow specific args
|
|
90
|
+
const baseArgs = baseLaunchOptions.args || [];
|
|
91
|
+
const customArgs = hasCustomViewport ? ['--window-size=1920,1040'] : ['--start-maximized'];
|
|
92
|
+
const mergedArgs = [
|
|
93
|
+
...baseArgs.filter(a => !a.startsWith('--window-size') && a !== '--start-maximized'),
|
|
94
|
+
...customArgs,
|
|
95
|
+
];
|
|
96
|
+
|
|
82
97
|
const browser = await chromium.launch({
|
|
83
|
-
|
|
98
|
+
...baseLaunchOptions,
|
|
99
|
+
args: mergedArgs,
|
|
84
100
|
headless: false,
|
|
85
|
-
channel: 'chrome',
|
|
86
|
-
// bypassCSP: true,
|
|
87
101
|
});
|
|
88
102
|
|
|
89
103
|
const context = await browser.newContext({
|
|
@@ -99,8 +113,7 @@ const runCustom = async (
|
|
|
99
113
|
try {
|
|
100
114
|
await context.close().catch(() => {});
|
|
101
115
|
await browser.close().catch(() => {});
|
|
102
|
-
} catch {
|
|
103
|
-
}
|
|
116
|
+
} catch {}
|
|
104
117
|
};
|
|
105
118
|
|
|
106
119
|
// For handling closing playwright browser and continue generate artifacts etc
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { AllIssues, ItemsInfo, RuleInfo } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds pre-computed HTML groups to optimize Group by HTML Element functionality.
|
|
5
|
+
* Keys are composite "html\x00xpath" strings to ensure unique matching per element instance.
|
|
6
|
+
*/
|
|
7
|
+
export const buildHtmlGroups = (rule: RuleInfo, items: ItemsInfo[], pageUrl: string) => {
|
|
8
|
+
if (!rule.htmlGroups) {
|
|
9
|
+
rule.htmlGroups = {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
items.forEach(item => {
|
|
13
|
+
// Use composite key of html + xpath for precise matching
|
|
14
|
+
const htmlKey = `${item.html || 'No HTML element'}\x00${item.xpath || ''}`;
|
|
15
|
+
|
|
16
|
+
if (!rule.htmlGroups![htmlKey]) {
|
|
17
|
+
// Create new group with the first occurrence
|
|
18
|
+
rule.htmlGroups![htmlKey] = {
|
|
19
|
+
html: item.html || '',
|
|
20
|
+
xpath: item.xpath || '',
|
|
21
|
+
message: item.message || '',
|
|
22
|
+
screenshotPath: item.screenshotPath || '',
|
|
23
|
+
displayNeedsReview: item.displayNeedsReview,
|
|
24
|
+
pageUrls: [],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!rule.htmlGroups![htmlKey].pageUrls.includes(pageUrl)) {
|
|
29
|
+
rule.htmlGroups![htmlKey].pageUrls.push(pageUrl);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Converts items in pagesAffected to references (html\x00xpath composite keys) for embedding in HTML report.
|
|
36
|
+
* Additionally, it deep-clones allIssues, replaces page.items objects with composite reference keys.
|
|
37
|
+
* Those refs are specifically for htmlGroups lookup (html + xpath).
|
|
38
|
+
*/
|
|
39
|
+
export const convertItemsToReferences = (allIssues: AllIssues): AllIssues => {
|
|
40
|
+
const cloned = JSON.parse(JSON.stringify(allIssues));
|
|
41
|
+
|
|
42
|
+
['mustFix', 'goodToFix', 'needsReview', 'passed'].forEach(category => {
|
|
43
|
+
if (!cloned.items[category]?.rules) return;
|
|
44
|
+
|
|
45
|
+
cloned.items[category].rules.forEach((rule: any) => {
|
|
46
|
+
if (!rule.pagesAffected || !rule.htmlGroups) return;
|
|
47
|
+
|
|
48
|
+
rule.pagesAffected.forEach((page: any) => {
|
|
49
|
+
if (!page.items) return;
|
|
50
|
+
|
|
51
|
+
page.items = page.items.map((item: any) => {
|
|
52
|
+
if (typeof item === 'string') return item; // Already a reference
|
|
53
|
+
// Use composite key matching buildHtmlGroups
|
|
54
|
+
const htmlKey = `${item.html || 'No HTML element'}\x00${item.xpath || ''}`;
|
|
55
|
+
return htmlKey;
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return cloned;
|
|
62
|
+
};
|