blob: 7504aa4e6e47fcaf2ec6f48c134c027539268630 [file] [log] [blame]
/*
* Copyright (C) 2013-2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
FrontendTestHarness = class FrontendTestHarness extends TestHarness
{
constructor()
{
super();
this._results = [];
this._shouldResendResults = true;
}
// TestHarness Overrides
completeTest()
{
if (this.dumpActivityToSystemConsole)
InspectorFrontendHost.unbufferedLog("completeTest()");
// Wait for results to be resent before requesting completeTest(). Otherwise, messages will be
// queued after pending dispatches run to zero and the test page will quit before processing them.
if (this._testPageIsReloading) {
this._completeTestAfterReload = true;
return;
}
InspectorBackend.runAfterPendingDispatches(this.evaluateInPage.bind(this, "TestPage.completeTest()"));
}
addResult(message)
{
let stringifiedMessage = TestHarness.messageAsString(message);
// Save the stringified message, since message may be a DOM element that won't survive reload.
this._results.push(stringifiedMessage);
if (this.dumpActivityToSystemConsole)
InspectorFrontendHost.unbufferedLog(stringifiedMessage);
if (!this._testPageIsReloading)
this.evaluateInPage(`TestPage.addResult(unescape("${escape(stringifiedMessage)}"))`);
}
debugLog(message)
{
let stringifiedMessage = TestHarness.messageAsString(message);
if (this.dumpActivityToSystemConsole)
InspectorFrontendHost.unbufferedLog(stringifiedMessage);
this.evaluateInPage(`TestPage.debugLog(unescape("${escape(stringifiedMessage)}"));`);
}
evaluateInPage(expression, callback)
{
// If we load this page outside of the inspector, or hit an early error when loading
// the test frontend, then defer evaluating the commands (indefinitely in the former case).
if (this._originalConsole && !window.RuntimeAgent) {
this._originalConsole["error"]("Tried to evaluate in test page, but connection not yet established:", expression);
return;
}
RuntimeAgent.evaluate.invoke({expression, objectGroup: "test", includeCommandLineAPI: false}, callback);
}
debug()
{
this.dumpActivityToSystemConsole = true;
InspectorBackend.dumpInspectorProtocolMessages = true;
}
// Frontend test-specific methods.
expectNoError(error)
{
if (error) {
InspectorTest.log("PROTOCOL ERROR: " + error);
InspectorTest.completeTest();
throw "PROTOCOL ERROR";
}
}
testPageDidLoad()
{
if (this.dumpActivityToSystemConsole)
InspectorFrontendHost.unbufferedLog("testPageDidLoad()");
this._testPageIsReloading = false;
this._resendResults();
this.dispatchEventToListeners(FrontendTestHarness.Event.TestPageDidLoad);
if (this._completeTestAfterReload)
this.completeTest();
}
reloadPage(shouldIgnoreCache)
{
console.assert(!this._testPageIsReloading);
console.assert(!this._testPageReloadedOnce);
this._testPageIsReloading = true;
return PageAgent.reload(!!shouldIgnoreCache)
.then(() => {
this._shouldResendResults = true;
this._testPageReloadedOnce = true;
return Promise.resolve(null);
});
}
redirectConsoleToTestOutput()
{
// We can't use arrow functions here because of 'arguments'. It might
// be okay once rest parameters work.
let self = this;
function createProxyConsoleHandler(type) {
return function() {
self.addResult(`${type}: ` + Array.from(arguments).join(" "));
};
}
function createProxyConsoleTraceHandler(){
return function() {
try {
throw new Exception();
} catch (e) {
// Skip the first frame which is added by this function.
let frames = e.stack.split("\n").slice(1);
let sanitizedFrames = frames.map((frame, i) => {
// Most frames are of the form "functionName@file:///foo/bar/File.js:345".
// But, some frames do not have a functionName. Get rid of the file path.
let nameAndURLSeparator = frame.indexOf("@");
let frameName = (nameAndURLSeparator > 0) ? frame.substr(0, nameAndURLSeparator) : "(anonymous)";
let lastPathSeparator = Math.max(frame.lastIndexOf("/"), frame.lastIndexOf("\\"));
let frameLocation = (lastPathSeparator > 0) ? frame.substr(lastPathSeparator + 1) : frame;
if (!frameLocation.length)
frameLocation = "unknown";
// Clean up the location so it is bracketed or in parenthesis.
if (frame.indexOf("[native code]") !== -1)
frameLocation = "[native code]";
else
frameLocation = "(" + frameLocation + ")";
return `#${i}: ${frameName} ${frameLocation}`;
});
self.addResult("TRACE: " + Array.from(arguments).join(" "));
self.addResult(sanitizedFrames.join("\n"));
}
};
}
let redirectedMethods = {};
for (let key in window.console)
redirectedMethods[key] = window.console[key];
for (let type of ["log", "error", "info", "warn"])
redirectedMethods[type] = createProxyConsoleHandler(type.toUpperCase());
redirectedMethods["trace"] = createProxyConsoleTraceHandler();
this._originalConsole = window.console;
window.console = redirectedMethods;
}
reportUncaughtException(message, url, lineNumber, columnNumber)
{
let result = `Uncaught exception in inspector page: ${message} [${url}:${lineNumber}:${columnNumber}]`;
// If the connection to the test page is not set up, then just dump to console and give up.
// Errors encountered this early can be debugged by loading Test.html in a normal browser page.
if (this._originalConsole && (!InspectorFrontendHost || !InspectorBackend)) {
this._originalConsole["error"](result);
return false;
}
this.addResult(result);
this.completeTest();
// Stop default handler so we can empty InspectorBackend's message queue.
return true;
}
// Private
_resendResults()
{
console.assert(this._shouldResendResults);
this._shouldResendResults = false;
if (this.dumpActivityToSystemConsole)
InspectorFrontendHost.unbufferedLog("_resendResults()");
for (let result of this._results)
this.evaluateInPage(`TestPage.addResult(unescape("${escape(result)}"))`);
}
};
FrontendTestHarness.Event = {
TestPageDidLoad: "frontend-test-test-page-did-load"
};