@govtechsg/oobee 0.10.78-alpha1 → 0.10.84

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 (63) hide show
  1. package/Dockerfile +3 -2
  2. package/INTEGRATION.md +1 -1
  3. package/README.md +6 -1
  4. package/dist/constants/common.js +166 -91
  5. package/dist/constants/constants.js +1 -0
  6. package/dist/crawlers/crawlDomain.js +220 -120
  7. package/dist/crawlers/crawlIntelligentSitemap.js +22 -7
  8. package/dist/crawlers/custom/extractAndGradeText.js +1 -1
  9. package/dist/crawlers/custom/gradeReadability.js +1 -1
  10. package/dist/crawlers/runCustom.js +8 -2
  11. package/dist/generateHtmlReport.js +8 -3
  12. package/dist/mergeAxeResults/itemReferences.js +55 -0
  13. package/dist/mergeAxeResults/jsonArtifacts.js +335 -0
  14. package/dist/mergeAxeResults/scanPages.js +159 -0
  15. package/dist/mergeAxeResults/sentryTelemetry.js +152 -0
  16. package/dist/mergeAxeResults/types.js +1 -0
  17. package/dist/mergeAxeResults/writeCsv.js +125 -0
  18. package/dist/mergeAxeResults/writeScanDetailsCsv.js +35 -0
  19. package/dist/mergeAxeResults/writeSitemap.js +10 -0
  20. package/dist/mergeAxeResults.js +157 -971
  21. package/dist/proxyService.js +90 -5
  22. package/dist/static/ejs/partials/scripts/decodeUnzipParse.ejs +39 -2
  23. package/dist/static/ejs/partials/scripts/pako.ejs +4 -0
  24. package/dist/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +48 -2
  25. package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +129 -48
  26. package/dist/static/ejs/partials/scripts/ruleModal/utilities.ejs +2 -0
  27. package/dist/static/ejs/partials/scripts/screenshotLightbox.ejs +9 -4
  28. package/dist/static/ejs/partials/scripts/wcagCompliance/FailedCriteria.ejs +0 -2
  29. package/dist/static/ejs/partials/styles/fontAwesomeCssAll.ejs +7 -0
  30. package/dist/static/ejs/partials/styles/ruleModal/ruleOffcanvas.ejs +2 -0
  31. package/dist/static/ejs/report.ejs +122 -127
  32. package/dist/utils.js +22 -10
  33. package/package.json +17 -10
  34. package/src/constants/common.ts +181 -104
  35. package/src/constants/constants.ts +1 -0
  36. package/src/crawlers/crawlDomain.ts +248 -137
  37. package/src/crawlers/crawlIntelligentSitemap.ts +22 -8
  38. package/src/crawlers/custom/extractAndGradeText.ts +1 -1
  39. package/src/crawlers/custom/gradeReadability.ts +1 -1
  40. package/src/crawlers/runCustom.ts +10 -2
  41. package/src/generateHtmlReport.ts +9 -3
  42. package/src/mergeAxeResults/itemReferences.ts +62 -0
  43. package/src/mergeAxeResults/jsonArtifacts.ts +451 -0
  44. package/src/mergeAxeResults/scanPages.ts +207 -0
  45. package/src/mergeAxeResults/sentryTelemetry.ts +183 -0
  46. package/src/mergeAxeResults/types.ts +99 -0
  47. package/src/mergeAxeResults/writeCsv.ts +145 -0
  48. package/src/mergeAxeResults/writeScanDetailsCsv.ts +51 -0
  49. package/src/mergeAxeResults/writeSitemap.ts +13 -0
  50. package/src/mergeAxeResults.ts +203 -1326
  51. package/src/proxyService.ts +96 -4
  52. package/src/static/ejs/partials/scripts/decodeUnzipParse.ejs +39 -2
  53. package/src/static/ejs/partials/scripts/pako.ejs +4 -0
  54. package/src/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +48 -2
  55. package/src/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +129 -48
  56. package/src/static/ejs/partials/scripts/ruleModal/utilities.ejs +2 -0
  57. package/src/static/ejs/partials/scripts/screenshotLightbox.ejs +9 -4
  58. package/src/static/ejs/partials/scripts/wcagCompliance/FailedCriteria.ejs +0 -2
  59. package/src/static/ejs/partials/styles/fontAwesomeCssAll.ejs +7 -0
  60. package/src/static/ejs/partials/styles/ruleModal/ruleOffcanvas.ejs +2 -0
  61. package/src/static/ejs/report.ejs +122 -127
  62. package/src/utils.ts +20 -9
  63. package/scripts/decodeUnzipParse.js +0 -29
package/Dockerfile CHANGED
@@ -1,6 +1,6 @@
1
1
  # Use Microsoft Playwright image as base image
2
2
  # Node version is v22
3
- FROM mcr.microsoft.com/playwright:v1.55.0-noble
3
+ FROM mcr.microsoft.com/playwright:v1.58.2-noble
4
4
 
5
5
  # Installation of packages for oobee and runner (locked versions from build log)
6
6
  RUN apt-get update && apt-get install -y \
@@ -23,7 +23,8 @@ ENV NODE_ENV=production
23
23
  ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD="true"
24
24
 
25
25
  # Install oobee dependencies
26
- RUN npm ci --omit=dev
26
+ # TODO: Move back to npm ci --omit=dev once module package-lock issue vs MacOS is resolved
27
+ RUN npm install --omit=dev
27
28
 
28
29
  # Compile TypeScript for oobee
29
30
  RUN npm run build || true # true exits with code 0 - workaround for TS errors
package/INTEGRATION.md CHANGED
@@ -468,4 +468,4 @@ Scans raw HTML string(s). Note that this runs in a JSDOM environment (NodeJS) us
468
468
 
469
469
  **Returns:**
470
470
 
471
- - Scan results object containing categorized violations and pass counts.
471
+ - Scan results object containing categorized violations (mustFix, goodToFux, needsReview).
package/README.md CHANGED
@@ -90,6 +90,11 @@ verapdf --version
90
90
  | WARN_LEVEL | Only used in tests. | |
91
91
  | OOBEE_DISABLE_BROWSER_DOWNLOAD | Experimental flag to disable file downloads on Chrome/Chromium/Edge. Does not affect Local File scan | |
92
92
  | OOBEE_SLOWMO | Experimental flag to slow down web browser behaviour by specified duration (in miliseconds) | |
93
+ | HTTP_PROXY | URL of the proxy server to be used for HTTP requests (e.g. `http://proxy.example.com:8080`). | |
94
+ | HTTPS_PROXY | URL of the proxy server to be used for HTTPS requests (e.g. `https://proxy.example.com:8080`). | |
95
+ | ALL_PROXY | URL of the proxy server to be used for all requests, typically used for SOCKS5 proxies (e.g. `socks5://proxy.example.com:1080`. Note: IPv6 direct connections may still continue even though socks5 proxy is specified due to a known issue with Chrome/Chromium. (Recommended workaround is to turn off IPv6 at host-level). | |
96
+ | NO_PROXY | Comma-separated list of domains that should bypass the proxy (e.g. `localhost,127.0.0.1,.example.com`). | |
97
+ | INCLUDE_PROXY | Comma-separated list of domains that should specifically be routed through the proxy. | |
93
98
 
94
99
  #### Environment variables used internally (Do not set)
95
100
  Do not set these environment variables or behaviour might change unexpectedly.
@@ -677,4 +682,4 @@ It uses the existing report *.json files for the embedded HTML dataset.
677
682
 
678
683
  ```
679
684
  npx tsx dev/runGenerateJustHtmlReport.ts results/<report directory>
680
- ```
685
+ ```
@@ -288,16 +288,45 @@ const checkUrlConnectivityWithBrowser = async (url, browserToRun, clonedDataDir,
288
288
  extraHTTPHeaders.Accept ||= 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
289
289
  await initModifiedUserAgent(browserToRun, playwrightDeviceDetailsObject, clonedDataDir);
290
290
  let browserContext;
291
+ let browserInstance;
292
+ const rawDevice = (playwrightDeviceDetailsObject || {});
293
+ const { viewport, isMobile, hasTouch, userAgent: deviceUserAgent, ...restDevice } = rawDevice;
294
+ const launchOptions = getPlaywrightLaunchOptions(browserToRun);
295
+ const contextOptions = {
296
+ ...restDevice,
297
+ ...(extraHTTPHeaders && { extraHTTPHeaders }),
298
+ ignoreHTTPSErrors: true,
299
+ ...(process.env.OOBEE_DISABLE_BROWSER_DOWNLOAD && { acceptDownloads: false }),
300
+ };
301
+ // Keep UA emulation explicitly.
302
+ contextOptions.userAgent = process.env.OOBEE_USER_AGENT || deviceUserAgent;
291
303
  try {
292
- browserContext = await constants.launcher.launchPersistentContext(clonedDataDir, {
293
- ...(extraHTTPHeaders && { extraHTTPHeaders }),
294
- ignoreHTTPSErrors: true,
295
- headless: true,
296
- ...getPlaywrightLaunchOptions(browserToRun),
297
- ...playwrightDeviceDetailsObject,
298
- ...(process.env.OOBEE_DISABLE_BROWSER_DOWNLOAD && { acceptDownloads: false }),
299
- });
300
- register(browserContext);
304
+ const launchPersistent = async () => {
305
+ browserContext = await constants.launcher.launchPersistentContext(clonedDataDir, {
306
+ ...launchOptions,
307
+ ...contextOptions,
308
+ });
309
+ register(browserContext);
310
+ };
311
+ const launchEphemeral = async () => {
312
+ browserInstance = await constants.launcher.launch(launchOptions);
313
+ register(browserInstance);
314
+ browserContext = await browserInstance.newContext(contextOptions);
315
+ };
316
+ if (process.env.CRAWLEE_HEADLESS === '1') {
317
+ try {
318
+ await launchPersistent();
319
+ }
320
+ catch (error) {
321
+ // Fallback to ephemeral context if persistent context fails (e.g. protocol errors)
322
+ // More prone to falling back here when running localFile scans
323
+ consoleLogger.warn(`Persistent context launch failed, retrying with ephemeral context: ${error.message}`);
324
+ await launchEphemeral();
325
+ }
326
+ }
327
+ else {
328
+ await launchEphemeral();
329
+ }
301
330
  }
302
331
  catch (err) {
303
332
  printMessage([`Unable to launch browser\n${err}`], messageOptions);
@@ -335,7 +364,19 @@ const checkUrlConnectivityWithBrowser = async (url, browserToRun, clonedDataDir,
335
364
  });
336
365
  if (!response)
337
366
  throw new Error('No response from navigation');
338
- // We use the response headers from the navigation we just performed.
367
+ // Wait briefly for JS/meta-refresh redirects to settle before reading the final URL.
368
+ // Server-side redirects are already reflected after goto(), but client-side redirects
369
+ // (e.g. domain.tld -> www.domain.tld via JS or meta-refresh) need extra time.
370
+ try {
371
+ await Promise.race([
372
+ page.waitForURL(currentUrl => currentUrl !== url, { timeout: 5000 }),
373
+ new Promise(resolve => setTimeout(resolve, 1000)), // minimum settle time
374
+ ]);
375
+ }
376
+ catch {
377
+ // No redirect happened within the window — that's fine, continue with current URL
378
+ }
379
+ // Re-read page.url() AFTER potential client-side redirects have resolved
339
380
  const finalUrl = page.url();
340
381
  const finalStatus = response.status();
341
382
  const headers = response.headers();
@@ -399,7 +440,10 @@ const checkUrlConnectivityWithBrowser = async (url, browserToRun, clonedDataDir,
399
440
  }
400
441
  }
401
442
  finally {
402
- await browserContext.close();
443
+ await browserContext?.close();
444
+ if (browserInstance) {
445
+ await browserInstance.close();
446
+ }
403
447
  }
404
448
  return res;
405
449
  };
@@ -630,24 +674,49 @@ export const getUrlsFromRobotsTxt = async (url, browserToRun, userDataDirectory,
630
674
  constants.robotsTxtUrls[domain] = { disallowedUrls, allowedUrls };
631
675
  };
632
676
  const getRobotsTxtViaPlaywright = async (robotsUrl, browser, userDataDirectory, extraHTTPHeaders) => {
633
- const robotsDataDir = '';
677
+ let robotsDataDir = '';
678
+ let browserContext;
679
+ let browserInstance;
634
680
  // Bug in Chrome which causes browser pool crash when userDataDirectory is set in non-headless mode
635
681
  if (process.env.CRAWLEE_HEADLESS === '1') {
636
682
  // Create robots own user data directory else SingletonLock: File exists (17) with crawlDomain or crawlSitemap's own browser
637
- const robotsDataDir = path.join(userDataDirectory, 'robots');
683
+ robotsDataDir = path.join(userDataDirectory, 'robots');
638
684
  if (!fs.existsSync(robotsDataDir)) {
639
685
  fs.mkdirSync(robotsDataDir, { recursive: true });
640
686
  }
641
687
  }
642
- const browserContext = await constants.launcher.launchPersistentContext(robotsDataDir, {
643
- ...getPlaywrightLaunchOptions(browser),
644
- ...(extraHTTPHeaders && { extraHTTPHeaders }),
645
- });
646
- register(browserContext);
647
- const page = await browserContext.newPage();
648
- await page.goto(robotsUrl, { waitUntil: 'networkidle', timeout: 30000 });
649
- const robotsTxt = await page.evaluate(() => document.body.textContent);
650
- return robotsTxt;
688
+ try {
689
+ if (process.env.CRAWLEE_HEADLESS === '1') {
690
+ browserContext = await constants.launcher.launchPersistentContext(robotsDataDir, {
691
+ ...getPlaywrightLaunchOptions(browser),
692
+ ...(extraHTTPHeaders && { extraHTTPHeaders }),
693
+ });
694
+ register(browserContext);
695
+ }
696
+ else {
697
+ // In headful mode, avoid launchPersistentContext with custom user data dir to prevent "Browser window not found"
698
+ const launchOptions = getPlaywrightLaunchOptions(browser);
699
+ browserInstance = await constants.launcher.launch(launchOptions);
700
+ register(browserInstance);
701
+ browserContext = await browserInstance.newContext({
702
+ ...(extraHTTPHeaders && { extraHTTPHeaders }),
703
+ });
704
+ }
705
+ const page = await browserContext.newPage();
706
+ await page.goto(robotsUrl, { waitUntil: 'networkidle', timeout: 30000 });
707
+ const robotsTxt = await page.evaluate(() => document.body.textContent);
708
+ return robotsTxt;
709
+ }
710
+ catch (e) {
711
+ consoleLogger.error(`Error fetching robots.txt: ${e.message}`);
712
+ throw e;
713
+ }
714
+ finally {
715
+ await browserContext?.close();
716
+ if (browserInstance) {
717
+ await browserInstance.close();
718
+ }
719
+ }
651
720
  };
652
721
  export const isDisallowedInRobotsTxt = (url) => {
653
722
  if (!constants.robotsTxtUrls)
@@ -771,39 +840,59 @@ export const getLinksFromSitemap = async (sitemapUrl, maxLinksCount, browser, us
771
840
  return;
772
841
  }
773
842
  const getDataUsingPlaywright = async () => {
774
- const browserContext = await constants.launcher.launchPersistentContext(finalUserDataDirectory, {
775
- ...getPlaywrightLaunchOptions(browser),
776
- // Not necessary to parse http_credentials as I am parsing it directly in URL
777
- // Bug in Chrome which causes browser pool crash when userDataDirectory is set in non-headless mode
778
- ...(process.env.CRAWLEE_HEADLESS === '1' && { userDataDir: userDataDirectory }),
779
- ...(extraHTTPHeaders && { extraHTTPHeaders }),
780
- });
781
- register(browserContext);
782
- const page = await browserContext.newPage();
783
- await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 });
784
- if ((await page.locator('body').count()) > 0) {
785
- data = await page.locator('body').innerText();
786
- }
787
- else {
788
- const urlSet = page.locator('urlset');
789
- const sitemapIndex = page.locator('sitemapindex');
790
- const rss = page.locator('rss');
791
- const feed = page.locator('feed');
792
- const isRoot = async (locator) => (await locator.count()) > 0;
793
- if (await isRoot(urlSet)) {
794
- data = await urlSet.evaluate(elem => elem.outerHTML);
843
+ let browserContext;
844
+ let browserInstance;
845
+ try {
846
+ if (process.env.CRAWLEE_HEADLESS === '1') {
847
+ browserContext = await constants.launcher.launchPersistentContext(finalUserDataDirectory, {
848
+ ...getPlaywrightLaunchOptions(browser),
849
+ // Not necessary to parse http_credentials as I am parsing it directly in URL
850
+ // Bug in Chrome which causes browser pool crash when userDataDirectory is set in non-headless mode
851
+ ...(process.env.CRAWLEE_HEADLESS === '1' && { userDataDir: userDataDirectory }),
852
+ ...(extraHTTPHeaders && { extraHTTPHeaders }),
853
+ });
854
+ register(browserContext);
795
855
  }
796
- else if (await isRoot(sitemapIndex)) {
797
- data = await sitemapIndex.evaluate(elem => elem.outerHTML);
856
+ else {
857
+ // In headful mode, avoid launchPersistentContext with custom user data dir to prevent "Browser window not found"
858
+ const launchOptions = getPlaywrightLaunchOptions(browser);
859
+ browserInstance = await constants.launcher.launch(launchOptions);
860
+ register(browserInstance);
861
+ browserContext = await browserInstance.newContext({
862
+ ...(extraHTTPHeaders && { extraHTTPHeaders }),
863
+ });
864
+ }
865
+ const page = await browserContext.newPage();
866
+ await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 });
867
+ if ((await page.locator('body').count()) > 0) {
868
+ data = await page.locator('body').innerText();
798
869
  }
799
- else if (await isRoot(rss)) {
800
- data = await rss.evaluate(elem => elem.outerHTML);
870
+ else {
871
+ const urlSet = page.locator('urlset');
872
+ const sitemapIndex = page.locator('sitemapindex');
873
+ const rss = page.locator('rss');
874
+ const feed = page.locator('feed');
875
+ const isRoot = async (locator) => (await locator.count()) > 0;
876
+ if (await isRoot(urlSet)) {
877
+ data = await urlSet.evaluate(elem => elem.outerHTML);
878
+ }
879
+ else if (await isRoot(sitemapIndex)) {
880
+ data = await sitemapIndex.evaluate(elem => elem.outerHTML);
881
+ }
882
+ else if (await isRoot(rss)) {
883
+ data = await rss.evaluate(elem => elem.outerHTML);
884
+ }
885
+ else if (await isRoot(feed)) {
886
+ data = await feed.evaluate(elem => elem.outerHTML);
887
+ }
801
888
  }
802
- else if (await isRoot(feed)) {
803
- data = await feed.evaluate(elem => elem.outerHTML);
889
+ }
890
+ finally {
891
+ await browserContext?.close();
892
+ if (browserInstance) {
893
+ await browserInstance.close();
804
894
  }
805
895
  }
806
- await browserContext.close();
807
896
  };
808
897
  if (validator.isURL(url, urlOptions)) {
809
898
  if (isUrlPdf(url)) {
@@ -1486,36 +1575,25 @@ export const submitForm = async (browserToRun, userDataDirectory, scannedUrl, en
1486
1575
  }
1487
1576
  };
1488
1577
  // Legacy code end - Google Sheets submission
1489
- export async function initModifiedUserAgent(browser, playwrightDeviceDetailsObject, userDataDirectory) {
1490
- const isHeadless = process.env.CRAWLEE_HEADLESS === '1';
1491
- // If headless mode is enabled, ensure the headless flag is set.
1492
- if (isHeadless && !constants.launchOptionsArgs.includes('--headless=new')) {
1493
- constants.launchOptionsArgs.push('--headless=new');
1494
- }
1495
- // Build the launch options using your production settings.
1496
- // headless is forced to false as in your persistent context, and we merge in getPlaywrightLaunchOptions and device details.
1497
- const launchOptions = {
1498
- headless: false,
1499
- ...getPlaywrightLaunchOptions(browser),
1500
- ...playwrightDeviceDetailsObject,
1501
- };
1502
- // Launch a temporary persistent context with an empty userDataDir to mimic your production browser setup.
1503
- const effectiveUserDataDirectory = process.env.CRAWLEE_HEADLESS === '1' ? userDataDirectory : '';
1504
- const browserContext = await constants.launcher.launchPersistentContext(effectiveUserDataDirectory, launchOptions);
1505
- register(browserContext);
1506
- const page = await browserContext.newPage();
1507
- // Retrieve the default user agent.
1508
- const defaultUA = await page.evaluate(() => navigator.userAgent);
1509
- await browserContext.close();
1510
- // Modify the UA:
1511
- // Replace "HeadlessChrome" with "Chrome" if present.
1512
- const modifiedUA = defaultUA.includes('HeadlessChrome')
1513
- ? defaultUA.replace('HeadlessChrome', 'Chrome')
1514
- : defaultUA;
1515
- // Push the modified UA flag into your global launch options.
1516
- constants.launchOptionsArgs.push(`--user-agent=${modifiedUA}`);
1517
- // Optionally log the modified UA.
1518
- // console.log('Modified User Agent:', modifiedUA);
1578
+ export async function initModifiedUserAgent(browser, _playwrightDeviceDetailsObject, _userDataDirectory) {
1579
+ // UA bootstrap must not use persistent context / user-data-dir.
1580
+ const launchOptions = getPlaywrightLaunchOptions(browser);
1581
+ const browserInstance = await constants.launcher.launch(launchOptions);
1582
+ register(browserInstance);
1583
+ try {
1584
+ const context = await browserInstance.newContext();
1585
+ const page = await context.newPage();
1586
+ const defaultUA = await page.evaluate(() => navigator.userAgent);
1587
+ await context.close();
1588
+ const modifiedUA = defaultUA.includes('HeadlessChrome')
1589
+ ? defaultUA.replace('HeadlessChrome', 'Chrome')
1590
+ : defaultUA;
1591
+ // Do not mutate global CLI args with --user-agent=
1592
+ process.env.OOBEE_USER_AGENT = modifiedUA;
1593
+ }
1594
+ finally {
1595
+ await browserInstance.close();
1596
+ }
1519
1597
  }
1520
1598
  const cacheProxyInfo = getProxyInfo();
1521
1599
  /**
@@ -1525,12 +1603,13 @@ const cacheProxyInfo = getProxyInfo();
1525
1603
  export const getPlaywrightLaunchOptions = (browser) => {
1526
1604
  const channel = browser || undefined;
1527
1605
  const resolution = proxyInfoToResolution(cacheProxyInfo);
1528
- // Start with your base args
1529
- const finalArgs = [...constants.launchOptionsArgs];
1606
+ // Start with your base args and sanitise
1607
+ const finalArgs = [...constants.launchOptionsArgs].filter(arg => !arg.startsWith('--headless') &&
1608
+ !arg.startsWith('--user-agent=') &&
1609
+ arg !== '--mute-audio' &&
1610
+ !(browser === BrowserTypes.CHROME && arg === '--edge-skip-compat-layer-relaunch'));
1530
1611
  // Headless flags (unchanged)
1531
1612
  if (process.env.CRAWLEE_HEADLESS === '1') {
1532
- if (!finalArgs.includes('--headless=new'))
1533
- finalArgs.push('--headless=new');
1534
1613
  if (!finalArgs.includes('--mute-audio'))
1535
1614
  finalArgs.push('--mute-audio');
1536
1615
  }
@@ -1551,21 +1630,17 @@ export const getPlaywrightLaunchOptions = (browser) => {
1551
1630
  break;
1552
1631
  }
1553
1632
  const options = {
1554
- ignoreDefaultArgs: ['--use-mock-keychain', '--headless'],
1633
+ ignoreDefaultArgs: ['--use-mock-keychain'],
1555
1634
  args: finalArgs,
1556
- headless: false,
1635
+ headless: process.env.CRAWLEE_HEADLESS === '1',
1557
1636
  ...(channel && { channel }),
1558
1637
  ...(proxyOpt ? { proxy: proxyOpt } : {}),
1559
1638
  };
1560
- // SlowMo (unchanged)
1639
+ // SlowMo for debugging, can be set via env variable OOBEE_SLOWMO to avoid adding it as a CLI argument and causing confusion for users who don't need it
1561
1640
  if (!options.slowMo && process.env.OOBEE_SLOWMO && Number(process.env.OOBEE_SLOWMO) >= 1) {
1562
1641
  options.slowMo = Number(process.env.OOBEE_SLOWMO);
1563
1642
  consoleLogger.info(`Enabled browser slowMo with value: ${process.env.OOBEE_SLOWMO}ms`);
1564
1643
  }
1565
- // Edge on Windows should not be headless (unchanged)
1566
- if (browser === BrowserTypes.EDGE && os.platform() === 'win32') {
1567
- options.headless = false;
1568
- }
1569
1644
  return options;
1570
1645
  };
1571
1646
  export const waitForPageLoaded = async (page, timeout = 10000) => {
@@ -725,6 +725,7 @@ export const disabilityBadgesMap = {
725
725
  'skip-link': ['Motor', 'Learning', 'Visual'],
726
726
  tabindex: ['Motor'],
727
727
  'meta-viewport': ['Visual'],
728
+ 'summary-name': ['Visual'],
728
729
  };
729
730
  export default {
730
731
  cliZipFileName: 'oobee-scan-results.zip',