testaro 67.0.0 → 68.0.0
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/LICENSE +4 -16
- package/README.md +10 -2
- package/UPGRADES.md +1 -1
- package/dirWatch.js +2 -3
- package/ed11y/editoria11y.min.js +109 -690
- package/ed11y/editoria11y210.min.js +747 -0
- package/netWatch.js +6 -6
- package/package.json +1 -1
- package/procs/aslint.js +2 -2
- package/procs/catalog.js +190 -0
- package/procs/{dateOf.js → dateTime.js} +6 -4
- package/procs/doActs.js +1227 -0
- package/procs/doTestAct.js +63 -29
- package/procs/error.js +53 -0
- package/procs/job.js +64 -38
- package/procs/launch.js +596 -0
- package/procs/nu.js +3 -18
- package/procs/shoot.js +18 -2
- package/procs/testaro.js +102 -125
- package/procs/xPath.js +62 -0
- package/run.js +42 -1938
- package/scratch/README.md +9 -0
- package/testaro/adbID.js +3 -3
- package/testaro/allCaps.js +4 -5
- package/testaro/allHidden.js +19 -18
- package/testaro/allSlanted.js +4 -5
- package/testaro/altScheme.js +3 -3
- package/testaro/attVal.js +19 -35
- package/testaro/autocomplete.js +65 -62
- package/testaro/bulk.js +21 -20
- package/testaro/buttonMenu.js +112 -33
- package/testaro/captionLoc.js +3 -3
- package/testaro/datalistRef.js +4 -5
- package/testaro/distortion.js +3 -3
- package/testaro/docType.js +6 -9
- package/testaro/dupAtt.js +12 -25
- package/testaro/elements.js +4 -3
- package/testaro/embAc.js +4 -2
- package/testaro/focAll.js +6 -13
- package/testaro/focAndOp.js +3 -3
- package/testaro/focInd.js +3 -3
- package/testaro/focVis.js +4 -3
- package/testaro/headEl.js +5 -12
- package/testaro/headingAmb.js +45 -88
- package/testaro/hovInd.js +5 -5
- package/testaro/hover.js +44 -8
- package/testaro/hr.js +4 -4
- package/testaro/imageLink.js +3 -3
- package/testaro/labClash.js +3 -3
- package/testaro/legendLoc.js +3 -3
- package/testaro/lineHeight.js +3 -3
- package/testaro/linkAmb.js +25 -17
- package/testaro/linkExt.js +5 -5
- package/testaro/linkOldAtt.js +4 -3
- package/testaro/linkTo.js +4 -3
- package/testaro/linkUl.js +4 -5
- package/testaro/miniText.js +4 -3
- package/testaro/motion.js +3 -22
- package/testaro/nonTable.js +4 -5
- package/testaro/optRoleSel.js +3 -3
- package/testaro/phOnly.js +3 -3
- package/testaro/pseudoP.js +5 -5
- package/testaro/radioSet.js +4 -5
- package/testaro/role.js +4 -5
- package/testaro/secHeading.js +4 -5
- package/testaro/shoot0.js +3 -2
- package/testaro/shoot1.js +3 -2
- package/testaro/styleDiff.js +5 -12
- package/testaro/tabNav.js +30 -118
- package/testaro/targetSmall.js +30 -15
- package/testaro/textNodes.js +3 -1
- package/testaro/textSem.js +4 -5
- package/testaro/title.js +4 -2
- package/testaro/titledEl.js +3 -3
- package/testaro/zIndex.js +3 -3
- package/tests/alfa.js +28 -54
- package/tests/aslint.js +20 -53
- package/tests/axe.js +76 -13
- package/tests/ed11y.js +69 -141
- package/tests/htmlcs.js +69 -38
- package/tests/ibm.js +54 -9
- package/tests/nuVal.js +65 -12
- package/tests/nuVnu.js +76 -26
- package/tests/qualWeb.js +89 -44
- package/tests/testaro.js +288 -273
- package/tests/wave.js +142 -117
- package/tests/wax.js +61 -42
- package/procs/getLocatorData.js +0 -192
- package/procs/identify.js +0 -250
- package/procs/isInlineLink.js +0 -42
- package/procs/screenShot.js +0 -32
- package/procs/standardize.js +0 -524
- package/procs/target.js +0 -90
- package/procs/tellServer.js +0 -43
- package/scripts/dumpAlts.js +0 -28
package/tests/wave.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2021–2024 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2026 Jonathan Robert Pool.
|
|
3
4
|
|
|
4
5
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
5
6
|
https://opensource.org/license/mit/ for details.
|
|
@@ -13,6 +14,10 @@
|
|
|
13
14
|
specifies a WAVE report type: 1, 2, 3, or 4.
|
|
14
15
|
*/
|
|
15
16
|
|
|
17
|
+
// IMPORTS
|
|
18
|
+
|
|
19
|
+
const {getXPathCatalogIndex} = require('../procs/xPath');
|
|
20
|
+
|
|
16
21
|
// CONSTANTS
|
|
17
22
|
|
|
18
23
|
const https = require('https');
|
|
@@ -21,6 +26,7 @@ const https = require('https');
|
|
|
21
26
|
|
|
22
27
|
// Conducts and reports the WAVE tests.
|
|
23
28
|
exports.reporter = async (page, report, actIndex) => {
|
|
29
|
+
// Create a host and a path for a request to the WAVE API.
|
|
24
30
|
const act = report.acts[actIndex];
|
|
25
31
|
const {reportType, url, prescript, postscript, rules} = act;
|
|
26
32
|
const waveKey = process.env.WAVE_KEY;
|
|
@@ -41,133 +47,152 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
41
47
|
];
|
|
42
48
|
const query = queryParams.filter(param => param).join('&');
|
|
43
49
|
const path = [wavePath, query].join('?');
|
|
44
|
-
// Initialize the
|
|
50
|
+
// Initialize the act report.
|
|
45
51
|
const data = {};
|
|
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
|
-
|
|
52
|
+
const result = {
|
|
53
|
+
nativeResult: {},
|
|
54
|
+
standardResult: {}
|
|
55
|
+
};
|
|
56
|
+
const standard = report.standard !== 'no';
|
|
57
|
+
// If standard results are to be reported:
|
|
58
|
+
if (standard) {
|
|
59
|
+
// Initialize the standard result.
|
|
60
|
+
result.standardResult = {
|
|
61
|
+
prevented: false,
|
|
62
|
+
totals: [0, 0, 0, 0],
|
|
63
|
+
instances: []
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Get and process a WAVE API report and return the results.
|
|
67
|
+
return await new Promise(resolve => https.get(
|
|
68
|
+
{
|
|
69
|
+
host,
|
|
70
|
+
path
|
|
71
|
+
},
|
|
72
|
+
response => {
|
|
73
|
+
let rawReport = '';
|
|
74
|
+
response.on('data', chunk => {
|
|
75
|
+
rawReport += chunk;
|
|
76
|
+
});
|
|
77
|
+
// When the response arrives:
|
|
78
|
+
response.on('end', async () => {
|
|
79
|
+
try {
|
|
80
|
+
// Parse it as JSON.
|
|
81
|
+
result.nativeResult = JSON.parse(rawReport);
|
|
82
|
+
}
|
|
83
|
+
// If it was not parsable:
|
|
84
|
+
catch (error) {
|
|
85
|
+
// Report this.
|
|
86
|
+
data.prevented = true;
|
|
87
|
+
data.error = error.message;
|
|
88
|
+
result.nativeResult = {};
|
|
89
|
+
}
|
|
90
|
+
// If the response was parsed:
|
|
91
|
+
if (! data.prevented) {
|
|
92
|
+
const {categories} = result.nativeResult;
|
|
93
|
+
// Delete its unnecessary properties.
|
|
94
|
+
delete categories.feature;
|
|
95
|
+
delete categories.structure;
|
|
96
|
+
delete categories.aria;
|
|
97
|
+
// For each WAVE rule category:
|
|
98
|
+
for (const categoryName of ['error', 'contrast', 'alert']) {
|
|
99
|
+
const category = categories[categoryName];
|
|
100
|
+
const ordinalSeverity = categoryName === 'alert' ? 0 : 3;
|
|
101
|
+
// If any violated rules (named items by WAVE) were reported:
|
|
102
|
+
if (
|
|
103
|
+
category?.items
|
|
104
|
+
&& Object.keys(category.items).length
|
|
105
|
+
) {
|
|
106
|
+
const {items} = category;
|
|
107
|
+
// If rules to be tested for were specified:
|
|
71
108
|
if (rules && rules.length) {
|
|
72
|
-
// For each
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
category
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
) {
|
|
81
|
-
const {items} = category;
|
|
82
|
-
// For each rule violated:
|
|
83
|
-
Object.keys(items).forEach(ruleID => {
|
|
84
|
-
// If it was not a specified rule:
|
|
85
|
-
if (! rules.includes(ruleID)) {
|
|
86
|
-
// Decrease the category violation count by the count of its violations.
|
|
87
|
-
category.count -= items[ruleID].count;
|
|
88
|
-
// Remove its violations from the report.
|
|
89
|
-
delete items[ruleID];
|
|
90
|
-
}
|
|
91
|
-
});
|
|
109
|
+
// For each rule violated:
|
|
110
|
+
Object.keys(items).forEach(ruleID => {
|
|
111
|
+
// If it was not a specified rule:
|
|
112
|
+
if (! rules.includes(ruleID)) {
|
|
113
|
+
// Decrease the category violation count by the count of its violations.
|
|
114
|
+
category.count -= items[ruleID].count;
|
|
115
|
+
// Remove its violations from the native result.
|
|
116
|
+
delete items[ruleID];
|
|
92
117
|
}
|
|
93
118
|
});
|
|
94
119
|
}
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
category
|
|
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
|
-
|
|
120
|
+
// If standard results are to be reported:
|
|
121
|
+
if (standard) {
|
|
122
|
+
const {standardResult} = result;
|
|
123
|
+
const {totals, instances} = standardResult;
|
|
124
|
+
// Add the category violation count to the standard-result totals.
|
|
125
|
+
totals[ordinalSeverity] += category.count;
|
|
126
|
+
const annotatedItems = await page.evaluate(items => {
|
|
127
|
+
const ruleIDs = Object.keys(items);
|
|
128
|
+
// For each rule of the category with any violations:
|
|
129
|
+
ruleIDs.forEach(ruleID => {
|
|
130
|
+
const {selectors} = items[ruleID];
|
|
131
|
+
// For each of those violations:
|
|
132
|
+
for (const index in selectors) {
|
|
133
|
+
const selector = selectors[index];
|
|
134
|
+
// Get the violator.
|
|
135
|
+
const violator = document.querySelector(selector);
|
|
136
|
+
// Concatenate its selector with its XPath in the native result.
|
|
137
|
+
selectors[index] = [selector, window.getXPath(violator) ?? ''];
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return items;
|
|
141
|
+
}, items);
|
|
142
|
+
const ruleIDs = Object.keys(annotatedItems);
|
|
143
|
+
// For each rule of the category with any violations:
|
|
144
|
+
for (const ruleID of ruleIDs) {
|
|
145
|
+
const {description, selectors} = annotatedItems[ruleID];
|
|
146
|
+
// For each violation of the rule:
|
|
147
|
+
for (const violation of selectors) {
|
|
148
|
+
// Initialize a standard instance.
|
|
149
|
+
const instance = {
|
|
150
|
+
ruleID,
|
|
151
|
+
what: description,
|
|
152
|
+
ordinalSeverity,
|
|
153
|
+
count: 1
|
|
154
|
+
};
|
|
155
|
+
const pathID = violation[1];
|
|
156
|
+
// If the path ID of the violator was found:
|
|
157
|
+
if (pathID) {
|
|
158
|
+
// Get the catalog index of the violator.
|
|
159
|
+
const catalogIndex = getXPathCatalogIndex(report.catalog, pathID);
|
|
160
|
+
// If the acquisition succeeded:
|
|
161
|
+
if (catalogIndex) {
|
|
162
|
+
// Add the catalog index to the instance.
|
|
163
|
+
instance.catalogIndex = catalogIndex;
|
|
164
|
+
}
|
|
165
|
+
// Otherwise, i.e. if the acquisition failed:
|
|
166
|
+
else {
|
|
167
|
+
// Add the path ID to the instance.
|
|
168
|
+
instance.pathID = pathID;
|
|
135
169
|
}
|
|
136
|
-
// Convert the violation selector to a selector-excerpt pair.
|
|
137
|
-
rule.selectors[index] = [selector, excerpt];
|
|
138
170
|
}
|
|
171
|
+
// Add the instance to the standard result.
|
|
172
|
+
instances.push(instance);
|
|
139
173
|
}
|
|
140
174
|
}
|
|
141
|
-
};
|
|
142
|
-
// Add important data to the result.
|
|
143
|
-
if (statistics) {
|
|
144
|
-
data.pageTitle = statistics.pagetitle || '';
|
|
145
|
-
data.pageURL = statistics.pageurl || '';
|
|
146
|
-
data.elapsedSeconds = statistics.time || null;
|
|
147
|
-
data.creditsRemaining = statistics.creditsremaining || null;
|
|
148
|
-
data.allItemCount = statistics.allitemcount || null;
|
|
149
|
-
data.totalElements = statistics.totalelements || null;
|
|
150
|
-
data.waveURL = statistics.waveurl || '';
|
|
151
175
|
}
|
|
152
|
-
// Return the result.
|
|
153
|
-
resolve(actResult);
|
|
154
176
|
}
|
|
155
|
-
|
|
156
|
-
data.prevented = true;
|
|
157
|
-
data.error = error.message;
|
|
158
|
-
resolve(actResult);
|
|
159
|
-
};
|
|
160
|
-
});
|
|
177
|
+
}
|
|
161
178
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
179
|
+
const {statistics} = result.nativeResult;
|
|
180
|
+
if (statistics) {
|
|
181
|
+
// Copy important data from the native result to the result.
|
|
182
|
+
data.pageTitle = statistics.pagetitle || '';
|
|
183
|
+
data.pageURL = statistics.pageurl || '';
|
|
184
|
+
data.elapsedSeconds = statistics.time || null;
|
|
185
|
+
data.creditsRemaining = statistics.creditsremaining || null;
|
|
186
|
+
data.allItemCount = statistics.allitemcount || null;
|
|
187
|
+
data.totalElements = statistics.totalelements || null;
|
|
188
|
+
data.waveURL = statistics.waveurl || '';
|
|
189
|
+
}
|
|
190
|
+
// Return the result.
|
|
191
|
+
resolve({
|
|
192
|
+
data,
|
|
193
|
+
result
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
));
|
|
173
198
|
};
|
package/tests/wax.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2024 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
© 2025 Jonathan Robert Pool.
|
|
3
|
+
© 2025–2026 Jonathan Robert Pool.
|
|
4
4
|
|
|
5
5
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
6
6
|
https://opensource.org/license/mit/ for details.
|
|
@@ -15,11 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
// IMPORTS
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
const {addTestaroIDs} = require('../procs/testaro');
|
|
20
|
-
// Function to get location data from an element.
|
|
21
|
-
const {getElementData} = require('../procs/getLocatorData');
|
|
22
|
-
// Modules to run WAX.
|
|
18
|
+
const {getAttributeXPath, getXPathCatalogIndex} = require('../procs/xPath');
|
|
23
19
|
const runWax = require('@wally-ax/wax-dev');
|
|
24
20
|
const waxDev = {runWax};
|
|
25
21
|
|
|
@@ -28,12 +24,23 @@ const waxDev = {runWax};
|
|
|
28
24
|
// Conducts and reports the WAX tests.
|
|
29
25
|
exports.reporter = async (page, report, actIndex) => {
|
|
30
26
|
// Initialize the act report.
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
const data = {};
|
|
28
|
+
const result = {
|
|
29
|
+
nativeResult: {},
|
|
30
|
+
standardResult: {}
|
|
31
|
+
};
|
|
32
|
+
const standard = report.standard !== 'no';
|
|
33
|
+
// If standard results are to be reported:
|
|
34
|
+
if (standard) {
|
|
35
|
+
// Initialize the standard result.
|
|
36
|
+
result.standardResult = {
|
|
37
|
+
prevented: false,
|
|
38
|
+
totals: [0, 0, 0, 0],
|
|
39
|
+
instances: []
|
|
40
|
+
};
|
|
41
|
+
}
|
|
33
42
|
const act = report.acts[actIndex];
|
|
34
43
|
const rules = act.rules || [];
|
|
35
|
-
// Annotate all elements on the page with unique identifiers.
|
|
36
|
-
await addTestaroIDs(page);
|
|
37
44
|
const pageCode = await page.content();
|
|
38
45
|
const waxOptions = {
|
|
39
46
|
rules,
|
|
@@ -60,15 +67,44 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
60
67
|
}
|
|
61
68
|
// Otherwise, i.e. if it is a successful report:
|
|
62
69
|
else {
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
// Populate the native result with it.
|
|
71
|
+
result.nativeResult = actReport;
|
|
72
|
+
// If standard results are to be reported:
|
|
73
|
+
if (standard) {
|
|
74
|
+
const {standardResult} = result;
|
|
75
|
+
const {instances, totals} = standardResult;
|
|
76
|
+
actReport.forEach(violation => {
|
|
77
|
+
const ordinalSeverity = ['Minor', 'Moderate', '', 'Severe'].indexOf(violation.severity);
|
|
78
|
+
// Increment the applicable total of the standard result.
|
|
79
|
+
totals[ordinalSeverity]++;
|
|
80
|
+
// Initialize a standard instance.
|
|
81
|
+
const instance = {
|
|
82
|
+
ruleID: violation.message,
|
|
83
|
+
what: violation.description || violation.message,
|
|
84
|
+
ordinalSeverity,
|
|
85
|
+
count: 1
|
|
86
|
+
};
|
|
87
|
+
const {element} = violation;
|
|
88
|
+
// Get the path ID of the element from its data-xpath attribute.
|
|
89
|
+
const pathID = getAttributeXPath(element);
|
|
90
|
+
// If the acquisition succeeded:
|
|
91
|
+
if (pathID) {
|
|
92
|
+
// Get the catalog index of the element.
|
|
93
|
+
const catalogIndex = getXPathCatalogIndex(report.catalog, pathID);
|
|
94
|
+
// If the acquisition succeeded:
|
|
95
|
+
if (catalogIndex) {
|
|
96
|
+
// Add the catalog index to the standard instance.
|
|
97
|
+
instance.catalogIndex = catalogIndex;
|
|
98
|
+
}
|
|
99
|
+
// Otherwise, i.e. if the acquisition failed:
|
|
100
|
+
else {
|
|
101
|
+
// Add the path ID to the standard instance.
|
|
102
|
+
instance.pathID = pathID;
|
|
103
|
+
}
|
|
104
|
+
// Add the standard instance to the standard result.
|
|
105
|
+
instances.push(instance);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
72
108
|
}
|
|
73
109
|
}
|
|
74
110
|
}
|
|
@@ -93,32 +129,15 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
93
129
|
data.error = 'wax failure';
|
|
94
130
|
}
|
|
95
131
|
}
|
|
96
|
-
try {
|
|
97
|
-
JSON.stringify(data);
|
|
98
|
-
}
|
|
99
|
-
catch(error) {
|
|
100
|
-
const message = `ERROR: WAX result cannot be made JSON (${error.message.slice(0, 200)})`;
|
|
101
|
-
data = {
|
|
102
|
-
prevented: true,
|
|
103
|
-
error: message
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
// Return the results.
|
|
107
|
-
return {
|
|
108
|
-
data,
|
|
109
|
-
result
|
|
110
|
-
};
|
|
111
132
|
}
|
|
112
133
|
catch(error) {
|
|
113
134
|
const message = `ERROR running WallyAX (${error.message})`;
|
|
114
|
-
data =
|
|
115
|
-
|
|
116
|
-
error: message
|
|
117
|
-
};
|
|
135
|
+
data.prevented = true;
|
|
136
|
+
data.error = message;
|
|
118
137
|
console.log(message);
|
|
119
|
-
return {
|
|
120
|
-
data,
|
|
121
|
-
result
|
|
122
|
-
};
|
|
123
138
|
}
|
|
139
|
+
return {
|
|
140
|
+
data,
|
|
141
|
+
result
|
|
142
|
+
};
|
|
124
143
|
};
|
package/procs/getLocatorData.js
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
© 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
|
|
4
|
-
Licensed under the MIT License. See LICENSE file at the project root or
|
|
5
|
-
https://opensource.org/license/mit/ for details.
|
|
6
|
-
|
|
7
|
-
SPDX-License-Identifier: MIT
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/*
|
|
11
|
-
getLocatorData
|
|
12
|
-
Returns data about the element identified by a locator.
|
|
13
|
-
*/
|
|
14
|
-
exports.getLocatorData = async loc => {
|
|
15
|
-
const locCount = await loc.count();
|
|
16
|
-
// If the locator identifies exactly 1 element:
|
|
17
|
-
if (locCount === 1) {
|
|
18
|
-
// Get the facts obtainable from the browser.
|
|
19
|
-
const data = await loc.evaluate(element => {
|
|
20
|
-
// Tag name.
|
|
21
|
-
const tagName = element.tagName;
|
|
22
|
-
// ID.
|
|
23
|
-
const id = element.id || '';
|
|
24
|
-
// Texts.
|
|
25
|
-
const {textContent} = element;
|
|
26
|
-
const alts = Array.from(element.querySelectorAll('img[alt]:not([alt=""])'));
|
|
27
|
-
const altTexts = alts.map(alt => alt.getAttribute('alt'));
|
|
28
|
-
const altsText = altTexts.join(' ');
|
|
29
|
-
const ariaLabelText = element.ariaLabel || '';
|
|
30
|
-
const refLabelID = element.getAttribute('aria-labelledby');
|
|
31
|
-
const refLabel = refLabelID ? document.getElementById(refLabelID) : '';
|
|
32
|
-
const refLabelText = refLabel ? refLabel.textContent : '';
|
|
33
|
-
let labelsText = '';
|
|
34
|
-
if (tagName === 'INPUT') {
|
|
35
|
-
const labels = element.labels || [];
|
|
36
|
-
const labelTexts = [];
|
|
37
|
-
labels.forEach(label => {
|
|
38
|
-
labelTexts.push(label.textContent);
|
|
39
|
-
});
|
|
40
|
-
labelsText = labelTexts.join(' ');
|
|
41
|
-
}
|
|
42
|
-
let text = [textContent, altsText, ariaLabelText, refLabelText, labelsText]
|
|
43
|
-
.join(' ')
|
|
44
|
-
.replace(/\s+/g, ' ')
|
|
45
|
-
.trim();
|
|
46
|
-
if (! text) {
|
|
47
|
-
text = element.outerHTML.replace(/\s+/g, ' ').trim();
|
|
48
|
-
}
|
|
49
|
-
if (/^<[^<>]+>$/.test(text)) {
|
|
50
|
-
text = element.parentElement.outerHTML.replace(/\s+/g, ' ').trim();
|
|
51
|
-
}
|
|
52
|
-
// Location.
|
|
53
|
-
let location = {
|
|
54
|
-
doc: 'dom',
|
|
55
|
-
type: 'box',
|
|
56
|
-
spec: {}
|
|
57
|
-
};
|
|
58
|
-
if (id) {
|
|
59
|
-
location.type = 'selector';
|
|
60
|
-
location.spec = `#${id}`;
|
|
61
|
-
}
|
|
62
|
-
// Return the data.
|
|
63
|
-
return {
|
|
64
|
-
tagName,
|
|
65
|
-
id,
|
|
66
|
-
location,
|
|
67
|
-
excerpt: text
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
// If an ID-based selector could not be defined:
|
|
71
|
-
if (data.location.type === 'box') {
|
|
72
|
-
// Define a bounding-box-based location.
|
|
73
|
-
const rawSpec = await loc.boundingBox();
|
|
74
|
-
// If there is a bounding box (i.e. the element is visible):
|
|
75
|
-
if (rawSpec) {
|
|
76
|
-
// Populate the location.
|
|
77
|
-
Object.keys(rawSpec).forEach(specName => {
|
|
78
|
-
data.location.spec[specName] = Math.round(rawSpec[specName]);
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
// Otherwise, i.e. if there is no bounding box:
|
|
82
|
-
else {
|
|
83
|
-
// Empty the location.
|
|
84
|
-
data.location.doc = '';
|
|
85
|
-
data.location.type = '';
|
|
86
|
-
data.location.spec = '';
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// If the text is long:
|
|
90
|
-
if (data.excerpt.length > 400) {
|
|
91
|
-
// Truncate its middle.
|
|
92
|
-
data.excerpt = `${data.excerpt.slice(0, 200)} … ${data.excerpt.slice(-200)}`;
|
|
93
|
-
}
|
|
94
|
-
// Return the data.
|
|
95
|
-
return data;
|
|
96
|
-
}
|
|
97
|
-
// Otherwise, i.e. if it does not identify exactly 1 element:
|
|
98
|
-
else {
|
|
99
|
-
// Report this.
|
|
100
|
-
console.log(`ERROR: Locator count to get data from is ${locCount} instead of 1`);
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
// Returns properties of an element with an excerpt when Testaro identifiers have been added.
|
|
105
|
-
exports.getElementData = async (page, excerpt) => {
|
|
106
|
-
// Initialize the properties.
|
|
107
|
-
let data = {
|
|
108
|
-
tagName: '',
|
|
109
|
-
id: '',
|
|
110
|
-
text: '',
|
|
111
|
-
notInDOM: true,
|
|
112
|
-
boxID: '',
|
|
113
|
-
pathID: '',
|
|
114
|
-
originalExcerpt: excerpt,
|
|
115
|
-
};
|
|
116
|
-
let elementProperties = {};
|
|
117
|
-
const testaroIDArray = excerpt.match(/data-testaro-id="(\d+)#([^"]*)"/);
|
|
118
|
-
// If the excerpt contains a Testaro identifier:
|
|
119
|
-
if (testaroIDArray) {
|
|
120
|
-
// Remove the identifier.
|
|
121
|
-
originalExcerpt = excerpt.replace(/ data-testaro-id="\d+#[^" ]+"?/, '');
|
|
122
|
-
elementProperties = await page.evaluate(testaroIDArray => {
|
|
123
|
-
let tagName = '';
|
|
124
|
-
let id = '';
|
|
125
|
-
const text = [];
|
|
126
|
-
let notInDOM = false;
|
|
127
|
-
let boxID = '';
|
|
128
|
-
let pathID = '';
|
|
129
|
-
const testaroID = `${testaroIDArray[1]}#${testaroIDArray[2]}`;
|
|
130
|
-
const element = document.querySelector(`[data-testaro-id="${testaroID}"]`);
|
|
131
|
-
// If any element has the identifier:
|
|
132
|
-
if (element) {
|
|
133
|
-
// Get properties of the element.
|
|
134
|
-
({tagName, id} = element);
|
|
135
|
-
const segments = element
|
|
136
|
-
.innerText
|
|
137
|
-
?.trim()
|
|
138
|
-
.split(/[\t\n]+/)
|
|
139
|
-
.filter(segment => segment.length);
|
|
140
|
-
if (segments?.length) {
|
|
141
|
-
if (segments.length > 1) {
|
|
142
|
-
text.push(segments[0], segments[segments.length - 1]);
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
text.push(segments[0]);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
const boundingBox = element.getBoundingClientRect() ?? {};
|
|
149
|
-
const box = {};
|
|
150
|
-
if (boundingBox.x) {
|
|
151
|
-
['x', 'y', 'width', 'height'].forEach(coordinate => {
|
|
152
|
-
box[coordinate] = Math.round(boundingBox[coordinate]);
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
if (typeof box.x === 'number') {
|
|
156
|
-
boxID = Object.values(box).join(':');
|
|
157
|
-
}
|
|
158
|
-
// Get a path ID from the identifier or, if necessary, the element.
|
|
159
|
-
pathID = testaroIDArray[2];
|
|
160
|
-
if (! pathID) {
|
|
161
|
-
pathID = window.getXPath(element);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
// Otherwise, i.e. if no element has the identifier:
|
|
165
|
-
else {
|
|
166
|
-
// Report this.
|
|
167
|
-
notInDOM = true;
|
|
168
|
-
}
|
|
169
|
-
// Report the properties.
|
|
170
|
-
return {
|
|
171
|
-
tagName,
|
|
172
|
-
id,
|
|
173
|
-
text,
|
|
174
|
-
notInDOM,
|
|
175
|
-
boxID,
|
|
176
|
-
pathID
|
|
177
|
-
};
|
|
178
|
-
}, testaroIDArray);
|
|
179
|
-
// Populate the data with any properties obtained from the element.
|
|
180
|
-
Object.assign(data, elementProperties);
|
|
181
|
-
}
|
|
182
|
-
// Otherwise, i.e. if the excerpt contains no Testaro identifier:
|
|
183
|
-
else {
|
|
184
|
-
// Get properties of the element that are gettable from the excerpt.
|
|
185
|
-
const tagNameArray = excerpt.match(/<([-a-z]+)/);
|
|
186
|
-
data.tagName = tagNameArray?.[1] ?? '';
|
|
187
|
-
const idArray = excerpt.match(/ id="([^"]*)"/);
|
|
188
|
-
data.id = idArray?.[1] ?? '';
|
|
189
|
-
}
|
|
190
|
-
// Return the properties.
|
|
191
|
-
return data;
|
|
192
|
-
};
|