testaro 5.13.2 → 5.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/README.md CHANGED
@@ -442,11 +442,16 @@ For example, a `test` command might have this `expect` property:
442
442
 
443
443
  That would state the expectation that the `result` property of the `acts` item for that test in the report will have a `total.links` property with the value 5, a `total.links.underlined` property with a value less than 6, **no** `total.links.outlined` property, and a `docLang` property with a value different from `es-ES`.
444
444
 
445
+ The first item in each array is an identifier of a property within the `result` property. The item has the format of a string with `.` delimiters. Each `.`-delimited segment its the name of the next property in the hierarchy. If the current object is an array, the next segment must be a non-negative integer, representing the index of an element of the array. For example, `items.1.attributes.0` references the first element of the array that is the `attributes` property of the object that is the second element of the array that is the `items` property of `result`. (In JavaScript, this would be written `items[1].attributes[0]`, but in the `expect` property all property names are `.`-delimited.)
446
+
445
447
  The second item in each array, if there are 3 items in the array, is an operator, drawn from:
446
448
  - `<`: less than
447
449
  - `=`: equal to
448
450
  - `>`: greater than
449
451
  - `!`: unequal to
452
+ - `i`: includes
453
+
454
+ The third item in each array, if there are 3 items in the array, is the criterion with which the value of the first property is compared.
450
455
 
451
456
  A typical use for an `expect` property is checking the correctness of a Testaro test. Thus, the validation scripts in the `validation/tests/scripts` directory all contain `test` commands with `expect` properties. See the “Validation” section below.
452
457
 
@@ -593,9 +598,9 @@ In order to make network watching possible, you must define these environment va
593
598
 
594
599
  As mentioned above, using the high-level method to run Testaro jobs requires `SCRIPTDIR`, `BATCHDIR`, and `REPORTDIR` environment variables.
595
600
 
596
- If a `tenon` test is included in the script, environment variables named `TENON_USER` and `TENON_PASSWORD` must exist, with your Tenon username and password, respectively, as their values.
601
+ If a `tenon` test is included in the script, environment variables named `TENON_USER` and `TENON_PASSWORD` must exist, with your Tenon username and password, respectively, as their values. You can obtain those from [Tenon](https://tenon.io/documentation/overview).
597
602
 
598
- If a `wave` test is included in the script, an environment variable named `WAVE_KEY` must exist, with your WAVE API key as its value.
603
+ If a `wave` test is included in the script, an environment variable named `WAVE_KEY` must exist, with your WAVE API key as its value. You can get it from [WebAIM](https://wave.webaim.org/api/).
599
604
 
600
605
  The `text` command can interpolate the value of an environment variable into text that it enters on a page, as documented in the `commands.js` file.
601
606
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "5.13.2",
3
+ "version": "5.14.0",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/run.js CHANGED
@@ -568,23 +568,34 @@ const isTrue = (object, specs) => {
568
568
  propertyTree.shift();
569
569
  actual = actual[propertyTree[0]];
570
570
  }
571
- // Determine whether the expectation was fulfilled.
572
- if (relation === '=') {
573
- satisfied = actual === criterion;
571
+ if (actual === undefined) {
572
+ return [null, false];
574
573
  }
575
- else if (relation === '<') {
576
- satisfied = actual < criterion;
577
- }
578
- else if (relation === '>') {
579
- satisfied = actual > criterion;
580
- }
581
- else if (relation === '!') {
582
- satisfied = actual !== criterion;
583
- }
584
- else if (! relation) {
585
- satisfied = actual === undefined;
574
+ else {
575
+ // Determine whether the expectation was fulfilled.
576
+ if (relation === '=') {
577
+ satisfied = actual === criterion;
578
+ }
579
+ else if (relation === '<') {
580
+ satisfied = actual < criterion;
581
+ }
582
+ else if (relation === '>') {
583
+ satisfied = actual > criterion;
584
+ }
585
+ else if (relation === '!') {
586
+ satisfied = actual !== criterion;
587
+ }
588
+ else if (relation === 'i') {
589
+ satisfied = actual.includes(criterion);
590
+ }
591
+ else if (relation === '!i') {
592
+ satisfied = ! actual.includes(criterion);
593
+ }
594
+ else if (! relation) {
595
+ satisfied = actual === undefined;
596
+ }
597
+ return [actual, satisfied];
586
598
  }
587
- return [actual, satisfied];
588
599
  };
589
600
  // Adds a wait error result to an act.
590
601
  const waitError = (page, act, error, what) => {
@@ -0,0 +1,48 @@
1
+ /*
2
+ allHidden
3
+ This test reports a page that is entirely or mainly hidden.
4
+ */
5
+ exports.reporter = async page => {
6
+ const data = await page.evaluate(() => {
7
+ const {body} = document;
8
+ const main = body ? body.querySelector('main') : null;
9
+ const styles = {
10
+ doc: window.getComputedStyle(document.documentElement),
11
+ body: body ? window.getComputedStyle(body) : null,
12
+ main: main ? window.getComputedStyle(main) : null
13
+ };
14
+ const data = {
15
+ hidden: {
16
+ document: document.documentElement.hidden,
17
+ body: body && body.hidden,
18
+ main: main && main.hidden
19
+ },
20
+ reallyHidden: {
21
+ document: false,
22
+ body: false,
23
+ main: false
24
+ },
25
+ display: {
26
+ document: styles.doc && styles.doc.display,
27
+ body: styles.body && styles.body.display,
28
+ main: styles.main && styles.main.display
29
+ },
30
+ visHidden: {
31
+ document: styles.doc && styles.doc.visibility === 'hidden',
32
+ body: styles.body && styles.body.visibility === 'hidden',
33
+ main: styles.main && styles.main.visibility === 'hidden'
34
+ },
35
+ ariaHidden: {
36
+ document: document.documentElement.ariaHidden === 'true',
37
+ body: body && body.ariaHidden === 'true',
38
+ main: main && main.ariaHidden === 'true'
39
+ }
40
+ };
41
+ ['document', 'body', 'main'].forEach(element => {
42
+ if (data.hidden[element] && ! ['block', 'flex'].includes(data.display[element])) {
43
+ data.reallyHidden[element] = true;
44
+ }
45
+ });
46
+ });
47
+ return {result: data};
48
+ };
package/tests/elements.js CHANGED
@@ -8,8 +8,8 @@
8
8
  3. Data on each specified element also include data on its sibling nodes.
9
9
  */
10
10
  exports.reporter = async (page, detailLevel, tagName, onlyVisible, attribute) => {
11
- // Determine a selector of the specified elements.
12
- let selector = tagName || '*';
11
+ // Determine a selector of the specified elements, including any descendants of open shadow roots.
12
+ let selector = `body ${tagName ? tagName.toLowerCase() : '*'}`;
13
13
  if (attribute) {
14
14
  selector += `[${attribute}]`;
15
15
  }
@@ -19,7 +19,8 @@ exports.reporter = async (page, detailLevel, tagName, onlyVisible, attribute) =>
19
19
  let data = {};
20
20
  // Get the data on the elements.
21
21
  try {
22
- data = await page.$$eval(selector, (elements, detailLevel) => {
22
+ const locator = page.locator(selector);
23
+ data = await locator.evaluateAll((elements, detailLevel) => {
23
24
  // FUNCTION DEFINITIONS START
24
25
  // Compacts a string.
25
26
  const compact = string => string.replace(/\s+/g, ' ').trim();
@@ -28,10 +29,11 @@ exports.reporter = async (page, detailLevel, tagName, onlyVisible, attribute) =>
28
29
  const sibInfo = {
29
30
  type: nodeType
30
31
  };
32
+ // If the sibling node is an element:
31
33
  if (nodeType === 1) {
32
34
  sibInfo.tagName = node.tagName;
33
35
  sibInfo.attributes = [];
34
- node.attributes.forEach(attribute => {
36
+ Array.from(node.attributes).forEach(attribute => {
35
37
  sibInfo.attributes.push({
36
38
  name: attribute.name,
37
39
  value: attribute.value
@@ -106,12 +108,12 @@ exports.reporter = async (page, detailLevel, tagName, onlyVisible, attribute) =>
106
108
  }
107
109
  // If the parental text content is required:
108
110
  if (detailLevel > 1) {
109
- // Add it to the element data.
110
- datum.parentTextContent = parent ? parent.textContent : '';
111
+ // Add it (excluding any shadow-root descendants) to the element data.
112
+ datum.parentTextContent = parent ? compact(parent.textContent) : '';
111
113
  }
112
114
  // If sibling itemization is required:
113
115
  if (detailLevel === 3) {
114
- // Add the sibling data to the element data.
116
+ // Add data on the siblings, excluding any descendants of shadow roots, to the data.
115
117
  datum.siblings = {
116
118
  before: [],
117
119
  after: []
@@ -1,15 +1,17 @@
1
- // app.js
2
- // Validator for Testaro tests.
1
+ // test.js
2
+ // Validator for one Testaro test.
3
+ // Execution example: node validation/executors/test focOp
3
4
 
4
5
  const fs = require('fs').promises;
5
6
  const {handleRequest} = require(`${__dirname}/../../run`);
7
+ const test = process.argv[2];
6
8
  const validateTests = async () => {
7
9
  const totals = {
8
10
  attempts: 0,
9
11
  successes: 0
10
12
  };
11
13
  const scriptFileNames = await fs.readdir(`${__dirname}/../tests/scripts`);
12
- for (const scriptFileName of scriptFileNames.filter(fileName => fileName === 'styleDiff.json')) {
14
+ for (const scriptFileName of scriptFileNames.filter(fileName => fileName === `${test}.json`)) {
13
15
  const rawScriptJSON = await fs
14
16
  .readFile(`${__dirname}/../tests/scripts/${scriptFileName}`, 'utf8');
15
17
  const scriptJSON = rawScriptJSON
@@ -1,4 +1,4 @@
1
- // app.js
1
+ // tests.js
2
2
  // Validator for Testaro tests.
3
3
 
4
4
  const fs = require('fs').promises;
@@ -1,4 +1,4 @@
1
- // watchDir.js
1
+ // watchNet.js
2
2
  // Validator for network watching.
3
3
 
4
4
  const fs = require('fs/promises');
@@ -0,0 +1,37 @@
1
+ {
2
+ "what": "validation of docType test",
3
+ "strict": true,
4
+ "commands": [
5
+ {
6
+ "type": "launch",
7
+ "which": "chromium",
8
+ "what": "usual browser"
9
+ },
10
+ {
11
+ "type": "url",
12
+ "which": "__targets__/docType/good.html",
13
+ "what": "page with doctype"
14
+ },
15
+ {
16
+ "type": "test",
17
+ "which": "docType",
18
+ "what": "doctype",
19
+ "expect": [
20
+ ["docHasType", "=", true]
21
+ ]
22
+ },
23
+ {
24
+ "type": "url",
25
+ "which": "__targets__/docType/bad.html",
26
+ "what": "page without doctype"
27
+ },
28
+ {
29
+ "type": "test",
30
+ "which": "docType",
31
+ "what": "doctype",
32
+ "expect": [
33
+ ["docHasType", "=", false]
34
+ ]
35
+ }
36
+ ]
37
+ }
@@ -0,0 +1,131 @@
1
+ {
2
+ "what": "validation of elements test",
3
+ "strict": true,
4
+ "commands": [
5
+ {
6
+ "type": "launch",
7
+ "which": "chromium",
8
+ "what": "usual browser"
9
+ },
10
+ {
11
+ "type": "url",
12
+ "which": "__targets__/elements/index.html",
13
+ "what": "page with a shadow root"
14
+ },
15
+ {
16
+ "type": "test",
17
+ "which": "elements",
18
+ "what": "elements",
19
+ "detailLevel": 0,
20
+ "expect": [
21
+ ["total", "=", 21]
22
+ ]
23
+ },
24
+ {
25
+ "type": "test",
26
+ "which": "elements",
27
+ "what": "elements",
28
+ "detailLevel": 0,
29
+ "tagName": "P",
30
+ "expect": [
31
+ ["total", "=", 5]
32
+ ]
33
+ },
34
+ {
35
+ "type": "test",
36
+ "which": "elements",
37
+ "what": "elements",
38
+ "detailLevel": 0,
39
+ "tagName": "LI",
40
+ "onlyVisible": true,
41
+ "expect": [
42
+ ["total", "=", 2]
43
+ ]
44
+ },
45
+ {
46
+ "type": "test",
47
+ "which": "elements",
48
+ "what": "elements",
49
+ "detailLevel": 0,
50
+ "tagName": "P",
51
+ "attribute": "class",
52
+ "expect": [
53
+ ["total", "=", 2]
54
+ ]
55
+ },
56
+ {
57
+ "type": "test",
58
+ "which": "elements",
59
+ "what": "elements",
60
+ "detailLevel": 1,
61
+ "tagName": "LI",
62
+ "attribute": "class=first",
63
+ "expect": [
64
+ ["total", "=", 1],
65
+ ["items.0.tagName", "=", "LI"],
66
+ ["items.0.parentTagName", "=", "UL"],
67
+ ["items.0.code", "i", "first"],
68
+ ["items.0.attributes.0.name", "=", "class"],
69
+ ["items.0.attributes.0.value", "=", "first"],
70
+ ["items.0.textContent", "=", "Io"]
71
+ ]
72
+ },
73
+ {
74
+ "type": "test",
75
+ "which": "elements",
76
+ "what": "elements",
77
+ "detailLevel": 1,
78
+ "tagName": "INPUT",
79
+ "expect": [
80
+ ["total", "=", 2],
81
+ ["items.0.labels.1", "i", "anything"],
82
+ ["items.1.labelers.0", "i", "something else"]
83
+ ]
84
+ },
85
+ {
86
+ "type": "test",
87
+ "which": "elements",
88
+ "what": "elements",
89
+ "detailLevel": 2,
90
+ "tagName": "LI",
91
+ "attribute": "class=first",
92
+ "expect": [
93
+ ["items.0.textContent", "=", "Io"],
94
+ ["items.0.parentTextContent", "i", "tute"]
95
+ ]
96
+ },
97
+ {
98
+ "type": "test",
99
+ "which": "elements",
100
+ "what": "elements",
101
+ "detailLevel": 2,
102
+ "tagName": "LI",
103
+ "onlyVisible": true,
104
+ "attribute": "class",
105
+ "expect": [
106
+ ["total", "=", 1],
107
+ ["items.0.parentTextContent", "i", "alia"],
108
+ ["items.0.parentTextContent", "i", "tute"]
109
+ ]
110
+ },
111
+ {
112
+ "type": "test",
113
+ "which": "elements",
114
+ "what": "elements",
115
+ "detailLevel": 3,
116
+ "tagName": "P",
117
+ "attribute": "class=first",
118
+ "expect": [
119
+ ["items.0.textContent", "=", "This is a paragraph."],
120
+ ["items.0.parentTagName", "=", "MAIN"],
121
+ ["items.0.parentTextContent", "i", "something."],
122
+ ["items.0.siblings.before.0.type", "=", 3],
123
+ ["items.0.siblings.before.1.type", "=", 1],
124
+ ["items.0.siblings.before.1.tagName", "=", "H1"],
125
+ ["items.0.siblings.after.12.type", "=", 1],
126
+ ["items.0.siblings.after.12.tagName", "=", "LABEL"],
127
+ ["items.0.siblings.after.12.attributes.0.name", "=", "for"]
128
+ ]
129
+ }
130
+ ]
131
+ }
@@ -0,0 +1,14 @@
1
+ <html lang="en-US">
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <title>Page without doctype</title>
5
+ <meta name="description" content="tester">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ </head>
8
+ <body>
9
+ <main>
10
+ <h1>Page without doctype</h1>
11
+ <p>This page has no doctype.</p>
12
+ </main>
13
+ </body>
14
+ </html>
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en-US">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Page with doctype</title>
6
+ <meta name="description" content="tester">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ </head>
9
+ <body>
10
+ <main>
11
+ <h1>Page with doctype</h1>
12
+ <p>This page has a doctype.</p>
13
+ </main>
14
+ </body>
15
+ </html>
@@ -0,0 +1,40 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en-US">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Page</title>
6
+ <meta name="description" content="tester">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ </head>
9
+ <body>
10
+ <main>
11
+ <h1>Page</h1>
12
+ <h2>Main part</h2>
13
+ <p class="first">This is a paragraph.</p>
14
+ <p>This is a list:</p>
15
+ <ul lang="eo">
16
+ <li class="first">Io</li>
17
+ <li>Io alia</li>
18
+ <li class="last" hidden>Io tute alia</li>
19
+ </ul>
20
+ <h2>Auxiliary part</h2>
21
+ <p>This is a summary.</p>
22
+ <div id="host"></div>
23
+ <div id="guest">
24
+ <h3>Details</h3>
25
+ <p class="sole">Here are some details.</p>
26
+ </div>
27
+ <label for="input0">Input something.</label>
28
+ <input id="input0">
29
+ <label for="input0">Did you input anything?</label>
30
+ <p id="input1Label">Input something else.</p>
31
+ <input aria-labelledby="input1Label">
32
+ </main>
33
+ <script>
34
+ document
35
+ .getElementById('host')
36
+ .attachShadow({mode: 'open'})
37
+ .appendChild(document.getElementById('guest'));
38
+ </script>
39
+ </body>
40
+ </html>