Skip to content
This repository was archived by the owner on Mar 19, 2021. It is now read-only.

Commit 819a234

Browse files
jugglinmikefoolip
authored andcommitted
[testharness.js] Implement single_test option (#19449)
The behavior and deployment schedule of this option was defined via the following web-platform-tests RFC: https://github.com/web-platform-tests/rfcs/blob/master/rfcs/single_test.md
1 parent 019a83d commit 819a234

File tree

4 files changed

+192
-5
lines changed

4 files changed

+192
-5
lines changed

docs/writing-tests/testharness-api.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,11 +261,8 @@ wrapping everything in functions for isolation becomes
261261
burdensome. For these cases `testharness.js` support "single page
262262
tests".
263263

264-
In order for a test to be interpreted as a single page test, then
265-
it must simply not call `test()` or `async_test()` anywhere on the page, and
266-
must call the `done()` function to indicate that the test is complete. All
267-
the `assert_*` functions are available as normal, but are called without
268-
the normal step function wrapper. For example:
264+
In order for a test to be interpreted as a single page test, it should set the
265+
`single_test` [setup option](#setup) to `true`.
269266

270267
```html
271268
<!doctype html>
@@ -274,13 +271,20 @@ the normal step function wrapper. For example:
274271
<script src="/resources/testharnessreport.js"></script>
275272
<body>
276273
<script>
274+
setup({ single_test: true });
277275
assert_equals(document.body, document.getElementsByTagName("body")[0])
278276
done()
279277
</script>
280278
```
281279

282280
The test title for single page tests is always taken from `document.title`.
283281

282+
(Prior to October 2019, this behavior would be enabled implicitly for any test
283+
that did not call `test()` or `async_test()` anywhere on the page and that
284+
eventually called the `done()` function. [This behavior was found to be prone
285+
to errors and is in the process of being
286+
removed.](https://github.com/web-platform-tests/rfcs/blob/master/rfcs/single_test.md))
287+
284288
## Making assertions ##
285289

286290
Functions for making assertions start `assert_`. The full list of
@@ -426,6 +430,13 @@ needed when e.g. testing the `window.onerror` handler.
426430
427431
`timeout_multiplier` - Multiplier to apply to per-test timeouts.
428432
433+
`single_test` - Test authors may set this property to `true` to enable [the
434+
"single page test" mode of testharness.js](#single-page-tests); the current
435+
test must not declare any subtests; testharness.js will interpret all events
436+
which normally influence the harness status (e.g. uncaught exceptions,
437+
unhandled promise rejections, and timeouts) in terms of a single
438+
implicitly-defined subtest.
439+
429440
## Determining when all tests are complete ##
430441
431442
By default the test harness will assume there are no more results to come

resources/test/nested-testharness.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict';
2+
3+
/**
4+
* Execute testharness.js and one or more scripts in an iframe. Report the
5+
* results of the execution.
6+
*
7+
* @param {...function|...string} bodies - a function body. If specified as a
8+
* function object, it will be
9+
* serialized to a string using the
10+
* built-in
11+
* `Function.prototype.toString` prior
12+
* to inclusion in the generated
13+
* iframe.
14+
*
15+
* @returns {Promise} eventual value describing the result of the test
16+
* execution; the summary object has two properties:
17+
* `harness` (a string describing the harness status) and
18+
* `tests` (an object whose "own" property names are the
19+
* titles of the defined sub-tests and whose associated
20+
* values are the subtest statuses).
21+
*/
22+
function makeTest(...bodies) {
23+
const closeScript = '<' + '/script>';
24+
let src = `
25+
<!DOCTYPE HTML>
26+
<html>
27+
<head>
28+
<title>Document title</title>
29+
<script src="/resources/testharness.js?${Math.random()}">${closeScript}
30+
</head>
31+
32+
<body>
33+
<div id="log"></div>`;
34+
bodies.forEach((body) => {
35+
src += '<script>(' + body + ')();' + closeScript;
36+
});
37+
38+
const iframe = document.createElement('iframe');
39+
40+
document.body.appendChild(iframe);
41+
iframe.contentDocument.write(src);
42+
43+
return new Promise((resolve) => {
44+
window.addEventListener('message', function onMessage(e) {
45+
if (e.source !== iframe.contentWindow) {
46+
return;
47+
}
48+
if (!e.data || e.data.type !=='complete') {
49+
return;
50+
}
51+
window.removeEventListener('message', onMessage);
52+
resolve(e.data);
53+
});
54+
55+
iframe.contentDocument.close();
56+
}).then(({ tests, status }) => {
57+
const summary = {
58+
harness: getEnumProp(status, status.status),
59+
tests: {}
60+
};
61+
62+
tests.forEach((test) => {
63+
summary.tests[test.name] = getEnumProp(test, test.status);
64+
});
65+
66+
return summary;
67+
});
68+
}
69+
70+
function getEnumProp(object, value) {
71+
for (let property in object) {
72+
if (!/^[A-Z]+$/.test(property)) {
73+
continue;
74+
}
75+
76+
if (object[property] === value) {
77+
return property;
78+
}
79+
}
80+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<!DOCTYPE HTML>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="timeout" content="long">
6+
<script src="/resources/testharness.js"></script>
7+
<script src="../../nested-testharness.js"></script>
8+
<title>single_test</title>
9+
</head>
10+
<body>
11+
<script>
12+
promise_test(() => {
13+
return makeTest(
14+
() => {
15+
setup({ single_test: true });
16+
done();
17+
}
18+
).then(({harness, tests}) => {
19+
assert_equals(harness, 'OK');
20+
assert_equals(tests['Document title'], 'PASS');
21+
});
22+
}, 'Expected usage');
23+
24+
promise_test(() => {
25+
return makeTest(
26+
() => {
27+
setup({ single_test: true });
28+
throw new Error('this error is expected');
29+
}
30+
).then(({harness, tests}) => {
31+
assert_equals(harness, 'OK');
32+
assert_equals(tests['Document title'], 'FAIL');
33+
});
34+
}, 'Uncaught exception');
35+
36+
promise_test(() => {
37+
return makeTest(
38+
() => {
39+
setup({ single_test: true });
40+
Promise.reject(new Error('this error is expected'));
41+
}
42+
).then(({harness, tests}) => {
43+
assert_equals(harness, 'OK');
44+
assert_equals(tests['Document title'], 'FAIL');
45+
});
46+
}, 'Unhandled rejection');
47+
48+
promise_test(() => {
49+
return makeTest(
50+
() => {
51+
setup({ single_test: true });
52+
test(function() {}, 'sync test');
53+
}
54+
).then(({harness, tests}) => {
55+
assert_equals(harness, 'OK');
56+
assert_equals(tests['Document title'], 'FAIL');
57+
assert_equals(
58+
Object.keys(tests).length, 1, 'no additional subtests created'
59+
);
60+
});
61+
}, 'Erroneous usage: subtest declaration (synchronous test)');
62+
63+
promise_test(() => {
64+
return makeTest(
65+
() => {
66+
setup({ single_test: true });
67+
async_test(function(t) { t.done(); }, 'async test');
68+
}
69+
).then(({harness, tests}) => {
70+
assert_equals(harness, 'OK');
71+
assert_equals(tests['Document title'], 'FAIL');
72+
assert_equals(
73+
Object.keys(tests).length, 1, 'no additional subtests created'
74+
);
75+
});
76+
}, 'Erroneous usage: subtest declaration (asynchronous test)');
77+
78+
promise_test(() => {
79+
return makeTest(
80+
() => {
81+
setup({ single_test: true });
82+
promise_test(function() { return Promise.resolve(); }, 'promise test');
83+
}
84+
).then(({harness, tests}) => {
85+
assert_equals(harness, 'OK');
86+
assert_equals(tests['Document title'], 'FAIL');
87+
assert_equals(
88+
Object.keys(tests).length, 1, 'no additional subtests created'
89+
);
90+
});
91+
}, 'Erroneous usage: subtest declaration (promise test)');
92+
</script>
93+
</body>
94+
</html>

resources/testharness.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,6 +2475,8 @@ policies and contribution forms [3].
24752475
{
24762476
clearTimeout(this.timeout_id);
24772477
}
2478+
} else if (p == "single_test" && value) {
2479+
this.set_file_is_test();
24782480
} else if (p == "timeout_multiplier") {
24792481
this.timeout_multiplier = value;
24802482
if (this.timeout_length) {

0 commit comments

Comments
 (0)