@govtechsg/oobee 0.10.93 → 0.10.95

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.
Files changed (71) hide show
  1. package/AGENTS.md +20 -0
  2. package/dist/cli.js +3 -2
  3. package/dist/combine.js +10 -3
  4. package/dist/constants/common.js +131 -52
  5. package/dist/crawlers/commonCrawlerFunc.js +11 -2
  6. package/dist/crawlers/crawlDomain.js +4 -6
  7. package/dist/crawlers/crawlSitemap.js +14 -2
  8. package/dist/crawlers/custom/utils.js +22 -9
  9. package/dist/crawlers/guards/urlGuard.js +19 -1
  10. package/dist/generateHtmlReport.js +12 -1
  11. package/dist/logs.js +6 -0
  12. package/dist/mergeAxeResults/jsonArtifacts.js +6 -1
  13. package/dist/mergeAxeResults.js +41 -15
  14. package/dist/static/ejs/partials/components/allIssues/CategoryBadges.ejs +3 -0
  15. package/dist/static/ejs/partials/components/allIssues/IssuesTable.ejs +3 -3
  16. package/dist/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +1 -1
  17. package/dist/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +3 -3
  18. package/dist/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +34 -27
  19. package/dist/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +1 -0
  20. package/dist/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +7 -0
  21. package/dist/static/ejs/partials/components/wcagCoverageDetails.ejs +5 -5
  22. package/dist/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +3 -3
  23. package/dist/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +21 -19
  24. package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +39 -8
  25. package/dist/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +11 -5
  26. package/dist/static/ejs/partials/scripts/screenshotLightbox.ejs +49 -31
  27. package/dist/static/ejs/partials/styles/header/SiteInfo.ejs +1 -1
  28. package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +36 -16
  29. package/dist/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +22 -1
  30. package/dist/static/ejs/partials/styles/styles.ejs +1 -1
  31. package/dist/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +6 -0
  32. package/dist/static/ejs/partials/styles/wcagCompliance.ejs +5 -4
  33. package/dist/static/ejs/partials/styles/wcagCoverageDetails.ejs +6 -1
  34. package/dist/utils.js +7 -0
  35. package/oobee-client-scanner.js +2 -2
  36. package/package.json +1 -1
  37. package/src/cli.ts +3 -2
  38. package/src/combine.ts +10 -2
  39. package/src/constants/common.ts +125 -36
  40. package/src/crawlers/commonCrawlerFunc.ts +11 -2
  41. package/src/crawlers/crawlDomain.ts +4 -5
  42. package/src/crawlers/crawlSitemap.ts +19 -2
  43. package/src/crawlers/custom/utils.ts +26 -13
  44. package/src/crawlers/guards/urlGuard.ts +18 -1
  45. package/src/generateHtmlReport.ts +13 -1
  46. package/src/logs.ts +6 -0
  47. package/src/mergeAxeResults/jsonArtifacts.ts +6 -1
  48. package/src/mergeAxeResults.ts +41 -15
  49. package/src/static/ejs/partials/components/allIssues/CategoryBadges.ejs +3 -0
  50. package/src/static/ejs/partials/components/allIssues/IssuesTable.ejs +3 -3
  51. package/src/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +1 -1
  52. package/src/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +3 -3
  53. package/src/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +34 -27
  54. package/src/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +1 -0
  55. package/src/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +7 -0
  56. package/src/static/ejs/partials/components/wcagCoverageDetails.ejs +5 -5
  57. package/src/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +3 -3
  58. package/src/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +21 -19
  59. package/src/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +39 -8
  60. package/src/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +11 -5
  61. package/src/static/ejs/partials/scripts/screenshotLightbox.ejs +49 -31
  62. package/src/static/ejs/partials/styles/header/SiteInfo.ejs +1 -1
  63. package/src/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +36 -16
  64. package/src/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +22 -1
  65. package/src/static/ejs/partials/styles/styles.ejs +1 -1
  66. package/src/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +6 -0
  67. package/src/static/ejs/partials/styles/wcagCompliance.ejs +5 -4
  68. package/src/static/ejs/partials/styles/wcagCoverageDetails.ejs +6 -1
  69. package/src/utils.ts +6 -0
  70. package/testStaticJSScanner.html +1 -1
  71. /package/{7339fae5-e8ed-4b50-af13-317847620dbf.txt → fb5db217-5ab7-4120-b33b-71fa69b454d0.txt} +0 -0
@@ -6,18 +6,32 @@
6
6
  const lightboxContent = document.getElementsByClassName('lightbox-content')[0];
7
7
  const lightboxImg = document.getElementById('lightbox-image');
8
8
 
9
- var customFlowScreenshots = document.getElementsByClassName('custom-flow-screenshot');
10
- Array.from(customFlowScreenshots).forEach(screenshot => {
11
- screenshot.onerror = function (event) {
12
- screenshot.onerror = null;
13
- screenshot.remove();
14
- };
15
- screenshot.onclick = event => {
16
- event.preventDefault();
17
- const pageTitle = screenshot.parentNode.getElementsByTagName('a')[0].textContent;
18
- const pageUrl = screenshot.parentNode.getElementsByTagName('a')[0].href;
19
- openLightbox(screenshot.src, pageTitle, pageUrl);
20
- };
9
+ // Handle dynamically inserted custom-flow screenshots (about scan modal content is rendered later)
10
+ document.addEventListener(
11
+ 'error',
12
+ event => {
13
+ const target = event.target;
14
+ if (target && target.classList && target.classList.contains('custom-flow-screenshot')) {
15
+ target.onerror = null;
16
+ target.remove();
17
+ }
18
+ },
19
+ true,
20
+ );
21
+
22
+ document.addEventListener('click', event => {
23
+ const screenshot = event.target.closest('.custom-flow-screenshot');
24
+ if (!screenshot) return;
25
+
26
+ event.preventDefault();
27
+
28
+ const container = screenshot.closest('.custom-flow-screenshot-container');
29
+ const link = container ? container.querySelector('.display-url-container a') : null;
30
+
31
+ const pageTitle = link?.textContent?.trim() || screenshot.getAttribute('alt') || 'Screenshot';
32
+ const pageUrl = link?.href || '';
33
+
34
+ openLightbox(screenshot.src, pageTitle, pageUrl);
21
35
  });
22
36
 
23
37
  lightbox.addEventListener('click', event => {
@@ -31,8 +45,9 @@
31
45
  });
32
46
 
33
47
  const offcanvasElem = document.getElementsByClassName('offcanvas')[0];
48
+ let offcanvasItem = null;
34
49
  if (offcanvasElem) {
35
- const offcanvasItem = new bootstrap.Offcanvas(offcanvasElem);
50
+ offcanvasItem = new bootstrap.Offcanvas(offcanvasElem);
36
51
  offcanvasItem._config.keyboard = false; // Disable default keyboard handling
37
52
  }
38
53
 
@@ -43,27 +58,30 @@
43
58
  pagesScannedModalItem._config.keyboard = false;
44
59
  }
45
60
 
46
- document.addEventListener('keydown', event => {
47
- if (event.key === 'Escape') {
48
- if (offcanvasItem._isShown) {
49
- if (lightbox.style.display === 'block') {
50
- event.preventDefault(); // Prevent default bootstrap behaviour
51
- closeLightbox();
52
- } else {
53
- offcanvasItem.hide();
54
- }
61
+ // Use capture phase so lightbox handles Escape before Bootstrap modal/offcanvas handlers.
62
+ document.addEventListener(
63
+ 'keydown',
64
+ event => {
65
+ if (event.key !== 'Escape') return;
66
+
67
+ if (lightbox.style.display === 'block') {
68
+ event.preventDefault();
69
+ event.stopPropagation();
70
+ event.stopImmediatePropagation();
71
+ closeLightbox();
72
+ return;
55
73
  }
56
74
 
57
- if (pagesScannedModalItem._isShown) {
58
- if (lightbox.style.display === 'block') {
59
- event.preventDefault(); // Prevent default bootstrap behaviour
60
- closeLightbox();
61
- } else {
62
- pagesScannedModalItem.hide();
63
- }
75
+ if (offcanvasItem && offcanvasItem._isShown) {
76
+ offcanvasItem.hide();
64
77
  }
65
- }
66
- });
78
+
79
+ if (pagesScannedModalItem && pagesScannedModalItem._isShown) {
80
+ pagesScannedModalItem.hide();
81
+ }
82
+ },
83
+ true,
84
+ );
67
85
 
68
86
  function openLightbox(imgSrc, pageTitle, pageUrl) {
69
87
  lightbox.style.display = 'block';
@@ -112,7 +112,7 @@
112
112
 
113
113
  .link {
114
114
  color: var(--a11y-majorelle-blue);
115
- text-decoration: none;
115
+ text-decoration: underline;
116
116
  }
117
117
 
118
118
  .link:hover {
@@ -3,12 +3,16 @@
3
3
  word-break: break-word;
4
4
  }
5
5
 
6
- #scan-about > ul > li:first-child {
7
- padding-top: 0.5rem;
6
+ #aboutScanModalLabel {
7
+ margin-bottom: 1rem;
8
8
  }
9
9
 
10
- #scan-about ul {
10
+ #scan-about > ul {
11
11
  padding-left: 0;
12
+ padding-top: 0;
13
+ padding-bottom: 1rem;
14
+ margin-top: 0;
15
+ margin-bottom: 0;
12
16
  }
13
17
 
14
18
  #scan-about li {
@@ -38,21 +42,28 @@
38
42
  margin-bottom: 0.3rem;
39
43
  }
40
44
 
41
- .advanced-group {
42
- display: flex;
43
- align-items: center;
44
- margin-bottom: 0 !important;
45
+ #scan-about li.advanced-group {
46
+ align-items: flex-start;
47
+ margin-bottom: 0;
48
+ }
49
+
50
+ #scan-about li.advanced-group > .advanced-group-content {
51
+ min-width: 0;
52
+ flex: 1;
45
53
  }
46
54
 
47
- .advanced-group + .advanced-sublist,
48
- .advanced-group + ul {
49
- list-style: disc !important;
50
- margin-left: 3.125rem;
55
+ #scan-about li.advanced-group > .advanced-group-content > ul.advanced-sublist {
56
+ list-style: disc;
57
+ list-style-position: outside;
58
+ margin-left: 8px;
59
+ margin-top: 0.5rem;
60
+ padding-left: 12px;
61
+ text-indent: -2px;
51
62
  }
52
63
 
53
- .advanced-group + .advanced-sublist li,
54
- .advanced-group + ul li {
64
+ #scan-about li.advanced-group > .advanced-group-content > ul.advanced-sublist li {
55
65
  margin: 0;
66
+ list-style-position: outside !important;
56
67
  }
57
68
 
58
69
  .advanced-sublist-li {
@@ -85,10 +96,14 @@
85
96
 
86
97
  .about-scan-toggle .about-us-type-of-scan-text {
87
98
  color: var(--a11y-majorelle-blue) !important;
88
- display: inline !important;
89
- text-decoration: none;
99
+ display: inline;
100
+ text-decoration: underline;
90
101
  cursor: pointer;
91
102
  }
103
+
104
+ #pagesScannedModalToggleTxt {
105
+ color: var(--a11y-majorelle-blue) !important;
106
+ }
92
107
  .about-scan-toggle:hover .about-us-type-of-scan-text,
93
108
  .about-scan-toggle .about-us-type-of-scan-text:focus-visible {
94
109
  text-decoration: underline;
@@ -112,9 +127,14 @@
112
127
  line-height: 1.2;
113
128
  }
114
129
 
130
+ .about-scan-divider-item {
131
+ display: block !important;
132
+ margin-bottom: 2rem !important;
133
+ }
134
+
115
135
  .about-scan-link-text {
116
136
  color: var(--a11y-majorelle-blue);
117
- text-decoration: none;
137
+ text-decoration: underline;
118
138
  cursor: pointer;
119
139
  }
120
140
 
@@ -120,15 +120,36 @@
120
120
 
121
121
  .priority-issue-item {
122
122
  padding: 0.75rem;
123
- cursor: pointer;
124
123
  position: relative;
125
124
  padding-left: 1.5rem;
125
+ display: flex;
126
+ align-items: flex-start;
127
+ }
128
+
129
+ .priority-issue-action {
130
+ appearance: none;
131
+ border: 0;
132
+ background: transparent;
133
+ width: 100%;
134
+ padding: 0;
135
+ margin: 0;
136
+ display: flex;
137
+ text-align: left;
138
+ cursor: pointer;
139
+ color: inherit;
140
+ line-height: 1.5;
141
+ }
142
+
143
+ .priority-issue-action > .w-90 {
144
+ width: 100% !important;
126
145
  }
127
146
 
128
147
  .priority-issue-item::before {
129
148
  counter-increment: issue-counter;
130
149
  content: counter(issue-counter) '.';
131
150
  line-height: 1.5;
151
+ margin-right: 0.5rem;
152
+ flex-shrink: 0;
132
153
  }
133
154
 
134
155
  .priority-issue-item:hover:not(.active) {
@@ -98,7 +98,7 @@
98
98
 
99
99
  a {
100
100
  color: var(--a11y-majorelle-blue);
101
- text-decoration: none;
101
+ text-decoration: underline;
102
102
  }
103
103
 
104
104
  a:hover {
@@ -39,6 +39,11 @@
39
39
  .gauge-number {
40
40
  font-weight: 700;
41
41
  font-size: 16px;
42
+ color: var(--dark-charcoal, #1f1f1f);
43
+ background-color: var(--true-white, #fff);
44
+ display: inline-block;
45
+ padding: 2px 8px;
46
+ border-radius: 999px;
42
47
  }
43
48
 
44
49
  .gauge-caption {
@@ -54,6 +59,7 @@
54
59
 
55
60
  .gauge-value-number {
56
61
  font-size: 24px;
62
+ color: var(--dark-charcoal, #1f1f1f);
57
63
  }
58
64
 
59
65
  .gauge-value-number.perfect-score {
@@ -18,10 +18,11 @@
18
18
  margin-bottom: 0rem;
19
19
  }
20
20
 
21
- .wcag-link {
22
- font-size: 1rem;
23
- border: 0;
24
- }
21
+ .wcag-link {
22
+ font-size: 1rem;
23
+ border: 0;
24
+ text-decoration: underline;
25
+ }
25
26
 
26
27
  .wcag-status {
27
28
  margin-bottom: 0.3rem;
@@ -12,9 +12,14 @@
12
12
  gap: 10px 18px;
13
13
  }
14
14
 
15
+ #wcagCoverage .wcag-criteria-heading {
16
+ font-size: 1.25rem;
17
+ line-height: 1.2;
18
+ }
19
+
15
20
  #wcagCoverage .wcag-grid a {
16
21
  color: var(--a11y-majorelle-blue, #5735DF);
17
- text-decoration: none;
22
+ text-decoration: underline;
18
23
  }
19
24
  #wcagCoverage .wcag-grid a:hover,
20
25
  #wcagCoverage .wcag-grid a:focus-visible {
package/dist/utils.js CHANGED
@@ -345,6 +345,13 @@ export const cleanUp = async (randomToken, isError = false) => {
345
345
  catch (error) {
346
346
  consoleLogger.warn(`Unable to force remove userDataDirectory: ${error.message}`);
347
347
  }
348
+ if (process.env.TMPDIR && fs.existsSync('/.dockerenv'))
349
+ try {
350
+ fs.rmSync(process.env.TMPDIR, { recursive: true, force: true });
351
+ }
352
+ catch (error) {
353
+ consoleLogger.warn(`Unable to force remove browser tmp dir: ${error.message}`);
354
+ }
348
355
  if (randomToken !== undefined) {
349
356
  const storagePath = getStoragePath(randomToken);
350
357
  try {
@@ -3,7 +3,7 @@
3
3
  * DO NOT EDIT MANUALLY. Re-generate with: node dist/generateOobeeClientScanner.js
4
4
  *
5
5
  * Embedded at generation time:
6
- * App version : 0.10.93
6
+ * App version : 0.10.95
7
7
  * Sentry DSN : (from OOBEE_SENTRY_DSN env var or constants.ts default)
8
8
  * Sentry SDK : @sentry/browser 10.58.0 (loaded from CDN at runtime)
9
9
  *
@@ -34883,7 +34883,7 @@
34883
34883
  // ── Sentry browser telemetry (Sentry JS SDK, loaded from CDN) ────────────
34884
34884
 
34885
34885
  var _oobeeSentryDsn = "https://3b8c7ee46b06f33815a1301b6713ebc3@o4509047624761344.ingest.us.sentry.io/4509327783559168";
34886
- var _oobeeAppVersion = "0.10.93";
34886
+ var _oobeeAppVersion = "0.10.95";
34887
34887
  var _oobeeSentryVersion = "10.58.0";
34888
34888
  var _oobeeSentryInitialized = false;
34889
34889
  var _oobeeSentryLoadPromise = null;
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.93",
4
+ "version": "0.10.95",
5
5
  "type": "module",
6
6
  "author": "Government Technology Agency <info@tech.gov.sg>",
7
7
  "bin": {
package/src/cli.ts CHANGED
@@ -264,9 +264,10 @@ const scanInit = async (argvs: Answers): Promise<string> => {
264
264
  consoleLogger.info(`Connectivity Check HTTP Response Code: ${res.httpStatus}`);
265
265
 
266
266
  if (res.status === statuses.success.code) {
267
- // Custom flow should continue from the user-provided entry URL so auth redirects
268
- // do not replace the original domain used for overlay gating and navigation.
267
+ // Keep browser-resolved URL as entryUrl for downstream scan metadata/events
268
+ // on non-custom scans.
269
269
  if (data.type !== ScannerTypes.CUSTOM) {
270
+ data.entryUrl = res.url;
270
271
  data.url = res.url;
271
272
  }
272
273
  if (process.env.OOBEE_VALIDATE_URL) {
package/src/combine.ts CHANGED
@@ -45,6 +45,7 @@ const combineRun = async (details: Data, deviceToScan: string) => {
45
45
  const {
46
46
  type,
47
47
  url,
48
+ entryUrl,
48
49
  nameEmail,
49
50
  randomToken,
50
51
  deviceChosen,
@@ -88,6 +89,13 @@ const combineRun = async (details: Data, deviceToScan: string) => {
88
89
  consoleLogger.info(`Suppressed Crawlee ps-tree locale error: ${err.message}`);
89
90
  return;
90
91
  }
92
+ // Suppress stale Playwright in-process connection errors that fire asynchronously
93
+ // after the browser is closed in writeSummaryPdf. The deferred IPC message arrives
94
+ // via setImmediate after the browser instance has already been disposed.
95
+ if (err.message?.includes('was not bound in the connection')) {
96
+ consoleLogger.info(`Suppressed Playwright post-close connection error: ${err.message}`);
97
+ return;
98
+ }
91
99
  throw err;
92
100
  };
93
101
  process.on('uncaughtException', psTreeHandler);
@@ -104,8 +112,8 @@ const combineRun = async (details: Data, deviceToScan: string) => {
104
112
 
105
113
  // remove basic-auth credentials from URL
106
114
  const finalUrl = !(type === ScannerTypes.SITEMAP || type === ScannerTypes.LOCALFILE)
107
- ? new URL(url)
108
- : new URL(pathToFileURL(url));
115
+ ? new URL(entryUrl)
116
+ : new URL(pathToFileURL(entryUrl));
109
117
 
110
118
  // Use the string version of finalUrl to reduce logic at submitForm
111
119
  const finalUrlString = finalUrl.toString();