testaro 5.13.1 → 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/high.js +1 -1
- package/package.json +1 -1
- package/run.js +53 -36
- 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/high.js
CHANGED
package/package.json
CHANGED
package/run.js
CHANGED
|
@@ -304,24 +304,30 @@ const launch = async typeName => {
|
|
|
304
304
|
browserContext.on('page', async page => {
|
|
305
305
|
// Make the page current.
|
|
306
306
|
currentPage = page;
|
|
307
|
-
// Make
|
|
307
|
+
// Make its console messages get reported in the Playwright console.
|
|
308
308
|
page.on('console', msg => {
|
|
309
309
|
const msgText = msg.text();
|
|
310
|
-
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
310
|
+
let indentedMsg = '';
|
|
311
|
+
if (debug) {
|
|
312
|
+
indentedMsg = ` | ${msg.text()}`;
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
const parts = [msgText.slice(0, 75)];
|
|
316
|
+
if (msgText.length > 75) {
|
|
317
|
+
parts.push(msgText.slice(75, 150));
|
|
318
|
+
if (msgText.length > 150) {
|
|
319
|
+
const tail = msgText.slice(150).slice(-150);
|
|
320
|
+
if (msgText.length > 300) {
|
|
321
|
+
parts.push('...');
|
|
322
|
+
}
|
|
323
|
+
parts.push(tail.slice(0, 75));
|
|
324
|
+
if (tail.length > 75) {
|
|
325
|
+
parts.push(tail.slice(75));
|
|
326
|
+
}
|
|
321
327
|
}
|
|
322
328
|
}
|
|
329
|
+
indentedMsg = parts.map(part => ` | ${part}`).join('\n');
|
|
323
330
|
}
|
|
324
|
-
const indentedMsg = parts.map(part => ` | ${part}`).join('\n');
|
|
325
331
|
console.log(`\n${indentedMsg}`);
|
|
326
332
|
const msgTextLC = msgText.toLowerCase();
|
|
327
333
|
const msgLength = msgText.length;
|
|
@@ -562,23 +568,34 @@ const isTrue = (object, specs) => {
|
|
|
562
568
|
propertyTree.shift();
|
|
563
569
|
actual = actual[propertyTree[0]];
|
|
564
570
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
satisfied = actual === criterion;
|
|
568
|
-
}
|
|
569
|
-
else if (relation === '<') {
|
|
570
|
-
satisfied = actual < criterion;
|
|
571
|
-
}
|
|
572
|
-
else if (relation === '>') {
|
|
573
|
-
satisfied = actual > criterion;
|
|
574
|
-
}
|
|
575
|
-
else if (relation === '!') {
|
|
576
|
-
satisfied = actual !== criterion;
|
|
571
|
+
if (actual === undefined) {
|
|
572
|
+
return [null, false];
|
|
577
573
|
}
|
|
578
|
-
else
|
|
579
|
-
|
|
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];
|
|
580
598
|
}
|
|
581
|
-
return [actual, satisfied];
|
|
582
599
|
};
|
|
583
600
|
// Adds a wait error result to an act.
|
|
584
601
|
const waitError = (page, act, error, what) => {
|
|
@@ -682,7 +699,7 @@ const doActs = async (report, actIndex, page) => {
|
|
|
682
699
|
const result = act.result = {};
|
|
683
700
|
// If the text is to be the URL:
|
|
684
701
|
if (what === 'url') {
|
|
685
|
-
// Wait for
|
|
702
|
+
// Wait for the URL to be the exact text and quit on failure.
|
|
686
703
|
try {
|
|
687
704
|
await page.waitForURL(which, {timeout: 15000});
|
|
688
705
|
result.found = true;
|
|
@@ -695,7 +712,7 @@ const doActs = async (report, actIndex, page) => {
|
|
|
695
712
|
}
|
|
696
713
|
// Otherwise, if the text is to be a substring of the page title:
|
|
697
714
|
else if (what === 'title') {
|
|
698
|
-
// Wait for
|
|
715
|
+
// Wait for the page title to include the text, case-insensitively, and quit on failure.
|
|
699
716
|
try {
|
|
700
717
|
await page.waitForFunction(
|
|
701
718
|
text => document
|
|
@@ -717,7 +734,7 @@ const doActs = async (report, actIndex, page) => {
|
|
|
717
734
|
}
|
|
718
735
|
// Otherwise, if the text is to be a substring of the text of the page body:
|
|
719
736
|
else if (what === 'body') {
|
|
720
|
-
// Wait for
|
|
737
|
+
// Wait for the body to include the text, case-insensitively, and quit on failure.
|
|
721
738
|
try {
|
|
722
739
|
await page.waitForFunction(
|
|
723
740
|
text => document
|
|
@@ -949,13 +966,12 @@ const doActs = async (report, actIndex, page) => {
|
|
|
949
966
|
// Otherwise, if the act is a move:
|
|
950
967
|
else if (moves[act.type]) {
|
|
951
968
|
const selector = typeof moves[act.type] === 'string' ? moves[act.type] : act.what;
|
|
952
|
-
// Try to
|
|
969
|
+
// Try up to 5 times to:
|
|
953
970
|
act.result = {found: false};
|
|
954
971
|
let selection = {};
|
|
955
972
|
let tries = 0;
|
|
956
973
|
const slimText = act.which ? debloat(act.which) : '';
|
|
957
974
|
while (tries++ < 5 && ! act.result.found) {
|
|
958
|
-
// If the page still exists:
|
|
959
975
|
if (page) {
|
|
960
976
|
// Identify the elements of the specified type.
|
|
961
977
|
const selections = await page.$$(selector);
|
|
@@ -967,16 +983,17 @@ const doActs = async (report, actIndex, page) => {
|
|
|
967
983
|
let matchCount = 0;
|
|
968
984
|
const selectionTexts = [];
|
|
969
985
|
for (selection of selections) {
|
|
970
|
-
// Add its text or an empty string to the list of texts
|
|
986
|
+
// Add its lower-case text or an empty string to the list of element texts.
|
|
971
987
|
const selectionText = slimText ? await textOf(page, selection) : '';
|
|
972
988
|
selectionTexts.push(selectionText);
|
|
973
|
-
// If its text includes any specified text:
|
|
989
|
+
// If its text includes any specified text, case-insensitively:
|
|
974
990
|
if (selectionText.includes(slimText)) {
|
|
975
991
|
// If the element has the specified index among such elements:
|
|
976
992
|
if (matchCount++ === (act.index || 0)) {
|
|
977
993
|
// Report it as the matching element and stop checking.
|
|
978
994
|
act.result.found = true;
|
|
979
|
-
act.result.
|
|
995
|
+
act.result.textSpec = slimText;
|
|
996
|
+
act.result.textContent = selectionText;
|
|
980
997
|
break;
|
|
981
998
|
}
|
|
982
999
|
}
|
|
@@ -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>
|