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 +7 -2
- package/package.json +1 -1
- package/run.js +26 -15
- package/tests/allHidden.js +48 -0
- package/tests/elements.js +9 -7
- package/validation/executors/{styleDiff.js → test.js} +5 -3
- package/validation/executors/tests.js +1 -1
- package/validation/executors/watchNet.js +1 -1
- package/validation/tests/scripts/docType.json +37 -0
- package/validation/tests/scripts/elements.json +131 -0
- package/validation/tests/targets/docType/bad.html +14 -0
- package/validation/tests/targets/docType/good.html +15 -0
- package/validation/tests/targets/elements/index.html +40 -0
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
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
|
-
|
|
572
|
-
|
|
573
|
-
satisfied = actual === criterion;
|
|
571
|
+
if (actual === undefined) {
|
|
572
|
+
return [null, false];
|
|
574
573
|
}
|
|
575
|
-
else
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
2
|
-
// Validator for Testaro
|
|
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 ===
|
|
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
|
|
@@ -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>
|