-
Notifications
You must be signed in to change notification settings - Fork 214
Expand file tree
/
Copy pathutils.js
More file actions
183 lines (165 loc) · 6.58 KB
/
utils.js
File metadata and controls
183 lines (165 loc) · 6.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/*
* Copyright (c) 2023, Salesforce, Inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
const AxeBuilder = require('@axe-core/playwright')
const {expect} = require('@playwright/test')
const {types} = require('util')
const fs = require('fs')
const promisify = require('util').promisify
const statAsync = promisify(fs.stat)
const mkdirAsync = promisify(fs.mkdir)
const isPrompt = (streamData, expectedText) => {
if (!streamData || !expectedText) return false
if (types.isRegExp(expectedText)) {
return streamData.match(expectedText)
} else return streamData.includes(expectedText)
}
const mkdirIfNotExists = (dirname) => statAsync(dirname).catch(() => mkdirAsync(dirname))
const diffArrays = (expectedArr, actualArr) => {
const actualSet = new Set(actualArr)
return [...expectedArr].filter((x) => !actualSet.has(x))
}
const getCreditCardExpiry = (yearsFromNow = 5) => {
const padMonth = '00'
return `${(padMonth + (new Date().getMonth() + 1)).slice(-padMonth.length)}/${
(new Date().getFullYear() % 100) + parseInt(yearsFromNow)
}`
}
/**
* Helper function to create simplified violation objects for snapshots
*
* @param {Array} violations - Array of axe-core violations
* @returns {Array} - Array of simplified violation objects
*/
function simplifyViolations(violations) {
return violations.map((violation) => ({
id: violation.id, // Rule ID
impact: violation.impact, // Impact (critical, serious, moderate, minor)
description: violation.description, // Description of the rule
help: violation.help, // Short description
helpUrl: violation.helpUrl,
nodes: violation.nodes.map((node) => ({
// Simplify the HTML to make it more stable for snapshots
html: sanitizeHtml(node.html),
// Include the important failure information
failureSummary: node.failureSummary,
// Simplify target selectors for stability
// #app-header[data-v-12345] > .navigation[data-testid="main-nav"] => #app-header > .navigation
target: node.target.map((t) => t.split(/\[.*?\]/).join(''))
}))
}))
}
/**
* Helper function to strip dynamic content from HTML to make snapshots more stable
*
* @param {string} html - HTML string
* @returns {string} - HTML string with dynamic content removed
*/
function sanitizeHtml(html) {
return (
html
// Remove IDs which may change
.replace(/id="[^"]*"/g, 'id="..."')
// Remove data attributes which may change
.replace(/data-[a-zA-Z0-9-]+="[^"]*"/g, '')
// Simplify classes which may change
.replace(/class="[^"]*"/g, 'class="..."')
// Remove inline styles which may change
.replace(/style="[^"]*"/g, '')
// Remove content of script tags
.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gi, '<script>...</script>')
// Trim whitespace
.trim()
)
}
/**
* Runs an accessibility analysis on the current page
*
* @param {Page} page - Playwright page object
* @param {string|string[]} snapshotName - Name for the snapshot file
* @param {Object} options - Optional configuration
* @param {string[]} options.exclude - CSS selectors to exclude from scan
*/
async function runAccessibilityTest(page, snapshotName, options = {}) {
const {exclude = []} = options
// Create AxeBuilder instance
let axeBuilder = new AxeBuilder({page})
// Add exclusions if provided
if (exclude.length > 0) {
axeBuilder = axeBuilder.exclude(exclude)
}
// Run the accessibility audit
const accessibilityScanResults = await axeBuilder.analyze()
// console.log(`Found ${accessibilityScanResults.violations.length} accessibility violations`)
// Create simplified versions of violations for more stable snapshots
const simplifiedViolations = simplifyViolations(accessibilityScanResults.violations)
// Convert to JSON string for stable snapshot comparison
const violationsJson = JSON.stringify(simplifiedViolations, null, 2)
// Compare with snapshot - using string comparison instead of object comparison
expect(violationsJson).toMatchSnapshot(snapshotName)
}
/**
* Generates a random string of given length containing uppercase letters, lowercase letters and numbers.
* @param {number} length Length of generated string required.
* @returns Randomly generated alphanumeric string.
*/
const generateRandomString = function (length) {
let randomString = ''
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
const charactersLength = characters.length
let counter = 0
while (counter < length) {
randomString += characters.charAt(Math.floor(Math.random() * charactersLength))
counter += 1
}
return randomString
}
/**
* Generates a random valid phone number string
* @param {number} length Length of generated string required.
* @returns Randomly generated numeric string.
*/
const generateRandomNumericString = function (length) {
// US Phone numbers must have the format NXX NXX-XXXX
// where N cannot be 0 or 1.
// The area code cannot have 9 in the 2nd digit
// The middle 3 digits cannot be N11
let randomPhone = ''
const validNumbers = '23456789' // exclude 0 or 1 to keep things simple
const validNumbersLength = validNumbers.length
let counter = 0
while (counter < length) {
randomPhone += validNumbers.charAt(Math.floor(Math.random() * validNumbersLength))
counter += 1
}
return randomPhone
}
/**
* Generates a random user object containing firstName, lastName, phone, email and password based on locale (Supports en_US and en_GB only).
* @returns Object containing randomly generated user data.
*/
const generateUserCredentials = function () {
const user = {}
user.firstName = generateRandomString(8)
user.lastName = generateRandomString(8)
user.phone = '857' + generateRandomNumericString(7)
user.email = (generateRandomString(12) + '@domain.com').toLowerCase()
user.password = generateRandomString(15) + 'Ab1!%&*$#@^+:;=?'
user.address = {}
user.address.street = generateRandomString(10)
user.address.city = 'Burlington'
user.address.state = 'MA'
user.address.zipcode = '02' + generateRandomNumericString(3)
return user
}
module.exports = {
isPrompt,
mkdirIfNotExists,
diffArrays,
getCreditCardExpiry,
generateUserCredentials,
runAccessibilityTest
}