From 98215a603fb798cdb4178e49061977544aaa45b7 Mon Sep 17 00:00:00 2001 From: wei <> Date: Sat, 10 Dec 2005 11:49:29 +0000 Subject: Initial import selenium functional test framework. --- .../selenium/selenium-testrunner.js | 597 +++++++++++++++++++++ 1 file changed, 597 insertions(+) create mode 100644 tests/FunctionalTests/selenium/selenium-testrunner.js (limited to 'tests/FunctionalTests/selenium/selenium-testrunner.js') diff --git a/tests/FunctionalTests/selenium/selenium-testrunner.js b/tests/FunctionalTests/selenium/selenium-testrunner.js new file mode 100644 index 00000000..e786b29b --- /dev/null +++ b/tests/FunctionalTests/selenium/selenium-testrunner.js @@ -0,0 +1,597 @@ +/* +* 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; + +// Where are we at in the current test +testTableRows = null; +currentRowInTest = -1; +currentCommandRow = 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; + +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() { + 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); + suiteTable = getIframeDocument(getSuiteFrame()).getElementsByTagName("table")[0]; + + // Add an onclick function to each link in the suite table + for(rowNum = 1;rowNum < suiteTable.rows.length; rowNum++) { + addOnclick(suiteTable, rowNum); + } + + + 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 getQueryParameter(searchKey) { + var clauses = location.search.substr(1).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); + } + + testTable = getIframeDocument(getTestFrame()).getElementsByTagName("table")[0]; + testTableRows = testTable.rows; + currentRowInTest = -1; + currentCommandRow = null; + + testFailed = false; + storedVars = new Object(); + + resetTestTable(); + + testLoop = initialiseTestLoop(); + testLoop.start(); +} + +function resetTestTable() { + for (var i = 0; i <= testTableRows.length - 1; i++) { + testTableRows[i].bgColor = "white"; + } +} + +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.cssText = "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("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); + } + } + + // Add HTML for the suite itself + form.createHiddenField("suite", suiteTable.parentNode.innerHTML); + + form.submit(); + document.body.removeChild(form); + +} + +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 findNextCommandRow() { + // Return the next row with >= 3 cells + while (currentRowInTest < testTableRows.length - 1) { + currentRowInTest++; + currentCommandRow = testTableRows[currentRowInTest]; + if (currentCommandRow.cells.length >= 3) { + return; + } + } + currentCommandRow = null; +} + +function nextCommand() { + findNextCommandRow(); + if (currentCommandRow == null) { + return null; + } + var row = currentCommandRow; + if (! row.cachedValue) { + row.cachedValue = removeNbsp(getText(row.cells[2])); + } + return new SeleniumCommand(getText(row.cells[0]), + getText(row.cells[1]), + row.cachedValue); +} + +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() { + currentCommandRow.bgColor = workingColor; + scrollIntoView(currentCommandRow.cells[0]); + printMetrics(); +} + +function commandComplete(result) { + if (result.failed) { + numCommandFailures += 1; + recordFailure(result.failureMessage); + } else if (result.passed) { + numCommandPasses += 1; + currentCommandRow.bgColor = passColor; + } else { + currentCommandRow.bgColor = doneColor; + } +} + +function commandError(errorMessage) { + numCommandErrors += 1; + recordFailure(errorMessage); +} + +function recordFailure(errorMsg) { + // Set cell background to red + currentCommandRow.bgColor = failColor; + + // Set error message + currentCommandRow.cells[2].innerHTML = errorMsg; + currentCommandRow.title = errorMsg; + testFailed = true; + suiteFailed = true; +} + +function testComplete() { + if(testFailed) { + testTableRows[0].bgColor = failColor; + numTestFailures += 1; + } + else { + testTableRows[0].bgColor = passColor; + numTestPasses += 1; + } + + printMetrics(); + + window.setTimeout("runNextTest()", 1); +} + +Selenium.prototype.doPause = function(waitTime) { + testLoop.pauseInterval = waitTime; +}; + +Selenium.prototype.doBreak = function() { + document.getElementById('modeStep').checked = true; + runInterval = -1; +}; -- cgit v1.2.3