@govtechsg/oobee 0.10.42 → 0.10.45

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/src/utils.ts CHANGED
@@ -2,6 +2,7 @@ import { execSync, spawnSync } from 'child_process';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
  import fs from 'fs-extra';
5
+ import axe, { Rule } from 'axe-core';
5
6
  import constants, {
6
7
  BrowserTypes,
7
8
  destinationPath,
@@ -9,25 +10,23 @@ import constants, {
9
10
  } from './constants/constants.js';
10
11
  import { consoleLogger, silentLogger } from './logs.js';
11
12
  import { getAxeConfiguration } from './crawlers/custom/getAxeConfiguration.js';
12
- import axe from 'axe-core';
13
- import { Rule, RuleMetadata } from 'axe-core';
14
13
 
15
14
  export const getVersion = () => {
16
- const loadJSON = filePath =>
15
+ const loadJSON = (filePath: string): { version: string } =>
17
16
  JSON.parse(fs.readFileSync(new URL(filePath, import.meta.url)).toString());
18
17
  const versionNum = loadJSON('../package.json').version;
19
18
 
20
19
  return versionNum;
21
20
  };
22
21
 
23
- export const getHost = url => new URL(url).host;
22
+ export const getHost = (url: string): string => new URL(url).host;
24
23
 
25
24
  export const getCurrentDate = () => {
26
25
  const date = new Date();
27
26
  return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
28
27
  };
29
28
 
30
- export const isWhitelistedContentType = contentType => {
29
+ export const isWhitelistedContentType = (contentType: string): boolean => {
31
30
  const whitelist = ['text/html'];
32
31
  return whitelist.filter(type => contentType.trim().startsWith(type)).length === 1;
33
32
  };
@@ -45,7 +44,7 @@ export const getStoragePath = (randomToken: string): string => {
45
44
  return `${constants.exportDirectory}/${randomToken}`;
46
45
  };
47
46
 
48
- export const createDetailsAndLogs = async randomToken => {
47
+ export const createDetailsAndLogs = async (randomToken: string): Promise<void> => {
49
48
  const storagePath = getStoragePath(randomToken);
50
49
  const logPath = `logs/${randomToken}`;
51
50
  try {
@@ -59,20 +58,24 @@ export const createDetailsAndLogs = async randomToken => {
59
58
  await fs.copy('errors.txt', `${logPath}/${randomToken}.txt`);
60
59
  } catch (error) {
61
60
  if (error.code === 'EBUSY') {
62
- console.log(
61
+ consoleLogger.error(
63
62
  `Unable to copy the file from 'errors.txt' to '${logPath}/${randomToken}.txt' because it is currently in use.`,
64
63
  );
65
- console.log(
64
+ consoleLogger.error(
66
65
  'Please close any applications that might be using this file and try again.',
67
66
  );
68
67
  } else {
69
- console.log(`An unexpected error occurred while copying the file: ${error.message}`);
68
+ consoleLogger.error(
69
+ `An unexpected error occurred while copying the file: ${error.message}`,
70
+ );
70
71
  }
71
72
  }
72
73
  }
73
74
  });
74
75
  } catch (error) {
75
- console.log(`An error occurred while setting up storage or log directories: ${error.message}`);
76
+ consoleLogger.error(
77
+ `An error occurred while setting up storage or log directories: ${error.message}`,
78
+ );
76
79
  }
77
80
  };
78
81
 
@@ -99,7 +102,7 @@ export const getUserDataTxt = () => {
99
102
  return null;
100
103
  };
101
104
 
102
- export const writeToUserDataTxt = async (key, value) => {
105
+ export const writeToUserDataTxt = async (key: string, value: string): Promise<void> => {
103
106
  const textFilePath = getUserDataFilePath();
104
107
 
105
108
  // Create file if it doesn't exist
@@ -116,25 +119,27 @@ export const writeToUserDataTxt = async (key, value) => {
116
119
  }
117
120
  };
118
121
 
119
- export const createAndUpdateResultsFolders = async randomToken => {
122
+ export const createAndUpdateResultsFolders = async (randomToken: string): Promise<void> => {
120
123
  const storagePath = getStoragePath(randomToken);
121
124
  await fs.ensureDir(`${storagePath}`);
122
125
 
123
126
  const intermediatePdfResultsPath = `${randomToken}/${constants.pdfScanResultFileName}`;
124
127
 
125
- const transferResults = async (intermPath, resultFile) => {
128
+ const transferResults = async (intermPath: string, resultFile: string): Promise<void> => {
126
129
  try {
127
130
  if (fs.existsSync(intermPath)) {
128
131
  await fs.copy(intermPath, `${storagePath}/${resultFile}`);
129
132
  }
130
133
  } catch (error) {
131
134
  if (error.code === 'EBUSY') {
132
- console.log(
135
+ consoleLogger.error(
133
136
  `Unable to copy the file from ${intermPath} to ${storagePath}/${resultFile} because it is currently in use.`,
134
137
  );
135
- console.log('Please close any applications that might be using this file and try again.');
138
+ consoleLogger.error(
139
+ 'Please close any applications that might be using this file and try again.',
140
+ );
136
141
  } else {
137
- console.log(
142
+ consoleLogger.error(
138
143
  `An unexpected error occurred while copying the file from ${intermPath} to ${storagePath}/${resultFile}: ${error.message}`,
139
144
  );
140
145
  }
@@ -144,20 +149,20 @@ export const createAndUpdateResultsFolders = async randomToken => {
144
149
  await Promise.all([transferResults(intermediatePdfResultsPath, constants.pdfScanResultFileName)]);
145
150
  };
146
151
 
147
- export const createScreenshotsFolder = randomToken => {
152
+ export const createScreenshotsFolder = (randomToken: string): void => {
148
153
  const storagePath = getStoragePath(randomToken);
149
154
  const intermediateScreenshotsPath = getIntermediateScreenshotsPath(randomToken);
150
155
  if (fs.existsSync(intermediateScreenshotsPath)) {
151
156
  fs.readdir(intermediateScreenshotsPath, (err, files) => {
152
157
  if (err) {
153
- console.log(`Screenshots were not moved successfully: ${err.message}`);
158
+ consoleLogger.error(`Screenshots were not moved successfully: ${err.message}`);
154
159
  }
155
160
 
156
161
  if (!fs.existsSync(destinationPath(storagePath))) {
157
162
  try {
158
163
  fs.mkdirSync(destinationPath(storagePath), { recursive: true });
159
164
  } catch (error) {
160
- console.error('Screenshots folder was not created successfully:', error);
165
+ consoleLogger.error('Screenshots folder was not created successfully:', error);
161
166
  }
162
167
  }
163
168
 
@@ -170,20 +175,20 @@ export const createScreenshotsFolder = randomToken => {
170
175
 
171
176
  fs.rmdir(intermediateScreenshotsPath, rmdirErr => {
172
177
  if (rmdirErr) {
173
- console.log(rmdirErr);
178
+ consoleLogger.error(rmdirErr);
174
179
  }
175
180
  });
176
181
  });
177
182
  }
178
183
  };
179
184
 
180
- export const cleanUp = async pathToDelete => {
185
+ export const cleanUp = (pathToDelete: string): void => {
181
186
  fs.removeSync(pathToDelete);
182
187
  };
183
188
 
184
189
  export const getWcagPassPercentage = (
185
190
  wcagViolations: string[],
186
- showEnableWcagAaa: boolean
191
+ showEnableWcagAaa: boolean,
187
192
  ): {
188
193
  passPercentageAA: string;
189
194
  totalWcagChecksAA: number;
@@ -192,20 +197,28 @@ export const getWcagPassPercentage = (
192
197
  totalWcagChecksAAandAAA: number;
193
198
  totalWcagViolationsAAandAAA: number;
194
199
  } => {
195
-
196
200
  // These AAA rules should not be counted as WCAG Pass Percentage only contains A and AA
197
201
  const wcagAAALinks = ['WCAG 1.4.6', 'WCAG 2.2.4', 'WCAG 2.4.9', 'WCAG 3.1.5', 'WCAG 3.2.5'];
198
202
  const wcagAAA = ['wcag146', 'wcag224', 'wcag249', 'wcag315', 'wcag325'];
199
-
203
+
200
204
  const wcagLinksAAandAAA = constants.wcagLinks;
201
-
205
+
202
206
  const wcagViolationsAAandAAA = showEnableWcagAaa ? wcagViolations.length : null;
203
207
  const totalChecksAAandAAA = showEnableWcagAaa ? Object.keys(wcagLinksAAandAAA).length : null;
204
- const passedChecksAAandAAA = showEnableWcagAaa ? totalChecksAAandAAA - wcagViolationsAAandAAA : null;
205
- const passPercentageAAandAAA = showEnableWcagAaa ? (totalChecksAAandAAA === 0 ? 0 : (passedChecksAAandAAA / totalChecksAAandAAA) * 100) : null;
208
+ const passedChecksAAandAAA = showEnableWcagAaa
209
+ ? totalChecksAAandAAA - wcagViolationsAAandAAA
210
+ : null;
211
+ // eslint-disable-next-line no-nested-ternary
212
+ const passPercentageAAandAAA = showEnableWcagAaa
213
+ ? totalChecksAAandAAA === 0
214
+ ? 0
215
+ : (passedChecksAAandAAA / totalChecksAAandAAA) * 100
216
+ : null;
206
217
 
207
218
  const wcagViolationsAA = wcagViolations.filter(violation => !wcagAAA.includes(violation)).length;
208
- const totalChecksAA = Object.keys(wcagLinksAAandAAA).filter(key => !wcagAAALinks.includes(key)).length;
219
+ const totalChecksAA = Object.keys(wcagLinksAAandAAA).filter(
220
+ key => !wcagAAALinks.includes(key),
221
+ ).length;
209
222
  const passedChecksAA = totalChecksAA - wcagViolationsAA;
210
223
  const passPercentageAA = totalChecksAA === 0 ? 0 : (passedChecksAA / totalChecksAA) * 100;
211
224
 
@@ -219,13 +232,15 @@ export const getWcagPassPercentage = (
219
232
  };
220
233
  };
221
234
 
222
- export interface ScanPagesDetail {
223
- oobeeAppVersion?: string;
224
- pagesAffected: PageDetail[];
225
- pagesNotAffected: PageDetail[];
226
- scannedPagesCount: number;
227
- pagesNotScanned: PageDetail[];
228
- pagesNotScannedCount: number;
235
+ export type IssueCategory = 'mustFix' | 'goodToFix' | 'needsReview' | 'passed';
236
+
237
+ export interface IssueDetail {
238
+ ruleId: string;
239
+ wcagConformance: string[];
240
+ occurrencesMustFix?: number;
241
+ occurrencesGoodToFix?: number;
242
+ occurrencesNeedsReview?: number;
243
+ occurrencesPassed: number;
229
244
  }
230
245
 
231
246
  export interface PageDetail {
@@ -245,43 +260,43 @@ export interface PageDetail {
245
260
  typesOfIssues: IssueDetail[];
246
261
  }
247
262
 
248
- export type IssueCategory = "mustFix" | "goodToFix" | "needsReview" | "passed";
249
-
250
- export interface IssueDetail {
251
- ruleId: string;
252
- wcagConformance: string[];
253
- occurrencesMustFix?: number;
254
- occurrencesGoodToFix?: number;
255
- occurrencesNeedsReview?: number;
256
- occurrencesPassed: number;
263
+ export interface ScanPagesDetail {
264
+ oobeeAppVersion?: string;
265
+ pagesAffected: PageDetail[];
266
+ pagesNotAffected: PageDetail[];
267
+ scannedPagesCount: number;
268
+ pagesNotScanned: PageDetail[];
269
+ pagesNotScannedCount: number;
257
270
  }
258
271
 
259
272
  export const getProgressPercentage = (
260
273
  scanPagesDetail: ScanPagesDetail,
261
- showEnableWcagAaa: boolean
274
+ showEnableWcagAaa: boolean,
262
275
  ): {
263
276
  averageProgressPercentageAA: string;
264
277
  averageProgressPercentageAAandAAA: string;
265
278
  } => {
266
279
  const pages = scanPagesDetail.pagesAffected || [];
267
-
268
- const progressPercentagesAA = pages.map((page: any) => {
280
+
281
+ const progressPercentagesAA = pages.map((page: PageDetail) => {
269
282
  const violations: string[] = page.conformance;
270
283
  return getWcagPassPercentage(violations, showEnableWcagAaa).passPercentageAA;
271
284
  });
272
-
273
- const progressPercentagesAAandAAA = pages.map((page: any) => {
285
+
286
+ const progressPercentagesAAandAAA = pages.map((page: PageDetail) => {
274
287
  const violations: string[] = page.conformance;
275
288
  return getWcagPassPercentage(violations, showEnableWcagAaa).passPercentageAAandAAA;
276
289
  });
277
-
290
+
278
291
  const totalAA = progressPercentagesAA.reduce((sum, p) => sum + parseFloat(p), 0);
279
292
  const avgAA = progressPercentagesAA.length ? totalAA / progressPercentagesAA.length : 0;
280
293
 
281
294
  const totalAAandAAA = progressPercentagesAAandAAA.reduce((sum, p) => sum + parseFloat(p), 0);
282
- const avgAAandAAA = progressPercentagesAAandAAA.length ? totalAAandAAA / progressPercentagesAAandAAA.length : 0;
283
-
284
- return {
295
+ const avgAAandAAA = progressPercentagesAAandAAA.length
296
+ ? totalAAandAAA / progressPercentagesAAandAAA.length
297
+ : 0;
298
+
299
+ return {
285
300
  averageProgressPercentageAA: avgAA.toFixed(2),
286
301
  averageProgressPercentageAAandAAA: avgAAandAAA.toFixed(2),
287
302
  };
@@ -289,7 +304,7 @@ export const getProgressPercentage = (
289
304
 
290
305
  export const getTotalRulesCount = async (
291
306
  enableWcagAaa: boolean,
292
- disableOobee: boolean
307
+ disableOobee: boolean,
293
308
  ): Promise<{
294
309
  totalRulesMustFix: number;
295
310
  totalRulesGoodToFix: number;
@@ -302,11 +317,11 @@ export const getTotalRulesCount = async (
302
317
  });
303
318
 
304
319
  // Get default rules from axe-core
305
- const defaultRules = await axe.getRules();
320
+ const defaultRules = axe.getRules();
306
321
 
307
322
  // Merge custom rules with default rules, converting RuleMetadata to Rule
308
- const mergedRules: Rule[] = defaultRules.map((defaultRule) => {
309
- const customRule = axeConfig.rules.find((r) => r.id === defaultRule.ruleId);
323
+ const mergedRules: Rule[] = defaultRules.map(defaultRule => {
324
+ const customRule = axeConfig.rules.find(r => r.id === defaultRule.ruleId);
310
325
  if (customRule) {
311
326
  // Merge properties from customRule into defaultRule (RuleMetadata) to create a Rule
312
327
  return {
@@ -317,15 +332,14 @@ export const getTotalRulesCount = async (
317
332
  tags: defaultRule.tags,
318
333
  metadata: customRule.metadata, // Use custom metadata if it exists
319
334
  };
320
- } else {
321
- // Convert defaultRule (RuleMetadata) to Rule
322
- return {
323
- id: defaultRule.ruleId,
324
- enabled: true, // Default to true if not overridden
325
- tags: defaultRule.tags,
326
- // No metadata here, since defaultRule.metadata might not exist
327
- };
328
335
  }
336
+ // Convert defaultRule (RuleMetadata) to Rule
337
+ return {
338
+ id: defaultRule.ruleId,
339
+ enabled: true, // Default to true if not overridden
340
+ tags: defaultRule.tags,
341
+ // No metadata here, since defaultRule.metadata might not exist
342
+ };
329
343
  });
330
344
 
331
345
  // Add any custom rules that don't override the default rules
@@ -346,9 +360,7 @@ export const getTotalRulesCount = async (
346
360
  });
347
361
 
348
362
  // Apply the merged configuration to axe-core
349
- await axe.configure({ ...axeConfig, rules: mergedRules });
350
-
351
- const rules = await axe.getRules();
363
+ axe.configure({ ...axeConfig, rules: mergedRules });
352
364
 
353
365
  // ... (rest of your logic)
354
366
  let totalRulesMustFix = 0;
@@ -357,7 +369,7 @@ export const getTotalRulesCount = async (
357
369
  const wcagRegex = /^wcag\d+a+$/;
358
370
 
359
371
  // Use mergedRules instead of rules to check enabled property
360
- mergedRules.forEach((rule) => {
372
+ mergedRules.forEach(rule => {
361
373
  if (!rule.enabled) {
362
374
  return;
363
375
  }
@@ -371,10 +383,14 @@ export const getTotalRulesCount = async (
371
383
  return;
372
384
  }
373
385
 
374
- let conformance = tags.filter(tag => tag.startsWith('wcag') || tag === 'best-practice');
386
+ const conformance = tags.filter(tag => tag.startsWith('wcag') || tag === 'best-practice');
375
387
 
376
388
  // Ensure conformance level is sorted correctly
377
- if (conformance.length > 0 && conformance[0] !== 'best-practice' && !wcagRegex.test(conformance[0])) {
389
+ if (
390
+ conformance.length > 0 &&
391
+ conformance[0] !== 'best-practice' &&
392
+ !wcagRegex.test(conformance[0])
393
+ ) {
378
394
  conformance.sort((a, b) => {
379
395
  if (wcagRegex.test(a) && !wcagRegex.test(b)) {
380
396
  return -1;
@@ -389,11 +405,11 @@ export const getTotalRulesCount = async (
389
405
  if (conformance.includes('best-practice')) {
390
406
  // console.log(`${totalRulesMustFix} Good To Fix: ${rule.id}`);
391
407
 
392
- totalRulesGoodToFix++; // Categorized as "Good to Fix"
408
+ totalRulesGoodToFix += 1; // Categorized as "Good to Fix"
393
409
  } else {
394
410
  // console.log(`${totalRulesMustFix} Must Fix: ${rule.id}`);
395
411
 
396
- totalRulesMustFix++; // Otherwise, it's "Must Fix"
412
+ totalRulesMustFix += 1; // Otherwise, it's "Must Fix"
397
413
  }
398
414
  });
399
415
 
@@ -404,10 +420,136 @@ export const getTotalRulesCount = async (
404
420
  };
405
421
  };
406
422
 
423
+ /**
424
+ * Dynamically generates a map of WCAG criteria IDs to their details (name and level)
425
+ * Reuses the rule processing logic from getTotalRulesCount
426
+ */
427
+ export const getWcagCriteriaMap = async (
428
+ enableWcagAaa: boolean = true,
429
+ disableOobee: boolean = false
430
+ ): Promise<Record<string, { name: string; level: string }>> => {
431
+ // Reuse the configuration setup from getTotalRulesCount
432
+ const axeConfig = getAxeConfiguration({
433
+ enableWcagAaa,
434
+ gradingReadabilityFlag: '',
435
+ disableOobee,
436
+ });
437
+
438
+ // Get default rules from axe-core
439
+ const defaultRules = axe.getRules();
440
+
441
+ // Merge custom rules with default rules
442
+ const mergedRules: Rule[] = defaultRules.map(defaultRule => {
443
+ const customRule = axeConfig.rules.find(r => r.id === defaultRule.ruleId);
444
+ if (customRule) {
445
+ return {
446
+ id: defaultRule.ruleId,
447
+ enabled: customRule.enabled,
448
+ selector: customRule.selector,
449
+ any: customRule.any,
450
+ tags: defaultRule.tags,
451
+ metadata: customRule.metadata,
452
+ };
453
+ }
454
+ return {
455
+ id: defaultRule.ruleId,
456
+ enabled: true,
457
+ tags: defaultRule.tags,
458
+ };
459
+ });
460
+
461
+ // Add custom rules that don't override default rules
462
+ axeConfig.rules.forEach(customRule => {
463
+ if (!mergedRules.some(rule => rule.id === customRule.id)) {
464
+ mergedRules.push({
465
+ id: customRule.id,
466
+ enabled: customRule.enabled,
467
+ selector: customRule.selector,
468
+ any: customRule.any,
469
+ tags: customRule.tags,
470
+ metadata: customRule.metadata,
471
+ });
472
+ }
473
+ });
474
+
475
+ // Apply configuration
476
+ axe.configure({ ...axeConfig, rules: mergedRules });
477
+
478
+ // Build WCAG criteria map
479
+ const wcagCriteriaMap: Record<string, { name: string; level: string }> = {};
480
+
481
+ // Process rules to extract WCAG information
482
+ mergedRules.forEach(rule => {
483
+ if (!rule.enabled) return;
484
+ if (rule.id === 'frame-tested') return;
485
+
486
+ const tags = rule.tags || [];
487
+ if (tags.includes('experimental') || tags.includes('deprecated')) return;
488
+
489
+ // Look for WCAG criteria tags (format: wcag111, wcag143, etc.)
490
+ tags.forEach(tag => {
491
+ const wcagMatch = tag.match(/^wcag(\d+)$/);
492
+ if (wcagMatch) {
493
+ const wcagId = tag;
494
+
495
+ // Default values
496
+ let level = 'a';
497
+ let name = '';
498
+
499
+ // Try to extract better info from metadata if available
500
+ const metadata = rule.metadata as any;
501
+ if (metadata && metadata.wcag) {
502
+ const wcagInfo = metadata.wcag as any;
503
+
504
+ // Find matching criterion in metadata
505
+ for (const key in wcagInfo) {
506
+ const criterion = wcagInfo[key];
507
+ if (criterion &&
508
+ criterion.num &&
509
+ `wcag${criterion.num.replace(/\./g, '')}` === wcagId) {
510
+
511
+ // Extract level
512
+ if (criterion.level) {
513
+ level = String(criterion.level).toLowerCase();
514
+ }
515
+
516
+ // Extract name
517
+ if (criterion.handle) {
518
+ name = String(criterion.handle);
519
+ } else if (criterion.id) {
520
+ name = String(criterion.id);
521
+ } else if (criterion.num) {
522
+ name = `wcag-${String(criterion.num).replace(/\./g, '-')}`;
523
+ }
524
+
525
+ break;
526
+ }
527
+ }
528
+ }
529
+
530
+ // Generate fallback name if none found
531
+ if (!name) {
532
+ const numStr = wcagMatch[1];
533
+ const formattedNum = numStr.replace(/(\d)(\d)(\d+)?/, '$1.$2.$3');
534
+ name = `wcag-${formattedNum.replace(/\./g, '-')}`;
535
+ }
536
+
537
+ // Store in map
538
+ wcagCriteriaMap[wcagId] = {
539
+ name: name.toLowerCase().replace(/_/g, '-'),
540
+ level
541
+ };
542
+ }
543
+ });
544
+ });
545
+
546
+ return wcagCriteriaMap;
547
+ };
548
+
407
549
  export const getIssuesPercentage = async (
408
550
  scanPagesDetail: ScanPagesDetail,
409
551
  enableWcagAaa: boolean,
410
- disableOobee: boolean
552
+ disableOobee: boolean,
411
553
  ): Promise<{
412
554
  avgTypesOfIssuesPercentageOfTotalRulesAtMustFix: string;
413
555
  avgTypesOfIssuesPercentageOfTotalRulesAtGoodToFix: string;
@@ -426,8 +568,8 @@ export const getIssuesPercentage = async (
426
568
 
427
569
  const pagesAffectedPerRule: Record<string, number> = {};
428
570
 
429
- pages.forEach((page) => {
430
- page.typesOfIssues.forEach((issue) => {
571
+ pages.forEach(page => {
572
+ page.typesOfIssues.forEach(issue => {
431
573
  if ((issue.occurrencesMustFix || issue.occurrencesGoodToFix) > 0) {
432
574
  pagesAffectedPerRule[issue.ruleId] = (pagesAffectedPerRule[issue.ruleId] || 0) + 1;
433
575
  }
@@ -435,55 +577,54 @@ export const getIssuesPercentage = async (
435
577
  });
436
578
 
437
579
  const pagesPercentageAffectedPerRule: Record<string, string> = {};
438
- for (const [ruleId, count] of Object.entries(pagesAffectedPerRule)) {
439
- pagesPercentageAffectedPerRule[ruleId] = totalPages > 0 ? ((count / totalPages) * 100).toFixed(2) : "0.00";
440
- }
580
+ Object.entries(pagesAffectedPerRule).forEach(([ruleId, count]) => {
581
+ pagesPercentageAffectedPerRule[ruleId] =
582
+ totalPages > 0 ? ((count / totalPages) * 100).toFixed(2) : '0.00';
583
+ });
441
584
 
442
- const typesOfIssuesCountAtMustFix = pages.map((page) =>
443
- page.typesOfIssues.filter((issue) => (issue.occurrencesMustFix || 0) > 0).length
585
+ const typesOfIssuesCountAtMustFix = pages.map(
586
+ page => page.typesOfIssues.filter(issue => (issue.occurrencesMustFix || 0) > 0).length,
444
587
  );
445
588
 
446
- const typesOfIssuesCountAtGoodToFix = pages.map((page) =>
447
- page.typesOfIssues.filter((issue) => (issue.occurrencesGoodToFix || 0) > 0).length
589
+ const typesOfIssuesCountAtGoodToFix = pages.map(
590
+ page => page.typesOfIssues.filter(issue => (issue.occurrencesGoodToFix || 0) > 0).length,
448
591
  );
449
592
 
450
593
  const typesOfIssuesCountSumMustFixAndGoodToFix = pages.map(
451
594
  (_, index) =>
452
- (typesOfIssuesCountAtMustFix[index] || 0) +
453
- (typesOfIssuesCountAtGoodToFix[index] || 0)
595
+ (typesOfIssuesCountAtMustFix[index] || 0) + (typesOfIssuesCountAtGoodToFix[index] || 0),
454
596
  );
455
597
 
456
- const { totalRulesMustFix, totalRulesGoodToFix, totalRulesMustFixAndGoodToFix } = await getTotalRulesCount(
457
- enableWcagAaa,
458
- disableOobee
459
- );
598
+ const { totalRulesMustFix, totalRulesGoodToFix, totalRulesMustFixAndGoodToFix } =
599
+ await getTotalRulesCount(enableWcagAaa, disableOobee);
460
600
 
461
- const avgMustFixPerPage = totalPages > 0
462
- ? typesOfIssuesCountAtMustFix.reduce((sum, count) => sum + count, 0) / totalPages
463
- : 0;
601
+ const avgMustFixPerPage =
602
+ totalPages > 0
603
+ ? typesOfIssuesCountAtMustFix.reduce((sum, count) => sum + count, 0) / totalPages
604
+ : 0;
464
605
 
465
- const avgGoodToFixPerPage = totalPages > 0
466
- ? typesOfIssuesCountAtGoodToFix.reduce((sum, count) => sum + count, 0) / totalPages
467
- : 0;
606
+ const avgGoodToFixPerPage =
607
+ totalPages > 0
608
+ ? typesOfIssuesCountAtGoodToFix.reduce((sum, count) => sum + count, 0) / totalPages
609
+ : 0;
468
610
 
469
- const avgMustFixAndGoodToFixPerPage = totalPages > 0
470
- ? typesOfIssuesCountSumMustFixAndGoodToFix.reduce((sum, count) => sum + count, 0) / totalPages
471
- : 0;
611
+ const avgMustFixAndGoodToFixPerPage =
612
+ totalPages > 0
613
+ ? typesOfIssuesCountSumMustFixAndGoodToFix.reduce((sum, count) => sum + count, 0) / totalPages
614
+ : 0;
472
615
 
473
616
  const avgTypesOfIssuesPercentageOfTotalRulesAtMustFix =
474
- totalRulesMustFix > 0
475
- ? ((avgMustFixPerPage / totalRulesMustFix) * 100).toFixed(2)
476
- : "0.00";
617
+ totalRulesMustFix > 0 ? ((avgMustFixPerPage / totalRulesMustFix) * 100).toFixed(2) : '0.00';
477
618
 
478
619
  const avgTypesOfIssuesPercentageOfTotalRulesAtGoodToFix =
479
620
  totalRulesGoodToFix > 0
480
621
  ? ((avgGoodToFixPerPage / totalRulesGoodToFix) * 100).toFixed(2)
481
- : "0.00";
622
+ : '0.00';
482
623
 
483
624
  const avgTypesOfIssuesPercentageOfTotalRulesAtMustFixAndGoodToFix =
484
625
  totalRulesMustFixAndGoodToFix > 0
485
626
  ? ((avgMustFixAndGoodToFixPerPage / totalRulesMustFixAndGoodToFix) * 100).toFixed(2)
486
- : "0.00";
627
+ : '0.00';
487
628
 
488
629
  const avgTypesOfIssuesCountAtMustFix = avgMustFixPerPage.toFixed(2);
489
630
  const avgTypesOfIssuesCountAtGoodToFix = avgGoodToFixPerPage.toFixed(2);
@@ -504,7 +645,7 @@ export const getIssuesPercentage = async (
504
645
  };
505
646
  };
506
647
 
507
- export const getFormattedTime = inputDate => {
648
+ export const getFormattedTime = (inputDate: Date): string => {
508
649
  if (inputDate) {
509
650
  return inputDate.toLocaleTimeString('en-GB', {
510
651
  year: 'numeric',
@@ -526,7 +667,7 @@ export const getFormattedTime = inputDate => {
526
667
  });
527
668
  };
528
669
 
529
- export const formatDateTimeForMassScanner = date => {
670
+ export const formatDateTimeForMassScanner = (date: Date): string => {
530
671
  // Format date and time parts separately
531
672
  const year = date.getFullYear().toString().slice(-2); // Get the last two digits of the year
532
673
  const month = `0${date.getMonth() + 1}`.slice(-2); // Month is zero-indexed
@@ -547,14 +688,13 @@ export const setHeadlessMode = (browser: string, isHeadless: boolean): void => {
547
688
  } else {
548
689
  process.env.CRAWLEE_HEADLESS = '0';
549
690
  }
550
-
551
691
  };
552
692
 
553
- export const setThresholdLimits = setWarnLevel => {
693
+ export const setThresholdLimits = (setWarnLevel: string): void => {
554
694
  process.env.WARN_LEVEL = setWarnLevel;
555
695
  };
556
696
 
557
- export const zipResults = (zipName, resultsPath) => {
697
+ export const zipResults = (zipName: string, resultsPath: string): void => {
558
698
  // Check prior zip file exist and remove
559
699
  if (fs.existsSync(zipName)) {
560
700
  fs.unlinkSync(zipName);
@@ -585,9 +725,9 @@ export const zipResults = (zipName, resultsPath) => {
585
725
 
586
726
  // areLinksEqual compares 2 string URLs and ignores comparison of 'www.' and url protocol
587
727
  // i.e. 'http://google.com' and 'https://www.google.com' returns true
588
- export const areLinksEqual = (link1, link2) => {
728
+ export const areLinksEqual = (link1: string, link2: string): boolean => {
589
729
  try {
590
- const format = link => {
730
+ const format = (link: string): URL => {
591
731
  return new URL(link.replace(/www\./, ''));
592
732
  };
593
733
  const l1 = format(link1);
@@ -612,7 +752,7 @@ export const randomThreeDigitNumberString = () => {
612
752
  return String(threeDigitNumber);
613
753
  };
614
754
 
615
- export const isFollowStrategy = (link1, link2, rule) => {
755
+ export const isFollowStrategy = (link1: string, link2: string, rule: string): boolean => {
616
756
  const parsedLink1 = new URL(link1);
617
757
  const parsedLink2 = new URL(link2);
618
758
  if (rule === 'same-domain') {
@@ -623,17 +763,17 @@ export const isFollowStrategy = (link1, link2, rule) => {
623
763
  return parsedLink1.hostname === parsedLink2.hostname;
624
764
  };
625
765
 
626
- /* eslint-disable no-await-in-loop */
627
- export const retryFunction = async (func, maxAttempt) => {
766
+ export const retryFunction = async <T>(func: () => Promise<T>, maxAttempt: number): Promise<T> => {
628
767
  let attemptCount = 0;
629
768
  while (attemptCount < maxAttempt) {
630
769
  attemptCount += 1;
631
770
  try {
771
+ // eslint-disable-next-line no-await-in-loop
632
772
  const result = await func();
633
773
  return result;
634
774
  } catch (error) {
635
775
  silentLogger.error(`(Attempt count: ${attemptCount} of ${maxAttempt}) ${error}`);
636
776
  }
637
777
  }
778
+ throw new Error('Maximum number of attempts reached');
638
779
  };
639
- /* eslint-enable no-await-in-loop */