testaro 4.12.2 → 4.14.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "4.12.2",
3
+ "version": "4.14.1",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/run.js CHANGED
@@ -70,16 +70,17 @@ const tenonData = {
70
70
  };
71
71
  // Keywords in log messages indicating errors.
72
72
  const errorWords = [
73
- 'failed',
73
+ 'content security policy',
74
+ 'deprecated',
74
75
  'error',
75
- 'suspicious',
76
+ 'failed',
77
+ 'missing',
78
+ 'but not used',
76
79
  'refused',
77
- 'content security policy',
78
- 'unrecognized',
79
80
  'requires',
80
- 'warning',
81
- 'missing',
82
- 'deprecated'
81
+ 'suspicious',
82
+ 'unrecognized',
83
+ 'warning'
83
84
  ];
84
85
 
85
86
  // ########## VARIABLES
@@ -288,9 +289,12 @@ const launch = async typeName => {
288
289
  browserContext = await browser.newContext(viewport);
289
290
  // When a page is added to the browser context:
290
291
  browserContext.on('page', page => {
291
- // Make its console messages get reported and appear in the Playwright console.
292
+ // Make abbreviations of its console messages get reported in the Playwright console.
292
293
  page.on('console', msg => {
293
- const msgText = msg.text();
294
+ let msgText = msg.text();
295
+ if (msgText.length > 300) {
296
+ msgText = `${msgText.slice(0, 150)} ... ${msgText.slice(-150)}`;
297
+ }
294
298
  console.log(`[${msgText}]`);
295
299
  const msgTextLC = msgText.toLowerCase();
296
300
  const msgLength = msgText.length;
package/tests/focInd.js CHANGED
@@ -128,7 +128,7 @@ exports.reporter = async (page, revealAll, allowedDelay, withItems) => {
128
128
  if (! hasOutline) {
129
129
  // Returns whether a style property differs between focused and not focused.
130
130
  const diff = prop => styleDec[prop] !== styleBlurred[prop];
131
- // Determine whether the element has another allowed focus indicator.
131
+ // Determine whether the element has another recognized focus indicator.
132
132
  const hasDiffOutline = styleDec.outlineWidth !== '0px'
133
133
  && styleDec.outlineColor !== 'rgba(0, 0, 0, 0)'
134
134
  && (diff('outlineStyle') || diff('outlineWidth'));
@@ -138,6 +138,7 @@ exports.reporter = async (page, revealAll, allowedDelay, withItems) => {
138
138
  const hasIndicator
139
139
  = hasDiffOutline
140
140
  || hasDiffBorder
141
+ || diff('box-shadow')
141
142
  || diff('fontSize')
142
143
  || diff('fontStyle')
143
144
  || diff('textDecorationLine')
package/tests/hover.js CHANGED
@@ -1,47 +1,43 @@
1
1
  /*
2
2
  hover
3
- This test reports unexpected effects of hovering. The effects include elements that are made
4
- visible, elements whose opacities are changed, elements with ancestors whose opacities are
5
- changed, and elements that cannot be hovered over. Only Playwright-visible elements in the
6
- DOM that have 'A', 'BUTTON', and 'LI' tag names or have 'onmouseenter' or 'onmouseover' attributes
7
- are considered as hovering targets. The elements considered when the effects of hovering are
8
- examined are the descendants of the grandparent of the element hovered over if that element
9
- has the tag name 'A' or 'BUTTON' or otherwise the descendants of the element. The only
10
- elements counted as being made visible by hovering are those with tag names 'A', 'BUTTON',
11
- 'INPUT', and 'SPAN', and those with 'role="menuitem"' attributes. The test waits 700 ms after
12
- each hover in case of delayed effects. Despite this delay, the test can make the execution
13
- time practical by randomly sampling targets instead of hovering over all of them. When
14
- sampling is performed, the results may vary from one execution to another. An element is
15
- reported as unhoverable when it fails the Playwright actionability checks for hovering, i.e.
16
- when it fails to be attached to the DOM, visible, stable (not or no longer animating), and
17
- able to receive events. All target candidates satisfy the first two conditions, so only the
18
- last two might fail. Playwright defines the ability to receive events as being the target of
19
- an action on the location where the center of the element is, rather than some other element
20
- with a higher zIndex value in the same location being the target.
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 = {
30
+ populationSize: 0,
31
31
  totals: {
32
- triggers: 0,
33
- madeVisible: 0,
34
- opacityChanged: 0,
35
- opacityAffected: 0,
32
+ impactTriggers: 0,
33
+ additions: 0,
34
+ removals: 0,
35
+ opacityChanges: 0,
36
+ opacityEffects: 0,
36
37
  unhoverables: 0
37
38
  }
38
39
  };
39
40
 
40
- // VARIABLES
41
-
42
- // Counter.
43
- let elementsChecked = 0;
44
-
45
41
  // FUNCTIONS
46
42
 
47
43
  // Samples a population and returns the sample.
@@ -65,9 +61,9 @@ const textOf = async (element, limit) => {
65
61
  text = text.trim() || await element.innerHTML();
66
62
  return text.trim().replace(/\s*/sg, '').slice(0, limit);
67
63
  };
68
- // Recursively finds and reports triggers and targets.
64
+ // Recursively reports impacts of hovering over triggers.
69
65
  const find = async (withItems, page, triggers) => {
70
- // If any potential disclosure triggers remain:
66
+ // If any potential triggers remain:
71
67
  if (triggers.length) {
72
68
  // Identify the first of them.
73
69
  const firstTrigger = triggers[0];
@@ -78,63 +74,86 @@ const find = async (withItems, page, triggers) => {
78
74
  });
79
75
  if (tagNameJSHandle) {
80
76
  const tagName = await tagNameJSHandle.jsonValue();
81
- // Identify the root of a subtree likely to contain disclosed elements.
77
+ // Identify the root of a subtree likely to contain impacted elements.
82
78
  let root = firstTrigger;
83
- if (['A', 'BUTTON'].includes(tagName)) {
79
+ if (['A', 'BUTTON', 'LI'].includes(tagName)) {
84
80
  const rootJSHandle = await page.evaluateHandle(
85
81
  firstTrigger => {
86
- const parent = firstTrigger.parentElement;
87
- if (parent) {
88
- return parent.parentElement || parent;
89
- }
90
- else {
91
- return firstTrigger;
92
- }
82
+ const parent = firstTrigger.parentElement || firstTrigger;
83
+ const grandparent = parent.parentElement || parent;
84
+ const greatGrandparent = grandparent.parentElement || parent;
85
+ return firstTrigger.tagName === 'LI' ? grandparent : greatGrandparent;
93
86
  },
94
87
  firstTrigger
95
88
  );
96
89
  root = rootJSHandle.asElement();
97
90
  }
98
- // Identify the visible active descendants of the root before the hover.
99
- const preVisibles = await root.$$(targetSelectors);
100
91
  // Identify all the descendants of the root.
101
- const descendants = await root.$$('*');
102
- // Identify their opacities before the hover.
103
- const preOpacities = await page.evaluate(
104
- elements => elements.map(el => window.getComputedStyle(el).opacity), descendants
105
- );
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);
106
97
  try {
107
- // Hover over the potential trigger.
108
- await firstTrigger.hover({timeout: 700});
109
- // Identify whether it is coded as controlling other elements.
110
- const isController = await page.evaluate(
111
- element => element.ariaHasPopup || element.hasAttribute('aria-controls'), firstTrigger
112
- );
113
- // Wait for any delayed and/or slowed hover reaction, longer if coded as a controller.
114
- await page.waitForTimeout(isController ? 1200 : 600);
115
- await root.waitForElementState('stable');
116
- // Identify the visible active descendants of the root during the hover.
117
- const postVisibles = await root.$$(targetSelectors);
118
- // Identify the opacities of the descendants of the root during the hover.
119
- const postOpacities = await page.evaluate(
120
- elements => elements.map(el => window.getComputedStyle(el).opacity), descendants
121
- );
122
- // Identify the elements with opacity changes.
123
- const opacityTargets = descendants
124
- .filter((descendant, index) => postOpacities[index] !== preOpacities[index]);
125
- // Count them and their descendants.
126
- const opacityAffected = opacityTargets.length
127
- ? await page.evaluate(elements => elements.reduce(
128
- (total, current) => total + 1 + current.querySelectorAll('*').length, 0
129
- ), opacityTargets)
130
- : 0;
131
- // If hovering disclosed any element or changed any opacity:
132
- if (postVisibles.length > preVisibles.length || opacityAffected) {
133
- // Preserve the lengthened reaction wait, if any, for the next 5 tries.
134
- if (elementsChecked < 11) {
135
- elementsChecked = 5;
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
+ }
136
147
  }
137
- // Hover over the upper-left corner of the page, to undo any hover reactions.
148
+ else {
149
+ return null;
150
+ }
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.
138
157
  await page.hover('body', {
139
158
  position: {
140
159
  x: 0,
@@ -144,54 +163,35 @@ const find = async (withItems, page, triggers) => {
144
163
  // Wait for any delayed and/or slowed hover reaction.
145
164
  await page.waitForTimeout(200);
146
165
  await root.waitForElementState('stable');
147
- // Increment the counts of triggers and targets.
148
- data.totals.triggers++;
149
- const madeVisible = Math.max(0, postVisibles.length - preVisibles.length);
150
- data.totals.madeVisible += madeVisible;
151
- data.totals.opacityChanged += opacityTargets.length;
152
- data.totals.opacityAffected += opacityAffected;
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;
153
173
  // If details are to be reported:
154
174
  if (withItems) {
155
175
  // Report them.
156
- const triggerDataJSHandle = await page.evaluateHandle(args => {
157
- // Returns the text of an element.
158
- const textOf = (element, limit) => {
159
- const text = element.textContent.trim() || element.outerHTML.trim();
160
- return text.replace(/\s{2,}/sg, ' ').slice(0, limit);
161
- };
162
- const trigger = args[0];
163
- const preVisibles = args[1];
164
- const postVisibles = args[2];
165
- const madeVisible = postVisibles
166
- .filter(el => ! preVisibles.includes(el))
167
- .map(el => ({
168
- tagName: el.tagName,
169
- text: textOf(el, 50)
170
- }));
171
- const opacityChanged = args[3].map(el => ({
172
- tagName: el.tagName,
173
- text: textOf(el, 50)
174
- }));
175
- return {
176
- tagName: trigger.tagName,
177
- id: trigger.id || '',
178
- text: textOf(trigger, 50),
179
- madeVisible,
180
- opacityChanged
181
- };
182
- }, [firstTrigger, preVisibles, postVisibles, opacityTargets]);
183
- const triggerData = await triggerDataJSHandle.jsonValue();
184
- 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
+ });
185
184
  }
186
185
  }
187
186
  }
188
187
  catch (error) {
189
- console.log('ERROR hovering');
188
+ console.log(`ERROR hovering (${error.message})`);
190
189
  data.totals.unhoverables++;
191
190
  if (withItems) {
191
+ const id = await firstTrigger.getAttribute('id');
192
192
  data.items.unhoverables.push({
193
- tagName: tagName,
194
- id: firstTrigger.id || '',
193
+ tagName,
194
+ id: id || '',
195
195
  text: await textOf(firstTrigger, 50)
196
196
  });
197
197
  }
@@ -207,18 +207,13 @@ exports.reporter = async (page, sampleSize = Infinity, withItems) => {
207
207
  if (withItems) {
208
208
  // Add properties for details to the initialized result.
209
209
  data.items = {
210
- triggers: [],
210
+ impactTriggers: [],
211
211
  unhoverables: []
212
212
  };
213
213
  }
214
214
  // Identify the triggers.
215
- const selectors = [
216
- 'body a:visible',
217
- 'body button:visible',
218
- 'body li:visible, body [onmouseenter]:visible',
219
- 'body [onmouseover]:visible'
220
- ];
221
- 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(', '))
222
217
  .catch(error => {
223
218
  console.log(`ERROR getting hover triggers (${error.message})`);
224
219
  data.prevented = true;
@@ -226,8 +221,9 @@ exports.reporter = async (page, sampleSize = Infinity, withItems) => {
226
221
  });
227
222
  // If they number more than the sample size limit, sample them.
228
223
  const triggerCount = triggers.length;
224
+ data.populationSize = triggerCount;
229
225
  const triggerSample = triggerCount > sampleSize ? getSample(triggers, sampleSize) : triggers;
230
- // Find and document the hover-triggered disclosures.
226
+ // Find and document the hover-triggered impacts.
231
227
  await find(withItems, page, triggerSample);
232
228
  // If the triggers were sampled:
233
229
  if (triggerCount > sampleSize) {
package/tests/ibm.js CHANGED
@@ -25,7 +25,6 @@ const run = async content => {
25
25
  const nowLabel = (new Date()).toISOString().slice(0, 19);
26
26
  // Return the result of a test.
27
27
  const ibmReport = await getCompliance(content, nowLabel);
28
- await close();
29
28
  return ibmReport;
30
29
  };
31
30
  // Trims an IBM report.
@@ -118,5 +117,6 @@ exports.reporter = async (page, withItems, withNewContent) => {
118
117
  console.log('ERROR: Getting ibm test report from URL took too long');
119
118
  }
120
119
  }
120
+ await close();
121
121
  return {result};
122
122
  };