testaro 4.13.0 → 4.14.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/package.json +1 -1
- package/tests/hover.js +118 -120
package/package.json
CHANGED
package/tests/hover.js
CHANGED
|
@@ -1,48 +1,43 @@
|
|
|
1
1
|
/*
|
|
2
2
|
hover
|
|
3
|
-
This test reports unexpected
|
|
4
|
-
visible
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
3
|
+
This test reports unexpected impacts of hovering. The effects include additions and removals
|
|
4
|
+
of visible elements, opacity changes, and unhoverable elements. The elements that are
|
|
5
|
+
subjected to hovering (called “triggers”) are the Playwright-visible elements that have 'A',
|
|
6
|
+
'BUTTON', or 'LI' tag names or have 'onmouseenter' or 'onmouseover' attributes. When such an
|
|
7
|
+
element is hovered over, the test examines the impacts on descendants of the great grandparents
|
|
8
|
+
of the elements with tag names 'A' and 'BUTTON', grandparents of elements with tag name 'LI',
|
|
9
|
+
and otherwise the descendants of the elements themselves. Four impacts are counted: (1) an
|
|
10
|
+
element is added or becomes visible, (2) an element is removed or becomes invisible, (3) the
|
|
11
|
+
opacity of an element changes, and (4) the element is a descendant of an element whose opacity
|
|
12
|
+
changes. The test checks up to 4 times for hovering impacts at intervals of 0.3 second.
|
|
13
|
+
|
|
14
|
+
Despite this delay, the test can make the execution time practical by randomly sampling targets
|
|
15
|
+
instead of hovering over all of them. When sampling is performed, the results may vary from one
|
|
16
|
+
execution to another.
|
|
17
|
+
|
|
18
|
+
An element is reported as unhoverable when it fails the Playwright actionability checks for
|
|
19
|
+
hovering, i.e. fails to be attached to the DOM, visible, stable (not or no longer animating), and
|
|
20
|
+
able to receive events. All triggers satisfy the first two conditions, so only the last two might
|
|
21
|
+
fail. Playwright defines the ability to receive events as being the target of an action on the
|
|
22
|
+
location where the center of the element is, rather than some other element with a higher zIndex
|
|
23
|
+
value in the same location being the target.
|
|
21
24
|
*/
|
|
22
25
|
|
|
23
26
|
// CONSTANTS
|
|
24
27
|
|
|
25
|
-
// Selectors of active elements likely to be disclosed by a hover.
|
|
26
|
-
const targetSelectors = ['a', 'button', 'input', '[role=menuitem]', 'span']
|
|
27
|
-
.map(selector => `${selector}:visible`)
|
|
28
|
-
.join(', ');
|
|
29
28
|
// Initialize the result.
|
|
30
29
|
const data = {
|
|
31
30
|
populationSize: 0,
|
|
32
31
|
totals: {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
impactTriggers: 0,
|
|
33
|
+
additions: 0,
|
|
34
|
+
removals: 0,
|
|
35
|
+
opacityChanges: 0,
|
|
36
|
+
opacityEffects: 0,
|
|
37
37
|
unhoverables: 0
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
// VARIABLES
|
|
42
|
-
|
|
43
|
-
// Counter.
|
|
44
|
-
let elementsChecked = 0;
|
|
45
|
-
|
|
46
41
|
// FUNCTIONS
|
|
47
42
|
|
|
48
43
|
// Samples a population and returns the sample.
|
|
@@ -66,9 +61,9 @@ const textOf = async (element, limit) => {
|
|
|
66
61
|
text = text.trim() || await element.innerHTML();
|
|
67
62
|
return text.trim().replace(/\s*/sg, '').slice(0, limit);
|
|
68
63
|
};
|
|
69
|
-
// Recursively
|
|
64
|
+
// Recursively reports impacts of hovering over triggers.
|
|
70
65
|
const find = async (withItems, page, triggers) => {
|
|
71
|
-
// If any potential
|
|
66
|
+
// If any potential triggers remain:
|
|
72
67
|
if (triggers.length) {
|
|
73
68
|
// Identify the first of them.
|
|
74
69
|
const firstTrigger = triggers[0];
|
|
@@ -79,59 +74,86 @@ const find = async (withItems, page, triggers) => {
|
|
|
79
74
|
});
|
|
80
75
|
if (tagNameJSHandle) {
|
|
81
76
|
const tagName = await tagNameJSHandle.jsonValue();
|
|
82
|
-
// Identify the root of a subtree likely to contain
|
|
77
|
+
// Identify the root of a subtree likely to contain impacted elements.
|
|
83
78
|
let root = firstTrigger;
|
|
84
|
-
if (['A', 'BUTTON'].includes(tagName)) {
|
|
79
|
+
if (['A', 'BUTTON', 'LI'].includes(tagName)) {
|
|
85
80
|
const rootJSHandle = await page.evaluateHandle(
|
|
86
81
|
firstTrigger => {
|
|
87
82
|
const parent = firstTrigger.parentElement || firstTrigger;
|
|
88
83
|
const grandparent = parent.parentElement || parent;
|
|
89
|
-
|
|
84
|
+
const greatGrandparent = grandparent.parentElement || parent;
|
|
85
|
+
return firstTrigger.tagName === 'LI' ? grandparent : greatGrandparent;
|
|
90
86
|
},
|
|
91
87
|
firstTrigger
|
|
92
88
|
);
|
|
93
89
|
root = rootJSHandle.asElement();
|
|
94
90
|
}
|
|
95
|
-
// Identify the visible active descendants of the root before the hover.
|
|
96
|
-
const preVisibles = await root.$$(targetSelectors);
|
|
97
91
|
// Identify all the descendants of the root.
|
|
98
|
-
const
|
|
99
|
-
// Identify their opacities
|
|
100
|
-
const preOpacities = await page.evaluate(
|
|
101
|
-
|
|
102
|
-
);
|
|
92
|
+
const preDescendants = await root.$$('*');
|
|
93
|
+
// Identify their opacities.
|
|
94
|
+
const preOpacities = await page.evaluate(elements => elements.map(
|
|
95
|
+
element => window.getComputedStyle(element).opacity
|
|
96
|
+
), preDescendants);
|
|
103
97
|
try {
|
|
104
|
-
// Hover over the
|
|
105
|
-
await firstTrigger.hover({
|
|
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
|
-
|
|
98
|
+
// Hover over the trigger.
|
|
99
|
+
await firstTrigger.hover({
|
|
100
|
+
timeout: 500,
|
|
101
|
+
noWaitAfter: true
|
|
102
|
+
});
|
|
103
|
+
// Repeatedly seeks impacts.
|
|
104
|
+
const getImpacts = async (interval, triesLeft) => {
|
|
105
|
+
if (triesLeft--) {
|
|
106
|
+
const postDescendants = await root.$$('*');
|
|
107
|
+
const remainerIndexes = await page.evaluate(args => {
|
|
108
|
+
const preDescendants = args[0];
|
|
109
|
+
const postDescendants = args[1];
|
|
110
|
+
const remainerIndexes = preDescendants
|
|
111
|
+
.map((element, index) => postDescendants.includes(element) ? index : -1)
|
|
112
|
+
.filter(index => index > -1);
|
|
113
|
+
return remainerIndexes;
|
|
114
|
+
}, [preDescendants, postDescendants]);
|
|
115
|
+
const additionCount = postDescendants.length - remainerIndexes.length;
|
|
116
|
+
const removalCount = preDescendants.length - remainerIndexes.length;
|
|
117
|
+
const remainers = [];
|
|
118
|
+
for (const index of remainerIndexes) {
|
|
119
|
+
remainers.push({
|
|
120
|
+
element: preDescendants[index],
|
|
121
|
+
preOpacity: preOpacities[index],
|
|
122
|
+
postOpacity: await page.evaluate(
|
|
123
|
+
element => window.getComputedStyle(element).opacity, preDescendants[index]
|
|
124
|
+
)
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const opacityChangers = remainers
|
|
128
|
+
.filter(remainer => remainer.postOpacity !== remainer.preOpacity);
|
|
129
|
+
const opacityImpact = await page.evaluate(changers => changers.reduce(
|
|
130
|
+
(total, current) => total + current.element.querySelectorAll('*').length, 0
|
|
131
|
+
), opacityChangers);
|
|
132
|
+
if (additionCount || removalCount || opacityChangers.length) {
|
|
133
|
+
return {
|
|
134
|
+
additionCount,
|
|
135
|
+
removalCount,
|
|
136
|
+
opacityChangers,
|
|
137
|
+
opacityImpact
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
return await new Promise(resolve => {
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
resolve(getImpacts(interval, triesLeft));
|
|
144
|
+
}, interval);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
return null;
|
|
133
150
|
}
|
|
134
|
-
|
|
151
|
+
};
|
|
152
|
+
// Repeatedly seek impacts of the hover at intervals.
|
|
153
|
+
const impacts = await getImpacts(300, 4);
|
|
154
|
+
// If there were any:
|
|
155
|
+
if (impacts) {
|
|
156
|
+
// Hover over the upper-left corner of the page, to undo any impacts.
|
|
135
157
|
await page.hover('body', {
|
|
136
158
|
position: {
|
|
137
159
|
x: 0,
|
|
@@ -141,54 +163,35 @@ const find = async (withItems, page, triggers) => {
|
|
|
141
163
|
// Wait for any delayed and/or slowed hover reaction.
|
|
142
164
|
await page.waitForTimeout(200);
|
|
143
165
|
await root.waitForElementState('stable');
|
|
144
|
-
// Increment the counts of triggers and
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
data.totals.
|
|
148
|
-
data.totals.
|
|
149
|
-
data.totals.
|
|
166
|
+
// Increment the counts of triggers and impacts.
|
|
167
|
+
const {additionCount, removalCount, opacityChangers, opacityImpact} = impacts;
|
|
168
|
+
data.totals.impactTriggers++;
|
|
169
|
+
data.totals.additions += additionCount;
|
|
170
|
+
data.totals.removals += removalCount;
|
|
171
|
+
data.totals.opacityChanges += opacityChangers.length;
|
|
172
|
+
data.totals.opacityImpact += opacityImpact;
|
|
150
173
|
// If details are to be reported:
|
|
151
174
|
if (withItems) {
|
|
152
175
|
// Report them.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const postVisibles = args[2];
|
|
162
|
-
const madeVisible = postVisibles
|
|
163
|
-
.filter(el => ! preVisibles.includes(el))
|
|
164
|
-
.map(el => ({
|
|
165
|
-
tagName: el.tagName,
|
|
166
|
-
text: textOf(el, 50)
|
|
167
|
-
}));
|
|
168
|
-
const opacityChanged = args[3].map(el => ({
|
|
169
|
-
tagName: el.tagName,
|
|
170
|
-
text: textOf(el, 50)
|
|
171
|
-
}));
|
|
172
|
-
return {
|
|
173
|
-
tagName: trigger.tagName,
|
|
174
|
-
id: trigger.id || '',
|
|
175
|
-
text: textOf(trigger, 50),
|
|
176
|
-
madeVisible,
|
|
177
|
-
opacityChanged
|
|
178
|
-
};
|
|
179
|
-
}, [firstTrigger, preVisibles, postVisibles, opacityTargets]);
|
|
180
|
-
const triggerData = await triggerDataJSHandle.jsonValue();
|
|
181
|
-
data.items.triggers.push(triggerData);
|
|
176
|
+
data.items.impactTriggers.push({
|
|
177
|
+
tagName,
|
|
178
|
+
text: await textOf(firstTrigger, 50),
|
|
179
|
+
additions: additionCount,
|
|
180
|
+
removals: removalCount,
|
|
181
|
+
opacityChanges: opacityChangers.length,
|
|
182
|
+
opacityImpact
|
|
183
|
+
});
|
|
182
184
|
}
|
|
183
185
|
}
|
|
184
186
|
}
|
|
185
187
|
catch (error) {
|
|
186
|
-
console.log(
|
|
188
|
+
console.log(`ERROR hovering (${error.message})`);
|
|
187
189
|
data.totals.unhoverables++;
|
|
188
190
|
if (withItems) {
|
|
191
|
+
const id = await firstTrigger.getAttribute('id');
|
|
189
192
|
data.items.unhoverables.push({
|
|
190
|
-
tagName
|
|
191
|
-
id:
|
|
193
|
+
tagName,
|
|
194
|
+
id: id || '',
|
|
192
195
|
text: await textOf(firstTrigger, 50)
|
|
193
196
|
});
|
|
194
197
|
}
|
|
@@ -204,18 +207,13 @@ exports.reporter = async (page, sampleSize = Infinity, withItems) => {
|
|
|
204
207
|
if (withItems) {
|
|
205
208
|
// Add properties for details to the initialized result.
|
|
206
209
|
data.items = {
|
|
207
|
-
|
|
210
|
+
impactTriggers: [],
|
|
208
211
|
unhoverables: []
|
|
209
212
|
};
|
|
210
213
|
}
|
|
211
214
|
// Identify the triggers.
|
|
212
|
-
const selectors = [
|
|
213
|
-
|
|
214
|
-
'body button:visible',
|
|
215
|
-
'body li:visible, body [onmouseenter]:visible',
|
|
216
|
-
'body [onmouseover]:visible'
|
|
217
|
-
];
|
|
218
|
-
const triggers = await page.$$(selectors.join(', '))
|
|
215
|
+
const selectors = ['a', 'button', 'li', '[onmouseenter]', '[onmouseover]'];
|
|
216
|
+
const triggers = await page.$$(selectors.map(selector => `body ${selector}:visible`).join(', '))
|
|
219
217
|
.catch(error => {
|
|
220
218
|
console.log(`ERROR getting hover triggers (${error.message})`);
|
|
221
219
|
data.prevented = true;
|
|
@@ -225,7 +223,7 @@ exports.reporter = async (page, sampleSize = Infinity, withItems) => {
|
|
|
225
223
|
const triggerCount = triggers.length;
|
|
226
224
|
data.populationSize = triggerCount;
|
|
227
225
|
const triggerSample = triggerCount > sampleSize ? getSample(triggers, sampleSize) : triggers;
|
|
228
|
-
// Find and document the hover-triggered
|
|
226
|
+
// Find and document the hover-triggered impacts.
|
|
229
227
|
await find(withItems, page, triggerSample);
|
|
230
228
|
// If the triggers were sampled:
|
|
231
229
|
if (triggerCount > sampleSize) {
|