testaro 5.7.5 → 5.7.6

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 (2) hide show
  1. package/package.json +1 -1
  2. package/tests/hover.js +181 -141
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "5.7.5",
3
+ "version": "5.7.6",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/tests/hover.js CHANGED
@@ -28,6 +28,10 @@
28
28
  value in the same location being the target.
29
29
  */
30
30
 
31
+ // VARIABLES
32
+
33
+ let hasTimedOut = false;
34
+
31
35
  // FUNCTIONS
32
36
 
33
37
  // Samples a population and returns the sample.
@@ -59,153 +63,172 @@ const textOf = async (element, limit) => {
59
63
  };
60
64
  // Recursively reports impacts of hovering over triggers.
61
65
  const find = async (data, withItems, page, region, sample, popRatio) => {
62
- // If any potential triggers remain:
63
- if (sample.length) {
64
- // Identify the first of them.
65
- const firstTrigger = sample[0];
66
- const tagNameJSHandle = await firstTrigger.getProperty('tagName')
67
- .catch(error => {
68
- return '';
69
- });
70
- if (tagNameJSHandle) {
71
- const tagName = await tagNameJSHandle.jsonValue();
72
- // Identify the root of a subtree likely to contain impacted elements.
73
- let root = firstTrigger;
74
- if (['A', 'BUTTON', 'LI'].includes(tagName)) {
75
- const rootJSHandle = await page.evaluateHandle(
76
- firstTrigger => {
77
- const parent = firstTrigger.parentElement || firstTrigger;
78
- const grandparent = parent.parentElement || parent;
79
- const greatGrandparent = grandparent.parentElement || parent;
80
- return firstTrigger.tagName === 'LI' ? grandparent : greatGrandparent;
81
- },
82
- firstTrigger
83
- );
84
- root = rootJSHandle.asElement();
85
- }
86
- // Identify all the descendants of the root.
87
- const preDescendants = await root.$$(':visible');
88
- // Identify their opacities.
89
- const preOpacities = await page.evaluate(elements => elements.map(
90
- element => window.getComputedStyle(element).opacity
91
- ), preDescendants);
92
- try {
93
- // Hover over the trigger.
94
- await firstTrigger.hover({
95
- timeout: 500,
96
- noWaitAfter: true
97
- });
98
- // Repeatedly seeks impacts.
99
- const getImpacts = async (interval, triesLeft) => {
100
- // If the allowed trial count has not yet been exhausted:
101
- if (triesLeft--) {
102
- // Get the collection of descendants of the root.
103
- const postDescendants = await root.$$(':visible');
104
- // Identify the prior descandants of the root still in existence.
105
- const remainerIndexes = await page.evaluate(args => {
106
- const preDescendants = args[0];
107
- const postDescendants = args[1];
108
- const remainerIndexes = preDescendants
109
- .map((element, index) => postDescendants.includes(element) ? index : -1)
110
- .filter(index => index > -1);
111
- return remainerIndexes;
112
- }, [preDescendants, postDescendants]);
113
- // Get the count of elements added by the hover event.
114
- const additionCount = postDescendants.length - remainerIndexes.length;
115
- const removalCount = preDescendants.length - remainerIndexes.length;
116
- const remainers = [];
117
- for (const index of remainerIndexes) {
118
- remainers.push({
119
- element: preDescendants[index],
120
- preOpacity: preOpacities[index],
121
- postOpacity: await page.evaluate(
122
- element => window.getComputedStyle(element).opacity, preDescendants[index]
123
- )
124
- });
125
- }
126
- const opacityChangers = remainers
127
- .filter(remainer => remainer.postOpacity !== remainer.preOpacity);
128
- const opacityImpact = opacityChangers ? await page.evaluate(changers => changers.reduce(
129
- (total, current) => total + current.element.querySelectorAll('*').length, 0
130
- ), opacityChangers) : 0;
131
- if (additionCount || removalCount || opacityChangers.length) {
132
- return {
133
- additionCount,
134
- removalCount,
135
- opacityChangers,
136
- opacityImpact
137
- };
138
- }
139
- else {
140
- return await new Promise(resolve => {
141
- setTimeout(() => {
142
- resolve(getImpacts(interval, triesLeft));
143
- }, interval);
144
- });
145
- }
146
- }
147
- else {
148
- return null;
149
- }
150
- };
151
- // Repeatedly seek impacts of the hover at intervals.
152
- const impacts = await getImpacts(300, 4);
153
- // If there were any:
154
- if (impacts) {
155
- // Hover over the upper-left corner of the page, to undo any impacts.
156
- await page.hover('body', {
157
- position: {
158
- x: 0,
159
- y: 0
66
+ // If any potential triggers remain and the test has not timed out:
67
+ if (sample.length && ! hasTimedOut) {
68
+ // Get and report the impacts until and unless the test times out.
69
+ try {
70
+ // Identify the first of them.
71
+ const firstTrigger = sample[0];
72
+ const tagNameJSHandle = await firstTrigger.getProperty('tagName')
73
+ .catch(error => '');
74
+ if (tagNameJSHandle) {
75
+ const tagName = await tagNameJSHandle.jsonValue();
76
+ // Identify the root of a subtree likely to contain impacted elements.
77
+ let root = firstTrigger;
78
+ if (['A', 'BUTTON', 'LI'].includes(tagName)) {
79
+ const rootJSHandle = await page.evaluateHandle(
80
+ firstTrigger => {
81
+ const parent = firstTrigger.parentElement || firstTrigger;
82
+ const grandparent = parent.parentElement || parent;
83
+ const greatGrandparent = grandparent.parentElement || parent;
84
+ return firstTrigger.tagName === 'LI' ? grandparent : greatGrandparent;
160
85
  },
86
+ firstTrigger
87
+ );
88
+ root = rootJSHandle.asElement();
89
+ }
90
+ // Identify all the visible descendants of the root.
91
+ const preDescendants = await root.$$(':visible');
92
+ // Identify their opacities.
93
+ const preOpacities = await page.evaluate(elements => elements.map(
94
+ element => window.getComputedStyle(element).opacity
95
+ ), preDescendants);
96
+ try {
97
+ // Hover over the trigger.
98
+ await firstTrigger.hover({
161
99
  timeout: 500,
162
- force: true,
163
100
  noWaitAfter: true
164
101
  });
165
- // Wait for any delayed and/or slowed hover reaction.
166
- await page.waitForTimeout(200);
167
- await root.waitForElementState('stable');
168
- // Increment the counts of triggers and impacts.
169
- const {additionCount, removalCount, opacityChangers, opacityImpact} = impacts;
170
- data.totals.impactTriggers += popRatio;
171
- data.totals.additions += popRatio * additionCount;
172
- data.totals.removals += popRatio * removalCount;
173
- data.totals.opacityChanges += popRatio * opacityChangers.length;
174
- data.totals.opacityImpact += popRatio * opacityImpact;
175
- // If details are to be reported:
176
- if (withItems) {
177
- // Report them.
178
- data.items[region].impactTriggers.push({
179
- tagName,
180
- text: await textOf(firstTrigger, 50),
181
- additions: additionCount,
182
- removals: removalCount,
183
- opacityChanges: opacityChangers.length,
184
- opacityImpact
102
+ // FUNCTION DEFINITION START
103
+ // Repeatedly seeks impacts.
104
+ const getImpacts = async (interval, triesLeft) => {
105
+ // If the allowed trial count has not yet been exhausted:
106
+ if (triesLeft-- && ! hasTimedOut) {
107
+ // Get the collection of descendants of the root.
108
+ const postDescendants = await root.$$(':visible');
109
+ // Identify the prior descandants of the root still in existence.
110
+ const remainerIndexes = await page.evaluate(args => {
111
+ const preDescendants = args[0];
112
+ const postDescendants = args[1];
113
+ const remainerIndexes = preDescendants
114
+ .map((element, index) => postDescendants.includes(element) ? index : -1)
115
+ .filter(index => index > -1);
116
+ return remainerIndexes;
117
+ }, [preDescendants, postDescendants]);
118
+ // Get the count of elements added by the hover event.
119
+ const additionCount = postDescendants.length - remainerIndexes.length;
120
+ const removalCount = preDescendants.length - remainerIndexes.length;
121
+ const remainers = [];
122
+ for (const index of remainerIndexes) {
123
+ remainers.push({
124
+ element: preDescendants[index],
125
+ preOpacity: preOpacities[index],
126
+ postOpacity: await page.evaluate(
127
+ element => window.getComputedStyle(element).opacity, preDescendants[index]
128
+ )
129
+ });
130
+ }
131
+ const opacityChangers = remainers
132
+ .filter(remainer => remainer.postOpacity !== remainer.preOpacity);
133
+ const opacityImpact = opacityChangers ? await page.evaluate(changers => changers.reduce(
134
+ (total, current) => total + current.element.querySelectorAll('*').length, 0
135
+ ), opacityChangers) : 0;
136
+ if (additionCount || removalCount || opacityChangers.length) {
137
+ return {
138
+ additionCount,
139
+ removalCount,
140
+ opacityChangers,
141
+ opacityImpact
142
+ };
143
+ }
144
+ else {
145
+ return await new Promise(resolve => {
146
+ setTimeout(() => {
147
+ resolve(getImpacts(interval, triesLeft));
148
+ }, interval);
149
+ });
150
+ }
151
+ }
152
+ else {
153
+ return null;
154
+ }
155
+ };
156
+ // FUNCTION DEFINITION END
157
+ // Repeatedly seek impacts of the hover at intervals.
158
+ const impacts = await getImpacts(300, 4);
159
+ // If there were any:
160
+ if (impacts) {
161
+ // Hover over the upper-left corner of the page, to undo any impacts.
162
+ await page.hover('body', {
163
+ position: {
164
+ x: 0,
165
+ y: 0
166
+ },
167
+ timeout: 500,
168
+ force: true,
169
+ noWaitAfter: true
185
170
  });
171
+ // Wait for any delayed and/or slowed hover reaction.
172
+ await page.waitForTimeout(200);
173
+ await root.waitForElementState('stable');
174
+ // Increment the counts of triggers and impacts.
175
+ const {additionCount, removalCount, opacityChangers, opacityImpact} = impacts;
176
+ if (hasTimedOut) {
177
+ return Promise.resolve('');
178
+ }
179
+ else {
180
+ data.totals.impactTriggers += popRatio;
181
+ data.totals.additions += popRatio * additionCount;
182
+ data.totals.removals += popRatio * removalCount;
183
+ data.totals.opacityChanges += popRatio * opacityChangers.length;
184
+ data.totals.opacityImpact += popRatio * opacityImpact;
185
+ // If details are to be reported:
186
+ if (withItems) {
187
+ // Report them.
188
+ data.items[region].impactTriggers.push({
189
+ tagName,
190
+ text: await textOf(firstTrigger, 50),
191
+ additions: additionCount,
192
+ removals: removalCount,
193
+ opacityChanges: opacityChangers.length,
194
+ opacityImpact
195
+ });
196
+ }
197
+ }
186
198
  }
187
199
  }
188
- }
189
- catch (error) {
190
- console.log(`ERROR hovering (${error.message.replace(/\n.+/s, '')})`);
191
- data.totals.unhoverables++;
192
- if (withItems) {
193
- try {
194
- const id = await firstTrigger.getAttribute('id');
195
- data.items[region].unhoverables.push({
196
- tagName,
197
- id: id || '',
198
- text: await textOf(firstTrigger, 50)
199
- });
200
+ catch (error) {
201
+ console.log(`ERROR hovering (${error.message.replace(/\n.+/s, '')})`);
202
+ if (hasTimedOut) {
203
+ return Promise.resolve('');
200
204
  }
201
- catch(error) {
202
- console.log('ERROR itemizing unhoverable element');
205
+ else {
206
+ data.totals.unhoverables++;
207
+ if (withItems) {
208
+ try {
209
+ const id = await firstTrigger.getAttribute('id');
210
+ data.items[region].unhoverables.push({
211
+ tagName,
212
+ id: id || '',
213
+ text: await textOf(firstTrigger, 50)
214
+ });
215
+ }
216
+ catch(error) {
217
+ console.log('ERROR itemizing unhoverable element');
218
+ }
219
+ }
203
220
  }
204
221
  }
205
222
  }
223
+ // Process the remaining potential triggers.
224
+ await find(data, withItems, page, region, sample.slice(1), popRatio);
206
225
  }
207
- // Process the remaining potential triggers.
208
- await find(data, withItems, page, region, sample.slice(1), popRatio);
226
+ catch(error) {
227
+ console.log(`ERROR: Test quit when remaining sample size was ${sample.length}`);
228
+ }
229
+ }
230
+ else {
231
+ return Promise.resolve('');
209
232
  }
210
233
  };
211
234
  // Performs the hover test and reports results.
@@ -213,7 +236,7 @@ exports.reporter = async (
213
236
  page, headSize = 0, headSampleSize = -1, tailSampleSize = -1, withItems
214
237
  ) => {
215
238
  // Initialize the result.
216
- const data = {
239
+ let data = {
217
240
  totals: {
218
241
  triggers: 0,
219
242
  headTriggers: 0,
@@ -259,17 +282,34 @@ exports.reporter = async (
259
282
  // Get the head and tail samples.
260
283
  const headSample = getSample(headTriggers, headSampleSize);
261
284
  const tailSample = tailSampleSize === -1 ? tailTriggers : getSample(tailTriggers, tailSampleSize);
285
+ // Set a time limit to handle pages that slow the operations of this test.
286
+ const timeLimit = Math.round(1.3 * (headSample.length + tailSample.length));
287
+ const timeout = setTimeout(async () => {
288
+ await page.close();
289
+ console.log(
290
+ `ERROR: hover test timed out at ${timeLimit} seconds; page closed`
291
+ );
292
+ hasTimedOut = true;
293
+ data = {
294
+ prevented: true,
295
+ error: 'ERROR: hover test timed out'
296
+ };
297
+ clearTimeout(timeout);
298
+ }, 1000 * timeLimit);
262
299
  // Find and document the impacts.
263
- if (headSample.length) {
300
+ if (headSample.length && ! hasTimedOut) {
264
301
  await find(data, withItems, page, 'head', headSample, headTriggerCount / headSample.length);
265
302
  }
266
- if (tailSample.length) {
303
+ if (tailSample.length && ! hasTimedOut) {
267
304
  await find(data, withItems, page, 'tail', tailSample, tailTriggerCount / tailSample.length);
268
305
  }
306
+ clearTimeout(timeout);
269
307
  // Round the reported totals.
270
- Object.keys(data.totals).forEach(key => {
271
- data.totals[key] = Math.round(data.totals[key]);
272
- });
308
+ if (! hasTimedOut) {
309
+ Object.keys(data.totals).forEach(key => {
310
+ data.totals[key] = Math.round(data.totals[key]);
311
+ });
312
+ }
273
313
  // Return the result.
274
314
  return {result: data};
275
315
  };