@govtechsg/oobee 0.10.42 → 0.10.43
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/REPORTS.md +71 -2
- package/package.json +3 -2
- package/src/cli.ts +2 -11
- package/src/constants/common.ts +68 -52
- package/src/constants/constants.ts +81 -1
- package/src/constants/oobeeAi.ts +6 -6
- package/src/constants/questions.ts +3 -2
- package/src/crawlers/commonCrawlerFunc.ts +16 -15
- package/src/crawlers/crawlDomain.ts +82 -84
- package/src/crawlers/crawlIntelligentSitemap.ts +21 -19
- package/src/crawlers/crawlSitemap.ts +120 -109
- package/src/crawlers/custom/findElementByCssSelector.ts +1 -1
- package/src/crawlers/custom/flagUnlabelledClickableElements.ts +8 -8
- package/src/crawlers/custom/xPathToCss.ts +10 -10
- package/src/crawlers/runCustom.ts +1 -1
- package/src/index.ts +3 -4
- package/src/logs.ts +1 -1
- package/src/mergeAxeResults.ts +3 -5
- package/src/npmIndex.ts +12 -8
- package/src/screenshotFunc/htmlScreenshotFunc.ts +7 -19
- package/src/types/text-readability.d.ts +3 -0
- package/src/types/types.ts +1 -1
- package/src/utils.ts +128 -114
- package/src/xPathToCss.ts +0 -186
- package/src/xPathToCssCypress.ts +0 -178
@@ -7,7 +7,7 @@ import { Result } from 'axe-core';
|
|
7
7
|
import { Page } from 'playwright';
|
8
8
|
import { NodeResultWithScreenshot, ResultWithScreenshot } from '../crawlers/commonCrawlerFunc.js';
|
9
9
|
|
10
|
-
const screenshotMap = {}; // Map of screenshot hashkey to its buffer value and screenshot path
|
10
|
+
const screenshotMap: Record<string, string> = {}; // Map of screenshot hashkey to its buffer value and screenshot path
|
11
11
|
|
12
12
|
export const takeScreenshotForHTMLElements = async (
|
13
13
|
violations: Result[],
|
@@ -75,30 +75,18 @@ export const takeScreenshotForHTMLElements = async (
|
|
75
75
|
return newViolations;
|
76
76
|
};
|
77
77
|
|
78
|
-
const generateBufferHash = (buffer: Buffer) => {
|
78
|
+
const generateBufferHash = (buffer: Buffer): string => {
|
79
79
|
const hash = createHash('sha256');
|
80
80
|
hash.update(buffer);
|
81
81
|
return hash.digest('hex');
|
82
82
|
};
|
83
83
|
|
84
|
-
const
|
85
|
-
|
86
|
-
|
87
|
-
};
|
88
|
-
|
89
|
-
const getIdenticalScreenshotKey = (buffer: Buffer) => {
|
90
|
-
for (const hashKey in screenshotMap) {
|
91
|
-
const isIdentical = isSameBufferHash(buffer, hashKey);
|
92
|
-
if (isIdentical) return hashKey;
|
93
|
-
}
|
94
|
-
return undefined;
|
95
|
-
};
|
96
|
-
|
97
|
-
const getScreenshotPath = (buffer: Buffer, randomToken: string) => {
|
98
|
-
let hashKey = getIdenticalScreenshotKey(buffer);
|
84
|
+
const getScreenshotPath = (buffer: Buffer, randomToken: string): string => {
|
85
|
+
let hashKey = generateBufferHash(buffer);
|
86
|
+
const existingPath = screenshotMap[hashKey];
|
99
87
|
// If exists identical entry in screenshot map, get its filepath
|
100
|
-
if (
|
101
|
-
return
|
88
|
+
if (existingPath) {
|
89
|
+
return existingPath;
|
102
90
|
}
|
103
91
|
// Create new entry in screenshot map
|
104
92
|
hashKey = generateBufferHash(buffer);
|
package/src/types/types.ts
CHANGED
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
|
-
|
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
|
-
|
64
|
+
consoleLogger.error(
|
66
65
|
'Please close any applications that might be using this file and try again.',
|
67
66
|
);
|
68
67
|
} else {
|
69
|
-
|
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
|
-
|
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
|
-
|
135
|
+
consoleLogger.error(
|
133
136
|
`Unable to copy the file from ${intermPath} to ${storagePath}/${resultFile} because it is currently in use.`,
|
134
137
|
);
|
135
|
-
|
138
|
+
consoleLogger.error(
|
139
|
+
'Please close any applications that might be using this file and try again.',
|
140
|
+
);
|
136
141
|
} else {
|
137
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
178
|
+
consoleLogger.error(rmdirErr);
|
174
179
|
}
|
175
180
|
});
|
176
181
|
});
|
177
182
|
}
|
178
183
|
};
|
179
184
|
|
180
|
-
export const cleanUp =
|
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
|
205
|
-
|
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(
|
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
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
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:
|
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:
|
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
|
283
|
-
|
284
|
-
|
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 =
|
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(
|
309
|
-
const customRule = axeConfig.rules.find(
|
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
|
-
|
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(
|
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
|
-
|
386
|
+
const conformance = tags.filter(tag => tag.startsWith('wcag') || tag === 'best-practice');
|
375
387
|
|
376
388
|
// Ensure conformance level is sorted correctly
|
377
|
-
if (
|
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
|
408
|
+
totalRulesGoodToFix += 1; // Categorized as "Good to Fix"
|
393
409
|
} else {
|
394
410
|
// console.log(`${totalRulesMustFix} Must Fix: ${rule.id}`);
|
395
411
|
|
396
|
-
totalRulesMustFix
|
412
|
+
totalRulesMustFix += 1; // Otherwise, it's "Must Fix"
|
397
413
|
}
|
398
414
|
});
|
399
415
|
|
@@ -407,7 +423,7 @@ export const getTotalRulesCount = async (
|
|
407
423
|
export const getIssuesPercentage = async (
|
408
424
|
scanPagesDetail: ScanPagesDetail,
|
409
425
|
enableWcagAaa: boolean,
|
410
|
-
disableOobee: boolean
|
426
|
+
disableOobee: boolean,
|
411
427
|
): Promise<{
|
412
428
|
avgTypesOfIssuesPercentageOfTotalRulesAtMustFix: string;
|
413
429
|
avgTypesOfIssuesPercentageOfTotalRulesAtGoodToFix: string;
|
@@ -426,8 +442,8 @@ export const getIssuesPercentage = async (
|
|
426
442
|
|
427
443
|
const pagesAffectedPerRule: Record<string, number> = {};
|
428
444
|
|
429
|
-
pages.forEach(
|
430
|
-
page.typesOfIssues.forEach(
|
445
|
+
pages.forEach(page => {
|
446
|
+
page.typesOfIssues.forEach(issue => {
|
431
447
|
if ((issue.occurrencesMustFix || issue.occurrencesGoodToFix) > 0) {
|
432
448
|
pagesAffectedPerRule[issue.ruleId] = (pagesAffectedPerRule[issue.ruleId] || 0) + 1;
|
433
449
|
}
|
@@ -435,55 +451,54 @@ export const getIssuesPercentage = async (
|
|
435
451
|
});
|
436
452
|
|
437
453
|
const pagesPercentageAffectedPerRule: Record<string, string> = {};
|
438
|
-
|
439
|
-
pagesPercentageAffectedPerRule[ruleId] =
|
440
|
-
|
454
|
+
Object.entries(pagesAffectedPerRule).forEach(([ruleId, count]) => {
|
455
|
+
pagesPercentageAffectedPerRule[ruleId] =
|
456
|
+
totalPages > 0 ? ((count / totalPages) * 100).toFixed(2) : '0.00';
|
457
|
+
});
|
441
458
|
|
442
|
-
const typesOfIssuesCountAtMustFix = pages.map(
|
443
|
-
page.typesOfIssues.filter(
|
459
|
+
const typesOfIssuesCountAtMustFix = pages.map(
|
460
|
+
page => page.typesOfIssues.filter(issue => (issue.occurrencesMustFix || 0) > 0).length,
|
444
461
|
);
|
445
462
|
|
446
|
-
const typesOfIssuesCountAtGoodToFix = pages.map(
|
447
|
-
page.typesOfIssues.filter(
|
463
|
+
const typesOfIssuesCountAtGoodToFix = pages.map(
|
464
|
+
page => page.typesOfIssues.filter(issue => (issue.occurrencesGoodToFix || 0) > 0).length,
|
448
465
|
);
|
449
466
|
|
450
467
|
const typesOfIssuesCountSumMustFixAndGoodToFix = pages.map(
|
451
468
|
(_, index) =>
|
452
|
-
(typesOfIssuesCountAtMustFix[index] || 0) +
|
453
|
-
(typesOfIssuesCountAtGoodToFix[index] || 0)
|
469
|
+
(typesOfIssuesCountAtMustFix[index] || 0) + (typesOfIssuesCountAtGoodToFix[index] || 0),
|
454
470
|
);
|
455
471
|
|
456
|
-
const { totalRulesMustFix, totalRulesGoodToFix, totalRulesMustFixAndGoodToFix } =
|
457
|
-
enableWcagAaa,
|
458
|
-
disableOobee
|
459
|
-
);
|
472
|
+
const { totalRulesMustFix, totalRulesGoodToFix, totalRulesMustFixAndGoodToFix } =
|
473
|
+
await getTotalRulesCount(enableWcagAaa, disableOobee);
|
460
474
|
|
461
|
-
const avgMustFixPerPage =
|
462
|
-
|
463
|
-
|
475
|
+
const avgMustFixPerPage =
|
476
|
+
totalPages > 0
|
477
|
+
? typesOfIssuesCountAtMustFix.reduce((sum, count) => sum + count, 0) / totalPages
|
478
|
+
: 0;
|
464
479
|
|
465
|
-
const avgGoodToFixPerPage =
|
466
|
-
|
467
|
-
|
480
|
+
const avgGoodToFixPerPage =
|
481
|
+
totalPages > 0
|
482
|
+
? typesOfIssuesCountAtGoodToFix.reduce((sum, count) => sum + count, 0) / totalPages
|
483
|
+
: 0;
|
468
484
|
|
469
|
-
const avgMustFixAndGoodToFixPerPage =
|
470
|
-
|
471
|
-
|
485
|
+
const avgMustFixAndGoodToFixPerPage =
|
486
|
+
totalPages > 0
|
487
|
+
? typesOfIssuesCountSumMustFixAndGoodToFix.reduce((sum, count) => sum + count, 0) / totalPages
|
488
|
+
: 0;
|
472
489
|
|
473
490
|
const avgTypesOfIssuesPercentageOfTotalRulesAtMustFix =
|
474
|
-
totalRulesMustFix > 0
|
475
|
-
? ((avgMustFixPerPage / totalRulesMustFix) * 100).toFixed(2)
|
476
|
-
: "0.00";
|
491
|
+
totalRulesMustFix > 0 ? ((avgMustFixPerPage / totalRulesMustFix) * 100).toFixed(2) : '0.00';
|
477
492
|
|
478
493
|
const avgTypesOfIssuesPercentageOfTotalRulesAtGoodToFix =
|
479
494
|
totalRulesGoodToFix > 0
|
480
495
|
? ((avgGoodToFixPerPage / totalRulesGoodToFix) * 100).toFixed(2)
|
481
|
-
:
|
496
|
+
: '0.00';
|
482
497
|
|
483
498
|
const avgTypesOfIssuesPercentageOfTotalRulesAtMustFixAndGoodToFix =
|
484
499
|
totalRulesMustFixAndGoodToFix > 0
|
485
500
|
? ((avgMustFixAndGoodToFixPerPage / totalRulesMustFixAndGoodToFix) * 100).toFixed(2)
|
486
|
-
:
|
501
|
+
: '0.00';
|
487
502
|
|
488
503
|
const avgTypesOfIssuesCountAtMustFix = avgMustFixPerPage.toFixed(2);
|
489
504
|
const avgTypesOfIssuesCountAtGoodToFix = avgGoodToFixPerPage.toFixed(2);
|
@@ -504,7 +519,7 @@ export const getIssuesPercentage = async (
|
|
504
519
|
};
|
505
520
|
};
|
506
521
|
|
507
|
-
export const getFormattedTime = inputDate => {
|
522
|
+
export const getFormattedTime = (inputDate: Date): string => {
|
508
523
|
if (inputDate) {
|
509
524
|
return inputDate.toLocaleTimeString('en-GB', {
|
510
525
|
year: 'numeric',
|
@@ -526,7 +541,7 @@ export const getFormattedTime = inputDate => {
|
|
526
541
|
});
|
527
542
|
};
|
528
543
|
|
529
|
-
export const formatDateTimeForMassScanner = date => {
|
544
|
+
export const formatDateTimeForMassScanner = (date: Date): string => {
|
530
545
|
// Format date and time parts separately
|
531
546
|
const year = date.getFullYear().toString().slice(-2); // Get the last two digits of the year
|
532
547
|
const month = `0${date.getMonth() + 1}`.slice(-2); // Month is zero-indexed
|
@@ -547,14 +562,13 @@ export const setHeadlessMode = (browser: string, isHeadless: boolean): void => {
|
|
547
562
|
} else {
|
548
563
|
process.env.CRAWLEE_HEADLESS = '0';
|
549
564
|
}
|
550
|
-
|
551
565
|
};
|
552
566
|
|
553
|
-
export const setThresholdLimits = setWarnLevel => {
|
567
|
+
export const setThresholdLimits = (setWarnLevel: string): void => {
|
554
568
|
process.env.WARN_LEVEL = setWarnLevel;
|
555
569
|
};
|
556
570
|
|
557
|
-
export const zipResults = (zipName, resultsPath) => {
|
571
|
+
export const zipResults = (zipName: string, resultsPath: string): void => {
|
558
572
|
// Check prior zip file exist and remove
|
559
573
|
if (fs.existsSync(zipName)) {
|
560
574
|
fs.unlinkSync(zipName);
|
@@ -585,9 +599,9 @@ export const zipResults = (zipName, resultsPath) => {
|
|
585
599
|
|
586
600
|
// areLinksEqual compares 2 string URLs and ignores comparison of 'www.' and url protocol
|
587
601
|
// i.e. 'http://google.com' and 'https://www.google.com' returns true
|
588
|
-
export const areLinksEqual = (link1, link2) => {
|
602
|
+
export const areLinksEqual = (link1: string, link2: string): boolean => {
|
589
603
|
try {
|
590
|
-
const format = link => {
|
604
|
+
const format = (link: string): URL => {
|
591
605
|
return new URL(link.replace(/www\./, ''));
|
592
606
|
};
|
593
607
|
const l1 = format(link1);
|
@@ -612,7 +626,7 @@ export const randomThreeDigitNumberString = () => {
|
|
612
626
|
return String(threeDigitNumber);
|
613
627
|
};
|
614
628
|
|
615
|
-
export const isFollowStrategy = (link1, link2, rule) => {
|
629
|
+
export const isFollowStrategy = (link1: string, link2: string, rule: string): boolean => {
|
616
630
|
const parsedLink1 = new URL(link1);
|
617
631
|
const parsedLink2 = new URL(link2);
|
618
632
|
if (rule === 'same-domain') {
|
@@ -623,17 +637,17 @@ export const isFollowStrategy = (link1, link2, rule) => {
|
|
623
637
|
return parsedLink1.hostname === parsedLink2.hostname;
|
624
638
|
};
|
625
639
|
|
626
|
-
|
627
|
-
export const retryFunction = async (func, maxAttempt) => {
|
640
|
+
export const retryFunction = async <T>(func: () => Promise<T>, maxAttempt: number): Promise<T> => {
|
628
641
|
let attemptCount = 0;
|
629
642
|
while (attemptCount < maxAttempt) {
|
630
643
|
attemptCount += 1;
|
631
644
|
try {
|
645
|
+
// eslint-disable-next-line no-await-in-loop
|
632
646
|
const result = await func();
|
633
647
|
return result;
|
634
648
|
} catch (error) {
|
635
649
|
silentLogger.error(`(Attempt count: ${attemptCount} of ${maxAttempt}) ${error}`);
|
636
650
|
}
|
637
651
|
}
|
652
|
+
throw new Error('Maximum number of attempts reached');
|
638
653
|
};
|
639
|
-
/* eslint-enable no-await-in-loop */
|