From 7eafd95bfa21c9c8603e9c437a9d96dc880d92e7 Mon Sep 17 00:00:00 2001 From: wei <> Date: Sat, 30 Sep 2006 05:18:39 +0000 Subject: Update selenium to 0.8 --- .../selenium/core/scripts/selenium-testrunner.js | 1549 +++++++++++++------- 1 file changed, 998 insertions(+), 551 deletions(-) (limited to 'tests/test_tools/selenium/core/scripts/selenium-testrunner.js') diff --git a/tests/test_tools/selenium/core/scripts/selenium-testrunner.js b/tests/test_tools/selenium/core/scripts/selenium-testrunner.js index 1ced0a11..b5104d39 100644 --- a/tests/test_tools/selenium/core/scripts/selenium-testrunner.js +++ b/tests/test_tools/selenium/core/scripts/selenium-testrunner.js @@ -15,433 +15,584 @@ * */ -// The current row in the list of tests (test suite) -currentRowInSuite = 0; +// 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 = Class.create(); +Object.extend(HtmlTestRunner.prototype, { + initialize: function() { + this.metrics = new Metrics(); + this.controlPanel = new HtmlTestRunnerControlPanel(); + this.htmlTestSuite = null; + 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(function() { + this.loadSuiteFrame(); + }.bind(this), 500); + }, -// An object representing the current test -currentTest = null; + markFailed: function() { + this.testFailed = true; + this.htmlTestSuite.markFailed(); + }, -// Whether or not the jsFT should run all tests in the suite -runAllTests = false; + loadSuiteFrame: function() { + if (selenium == null) { + selenium = Selenium.createForWindow(this._getApplicationWindow()); + this._registerCommandHandlers(); + } + this.controlPanel.setHighlightOption(); + var testSuiteName = this.controlPanel.getTestSuiteName(); + if (testSuiteName) { + suiteFrame.load(testSuiteName, this._onloadTestSuite.bind(this)); + } + }, -// Whether or not the current test has any errors; -testFailed = false; -suiteFailed = false; + _getApplicationWindow: function () { + if (this.controlPanel.isMultiWindowMode()) { + return this._getSeparateApplicationWindow(); + } + return $('myiframe').contentWindow; + }, -// Colors used to provide feedback -passColor = "#ccffcc"; -doneColor = "#eeffee"; -failColor = "#ffcccc"; -workingColor = "#ffffcc"; + _getSeparateApplicationWindow: function () { + if (this.appWindow == null) { + this.appWindow = openSeparateApplicationWindow('TestRunner-splash.html'); + } + return this.appWindow; + }, -// Holds the handlers for each command. -commandHandlers = null; + _onloadTestSuite:function () { + this.htmlTestSuite = new HtmlTestSuite(suiteFrame.getDocument()); + if (! this.htmlTestSuite.isAvailable()) { + return; + } + if (this.controlPanel.isAutomatedRun()) { + htmlTestRunner.startTestSuite(); + } else if (this.controlPanel.getAutoUrl()) { + //todo what is the autourl doing, left to check it out + addLoadListener(this._getApplicationWindow(), this._startSingleTest.bind(this)); + this._getApplicationWindow().src = this.controlPanel.getAutoUrl(); + } else { + this.htmlTestSuite.getSuiteRows()[0].loadTestCase(); + } + }, -// The number of tests run -numTestPasses = 0; + _startSingleTest:function () { + removeLoadListener(getApplicationWindow(), this._startSingleTest.bind(this)); + var singleTestName = this.controlPanel.getSingleTestName(); + testFrame.load(singleTestName, this.startTest.bind(this)); + }, -// The number of tests that have failed -numTestFailures = 0; + _registerCommandHandlers: function () { + this.commandFactory = new CommandHandlerFactory(); + this.commandFactory.registerAll(selenium); + }, -// The number of commands which have passed -numCommandPasses = 0; + startTestSuite: function() { + this.controlPanel.reset(); + this.metrics.resetMetrics(); + this.htmlTestSuite.reset(); + this.runAllTests = true; + this.runNextTest(); + }, -// The number of commands which have failed -numCommandFailures = 0; + runNextTest: function () { + if (!this.runAllTests) { + return; + } + this.htmlTestSuite.runNextTestInSuite(); + }, -// The number of commands which have caused errors (element not found) -numCommandErrors = 0; + 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(); + }, -// The time that the test was started. -startTime = null; + runSingleTest:function() { + this.runAllTests = false; + this.metrics.resetMetrics(); + this.startTest(); + } +}); -// The current time. -currentTime = null; +var FeedbackColors = Class.create(); +Object.extend(FeedbackColors, { + passColor : "#ccffcc", + doneColor : "#eeffee", + failColor : "#ffcccc", + workingColor : "#ffffcc", + breakpointColor : "#cccccc" +}); -// An simple enum for failureType -ERROR = 0; -FAILURE = 1; -runInterval = 0; +var 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; - } - } -} +/** SeleniumFrame encapsulates an iframe element */ +var SeleniumFrame = Class.create(); +Object.extend(SeleniumFrame.prototype, { -function continueCurrentTest() { - document.getElementById('continueTest').disabled = true; - testLoop.resume(); -} + initialize : function(frame) { + this.frame = frame; + addLoadListener(this.frame, this._handleLoad.bind(this)); + }, -function getApplicationFrame() { - return document.getElementById('myiframe'); -} + getDocument : function() { + return this.frame.contentWindow.document; + }, -function getSuiteFrame() { - return document.getElementById('testSuiteFrame'); -} + _handleLoad: function() { + this._onLoad(); + if (this.loadCallback) { + this.loadCallback(); + this.loadCallback = null; + } + }, -function getTestFrame(){ - return document.getElementById('testFrame'); -} + _onLoad: function() { + }, -function loadAndRunIfAuto() { - loadSuiteFrame(); -} + scrollToTop : function() { + this.frame.contentWindow.scrollTo(0, 0); + }, -function start() { - queryString = null; - setRunInterval(); - loadSuiteFrame(); -} + _setLocation: function(location) { + 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); + } + }, -function loadSuiteFrame() { - var testAppFrame = document.getElementById('myiframe'); - selenium = Selenium.createForFrame(testAppFrame); - registerCommandHandlers(); + load: function(/* url, [callback] */) { + if (arguments.length > 1) { + this.loadCallback = arguments[1]; - //set the runInterval if there is a queryParameter for it - var tempRunInterval = getQueryParameter("runInterval"); - if (tempRunInterval) { - runInterval = tempRunInterval; + } + this._setLocation(arguments[0]); } - document.getElementById("modeRun").onclick = setRunInterval; - document.getElementById('modeWalk').onclick = setRunInterval; - document.getElementById('modeStep').onclick = setRunInterval; - document.getElementById('continueTest').onclick = continueCurrentTest; +}); + +/** HtmlTestFrame - encapsulates the test-case iframe element */ +var HtmlTestFrame = Class.create(); +Object.extend(HtmlTestFrame.prototype, SeleniumFrame.prototype); +Object.extend(HtmlTestFrame.prototype, { - var testSuiteName = getQueryParameter("test"); + _onLoad: function() { + this.setCurrentTestCase(); + }, + + setCurrentTestCase: function() { + //todo: this is not good looking + this.currentTestCase = new HtmlTestCase(this.getDocument(), htmlTestRunner.htmlTestSuite.getCurrentRow()); + }, - if (testSuiteName) { - addLoadListener(getSuiteFrame(), onloadTestSuite); - getSuiteFrame().src = testSuiteName; - } else { - onloadTestSuite(); + getCurrentTestCase: function() { + return this.currentTestCase; } -} -function startSingleTest() { - removeLoadListener(getApplicationFrame(), startSingleTest); - var singleTestName = getQueryParameter("singletest"); - addLoadListener(getTestFrame(), startTest); - getTestFrame().src = singleTestName; +}); + +function onSeleniumLoad() { + suiteFrame = new SeleniumFrame(getSuiteFrame()); + testFrame = new HtmlTestFrame(getTestFrame()); + htmlTestRunner = new HtmlTestRunner(); } -function getIframeDocument(iframe) -{ - if (iframe.contentDocument) { - return iframe.contentDocument; - } - else { - return iframe.contentWindow.document; + +var suiteFrame; +var testFrame; +function getSuiteFrame() { + var f = $('testSuiteFrame'); + if (f == null) { + f = top; + // proxyInjection mode does not set myiframe } + return f; } -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); - } +function getTestFrame() { + var f = $('testFrame'); + if (f == null) { + f = top; + // proxyInjection mode does not set myiframe } + return f; +} + +var HtmlTestRunnerControlPanel = Class.create(); +Object.extend(HtmlTestRunnerControlPanel.prototype, URLConfiguration.prototype); +Object.extend(HtmlTestRunnerControlPanel.prototype, { + initialize: function() { + this._acquireQueryString(); + + this.runInterval = 0; - suiteTable = getIframeDocument(getSuiteFrame()).getElementsByTagName("table")[0]; - if (suiteTable!=null) { + this.highlightOption = $('highlightOption'); + this.pauseButton = $('pauseTest'); + this.stepButton = $('stepTest'); - if (isAutomatedRun()) { - startTestSuite(); - } else if (getQueryParameter("autoURL")) { + this.highlightOption.onclick = (function() { + this.setHighlightOption(); + }).bindAsEventListener(this); + this.pauseButton.onclick = this.pauseCurrentTest.bindAsEventListener(this); + this.stepButton.onclick = this.stepCurrentTest.bindAsEventListener(this); - addLoadListener(getApplicationFrame(), startSingleTest); + this.speedController = new Control.Slider('speedHandle', 'speedTrack', { + range: $R(0, 1000), + onSlide: this.setRunInterval.bindAsEventListener(this), + onChange: this.setRunInterval.bindAsEventListener(this) + }); - getApplicationFrame().src = getQueryParameter("autoURL"); + this._parseQueryParameter(); + }, + + setHighlightOption: function () { + var isHighlight = this.highlightOption.checked; + selenium.browserbot.getCurrentPage().setHighlightElement(isHighlight); + }, - } else { - testLink = suiteTable.rows[currentRowInSuite+1].cells[0].getElementsByTagName("a")[0]; - getTestFrame().src = testLink.href; + _parseQueryParameter: function() { + var tempRunInterval = this._getQueryParameter("runInterval"); + if (tempRunInterval) { + this.setRunInterval(tempRunInterval); } - } -} + this.highlightOption.checked = this._getQueryParameter("highlight"); + }, -// 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; + setRunInterval: function(runInterval) { + this.runInterval = runInterval; + }, - // For mozilla-like browsers - if(eventObj) - srcObj = eventObj.target; + setToPauseAtNextCommand: function() { + this.runInterval = -1; + }, - // For IE-like browsers - else if (getSuiteFrame().contentWindow.event) - srcObj = getSuiteFrame().contentWindow.event.srcElement; + pauseCurrentTest: function () { + this.setToPauseAtNextCommand(); + this._switchPauseButtonToContinue(); + }, - // The target row (the event source is not consistently reported by browsers) - row = srcObj.parentNode.parentNode.rowIndex || srcObj.parentNode.parentNode.parentNode.rowIndex; + continueCurrentTest: function () { + this.reset(); + currentTest.resume(); + }, - // 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; - } + reset: function() { + this.runInterval = this.speedController.value; + this._switchContinueButtonToPause(); + }, - return false; - }; -} + _switchContinueButtonToPause: function() { + this.pauseButton.innerHTML = "Pause"; + this.pauseButton.onclick = this.pauseCurrentTest.bindAsEventListener(this); + }, -function isQueryParameterTrue(name) { - parameterValue = getQueryParameter(name); - return (parameterValue != null && parameterValue.toLowerCase() == "true"); -} + _switchPauseButtonToContinue: function() { + $('stepTest').disabled = false; + this.pauseButton.innerHTML = "Continue"; + this.pauseButton.onclick = this.continueCurrentTest.bindAsEventListener(this); + }, -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); - } -} + stepCurrentTest: function () { + this.setToPauseAtNextCommand(); + currentTest.resume(); + }, -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; -} + isAutomatedRun: function() { + return this._isQueryParameterTrue("auto"); + }, + + shouldSaveResultsToFile: function() { + return this._isQueryParameterTrue("save"); + }, + + closeAfterTests: function() { + return this._isQueryParameterTrue("close"); + }, -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]); + 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); } } - 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(); -} +var AbstractResultAwareRow = Class.create(); +Object.extend(AbstractResultAwareRow.prototype, { -function runSingleTest() { - runAllTests = false; - resetMetrics(); - startTest(); -} + initialize: function(trElement) { + this.trElement = trElement; + }, -function startTest() { - removeLoadListener(getTestFrame(), startTest); + markWorking: function() { + this.trElement.bgColor = FeedbackColors.workingColor; + safeScrollIntoView(this.trElement); + }, - // Scroll to the top of the test frame - if (getTestFrame().contentWindow) { - getTestFrame().contentWindow.scrollTo(0,0); - } - else { - frames['testFrame'].scrollTo(0,0); + markPassed: function() { + this.trElement.bgColor = FeedbackColors.passColor; + }, + + markDone: function() { + this.trElement.bgColor = FeedbackColors.doneColor; + }, + + markFailed: function() { + this.trElement.bgColor = FeedbackColors.failColor; } - currentTest = new HtmlTest(getIframeDocument(getTestFrame())); +}); - testFailed = false; - storedVars = new Object(); +var HtmlTestCaseRow = Class.create(); +Object.extend(HtmlTestCaseRow.prototype, AbstractResultAwareRow.prototype); +Object.extend(HtmlTestCaseRow.prototype, { - testLoop = initialiseTestLoop(); - testLoop.start(); -} + getCommand: function () { + return new SeleniumCommand(getText(this.trElement.cells[0]), + getText(this.trElement.cells[1]), + getText(this.trElement.cells[2]), + this.isBreakpoint()); + }, -function HtmlTest(testDocument) { - this.init(testDocument); -} + markFailed: function(errorMsg) { + this.trElement.bgColor = FeedbackColors.failColor; + this.setMessage(errorMsg); + }, -HtmlTest.prototype = { + setMessage: function(message) { + this.trElement.cells[2].innerHTML = message; + }, - 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]); - } + reset: function() { + this.trElement.bgColor = ''; + var thirdCell = this.trElement.cells[2]; + if (thirdCell) { + if (thirdCell.originalHTML) { + thirdCell.innerHTML = thirdCell.originalHTML; + } else { + thirdCell.originalHTML = thirdCell.innerHTML; } } }, - addCommandRow: function(row) { - if (row.cells[2] && row.cells[2].originalHTML) { - row.cells[2].innerHTML = row.cells[2].originalHTML; + onClick: function() { + if (this.trElement.isBreakpoint == undefined) { + this.trElement.isBreakpoint = true; + this.trElement.beforeBackgroundColor = Element.getStyle(this.trElement, "backgroundColor"); + Element.setStyle(this.trElement, {"background-color" : FeedbackColors.breakpointColor}); + } else { + this.trElement.isBreakpoint = undefined; + Element.setStyle(this.trElement, {"background-color" : this.trElement.beforeBackgroundColor}); } - row.bgColor = ""; - this.commandRows.push(row); }, - nextCommand: function() { - if (this.commandRows.length > 0) { - this.currentRow = this.commandRows.shift(); - } else { - this.currentRow = null; + addBreakpointSupport: function() { + Element.setStyle(this.trElement, {"cursor" : "pointer"}); + this.trElement.onclick = function() { + this.onClick(); + }.bindAsEventListener(this); + }, + + isBreakpoint: function() { + if (this.trElement.isBreakpoint == undefined || this.trElement.isBreakpoint == null) { + return false } - return this.currentRow; + return this.trElement.isBreakpoint; } +}); + +var HtmlTestSuiteRow = Class.create(); +Object.extend(HtmlTestSuiteRow.prototype, AbstractResultAwareRow.prototype); +Object.extend(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 = this._onClick.bindAsEventListener(this); + }, -}; + reset: function() { + this.trElement.bgColor = ''; + }, -function startTestSuite() { - resetMetrics(); - currentRowInSuite = 0; - runAllTests = true; - suiteFailed = false; + _onClick: function() { + // todo: just send a message to the testSuite + this.loadTestCase(null); + return false; + }, - runNextTest(); -} + loadTestCase: function(onloadFunction) { + 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) { + // this.testFrame.restoreTestCase(resultsFromPreviousRun.innerHTML); + var testBody = this.testFrame.getDocument().body; + testBody.innerHTML = resultsFromPreviousRun.innerHTML; + testFrame.setCurrentTestCase(); + if (onloadFunction) { + onloadFunction(); + } + } else { + this.testFrame.load(this.link.href, onloadFunction); + } + }, -function runNextTest() { - if (!runAllTests) - return; + saveTestResults: function() { + // todo: GLOBAL ACCESS! + var resultHTML = this.testFrame.getDocument().body.innerHTML; + if (!resultHTML) return; - suiteTable = getIframeDocument(getSuiteFrame()).getElementsByTagName("table")[0]; + // todo: why create this div? + var divElement = this.trElement.ownerDocument.createElement("div"); + divElement.innerHTML = resultHTML; - // 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); - } + var hiddenCell = this.trElement.ownerDocument.createElement("td"); + hiddenCell.appendChild(divElement); + hiddenCell.style.display = "none"; - // Set the results from the previous test run - setResultsData(suiteTable, currentRowInSuite); + this.trElement.appendChild(hiddenCell); } - 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); +var HtmlTestSuite = Class.create(); +Object.extend(HtmlTestSuite.prototype, { + + initialize: function(suiteDocument) { + this.suiteDocument = suiteDocument; + this.suiteRows = this._collectSuiteRows(); + this.titleRow = this.getTestTable().rows[0]; + this.title = this.titleRow.cells[0].innerHTML; + this.reset(); + }, + + reset: function() { + this.failed = false; + this.currentRowInSuite = -1; + this.titleRow.bgColor = ''; + this.suiteRows.each(function(row) { + row.reset(); + }); + }, + + getTitle: function() { + return this.title; + }, + + 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 = []; + for (rowNum = 1; rowNum < this.getTestTable().rows.length; rowNum++) { + var rowElement = this.getTestTable().rows[rowNum]; + result.push(new HtmlTestSuiteRow(rowElement, testFrame, this)); } + return result; + }, - // 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); - } + getCurrentRow: function() { + return this.suiteRows[this.currentRowInSuite]; + }, - else { - // Make the current row blue - setCellColor(suiteTable.rows, currentRowInSuite, 0, workingColor); + markFailed: function() { + this.failed = true; + this.titleRow.bgColor = FeedbackColors.failColor; + }, - testLink = suiteTable.rows[currentRowInSuite].cells[0].getElementsByTagName("a")[0]; - testLink.focus(); + markDone: function() { + if (!this.failed) { + this.titleRow.bgColor = FeedbackColors.passColor; + } + }, - var testFrame = getTestFrame(); - addLoadListener(testFrame, startTest); + _startCurrentTestCase: function() { + this.getCurrentRow().markWorking(); + this.getCurrentRow().loadTestCase(htmlTestRunner.startTest.bind(htmlTestRunner)); + }, - selenium.browserbot.setIFrameLocation(testFrame, testLink.href); - } -} + _onTestSuiteComplete: function() { + this.markDone(); + new TestResult(this.failed, this.getTestTable()).post(); + }, -function setCellColor(tableRows, row, col, colorStr) { - tableRows[row].cells[col].bgColor = colorStr; -} + _updateSuiteWithResultOfPreviousTest: function() { + if (this.currentRowInSuite >= 0) { + this.getCurrentRow().saveTestResults(); + } + }, -// 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; + runNextTestInSuite: function() { + this._updateSuiteWithResultOfPreviousTest(); + this.currentRowInSuite++; - var tableNode = suiteTable.ownerDocument.createElement("div"); - tableNode.innerHTML = resultTable; + // 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 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); -} +}); + +var TestResult = Class.create(); +Object.extend(TestResult.prototype, { // Post the results to a servlet, CGI-script, etc. The URL of the // results-handler defaults to "/postResults", but an alternative location @@ -461,288 +612,584 @@ function setResultsData(suiteTable, row) { // suite: the suite table, including the hidden column of test results // testTable.1 to testTable.N: the individual test tables // -function postTestResults(suiteFailed, suiteTable) { + initialize: function (suiteFailed, suiteTable) { + this.controlPanel = htmlTestRunner.controlPanel; + this.metrics = htmlTestRunner.metrics; + this.suiteFailed = suiteFailed; + this.suiteTable = suiteTable; + }, - form = document.createElement("form"); - document.body.appendChild(form); + 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"; + form.id = "resultsForm"; + form.method = "post"; + form.target = "myiframe"; - var resultsUrl = getQueryParameter("resultsUrl"); - if (!resultsUrl) { - resultsUrl = "./postResults"; - } + var resultsUrl = this.controlPanel.getResultsUrl(); + if (!resultsUrl) { + resultsUrl = "./postResults"; + } - var actionAndParameters = resultsUrl.split('?',2); - form.action = actionAndParameters[0]; - var resultsUrlQueryString = actionAndParameters[1]; + 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 = function(name, value) { - input = document.createElement("input"); - input.type = "hidden"; - input.name = name; - input.value = value; - this.appendChild(input); - }; + 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); - 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); + // 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(); + } + }, - 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); + _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("
\nresult: | \n" + inputs["result"] + " | \n" + + "
totalTime: | \n" + inputs["totalTime"] + " | \n
numTestPasses: | \n" + inputs["numTestPasses"] + " | \n
numTestFailures: | \n" + inputs["numTestFailures"] + " | \n
numCommandPasses: | \n" + inputs["numCommandPasses"] + " | \n
numCommandFailures: | \n" + inputs["numCommandFailures"] + " | \n
numCommandErrors: | \n" + inputs["numCommandErrors"] + " | \n
" + inputs["suite"] + " | \n\n |
" + inputs["testTable." + rowNum] + " | \n\n |
result: | \n" + inputs["result"] + " | \n" + - "
totalTime: | \n" + inputs["totalTime"] + " | \n
numTestPasses: | \n" + inputs["numTestPasses"] + " | \n
numTestFailures: | \n" + inputs["numTestFailures"] + " | \n
numCommandPasses: | \n" + inputs["numCommandPasses"] + " | \n
numCommandFailures: | \n" + inputs["numCommandFailures"] + " | \n
numCommandErrors: | \n" + inputs["numCommandErrors"] + " | \n
" + inputs["suite"] + " | \n\n |
" + inputs["testTable." + rowNum] + " | \n\n |
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"); } - var element = this.page().findElement(target); - storedVars[varName] = getInputValue(element); + locator.assertSelected(element); }; -/* - * Store the text of an element in a variable +/** + * Tell Selenium to expect a failure on the next command execution. This + * command temporarily installs a CommandFactory that generates + * CommandHandlers that expect a failure. */ -Selenium.prototype.doStoreText = function(target, varName) { - var element = this.page().findElement(target); - storedVars[varName] = getText(element); +Selenium.prototype.assertFailureOnNext = function(message) { + if (!message) { + throw new Error("Message must be provided"); + } + + var expectFailureCommandFactory = + new ExpectFailureCommandFactory(currentTest.commandFactory, message, "failure", executeCommandAndReturnFailureMessage); + currentTest.commandFactory = expectFailureCommandFactory; }; -/* - * Store the value of an element attribute in a variable +/** + * Tell Selenium to expect an error on the next command execution. This + * command temporarily installs a CommandFactory that generates + * CommandHandlers that expect a failure. */ -Selenium.prototype.doStoreAttribute = function(target, varName) { - storedVars[varName] = this.page().findAttribute(target); +Selenium.prototype.assertErrorOnNext = function(message) { + if (!message) { + throw new Error("Message must be provided"); + } + + var expectFailureCommandFactory = + new ExpectFailureCommandFactory(currentTest.commandFactory, message, "error", executeCommandAndReturnErrorMessage); + currentTest.commandFactory = expectFailureCommandFactory; }; -/* - * Store the result of a literal value - */ -Selenium.prototype.doStore = function(value, varName) { - storedVars[varName] = value; +function executeCommandAndReturnFailureMessage(baseHandler, originalArguments) { + var baseResult = baseHandler.execute.apply(baseHandler, originalArguments); + if (baseResult.passed) { + return null; + } + return baseResult.failureMessage; +}; + +function executeCommandAndReturnErrorMessage(baseHandler, originalArguments) { + try { + baseHandler.execute.apply(baseHandler, originalArguments); + return null; + } + catch (expected) { + return expected.message; + } }; -Selenium.prototype.doEcho = function(msg) { - currentTest.currentRow.cells[2].innerHTML = msg; +function ExpectFailureCommandHandler(baseHandler, originalCommandFactory, expectedErrorMessage, errorType, decoratedExecutor) { + this.execute = function() { + var baseFailureMessage = decoratedExecutor(baseHandler, arguments); + var result = {}; + if (!baseFailureMessage) { + result.failed = true; + result.failureMessage = "Expected " + errorType + " did not occur."; + } + else { + if (! PatternMatcher.matches(expectedErrorMessage, baseFailureMessage)) { + result.failed = true; + result.failureMessage = "Expected " + errorType + " message '" + expectedErrorMessage + + "' but was '" + baseFailureMessage + "'"; + } + else { + result.passed = true; + result.result = baseFailureMessage; + } + } + currentTest.commandFactory = originalCommandFactory; + return result; + }; } + +function ExpectFailureCommandFactory(originalCommandFactory, expectedErrorMessage, errorType, decoratedExecutor) { + this.getCommandHandler = function(name) { + var baseHandler = originalCommandFactory.getCommandHandler(name); + return new ExpectFailureCommandHandler(baseHandler, originalCommandFactory, expectedErrorMessage, errorType, decoratedExecutor); + }; +}; -- cgit v1.2.3