| /* |
| * This file is intended for vendors to implement code needed to integrate |
| * testharness.js tests with their own test systems. |
| * |
| * Typically such integration will attach callbacks when each test is |
| * has run, using add_result_callback(callback(test)), or when the whole test |
| * file has completed, using add_completion_callback(callback(tests, |
| * harness_status)). |
| * |
| * For more documentation about the callback functions and the |
| * parameters they are called with, see testharness.js, or the docs at: |
| * https://web-platform-tests.org/writing-tests/testharness-api.html |
| */ |
| |
| (function() { |
| |
| let outputDocument = document; |
| let didDispatchLoadEvent = false; |
| let localPathRegExp = undefined; |
| |
| // Setup for Blink JavaScript tests. self.testRunner is expected to be |
| // present when tests are run. |
| if (self.testRunner) { |
| testRunner.dumpAsText(); |
| testRunner.waitUntilDone(); |
| testRunner.setCanOpenWindows(); |
| testRunner.setCloseRemainingWindowsWhenComplete(true); |
| testRunner.setDumpJavaScriptDialogs(false); |
| |
| // Some tests intentionally load mixed content in order to test the |
| // referrer policy, so WebKitAllowRunningInsecureContent must be set |
| // to true or else the load would be blocked. |
| const paths = [ |
| 'service-workers/service-worker/fetch-event-referrer-policy.https.html', |
| 'beacon/headers/header-referrer-no-referrer-when-downgrade.https.html', |
| 'beacon/headers/header-referrer-strict-origin-when-cross-origin.https.html', |
| 'beacon/headers/header-referrer-strict-origin.https.html', |
| 'beacon/headers/header-referrer-unsafe-url.https.html', |
| ]; |
| for (const path of paths) { |
| if (document.URL.endsWith(path)) { |
| testRunner.overridePreference('WebKitAllowRunningInsecureContent', true); |
| break; |
| } |
| } |
| } |
| |
| if (document.URL.startsWith('file:///')) { |
| const index = document.URL.indexOf('/external/wpt'); |
| if (index >= 0) { |
| const localPath = document.URL.substring( |
| 'file:///'.length, index + '/external/wpt'.length); |
| localPathRegExp = new RegExp(localPath.replace(/(\W)/g, '\\$1'), 'g'); |
| } |
| } |
| |
| window.addEventListener('load', loadCallback, {'once': true}); |
| |
| setup({ |
| // The default output formats test results into an HTML table, but for |
| // the Blink layout test runner, we dump the results as text in the |
| // completion callback, so we disable the default output. |
| 'output': false, |
| // The Blink layout test runner has its own timeout mechanism. |
| 'explicit_timeout': true |
| }); |
| |
| add_start_callback(startCallback); |
| add_completion_callback(completionCallback); |
| |
| /** Loads an automation script if necessary. */ |
| function loadCallback() { |
| didDispatchLoadEvent = true; |
| if (isWPTManualTest()) { |
| setTimeout(loadAutomationScript, 0); |
| } |
| } |
| |
| /** Checks whether the current path is a manual test in WPT. */ |
| function isWPTManualTest() { |
| // Here we assume that if wptserve is running, then the hostname |
| // is web-platform.test. |
| const path = location.pathname; |
| if (location.hostname == 'web-platform.test' && |
| /.*-manual(\.sub)?(\.https)?(\.tentative)?\.html$/.test(path)) { |
| return true; |
| } |
| // If the file is loaded locally via file://, it must include |
| // the wpt directory in the path. |
| return /\/external\/wpt\/.*-manual(\.sub)?(\.https)?(\.tentative)?\.html$/.test(path); |
| } |
| |
| /** Loads the WPT automation script for the current test, if applicable. */ |
| function loadAutomationScript() { |
| const pathAndBase = pathAndBaseNameInWPT(); |
| if (!pathAndBase) { |
| return; |
| } |
| let automationPath = location.pathname.replace( |
| /\/external\/wpt\/.*$/, '/external/wpt_automation'); |
| if (location.hostname == 'web-platform.test') { |
| automationPath = '/wpt_automation'; |
| } |
| |
| // Export importAutomationScript for use by the automation scripts. |
| window.importAutomationScript = function(relativePath) { |
| const script = document.createElement('script'); |
| script.src = automationPath + relativePath; |
| document.head.appendChild(script); |
| }; |
| |
| let src; |
| if (pathAndBase.startsWith('/fullscreen/')) { |
| // Fullscreen tests all use the same automation script. |
| src = automationPath + '/fullscreen/auto-click.js'; |
| } else if ( |
| pathAndBase.startsWith('/css/') || |
| pathAndBase.startsWith('/pointerevents/') || |
| pathAndBase.startsWith('/uievents/') || |
| pathAndBase.startsWith('/pointerlock/') || |
| pathAndBase.startsWith('/html/') || |
| pathAndBase.startsWith('/input-events/') || |
| pathAndBase.startsWith('/css/selectors/') || |
| pathAndBase.startsWith('/css/cssom-view/') || |
| pathAndBase.startsWith('/css/css-scroll-snap/') || |
| pathAndBase.startsWith('/dom/events/') || |
| pathAndBase.startsWith('/feature-policy/experimental-features/')) { |
| // Per-test automation scripts. |
| src = automationPath + pathAndBase + '-automation.js'; |
| } else { |
| return; |
| } |
| const script = document.createElement('script'); |
| script.src = src; |
| document.head.appendChild(script); |
| } |
| |
| /** |
| * Sets the output document based on the given properties. |
| * Usually the output document is the current document, but it could be |
| * a separate document in some cases. |
| */ |
| function startCallback(properties) { |
| if (properties.output_document) { |
| outputDocument = properties.output_document; |
| } |
| } |
| |
| /** |
| * Adds results to the page in a manner that allows dumpAsText to produce |
| * readable test results. |
| */ |
| function completionCallback(tests, harness_status) { |
| const xhtmlNS = 'http://www.w3.org/1999/xhtml'; |
| |
| // Create element to hold results. |
| const resultsElement = outputDocument.createElementNS(xhtmlNS, 'pre'); |
| resultsElement.style.whiteSpace = 'pre-wrap'; |
| resultsElement.style.lineHeight = '1.5'; |
| |
| // Declare result string. |
| let resultStr = 'This is a testharness.js-based test.\n'; |
| |
| // Check harness_status. If it is not 0, tests did not execute |
| // correctly, output the error code and message. |
| if (harness_status.status != 0) { |
| resultStr += `Harness Error. ` + |
| `harness_status.status = ${harness_status.status} , ` + |
| `harness_status.message = ${harness_status.message}\n`; |
| } |
| |
| // Output failure metrics if there are many. |
| resultCounts = countResultTypes(tests); |
| if (outputDocument.URL.indexOf('://web-platform.test') >= 0 && |
| tests.length >= 50 && |
| (resultCounts[1] || resultCounts[2] || resultCounts[3])) { |
| |
| resultStr += failureMetricSummary(resultCounts); |
| } |
| |
| if (outputDocument.URL.indexOf('/html/dom/reflection') >= 0) { |
| resultStr += compactTestOutput(tests); |
| } else { |
| resultStr += testOutput(tests); |
| } |
| |
| resultStr += 'Harness: the test ran to completion.\n'; |
| |
| resultsElement.textContent = resultStr; |
| |
| function done() { |
| let body = null; |
| if (outputDocument.body && outputDocument.body.tagName == 'BODY' && |
| outputDocument.body.namespaceURI == xhtmlNS) { |
| body = outputDocument.body; |
| } |
| // A temporary workaround since |window.self| property lookup starts |
| // failing if the frame is detached. |outputDocument| may be an |
| // ancestor of |self| so clearing |textContent| may detach |self|. |
| // To get around this, cache window.self now and use the cached |
| // value. |
| // TODO(dcheng): Remove this hack after fixing window/self/frames |
| // lookup in https://crbug.com/618672 |
| const cachedSelf = window.self; |
| if (cachedSelf.testRunner) { |
| // The following DOM operations may show console messages. We |
| // suppress them because they are not related to the running |
| // test. |
| testRunner.setDumpConsoleMessages(false); |
| |
| // Anything in the body isn't part of the output and so should |
| // be hidden from the text dump. |
| if (body) { |
| body.textContent = ''; |
| } |
| } |
| |
| // Add the results element to the output document. |
| if (!body) { |
| // |outputDocument| might be an SVG document. |
| if (outputDocument.documentElement) { |
| outputDocument.documentElement.remove(); |
| } |
| let html = outputDocument.createElementNS(xhtmlNS, 'html'); |
| outputDocument.appendChild(html); |
| body = outputDocument.createElementNS(xhtmlNS, 'body'); |
| body.setAttribute('style', 'white-space:pre;'); |
| html.appendChild(body); |
| } |
| outputDocument.body.appendChild(resultsElement); |
| |
| if (cachedSelf.testRunner) { |
| testRunner.notifyDone(); |
| } |
| } |
| |
| if (didDispatchLoadEvent || outputDocument.readyState != 'loading') { |
| // This function might not be the last completion callback, and |
| // another completion callback might generate more results. |
| // So, we don't dump the results immediately. |
| setTimeout(done, 0); |
| } else { |
| // Parsing the test HTML isn't finished yet. |
| window.addEventListener('load', done); |
| } |
| } |
| |
| /** |
| * Returns a directory part relative to WPT root and a basename part of the |
| * current test. e.g. |
| * Current test: file:///.../LayoutTests/external/wpt/pointerevents/foobar.html |
| * Output: "/pointerevents/foobar" |
| */ |
| function pathAndBaseNameInWPT() { |
| const path = location.pathname; |
| let matches; |
| if (location.hostname == 'web-platform.test') { |
| matches = path.match(/^(\/.*)\.html$/); |
| return matches ? matches[1] : null; |
| } |
| matches = path.match(/external\/wpt(\/.*)\.html$/); |
| return matches ? matches[1] : null; |
| } |
| |
| /** Converts the testharness test status into the corresponding string. */ |
| function convertResult(resultStatus) { |
| switch (resultStatus) { |
| case 0: |
| return 'PASS'; |
| case 1: |
| return 'FAIL'; |
| case 2: |
| return 'TIMEOUT'; |
| default: |
| return 'NOTRUN'; |
| } |
| } |
| |
| /** |
| * Returns a compact output for reflection test results. |
| * |
| * The reflection tests contain a large number of tests. |
| * This test output merges PASS lines to make baselines smaller. |
| */ |
| function compactTestOutput(tests) { |
| let testResults = []; |
| for (let i = 0; i < tests.length; ++i) { |
| if (tests[i].status == 0) { |
| const colon = tests[i].name.indexOf(':'); |
| if (colon > 0) { |
| const prefix = tests[i].name.substring(0, colon + 1); |
| let j = i + 1; |
| for (; j < tests.length; ++j) { |
| if (!tests[j].name.startsWith(prefix) || |
| tests[j].status != 0) |
| break; |
| } |
| const numPasses = j - i; |
| if (numPasses > 1) { |
| testResults.push( |
| `${convertResult(tests[i].status)} ` + |
| `${sanitize(prefix)} ${numPasses} tests\n`); |
| i = j - 1; |
| continue; |
| } |
| } |
| } |
| testResults.push(resultLine(tests[i])); |
| } |
| return testResults.join(''); |
| } |
| |
| function testOutput(tests) { |
| let testResults = ''; |
| window.tests = tests; |
| for (let test of tests) { |
| testResults += resultLine(test); |
| } |
| return testResults; |
| } |
| |
| function resultLine(test) { |
| let result = `${convertResult(test.status)} ${sanitize(test.name)}`; |
| if (test.message) { |
| result += ' ' + sanitize(test.message).trim(); |
| } |
| return result + '\n'; |
| } |
| |
| /** Prepares the given text for display in test results. */ |
| function sanitize(text) { |
| if (!text) { |
| return ''; |
| } |
| // Escape null characters, otherwise diff will think the file is binary. |
| text = text.replace(/\0/g, '\\0'); |
| // Escape some special characters to improve readability of the output. |
| text = text.replace(/\r/g, '\\r'); |
| |
| // Replace machine-dependent path with "...". |
| if (localPathRegExp) { |
| text = text.replace(localPathRegExp, '...'); |
| } |
| return text; |
| } |
| |
| function countResultTypes(tests) { |
| const resultCounts = [0, 0, 0, 0]; |
| for (let test of tests) { |
| resultCounts[test.status]++; |
| } |
| return resultCounts; |
| } |
| |
| function failureMetricSummary(resultCounts) { |
| const total = resultCounts[0] + resultCounts[1] + resultCounts[2] + resultCounts[3]; |
| return `Found ${total} tests;` + |
| ` ${resultCounts[0]} PASS,` + |
| ` ${resultCounts[1]} FAIL,` + |
| ` ${resultCounts[2]} TIMEOUT,` + |
| ` ${resultCounts[3]} NOTRUN.\n`; |
| } |
| |
| })(); |