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.
Files changed (95) hide show
  1. package/LICENSE +4 -16
  2. package/README.md +10 -2
  3. package/UPGRADES.md +1 -1
  4. package/dirWatch.js +2 -3
  5. package/ed11y/editoria11y.min.js +109 -690
  6. package/ed11y/editoria11y210.min.js +747 -0
  7. package/netWatch.js +6 -6
  8. package/package.json +1 -1
  9. package/procs/aslint.js +2 -2
  10. package/procs/catalog.js +190 -0
  11. package/procs/{dateOf.js → dateTime.js} +6 -4
  12. package/procs/doActs.js +1227 -0
  13. package/procs/doTestAct.js +63 -29
  14. package/procs/error.js +53 -0
  15. package/procs/job.js +64 -38
  16. package/procs/launch.js +596 -0
  17. package/procs/nu.js +3 -18
  18. package/procs/shoot.js +18 -2
  19. package/procs/testaro.js +102 -125
  20. package/procs/xPath.js +62 -0
  21. package/run.js +42 -1938
  22. package/scratch/README.md +9 -0
  23. package/testaro/adbID.js +3 -3
  24. package/testaro/allCaps.js +4 -5
  25. package/testaro/allHidden.js +19 -18
  26. package/testaro/allSlanted.js +4 -5
  27. package/testaro/altScheme.js +3 -3
  28. package/testaro/attVal.js +19 -35
  29. package/testaro/autocomplete.js +65 -62
  30. package/testaro/bulk.js +21 -20
  31. package/testaro/buttonMenu.js +112 -33
  32. package/testaro/captionLoc.js +3 -3
  33. package/testaro/datalistRef.js +4 -5
  34. package/testaro/distortion.js +3 -3
  35. package/testaro/docType.js +6 -9
  36. package/testaro/dupAtt.js +12 -25
  37. package/testaro/elements.js +4 -3
  38. package/testaro/embAc.js +4 -2
  39. package/testaro/focAll.js +6 -13
  40. package/testaro/focAndOp.js +3 -3
  41. package/testaro/focInd.js +3 -3
  42. package/testaro/focVis.js +4 -3
  43. package/testaro/headEl.js +5 -12
  44. package/testaro/headingAmb.js +45 -88
  45. package/testaro/hovInd.js +5 -5
  46. package/testaro/hover.js +44 -8
  47. package/testaro/hr.js +4 -4
  48. package/testaro/imageLink.js +3 -3
  49. package/testaro/labClash.js +3 -3
  50. package/testaro/legendLoc.js +3 -3
  51. package/testaro/lineHeight.js +3 -3
  52. package/testaro/linkAmb.js +25 -17
  53. package/testaro/linkExt.js +5 -5
  54. package/testaro/linkOldAtt.js +4 -3
  55. package/testaro/linkTo.js +4 -3
  56. package/testaro/linkUl.js +4 -5
  57. package/testaro/miniText.js +4 -3
  58. package/testaro/motion.js +3 -22
  59. package/testaro/nonTable.js +4 -5
  60. package/testaro/optRoleSel.js +3 -3
  61. package/testaro/phOnly.js +3 -3
  62. package/testaro/pseudoP.js +5 -5
  63. package/testaro/radioSet.js +4 -5
  64. package/testaro/role.js +4 -5
  65. package/testaro/secHeading.js +4 -5
  66. package/testaro/shoot0.js +3 -2
  67. package/testaro/shoot1.js +3 -2
  68. package/testaro/styleDiff.js +5 -12
  69. package/testaro/tabNav.js +30 -118
  70. package/testaro/targetSmall.js +30 -15
  71. package/testaro/textNodes.js +3 -1
  72. package/testaro/textSem.js +4 -5
  73. package/testaro/title.js +4 -2
  74. package/testaro/titledEl.js +3 -3
  75. package/testaro/zIndex.js +3 -3
  76. package/tests/alfa.js +28 -54
  77. package/tests/aslint.js +20 -53
  78. package/tests/axe.js +76 -13
  79. package/tests/ed11y.js +69 -141
  80. package/tests/htmlcs.js +69 -38
  81. package/tests/ibm.js +54 -9
  82. package/tests/nuVal.js +65 -12
  83. package/tests/nuVnu.js +76 -26
  84. package/tests/qualWeb.js +89 -44
  85. package/tests/testaro.js +288 -273
  86. package/tests/wave.js +142 -117
  87. package/tests/wax.js +61 -42
  88. package/procs/getLocatorData.js +0 -192
  89. package/procs/identify.js +0 -250
  90. package/procs/isInlineLink.js +0 -42
  91. package/procs/screenShot.js +0 -32
  92. package/procs/standardize.js +0 -524
  93. package/procs/target.js +0 -90
  94. package/procs/tellServer.js +0 -43
  95. 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 results.
50
+ // Initialize the act report.
45
51
  const data = {};
46
- let result = {};
47
- try {
48
- result = await new Promise(resolve => {
49
- // Get the test results.
50
- https.get(
51
- {
52
- host,
53
- path
54
- },
55
- response => {
56
- let rawReport = '';
57
- response.on('data', chunk => {
58
- rawReport += chunk;
59
- });
60
- // When they arrive:
61
- response.on('end', async () => {
62
- let actResult = {};
63
- // Delete unnecessary properties.
64
- try {
65
- actResult = JSON.parse(rawReport);
66
- const {categories, statistics} = actResult;
67
- delete categories.feature;
68
- delete categories.structure;
69
- delete categories.aria;
70
- // If rules were specified:
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 WAVE rule category:
73
- ['error', 'contrast', 'alert'].forEach(categoryName => {
74
- const category = categories[categoryName];
75
- // If any violations were reported:
76
- if (
77
- category
78
- && category.items
79
- && Object.keys(category.items).length
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
- // Get the WAVE WCAG documentation.
96
- const waveDocResponse = await fetch('https://wave.webaim.org/api/docs');
97
- const waveDoc = waveDocResponse.ok ? await waveDocResponse.json() : [];
98
- // For each rule category:
99
- for (const categoryName of Object.keys(categories)) {
100
- const category = categories[categoryName];
101
- // If any violations were reported in the category:
102
- if (
103
- category
104
- && category.items
105
- && Object.keys(category.items).length
106
- ) {
107
- const {items} = category;
108
- // For each rule violated (named item by WAVE):
109
- for (const ruleName of Object.keys(items)) {
110
- const ruleDoc = waveDoc.find((rule => rule.name === ruleName));
111
- const {guidelines} = ruleDoc;
112
- const rule = items[ruleName];
113
- // Add WCAG information to the rule data.
114
- rule.wcag = guidelines || [];
115
- // For each violation:
116
- for (const index in rule.selectors) {
117
- const selector = rule.selectors[index] || '';
118
- let excerpt = '';
119
- // If a selector is provided:
120
- if (selector) {
121
- // Get an excerpt of the element.
122
- excerpt = await page.evaluate(selector => {
123
- const element = document.querySelector(selector);
124
- // If the selector matches an element:
125
- if (element) {
126
- // Get an excerpt of the element.
127
- const rawExcerpt = element.textContent.trim() || element.outerHTML.trim();
128
- const normalizedExcerpt = rawExcerpt.replace(/\s+/g, ' ');
129
- return normalizedExcerpt.slice(0, 300);
130
- }
131
- else {
132
- return '';
133
- }
134
- }, selector);
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
- catch(error) {
156
- data.prevented = true;
157
- data.error = error.message;
158
- resolve(actResult);
159
- };
160
- });
177
+ }
161
178
  }
162
- );
163
- });
164
- }
165
- catch (error) {
166
- data.prevented = true;
167
- data.error = error.message;
168
- };
169
- return {
170
- data,
171
- result
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
- // Function to add and use unique element IDs.
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
- let data = {};
32
- let result = {};
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
- // Add location data to its reported violations.
64
- for (const violation of actReport) {
65
- const {element} = violation;
66
- const elementLocation = await getElementData(page, element);
67
- Object.assign(violation, elementLocation);
68
- }
69
- // Populate the act report.
70
- result = {
71
- violations: actReport
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
- prevented: true,
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
  };
@@ -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
- };