/* * 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. * */ // An object representing the current test, used external var currentTest = null; // TODO: get rid of this global, which mirrors the htmlTestRunner.currentTest var selenium = null; var htmlTestRunner; var HtmlTestRunner = classCreate(); objectExtend(HtmlTestRunner.prototype, { initialize: function() { this.metrics = new Metrics(); this.controlPanel = new HtmlTestRunnerControlPanel(); this.testFailed = false; this.currentTest = null; this.runAllTests = false; this.appWindow = null; // we use a timeout here to make sure the LOG has loaded first, so we can see _every_ error setTimeout(fnBind(function() { this.loadSuiteFrame(); }, this), 500); }, getTestSuite: function() { return suiteFrame.getCurrentTestSuite(); }, markFailed: function() { this.testFailed = true; this.getTestSuite().markFailed(); }, loadSuiteFrame: function() { if (selenium == null) { var appWindow = this._getApplicationWindow(); try { appWindow.location; } catch (e) { // when reloading, we may be pointing at an old window (Perm Denied) setTimeout(fnBind(function() { this.loadSuiteFrame(); }, this), 50); return; } selenium = Selenium.createForWindow(appWindow); this._registerCommandHandlers(); } this.controlPanel.setHighlightOption(); var testSuiteName = this.controlPanel.getTestSuiteName(); var self = this; if (testSuiteName) { suiteFrame.load(testSuiteName, function() {setTimeout(fnBind(self._onloadTestSuite, self), 50)} ); selenium.browserbot.baseUrl = absolutify(testSuiteName, window.location.href); } // DGF or should we use the old default? // selenium.browserbot.baseUrl = window.location.href; if (this.controlPanel.getBaseUrl()) { selenium.browserbot.baseUrl = this.controlPanel.getBaseUrl(); } }, _getApplicationWindow: function () { if (this.controlPanel.isMultiWindowMode()) { return this._getSeparateApplicationWindow(); } return $('myiframe').contentWindow; }, _getSeparateApplicationWindow: function () { if (this.appWindow == null) { this.appWindow = openSeparateApplicationWindow('TestRunner-splash.html', this.controlPanel.isAutomatedRun()); } return this.appWindow; }, _onloadTestSuite:function () { if (! this.getTestSuite().isAvailable()) { return; } if (this.controlPanel.isAutomatedRun()) { this.startTestSuite(); } else if (this.controlPanel.getAutoUrl()) { //todo what is the autourl doing, left to check it out addLoadListener(this._getApplicationWindow(), fnBind(this._startSingleTest, this)); this._getApplicationWindow().src = this.controlPanel.getAutoUrl(); } else { this.getTestSuite().getSuiteRows()[0].loadTestCase(); } }, _startSingleTest:function () { removeLoadListener(getApplicationWindow(), fnBind(this._startSingleTest, this)); var singleTestName = this.controlPanel.getSingleTestName(); testFrame.load(singleTestName, fnBind(this.startTest, this)); }, _registerCommandHandlers: function () { this.commandFactory = new CommandHandlerFactory(); this.commandFactory.registerAll(selenium); }, startTestSuite: function() { this.controlPanel.reset(); this.metrics.resetMetrics(); this.getTestSuite().reset(); this.runAllTests = true; this.runNextTest(); }, runNextTest: function () { this.getTestSuite().updateSuiteWithResultOfPreviousTest(); if (!this.runAllTests) { return; } this.getTestSuite().runNextTestInSuite(); }, startTest: function () { this.controlPanel.reset(); testFrame.scrollToTop(); //todo: move testFailed and storedVars to TestCase this.testFailed = false; storedVars = new Object(); this.currentTest = new HtmlRunnerTestLoop(testFrame.getCurrentTestCase(), this.metrics, this.commandFactory); currentTest = this.currentTest; this.currentTest.start(); }, runSingleTest:function() { this.runAllTests = false; this.metrics.resetMetrics(); this.startTest(); } }); var runInterval = 0; /** SeleniumFrame encapsulates an iframe element */ var SeleniumFrame = classCreate(); objectExtend(SeleniumFrame.prototype, { initialize : function(frame) { this.frame = frame; addLoadListener(this.frame, fnBind(this._handleLoad, this)); }, getDocument : function() { return this.frame.contentWindow.document; }, _handleLoad: function() { this._attachStylesheet(); this._onLoad(); if (this.loadCallback) { this.loadCallback(); this.loadCallback = null; } }, _attachStylesheet: function() { var d = this.getDocument(); var head = d.getElementsByTagName('head').item(0); var styleLink = d.createElement("link"); styleLink.rel = "stylesheet"; styleLink.type = "text/css"; if (browserVersion && browserVersion.isChrome) { // DGF We have to play a clever trick to get the right absolute path. // This trick works on most browsers, (not IE), but is only needed in // chrome var tempLink = window.document.createElement("link"); tempLink.href = "selenium-test.css"; // this will become an absolute href styleLink.href = tempLink.href; } else { // this works in every browser (except Firefox in chrome mode) var styleSheetPath = window.location.pathname.replace(/[^\/\\]+$/, "selenium-test.css"); if (browserVersion.isIE && window.location.protocol == "file:") { styleSheetPath = "file://" + styleSheetPath; } styleLink.href = styleSheetPath; } head.appendChild(styleLink); }, _onLoad: function() { }, scrollToTop : function() { this.frame.contentWindow.scrollTo(0, 0); }, _setLocation: function(location) { var isChrome = browserVersion.isChrome || false; var isHTA = browserVersion.isHTA || false; // DGF TODO multiWindow location += "?thisIsChrome=" + isChrome + "&thisIsHTA=" + isHTA; if (browserVersion.isSafari) { // safari doesn't reload the page when the location equals to current location. // hence, set the location to blank so that the page will reload automatically. this.frame.src = "about:blank"; this.frame.src = location; } else { this.frame.contentWindow.location.replace(location); } }, load: function(/* url, [callback] */) { if (arguments.length > 1) { this.loadCallback = arguments[1]; } this._setLocation(arguments[0]); } }); /** HtmlTestSuiteFrame - encapsulates the suite iframe element */ var HtmlTestSuiteFrame = classCreate(); objectExtend(HtmlTestSuiteFrame.prototype, SeleniumFrame.prototype); objectExtend(HtmlTestSuiteFrame.prototype, { getCurrentTestSuite: function() { if (!this.currentTestSuite) { this.currentTestSuite = new HtmlTestSuite(this.getDocument()); } return this.currentTestSuite; } }); /** HtmlTestFrame - encapsulates the test-case iframe element */ var HtmlTestFrame = classCreate(); objectExtend(HtmlTestFrame.prototype, SeleniumFrame.prototype); objectExtend(HtmlTestFrame.prototype, { _onLoad: function() { this.currentTestCase = new HtmlTestCase(this.getDocument(), htmlTestRunner.getTestSuite().getCurrentRow()); }, getCurrentTestCase: function() { return this.currentTestCase; } }); function onSeleniumLoad() { suiteFrame = new HtmlTestSuiteFrame(getSuiteFrame()); testFrame = new HtmlTestFrame(getTestFrame()); htmlTestRunner = new HtmlTestRunner(); } var suiteFrame; var testFrame; function getSuiteFrame() { var f = $('testSuiteFrame'); if (f == null) { f = top; // proxyInjection mode does not set myiframe } return f; } function getTestFrame() { var f = $('testFrame'); if (f == null) { f = top; // proxyInjection mode does not set myiframe } return f; } var HtmlTestRunnerControlPanel = classCreate(); objectExtend(HtmlTestRunnerControlPanel.prototype, URLConfiguration.prototype); objectExtend(HtmlTestRunnerControlPanel.prototype, { initialize: function() { this._acquireQueryString(); this.runInterval = 0; this.highlightOption = $('highlightOption'); this.pauseButton = $('pauseTest'); this.stepButton = $('stepTest'); this.highlightOption.onclick = fnBindAsEventListener((function() { this.setHighlightOption(); }), this); this.pauseButton.onclick = fnBindAsEventListener(this.pauseCurrentTest, this); this.stepButton.onclick = fnBindAsEventListener(this.stepCurrentTest, this); this.speedController = new Control.Slider('speedHandle', 'speedTrack', { range: $R(0, 1000), onSlide: fnBindAsEventListener(this.setRunInterval, this), onChange: fnBindAsEventListener(this.setRunInterval, this) }); this._parseQueryParameter(); }, setHighlightOption: function () { var isHighlight = this.highlightOption.checked; selenium.browserbot.setShouldHighlightElement(isHighlight); }, _parseQueryParameter: function() { var tempRunInterval = this._getQueryParameter("runInterval"); if (tempRunInterval) { this.setRunInterval(tempRunInterval); } this.highlightOption.checked = this._getQueryParameter("highlight"); }, setRunInterval: function(runInterval) { this.runInterval = runInterval; }, setToPauseAtNextCommand: function() { this.runInterval = -1; }, pauseCurrentTest: function () { this.setToPauseAtNextCommand(); this._switchPauseButtonToContinue(); }, continueCurrentTest: function () { this.reset(); currentTest.resume(); }, reset: function() { // this.runInterval = this.speedController.value; this._switchContinueButtonToPause(); }, _switchContinueButtonToPause: function() { this.pauseButton.className = "cssPauseTest"; this.pauseButton.onclick = fnBindAsEventListener(this.pauseCurrentTest, this); }, _switchPauseButtonToContinue: function() { $('stepTest').disabled = false; this.pauseButton.className = "cssContinueTest"; this.pauseButton.onclick = fnBindAsEventListener(this.continueCurrentTest, this); }, stepCurrentTest: function () { this.setToPauseAtNextCommand(); currentTest.resume(); }, isAutomatedRun: function() { return this._isQueryParameterTrue("auto"); }, shouldSaveResultsToFile: function() { return this._isQueryParameterTrue("save"); }, closeAfterTests: function() { return this._isQueryParameterTrue("close"); }, getTestSuiteName: function() { return this._getQueryParameter("test"); }, getSingleTestName: function() { return this._getQueryParameter("singletest"); }, getAutoUrl: function() { return this._getQueryParameter("autoURL"); }, getResultsUrl: function() { return this._getQueryParameter("resultsUrl"); }, _acquireQueryString: function() { if (this.queryString) return; if (browserVersion.isHTA) { var args = this._extractArgs(); if (args.length < 2) return null; this.queryString = args[1]; } else { this.queryString = location.search.substr(1); } } }); var AbstractResultAwareRow = classCreate(); objectExtend(AbstractResultAwareRow.prototype, { initialize: function(trElement) { this.trElement = trElement; }, setStatus: function(status) { this.unselect(); this.trElement.className = this.trElement.className.replace(/status_[a-z]+/, ""); if (status) { addClassName(this.trElement, "status_" + status); } }, select: function() { addClassName(this.trElement, "selected"); safeScrollIntoView(this.trElement); }, unselect: function() { removeClassName(this.trElement, "selected"); }, markPassed: function() { this.setStatus("passed"); }, markDone: function() { this.setStatus("done"); }, markFailed: function() { this.setStatus("failed"); } }); var TitleRow = classCreate(); objectExtend(TitleRow.prototype, AbstractResultAwareRow.prototype); objectExtend(TitleRow.prototype, { initialize: function(trElement) { this.trElement = trElement; trElement.className = "title"; } }); var HtmlTestCaseRow = classCreate(); objectExtend(HtmlTestCaseRow.prototype, AbstractResultAwareRow.prototype); objectExtend(HtmlTestCaseRow.prototype, { getCommand: function () { return new SeleniumCommand(getText(this.trElement.cells[0]), getText(this.trElement.cells[1]), getText(this.trElement.cells[2]), this.isBreakpoint()); }, markFailed: function(errorMsg) { AbstractResultAwareRow.prototype.markFailed.call(this, errorMsg); this.setMessage(errorMsg); }, setMessage: function(message) { setText(this.trElement.cells[2], message); }, reset: function() { this.setStatus(null); var thirdCell = this.trElement.cells[2]; if (thirdCell) { if (thirdCell.originalHTML) { thirdCell.innerHTML = thirdCell.originalHTML; } else { thirdCell.originalHTML = thirdCell.innerHTML; } } }, onClick: function() { if (this.trElement.isBreakpoint == undefined) { this.trElement.isBreakpoint = true; addClassName(this.trElement, "breakpoint"); } else { this.trElement.isBreakpoint = undefined; removeClassName(this.trElement, "breakpoint"); } }, addBreakpointSupport: function() { elementSetStyle(this.trElement, {"cursor" : "pointer"}); this.trElement.onclick = fnBindAsEventListener(function() { this.onClick(); }, this); }, isBreakpoint: function() { if (this.trElement.isBreakpoint == undefined || this.trElement.isBreakpoint == null) { return false } return this.trElement.isBreakpoint; } }); var HtmlTestSuiteRow = classCreate(); objectExtend(HtmlTestSuiteRow.prototype, AbstractResultAwareRow.prototype); objectExtend(HtmlTestSuiteRow.prototype, { initialize: function(trElement, testFrame, htmlTestSuite) { this.trElement = trElement; this.testFrame = testFrame; this.htmlTestSuite = htmlTestSuite; this.link = trElement.getElementsByTagName("a")[0]; this.link.onclick = fnBindAsEventListener(this._onClick, this); }, reset: function() { this.setStatus(null); }, _onClick: function() { this.loadTestCase(null); return false; }, loadTestCase: function(onloadFunction) { this.htmlTestSuite.unselectCurrentRow(); this.select(); this.htmlTestSuite.currentRowInSuite = this.trElement.rowIndex - 1; // If the row has a stored results table, use that var resultsFromPreviousRun = this.trElement.cells[1]; if (resultsFromPreviousRun) { // todo: delegate to TestFrame, e.g. // this.testFrame.restoreTestCase(resultsFromPreviousRun.innerHTML); var testBody = this.testFrame.getDocument().body; testBody.innerHTML = resultsFromPreviousRun.innerHTML; this.testFrame._onLoad(); if (onloadFunction) { onloadFunction(); } } else { this.testFrame.load(this.link.href, onloadFunction); } }, saveTestResults: function() { // todo: GLOBAL ACCESS! var resultHTML = this.testFrame.getDocument().body.innerHTML; if (!resultHTML) return; // todo: why create this div? var divElement = this.trElement.ownerDocument.createElement("div"); divElement.innerHTML = resultHTML; var hiddenCell = this.trElement.ownerDocument.createElement("td"); hiddenCell.appendChild(divElement); hiddenCell.style.display = "none"; this.trElement.appendChild(hiddenCell); } }); var HtmlTestSuite = classCreate(); objectExtend(HtmlTestSuite.prototype, { initialize: function(suiteDocument) { this.suiteDocument = suiteDocument; this.suiteRows = this._collectSuiteRows(); this.titleRow = new TitleRow(this.getTestTable().rows[0]); this.reset(); }, reset: function() { this.failed = false; this.currentRowInSuite = -1; this.titleRow.setStatus(null); for (var i = 0; i < this.suiteRows.length; i++) { var row = this.suiteRows[i]; row.reset(); } }, getSuiteRows: function() { return this.suiteRows; }, getTestTable: function() { var tables = $A(this.suiteDocument.getElementsByTagName("table")); return tables[0]; }, isAvailable: function() { return this.getTestTable() != null; }, _collectSuiteRows: function () { var result = []; var tables = $A(this.suiteDocument.getElementsByTagName("table")); var testTable = tables[0]; for (rowNum = 1; rowNum < testTable.rows.length; rowNum++) { var rowElement = testTable.rows[rowNum]; result.push(new HtmlTestSuiteRow(rowElement, testFrame, this)); } // process the unsuited rows as well for (var tableNum = 1; tableNum < $A(this.suiteDocument.getElementsByTagName("table")).length; tableNum++) { testTable = tables[tableNum]; for (rowNum = 1; rowNum < testTable.rows.length; rowNum++) { var rowElement = testTable.rows[rowNum]; new HtmlTestSuiteRow(rowElement, testFrame, this); } } return result; }, getCurrentRow: function() { if (this.currentRowInSuite == -1) { return null; } return this.suiteRows[this.currentRowInSuite]; }, unselectCurrentRow: function() { var currentRow = this.getCurrentRow() if (currentRow) { currentRow.unselect(); } }, markFailed: function() { this.failed = true; this.titleRow.markFailed(); }, markDone: function() { if (!this.failed) { this.titleRow.markPassed(); } }, _startCurrentTestCase: function() { this.getCurrentRow().loadTestCase(fnBind(htmlTestRunner.startTest, htmlTestRunner)); }, _onTestSuiteComplete: function() { this.markDone(); new TestResult(this.failed, this.getTestTable()).post(); }, updateSuiteWithResultOfPreviousTest: function() { if (this.currentRowInSuite >= 0) { this.getCurrentRow().saveTestResults(); } }, runNextTestInSuite: function() { this.currentRowInSuite++; // If we are done with all of the tests, set the title bar as pass or fail if (this.currentRowInSuite >= this.suiteRows.length) { this._onTestSuiteComplete(); } else { this._startCurrentTestCase(); } } }); var TestResult = classCreate(); objectExtend(TestResult.prototype, { // 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 // initialize: function (suiteFailed, suiteTable) { this.controlPanel = htmlTestRunner.controlPanel; this.metrics = htmlTestRunner.metrics; this.suiteFailed = suiteFailed; this.suiteTable = suiteTable; }, post: function () { if (!this.controlPanel.isAutomatedRun()) { return; } var form = document.createElement("form"); document.body.appendChild(form); form.id = "resultsForm"; form.method = "post"; form.target = "myiframe"; var resultsUrl = this.controlPanel.getResultsUrl(); 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", this.suiteFailed ? "failed" : "passed"); form.createHiddenField("totalTime", Math.floor((this.metrics.currentTime - this.metrics.startTime) / 1000)); form.createHiddenField("numTestPasses", this.metrics.numTestPasses); form.createHiddenField("numTestFailures", this.metrics.numTestFailures); form.createHiddenField("numCommandPasses", this.metrics.numCommandPasses); form.createHiddenField("numCommandFailures", this.metrics.numCommandFailures); form.createHiddenField("numCommandErrors", this.metrics.numCommandErrors); // Create an input for each test table. The inputs are named // testTable.1, testTable.2, etc. for (rowNum = 1; rowNum < this.suiteTable.rows.length; rowNum++) { // If there is a second column, then add a new input if (this.suiteTable.rows[rowNum].cells.length > 1) { var resultCell = this.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", this.suiteTable.parentNode.innerHTML); if (this.controlPanel.shouldSaveResultsToFile()) { this._saveToFile(resultsUrl, form); } else { form.submit(); } document.body.removeChild(form); if (this.controlPanel.closeAfterTests()) { window.top.close(); } }, _saveToFile: function (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(); } }); /** HtmlTestCase encapsulates an HTML test document */ var HtmlTestCase = classCreate(); objectExtend(HtmlTestCase.prototype, { initialize: function(testDocument, htmlTestSuiteRow) { if (testDocument == null) { throw "testDocument should not be null"; } if (htmlTestSuiteRow == null) { throw "htmlTestSuiteRow should not be null"; } this.testDocument = testDocument; this.htmlTestSuiteRow = htmlTestSuiteRow; this.headerRow = new TitleRow(this.testDocument.getElementsByTagName("tr")[0]); this.commandRows = this._collectCommandRows(); this.nextCommandRowIndex = 0; this._addBreakpointSupport(); }, _collectCommandRows: function () { var commandRows = []; var tables = $A(this.testDocument.getElementsByTagName("table")); var self = this; for (var i = 0; i < tables.length; i++) { var table = tables[i]; var tableRows = $A(table.rows); for (var j = 0; j < tableRows.length; j++) { var candidateRow = tableRows[j]; if (self.isCommandRow(candidateRow)) { commandRows.push(new HtmlTestCaseRow(candidateRow)); } } } return commandRows; }, isCommandRow: function (row) { return row.cells.length >= 3; }, reset: function() { /** * reset the test to runnable state */ this.nextCommandRowIndex = 0; this.setStatus(''); for (var i = 0; i < this.commandRows.length; i++) { var row = this.commandRows[i]; row.reset(); } // remove any additional fake "error" row added to the end of the document var errorElement = this.testDocument.getElementById('error'); if (errorElement) { errorElement.parentNode.removeChild(errorElement); } }, getCommandRows: function () { return this.commandRows; }, setStatus: function(status) { this.headerRow.setStatus(status); }, markFailed: function() { this.setStatus("failed"); this.htmlTestSuiteRow.markFailed(); }, markPassed: function() { this.setStatus("passed"); this.htmlTestSuiteRow.markPassed(); }, addErrorMessage: function(errorMsg, currentRow) { errorMsg = errorMsg.replace(/ /g, String.fromCharCode(160)).replace("\n", '\\n'); if (currentRow) { currentRow.markFailed(errorMsg); } else { var errorElement = this.testDocument.createElement("p"); errorElement.id = "error"; setText(errorElement, errorMsg); this.testDocument.body.appendChild(errorElement); errorElement.className = "status_failed"; } }, _addBreakpointSupport: function() { for (var i = 0; i < this.commandRows.length; i++) { var row = this.commandRows[i]; row.addBreakpointSupport(); } }, hasMoreCommandRows: function() { return this.nextCommandRowIndex < this.commandRows.length; }, getNextCommandRow: function() { if (this.hasMoreCommandRows()) { return this.commandRows[this.nextCommandRowIndex++]; } return null; } }); // TODO: split out an JavascriptTestCase class to handle the "sejs" stuff var get_new_rows = function() { var row_array = new Array(); for (var i = 0; i < new_block.length; i++) { var new_source = (new_block[i][0].tokenizer.source.slice(new_block[i][0].start, new_block[i][0].end)); var row = 'getEval' + 'currentTest.doNextCommand()' + '' + new_source + '' + '' row_array.push(row); } return row_array; }; var Metrics = classCreate(); objectExtend(Metrics.prototype, { initialize: function() { // The number of tests run this.numTestPasses = 0; // The number of tests that have failed this.numTestFailures = 0; // The number of commands which have passed this.numCommandPasses = 0; // The number of commands which have failed this.numCommandFailures = 0; // The number of commands which have caused errors (element not found) this.numCommandErrors = 0; // The time that the test was started. this.startTime = null; // The current time. this.currentTime = null; }, printMetrics: function() { setText($('commandPasses'), this.numCommandPasses); setText($('commandFailures'), this.numCommandFailures); setText($('commandErrors'), this.numCommandErrors); setText($('testRuns'), this.numTestPasses + this.numTestFailures); setText($('testFailures'), this.numTestFailures); this.currentTime = new Date().getTime(); var timeDiff = this.currentTime - this.startTime; var totalSecs = Math.floor(timeDiff / 1000); var minutes = Math.floor(totalSecs / 60); var seconds = totalSecs % 60; setText($('elapsedTime'), this._pad(minutes) + ":" + this._pad(seconds)); }, // Puts a leading 0 on num if it is less than 10 _pad: function(num) { return (num > 9) ? num : "0" + num; }, resetMetrics: function() { this.numTestPasses = 0; this.numTestFailures = 0; this.numCommandPasses = 0; this.numCommandFailures = 0; this.numCommandErrors = 0; this.startTime = new Date().getTime(); } }); var HtmlRunnerCommandFactory = classCreate(); objectExtend(HtmlRunnerCommandFactory.prototype, { initialize: function(seleniumCommandFactory, testLoop) { this.seleniumCommandFactory = seleniumCommandFactory; this.testLoop = testLoop; this.handlers = {}; //todo: register commands }, getCommandHandler: function(command) { if (this.handlers[command]) { return this.handlers[command]; } return this.seleniumCommandFactory.getCommandHandler(command); } }); var HtmlRunnerTestLoop = classCreate(); objectExtend(HtmlRunnerTestLoop.prototype, new TestLoop()); objectExtend(HtmlRunnerTestLoop.prototype, { initialize: function(htmlTestCase, metrics, seleniumCommandFactory) { this.commandFactory = new HtmlRunnerCommandFactory(seleniumCommandFactory, this); this.metrics = metrics; this.htmlTestCase = htmlTestCase; se = selenium; global.se = selenium; this.currentRow = null; this.currentRowIndex = 0; // used for selenium tests in javascript this.currentItem = null; this.commandAgenda = new Array(); this.expectedFailure = null; this.expectedFailureType = null; this.htmlTestCase.reset(); this.sejsElement = this.htmlTestCase.testDocument.getElementById('sejs'); if (this.sejsElement) { var fname = 'Selenium JavaScript'; parse_result = parse(this.sejsElement.innerHTML, fname, 0); var x2 = new ExecutionContext(GLOBAL_CODE); ExecutionContext.current = x2; execute(parse_result, x2) } }, _advanceToNextRow: function() { if (this.htmlTestCase.hasMoreCommandRows()) { this.currentRow = this.htmlTestCase.getNextCommandRow(); if (this.sejsElement) { this.currentItem = agenda.pop(); this.currentRowIndex++; } } else { this.currentRow = null; this.currentItem = null; } }, nextCommand : function() { this._advanceToNextRow(); if (this.currentRow == null) { return null; } return this.currentRow.getCommand(); }, commandStarted : function() { $('pauseTest').disabled = false; this.currentRow.select(); this.metrics.printMetrics(); }, commandComplete : function(result) { this._checkExpectedFailure(result); if (result.failed) { this.metrics.numCommandFailures += 1; this._recordFailure(result.failureMessage); } else if (result.passed) { this.metrics.numCommandPasses += 1; this.currentRow.markPassed(); } else { this.currentRow.markDone(); } }, _checkExpectedFailure : function(result) { if (this.expectedFailure != null) { if (this.expectedFailureJustSet) { this.expectedFailureJustSet = false; return; } if (!result.failed) { result.passed = false; result.failed = true; result.failureMessage = "Expected " + this.expectedFailureType + " did not occur."; } else { if (PatternMatcher.matches(this.expectedFailure, result.failureMessage)) { var failureType = result.error ? "error" : "failure"; if (failureType == this.expectedFailureType) { result.failed = false; result.passed = true; } else { result.failed = true; result.failureMessage = "Expected "+this.expectedFailureType+", but "+failureType+" occurred instead"; } } else { result.failed = true; result.failureMessage = "Expected " + this.expectedFailureType + " message '" + this.expectedFailure + "' but was '" + result.failureMessage + "'"; } } this.expectedFailure = null; this.expectedFailureType = null; } }, commandError : function(errorMessage) { var tempResult = {}; tempResult.passed = false; tempResult.failed = true; tempResult.error = true; tempResult.failureMessage = errorMessage; this._checkExpectedFailure(tempResult); if (tempResult.passed) { this.currentRow.markDone(); return true; } errorMessage = tempResult.failureMessage; this.metrics.numCommandErrors += 1; this._recordFailure(errorMessage); }, _recordFailure : function(errorMsg) { LOG.warn("currentTest.recordFailure: " + errorMsg); htmlTestRunner.markFailed(); this.htmlTestCase.addErrorMessage(errorMsg, this.currentRow); }, testComplete : function() { $('pauseTest').disabled = true; $('stepTest').disabled = true; if (htmlTestRunner.testFailed) { this.htmlTestCase.markFailed(); this.metrics.numTestFailures += 1; } else { this.htmlTestCase.markPassed(); this.metrics.numTestPasses += 1; } this.metrics.printMetrics(); window.setTimeout(function() { htmlTestRunner.runNextTest(); }, 1); }, getCommandInterval : function() { return htmlTestRunner.controlPanel.runInterval; }, pause : function() { htmlTestRunner.controlPanel.pauseCurrentTest(); }, doNextCommand: function() { var _n = this.currentItem[0]; var _x = this.currentItem[1]; new_block = new Array() execute(_n, _x); if (new_block.length > 0) { var the_table = this.htmlTestCase.testDocument.getElementById("se-js-table") var loc = this.currentRowIndex var new_rows = get_new_rows() // make the new statements visible on screen... for (var i = 0; i < new_rows.length; i++) { the_table.insertRow(loc + 1); the_table.rows[loc + 1].innerHTML = new_rows[i]; this.commandRows.unshift(the_table.rows[loc + 1]) } } } }); Selenium.prototype.doPause = function(waitTime) { /** Wait for the specified amount of time (in milliseconds) * @param waitTime the amount of time to sleep (in milliseconds) */ // todo: should not refer to currentTest directly currentTest.pauseInterval = waitTime; }; Selenium.prototype.doBreak = function() { /** Halt the currently running test, and wait for the user to press the Continue button. * This command is useful for debugging, but be careful when using it, because it will * force automated tests to hang until a user intervenes manually. */ // todo: should not refer to controlPanel directly htmlTestRunner.controlPanel.setToPauseAtNextCommand(); }; Selenium.prototype.doStore = function(expression, variableName) { /** This command is a synonym for storeExpression. * @param expression the value to store * @param variableName the name of a variable in which the result is to be stored. */ storedVars[variableName] = expression; } /* * Click on the located element, and attach a callback to notify * when the page is reloaded. */ // DGF TODO this code has been broken for some time... what is it trying to accomplish? Selenium.prototype.XXXdoModalDialogTest = function(returnValue) { this.browserbot.doModalDialogTest(returnValue); }; Selenium.prototype.doEcho = function(message) { /** Prints the specified message into the third table cell in your Selenese tables. * Useful for debugging. * @param message the message to print */ currentTest.currentRow.setMessage(message); } Selenium.prototype.assertSelected = function(selectLocator, optionLocator) { /** * Verifies that the selected option of a drop-down satisfies the optionSpecifier. Note that this command is deprecated; you should use assertSelectedLabel, assertSelectedValue, assertSelectedIndex, or assertSelectedId instead. * *

See the select command for more information about option locators.

* * @param selectLocator an element locator identifying a drop-down menu * @param optionLocator an option locator, typically just an option label (e.g. "John Smith") */ var element = this.page().findElement(selectLocator); var locator = this.optionLocatorFactory.fromLocatorString(optionLocator); if (element.selectedIndex == -1) { Assert.fail("No option selected"); } locator.assertSelected(element); }; Selenium.prototype.assertFailureOnNext = function(message) { /** * Tell Selenium to expect a failure on the next command execution. * @param message The failure message we should expect. This command will fail if the wrong failure message appears. */ if (!message) { throw new SeleniumError("Message must be provided"); } currentTest.expectedFailure = message; currentTest.expectedFailureType = "failure"; currentTest.expectedFailureJustSet = true; }; Selenium.prototype.assertErrorOnNext = function(message) { /** * Tell Selenium to expect an error on the next command execution. * @param message The error message we should expect. This command will fail if the wrong error message appears. */ // This command temporarily installs a CommandFactory that generates // CommandHandlers that expect an error. if (!message) { throw new SeleniumError("Message must be provided"); } currentTest.expectedFailure = message; currentTest.expectedFailureType = "error"; currentTest.expectedFailureJustSet = true; };