/* * Copyright 2004 ThoughtWorks, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ // The current row in the list of tests (test suite) currentRowInSuite = 0; // An object representing the current test currentTest = null; // Whether or not the jsFT should run all tests in the suite runAllTests = false; // Whether or not the current test has any errors; testFailed = false; suiteFailed = false; // Colors used to provide feedback passColor = "#ccffcc"; doneColor = "#eeffee"; failColor = "#ffcccc"; workingColor = "#ffffcc"; // Holds the handlers for each command. commandHandlers = null; // The number of tests run numTestPasses = 0; // The number of tests that have failed numTestFailures = 0; // The number of commands which have passed numCommandPasses = 0; // The number of commands which have failed numCommandFailures = 0; // The number of commands which have caused errors (element not found) numCommandErrors = 0; // The time that the test was started. startTime = null; // The current time. currentTime = null; // An simple enum for failureType ERROR = 0; FAILURE = 1; runInterval = 0; queryString = null; function setRunInterval() { // Get the value of the checked runMode option. // There should be a way of getting the value of the "group", but I don't know how. var runModeOptions = document.forms['controlPanel'].runMode; for (var i = 0; i < runModeOptions.length; i++) { if (runModeOptions[i].checked) { runInterval = runModeOptions[i].value; break; } } } function continueCurrentTest() { document.getElementById('continueTest').disabled = true; testLoop.resume(); } function getApplicationFrame() { return document.getElementById('myiframe'); } function getSuiteFrame() { return document.getElementById('testSuiteFrame'); } function getTestFrame(){ return document.getElementById('testFrame'); } function loadAndRunIfAuto() { loadSuiteFrame(); } function start() { queryString = null; setRunInterval(); loadSuiteFrame(); } function loadSuiteFrame() { var testAppFrame = document.getElementById('myiframe'); selenium = Selenium.createForFrame(testAppFrame); registerCommandHandlers(); //set the runInterval if there is a queryParameter for it var tempRunInterval = getQueryParameter("runInterval"); if (tempRunInterval) { runInterval = tempRunInterval; } document.getElementById("modeRun").onclick = setRunInterval; document.getElementById('modeWalk').onclick = setRunInterval; document.getElementById('modeStep').onclick = setRunInterval; document.getElementById('continueTest').onclick = continueCurrentTest; var testSuiteName = getQueryParameter("test"); if (testSuiteName) { addLoadListener(getSuiteFrame(), onloadTestSuite); getSuiteFrame().src = testSuiteName; } else { onloadTestSuite(); } } function startSingleTest() { removeLoadListener(getApplicationFrame(), startSingleTest); var singleTestName = getQueryParameter("singletest"); addLoadListener(getTestFrame(), startTest); getTestFrame().src = singleTestName; } function getIframeDocument(iframe) { if (iframe.contentDocument) { return iframe.contentDocument; } else { return iframe.contentWindow.document; } } function onloadTestSuite() { removeLoadListener(getSuiteFrame(), onloadTestSuite); // Add an onclick function to each link in all suite tables var allTables = getIframeDocument(getSuiteFrame()).getElementsByTagName("table"); for (var tableNum = 0; tableNum < allTables.length; tableNum++) { var skippedTable = allTables[tableNum]; for(rowNum = 1;rowNum < skippedTable.rows.length; rowNum++) { addOnclick(skippedTable, rowNum); } } suiteTable = getIframeDocument(getSuiteFrame()).getElementsByTagName("table")[0]; if (suiteTable!=null) { if (isAutomatedRun()) { startTestSuite(); } else if (getQueryParameter("autoURL")) { addLoadListener(getApplicationFrame(), startSingleTest); getApplicationFrame().src = getQueryParameter("autoURL"); } else { testLink = suiteTable.rows[currentRowInSuite+1].cells[0].getElementsByTagName("a")[0]; getTestFrame().src = testLink.href; } } } // Adds an onclick function to the link in the given row in suite table. // This function checks whether the test has already been run and the data is // stored. If the data is stored, it sets the test frame to be the stored data. // Otherwise, it loads the fresh page. function addOnclick(suiteTable, rowNum) { aLink = suiteTable.rows[rowNum].cells[0].getElementsByTagName("a")[0]; aLink.onclick = function(eventObj) { srcObj = null; // For mozilla-like browsers if(eventObj) srcObj = eventObj.target; // For IE-like browsers else if (getSuiteFrame().contentWindow.event) srcObj = getSuiteFrame().contentWindow.event.srcElement; // The target row (the event source is not consistently reported by browsers) row = srcObj.parentNode.parentNode.rowIndex || srcObj.parentNode.parentNode.parentNode.rowIndex; // If the row has a stored results table, use that if(suiteTable.rows[row].cells[1]) { var bodyElement = getIframeDocument(getTestFrame()).body; // Create a div element to hold the results table. var tableNode = getIframeDocument(getTestFrame()).createElement("div"); var resultsCell = suiteTable.rows[row].cells[1]; tableNode.innerHTML = resultsCell.innerHTML; // Append this text node, and remove all the preceding nodes. bodyElement.appendChild(tableNode); while (bodyElement.firstChild != bodyElement.lastChild) { bodyElement.removeChild(bodyElement.firstChild); } } // Otherwise, just open up the fresh page. else { getTestFrame().src = suiteTable.rows[row].cells[0].getElementsByTagName("a")[0].href; } return false; }; } function isQueryParameterTrue(name) { parameterValue = getQueryParameter(name); return (parameterValue != null && parameterValue.toLowerCase() == "true"); } function getQueryString() { if (queryString != null) return queryString; if (browserVersion.isHTA) { var args = extractArgs(); if (args.length < 2) return null; queryString = args[1]; return queryString; } else { return location.search.substr(1); } } function extractArgs() { var str = SeleniumHTARunner.commandLine; if (str == null || str == "") return new Array(); var matches = str.match(/(?:"([^"]+)"|(?!"([^"]+)")(\S+))/g); // We either want non quote stuff ([^"]+) surrounded by quotes // or we want to look-ahead, see that the next character isn't // a quoted argument, and then grab all the non-space stuff // this will return for the line: "foo" bar // the results "\"foo\"" and "bar" // So, let's unquote the quoted arguments: var args = new Array; for (var i = 0; i < matches.length; i++) { args[i] = matches[i]; args[i] = args[i].replace(/^"(.*)"$/, "$1"); } return args; } function getQueryParameter(searchKey) { var str = getQueryString(); if (str == null) return null; var clauses = str.split('&'); for (var i = 0; i < clauses.length; i++) { var keyValuePair = clauses[i].split('=',2); var key = unescape(keyValuePair[0]); if (key == searchKey) { return unescape(keyValuePair[1]); } } return null; } function isNewWindow() { return isQueryParameterTrue("newWindow"); } function isAutomatedRun() { return isQueryParameterTrue("auto"); } function resetMetrics() { numTestPasses = 0; numTestFailures = 0; numCommandPasses = 0; numCommandFailures = 0; numCommandErrors = 0; startTime = new Date().getTime(); } function runSingleTest() { runAllTests = false; resetMetrics(); startTest(); } function startTest() { removeLoadListener(getTestFrame(), startTest); // Scroll to the top of the test frame if (getTestFrame().contentWindow) { getTestFrame().contentWindow.scrollTo(0,0); } else { frames['testFrame'].scrollTo(0,0); } currentTest = new HtmlTest(getIframeDocument(getTestFrame())); testFailed = false; storedVars = new Object(); testLoop = initialiseTestLoop(); testLoop.start(); } function HtmlTest(testDocument) { this.init(testDocument); } HtmlTest.prototype = { init: function(testDocument) { this.document = testDocument; this.document.bgColor = ""; this.currentRow = null; this.commandRows = new Array(); this.headerRow = null; var tables = this.document.getElementsByTagName("table"); for (var i = 0; i < tables.length; i++) { var candidateRows = tables[i].rows; for (var j = 0; j < candidateRows.length; j++) { if (!this.headerRow) { this.headerRow = candidateRows[j]; } if (candidateRows[j].cells.length >= 3) { this.addCommandRow(candidateRows[j]); } } } }, addCommandRow: function(row) { if (row.cells[2] && row.cells[2].originalHTML) { row.cells[2].innerHTML = row.cells[2].originalHTML; } row.bgColor = ""; this.commandRows.push(row); }, nextCommand: function() { if (this.commandRows.length > 0) { this.currentRow = this.commandRows.shift(); } else { this.currentRow = null; } return this.currentRow; } }; function startTestSuite() { resetMetrics(); currentRowInSuite = 0; runAllTests = true; suiteFailed = false; runNextTest(); } function runNextTest() { if (!runAllTests) return; suiteTable = getIframeDocument(getSuiteFrame()).getElementsByTagName("table")[0]; // Do not change the row color of the first row if (currentRowInSuite > 0) { // Provide test-status feedback if (testFailed) { setCellColor(suiteTable.rows, currentRowInSuite, 0, failColor); } else { setCellColor(suiteTable.rows, currentRowInSuite, 0, passColor); } // Set the results from the previous test run setResultsData(suiteTable, currentRowInSuite); } currentRowInSuite++; // If we are done with all of the tests, set the title bar as pass or fail if (currentRowInSuite >= suiteTable.rows.length) { if (suiteFailed) { setCellColor(suiteTable.rows, 0, 0, failColor); } else { setCellColor(suiteTable.rows, 0, 0, passColor); } // If this is an automated run (i.e., build script), then submit // the test results by posting to a form if (isAutomatedRun()) postTestResults(suiteFailed, suiteTable); } else { // Make the current row blue setCellColor(suiteTable.rows, currentRowInSuite, 0, workingColor); testLink = suiteTable.rows[currentRowInSuite].cells[0].getElementsByTagName("a")[0]; testLink.focus(); var testFrame = getTestFrame(); addLoadListener(testFrame, startTest); selenium.browserbot.setIFrameLocation(testFrame, testLink.href); } } function setCellColor(tableRows, row, col, colorStr) { tableRows[row].cells[col].bgColor = colorStr; } // Sets the results from a test into a hidden column on the suite table. So, // for each tests, the second column is set to the HTML from the test table. function setResultsData(suiteTable, row) { // Create a text node of the test table var resultTable = getIframeDocument(getTestFrame()).body.innerHTML; if (!resultTable) return; var tableNode = suiteTable.ownerDocument.createElement("div"); tableNode.innerHTML = resultTable; var new_column = suiteTable.ownerDocument.createElement("td"); new_column.appendChild(tableNode); // Set the column to be invisible new_column.style.display = "none"; // Add the invisible column suiteTable.rows[row].appendChild(new_column); } // Post the results to a servlet, CGI-script, etc. The URL of the // results-handler defaults to "/postResults", but an alternative location // can be specified by providing a "resultsUrl" query parameter. // // Parameters passed to the results-handler are: // result: passed/failed depending on whether the suite passed or failed // totalTime: the total running time in seconds for the suite. // // numTestPasses: the total number of tests which passed. // numTestFailures: the total number of tests which failed. // // numCommandPasses: the total number of commands which passed. // numCommandFailures: the total number of commands which failed. // numCommandErrors: the total number of commands which errored. // // suite: the suite table, including the hidden column of test results // testTable.1 to testTable.N: the individual test tables // function postTestResults(suiteFailed, suiteTable) { form = document.createElement("form"); document.body.appendChild(form); form.id = "resultsForm"; form.method="post"; form.target="myiframe"; var resultsUrl = getQueryParameter("resultsUrl"); if (!resultsUrl) { resultsUrl = "./postResults"; } var actionAndParameters = resultsUrl.split('?',2); form.action = actionAndParameters[0]; var resultsUrlQueryString = actionAndParameters[1]; form.createHiddenField = function(name, value) { input = document.createElement("input"); input.type = "hidden"; input.name = name; input.value = value; this.appendChild(input); }; if (resultsUrlQueryString) { var clauses = resultsUrlQueryString.split('&'); for (var i = 0; i < clauses.length; i++) { var keyValuePair = clauses[i].split('=',2); var key = unescape(keyValuePair[0]); var value = unescape(keyValuePair[1]); form.createHiddenField(key, value); } } form.createHiddenField("selenium.version", Selenium.version); form.createHiddenField("selenium.revision", Selenium.revision); form.createHiddenField("result", suiteFailed == true ? "failed" : "passed"); form.createHiddenField("totalTime", Math.floor((currentTime - startTime) / 1000)); form.createHiddenField("numTestPasses", numTestPasses); form.createHiddenField("numTestFailures", numTestFailures); form.createHiddenField("numCommandPasses", numCommandPasses); form.createHiddenField("numCommandFailures", numCommandFailures); form.createHiddenField("numCommandErrors", numCommandErrors); // Create an input for each test table. The inputs are named // testTable.1, testTable.2, etc. for (rowNum = 1; rowNum < suiteTable.rows.length;rowNum++) { // If there is a second column, then add a new input if (suiteTable.rows[rowNum].cells.length > 1) { var resultCell = suiteTable.rows[rowNum].cells[1]; form.createHiddenField("testTable." + rowNum, resultCell.innerHTML); // remove the resultCell, so it's not included in the suite HTML resultCell.parentNode.removeChild(resultCell); } } form.createHiddenField("numTestTotal", rowNum); // Add HTML for the suite itself form.createHiddenField("suite", suiteTable.parentNode.innerHTML); if (isQueryParameterTrue("save")) { saveToFile(resultsUrl, form); } else { form.submit(); } document.body.removeChild(form); if (isQueryParameterTrue("close")) { window.top.close(); } } function saveToFile(fileName, form) { // This only works when run as an IE HTA var inputs = new Object(); for (var i = 0; i < form.elements.length; i++) { inputs[form.elements[i].name] = form.elements[i].value; } var objFSO = new ActiveXObject("Scripting.FileSystemObject") var scriptFile = objFSO.CreateTextFile(fileName); scriptFile.WriteLine("\n

Test suite results

" + "\n\n\n\n\n\n" + "\n\n\n\n\n" + "\n\n\n\n" + "\n\n\n\n" + "\n\n\n\n" + "\n\n\n\n" + "\n\n\n\n" + "\n\n\n"); var testNum = inputs["numTestTotal"]; for (var rowNum = 1; rowNum < testNum; rowNum++) { scriptFile.WriteLine("\n\n\n"); } scriptFile.WriteLine("
result:" + inputs["result"] + "
totalTime:" + inputs["totalTime"] + "
numTestPasses:" + inputs["numTestPasses"] + "
numTestFailures:" + inputs["numTestFailures"] + "
numCommandPasses:" + inputs["numCommandPasses"] + "
numCommandFailures:" + inputs["numCommandFailures"] + "
numCommandErrors:" + inputs["numCommandErrors"] + "
" + inputs["suite"] + " 
" + inputs["testTable." + rowNum] + " 
"); scriptFile.Close(); } function printMetrics() { setText(document.getElementById("commandPasses"), numCommandPasses); setText(document.getElementById("commandFailures"), numCommandFailures); setText(document.getElementById("commandErrors"), numCommandErrors); setText(document.getElementById("testRuns"), numTestPasses + numTestFailures); setText(document.getElementById("testFailures"), numTestFailures); currentTime = new Date().getTime(); timeDiff = currentTime - startTime; totalSecs = Math.floor(timeDiff / 1000); minutes = Math.floor(totalSecs / 60); seconds = totalSecs % 60; setText(document.getElementById("elapsedTime"), pad(minutes)+":"+pad(seconds)); } // Puts a leading 0 on num if it is less than 10 function pad (num) { return (num > 9) ? num : "0" + num; } /* * Register all of the built-in command handlers with the CommandHandlerFactory. * TODO work out an easy way for people to register handlers without modifying the Selenium sources. */ function registerCommandHandlers() { commandFactory = new CommandHandlerFactory(); commandFactory.registerAll(selenium); } function initialiseTestLoop() { testLoop = new TestLoop(commandFactory); testLoop.getCommandInterval = function() { return runInterval; }; testLoop.nextCommand = nextCommand; testLoop.commandStarted = commandStarted; testLoop.commandComplete = commandComplete; testLoop.commandError = commandError; testLoop.testComplete = testComplete; testLoop.pause = function() { document.getElementById('continueTest').disabled = false; }; return testLoop; } function nextCommand() { var row = currentTest.nextCommand(); if (row == null) { return null; } row.cells[2].originalHTML = row.cells[2].innerHTML; return new SeleniumCommand(getText(row.cells[0]), getText(row.cells[1]), getText(row.cells[2])); } function removeNbsp(value) { return value.replace(/\240/g, ""); } function scrollIntoView(element) { if (element.scrollIntoView) { element.scrollIntoView(false); return; } // TODO: work out how to scroll browsers that don't support // scrollIntoView (like Konqueror) } function commandStarted() { currentTest.currentRow.bgColor = workingColor; scrollIntoView(currentTest.currentRow.cells[0]); printMetrics(); } function commandComplete(result) { if (result.failed) { numCommandFailures += 1; recordFailure(result.failureMessage); } else if (result.passed) { numCommandPasses += 1; currentTest.currentRow.bgColor = passColor; } else { currentTest.currentRow.bgColor = doneColor; } } function commandError(errorMessage) { numCommandErrors += 1; recordFailure(errorMessage); } function recordFailure(errorMsg) { LOG.warn("recordFailure: " + errorMsg); // Set cell background to red currentTest.currentRow.bgColor = failColor; // Set error message currentTest.currentRow.cells[2].innerHTML = errorMsg; currentTest.currentRow.title = errorMsg; testFailed = true; suiteFailed = true; } function testComplete() { var resultColor = passColor; if (testFailed) { resultColor = failColor; numTestFailures += 1; } else { numTestPasses += 1; } if (currentTest.headerRow) { currentTest.headerRow.bgColor = resultColor; } printMetrics(); window.setTimeout("runNextTest()", 1); } Selenium.prototype.doPause = function(waitTime) { testLoop.pauseInterval = waitTime; }; Selenium.prototype.doPause.dontCheckAlertsAndConfirms = true; Selenium.prototype.doBreak = function() { document.getElementById('modeStep').checked = true; runInterval = -1; }; /* * Click on the located element, and attach a callback to notify * when the page is reloaded. */ Selenium.prototype.doModalDialogTest = function(returnValue) { this.browserbot.doModalDialogTest(returnValue); }; /* * Store the value of a form input in a variable */ Selenium.prototype.doStoreValue = function(target, varName) { if (!varName) { // Backward compatibility mode: read the ENTIRE text of the page // and stores it in a variable with the name of the target value = this.page().bodyText(); storedVars[target] = value; return; } var element = this.page().findElement(target); storedVars[varName] = getInputValue(element); }; /* * Store the text of an element in a variable */ Selenium.prototype.doStoreText = function(target, varName) { var element = this.page().findElement(target); storedVars[varName] = getText(element); }; /* * Store the value of an element attribute in a variable */ Selenium.prototype.doStoreAttribute = function(target, varName) { storedVars[varName] = this.page().findAttribute(target); }; /* * Store the result of a literal value */ Selenium.prototype.doStore = function(value, varName) { storedVars[varName] = value; }; Selenium.prototype.doEcho = function(msg) { currentTest.currentRow.cells[2].innerHTML = msg; }