@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 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
- if (argvs.scanner !== ScannerTypes.WEBSITE && argvs.strategy) {
150
- throw new Error('-s or --strategy is only available in website scans.');
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', (option) => {
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 === ScannerTypes.CUSTOM && typeof argvs.scanDuration === 'number' && argvs.scanDuration > 0) {
164
- throw new Error('-l or --scanDuration is not allowed for custom flow scans.');
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('#oobee-shadow-host');
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 = (vars.menuPos || 'RIGHT');
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 = (typeof force === 'boolean') ? force : !isCollapsed();
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((item) => {
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 = (item.pageTitle && item.pageTitle.trim()) ? item.pageTitle : item.url;
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, { margin: '0', fontSize: '22px', fontWeight: '700', lineHeight: '1.25' });
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 = () => { try {
796
- stopDialog.close();
797
- }
798
- catch { } stopResolver = null; };
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', (e) => {
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', (e) => {
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((resolve) => {
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
- await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
914
- inProgress: false,
915
- collapsed: !!pagesDict[pageId]?.collapsed,
916
- hideStopInput: !!processPageParams.customFlowLabel,
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('chrome');
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 = [...baseArgs.filter(a => !a.startsWith('--window-size') && a !== '--start-maximized'), ...customArgs];
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);
@@ -247,35 +247,54 @@ const cleanUpJsonFiles = async (filesToDelete) => {
247
247
  });
248
248
  };
249
249
  const writeSummaryPdf = async (storagePath, pagesScanned, filename = 'summary', browser, _userDataDirectory) => {
250
- const htmlFilePath = `${storagePath}/${filename}.html`;
251
- const fileDestinationPath = `${storagePath}/${filename}.pdf`;
252
- const launchOptions = getPlaywrightLaunchOptions(browser);
253
- const browserInstance = await constants.launcher.launch({
254
- ...launchOptions,
255
- headless: true, // force headless for PDF
256
- });
257
- register(browserInstance);
258
- const context = await browserInstance.newContext();
259
- const page = await context.newPage();
260
- const data = fs.readFileSync(htmlFilePath, { encoding: 'utf-8' });
261
- await page.setContent(data, { waitUntil: 'domcontentloaded' });
262
- await page.emulateMedia({ media: 'print' });
263
- await page.pdf({
264
- margin: { bottom: '32px' },
265
- path: fileDestinationPath,
266
- format: 'A4',
267
- displayHeaderFooter: true,
268
- footerTemplate: `
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
- await page.close();
275
- await context.close().catch(() => { });
276
- await browserInstance.close().catch(() => { });
277
- if (pagesScanned < 2000) {
278
- fs.unlinkSync(htmlFilePath);
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.84",
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.26",
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 { setHeadlessMode, getVersion, getStoragePath, listenForCleanUp, cleanUpAndExit } from './utils.js';
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
- if (argvs.scanner !== ScannerTypes.WEBSITE && argvs.strategy) {
189
- throw new Error('-s or --strategy is only available in website scans.');
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', (option) => {
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 === ScannerTypes.CUSTOM && typeof argvs.scanDuration === 'number' && argvs.scanDuration > 0) {
206
- throw new Error('-l or --scanDuration is not allowed for custom flow scans.');
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) consoleLogger.info(`Connectivity Check HTTP Response Code: ${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('#oobee-shadow-host');
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' = (vars.menuPos || '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 = (typeof force === 'boolean') ? force : !isCollapsed();
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((item) => {
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 = (item.pageTitle && item.pageTitle.trim()) ? item.pageTitle : item.url;
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); // <-- use the button
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; // <-- check the button
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 { grip.releasePointerCapture(e.pointerId); } catch {}
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, { margin: '0', fontSize: '22px', fontWeight: '700', lineHeight: '1.25' });
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
- form.appendChild(actions);
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 = () => { try { stopDialog.close(); } catch {} stopResolver = null; };
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 { stopDialog.showModal(); } catch {}
995
+ try {
996
+ stopDialog.showModal();
997
+ } catch {}
951
998
  if (!shouldHideInput) {
952
999
  requestAnimationFrame(() => {
953
- try { input.focus({ preventScroll: true }); input.select(); } catch {}
1000
+ try {
1001
+ input.focus({ preventScroll: true });
1002
+ input.select();
1003
+ } catch {}
954
1004
  });
955
1005
  }
956
1006
  };
957
- form.addEventListener('submit', (e) => {
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', (e) => {
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 }>((resolve) => {
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
- await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
1062
- inProgress: false,
1063
- collapsed: !!pagesDict[pageId]?.collapsed,
1064
- hideStopInput: !!processPageParams.customFlowLabel,
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('chrome');
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 = [...baseArgs.filter(a => !a.startsWith('--window-size') && a !== '--start-maximized'), ...customArgs];
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
@@ -349,44 +349,61 @@ const writeSummaryPdf = async (
349
349
  browser: string,
350
350
  _userDataDirectory: string,
351
351
  ) => {
352
- const htmlFilePath = `${storagePath}/${filename}.html`;
353
- const fileDestinationPath = `${storagePath}/${filename}.pdf`;
352
+ let browserInstance;
353
+ let context;
354
+ let page;
354
355
 
355
- const launchOptions = getPlaywrightLaunchOptions(browser);
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
- const browserInstance = await constants.launcher.launch({
358
- ...launchOptions,
359
- headless: true, // force headless for PDF
360
- });
361
+ const launchOptions = getPlaywrightLaunchOptions(browser);
362
+
363
+ browserInstance = await constants.launcher.launch({
364
+ ...launchOptions,
365
+ headless: true,
366
+ });
361
367
 
362
- register(browserInstance as unknown as { close: () => Promise<void> });
368
+ register(browserInstance as unknown as { close: () => Promise<void> });
363
369
 
364
- const context = await browserInstance.newContext();
365
- const page = await context.newPage();
370
+ context = await browserInstance.newContext();
371
+ page = await context.newPage();
366
372
 
367
- const data = fs.readFileSync(htmlFilePath, { encoding: 'utf-8' });
368
- await page.setContent(data, { waitUntil: 'domcontentloaded' });
373
+ await page.goto(htmlFileUrl, {
374
+ waitUntil: 'domcontentloaded',
375
+ timeout: 120000,
376
+ });
369
377
 
370
- await page.emulateMedia({ media: 'print' });
378
+ await page.emulateMedia({ media: 'print' });
371
379
 
372
- await page.pdf({
373
- margin: { bottom: '32px' },
374
- path: fileDestinationPath,
375
- format: 'A4',
376
- displayHeaderFooter: true,
377
- footerTemplate: `
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
- if (pagesScanned < 2000) {
389
- fs.unlinkSync(htmlFilePath);
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