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.
- package/package.json +1 -1
- package/tests/hover.js +181 -141
package/package.json
CHANGED
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
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
202
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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
|
-
|
|
271
|
-
data.totals
|
|
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
|
};
|