/testing/selenium-core/scripts/selenium-testrunner.js

http://datanucleus-appengine.googlecode.com/ · JavaScript · 1362 lines · 1155 code · 139 blank · 68 comment · 83 complexity · ff6bd2bb6074279595c3fb41b082db02 MD5 · raw file

  1. /*
  2. * Copyright 2004 ThoughtWorks, Inc
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. // An object representing the current test, used external
  18. var currentTest = null; // TODO: get rid of this global, which mirrors the htmlTestRunner.currentTest
  19. var selenium = null;
  20. var htmlTestRunner;
  21. var HtmlTestRunner = classCreate();
  22. objectExtend(HtmlTestRunner.prototype, {
  23. initialize: function() {
  24. this.metrics = new Metrics();
  25. this.controlPanel = new HtmlTestRunnerControlPanel();
  26. this.testFailed = false;
  27. this.currentTest = null;
  28. this.runAllTests = false;
  29. this.appWindow = null;
  30. // we use a timeout here to make sure the LOG has loaded first, so we can see _every_ error
  31. setTimeout(fnBind(function() {
  32. this.loadSuiteFrame();
  33. }, this), 500);
  34. },
  35. getTestSuite: function() {
  36. return suiteFrame.getCurrentTestSuite();
  37. },
  38. markFailed: function() {
  39. this.testFailed = true;
  40. this.getTestSuite().markFailed();
  41. },
  42. loadSuiteFrame: function() {
  43. var logLevel = this.controlPanel.getDefaultLogLevel();
  44. if (logLevel) {
  45. LOG.setLogLevelThreshold(logLevel);
  46. }
  47. if (selenium == null) {
  48. var appWindow = this._getApplicationWindow();
  49. try { appWindow.location; }
  50. catch (e) {
  51. // when reloading, we may be pointing at an old window (Perm Denied)
  52. setTimeout(fnBind(function() {
  53. this.loadSuiteFrame();
  54. }, this), 50);
  55. return;
  56. }
  57. selenium = Selenium.createForWindow(appWindow);
  58. this._registerCommandHandlers();
  59. }
  60. this.controlPanel.setHighlightOption();
  61. var testSuiteName = this.controlPanel.getTestSuiteName();
  62. var self = this;
  63. if (testSuiteName) {
  64. suiteFrame.load(testSuiteName, function() {setTimeout(fnBind(self._onloadTestSuite, self), 50)} );
  65. selenium.browserbot.baseUrl = absolutify(testSuiteName, window.location.href);
  66. }
  67. // DGF or should we use the old default?
  68. // selenium.browserbot.baseUrl = window.location.href;
  69. if (this.controlPanel.getBaseUrl()) {
  70. selenium.browserbot.baseUrl = this.controlPanel.getBaseUrl();
  71. }
  72. },
  73. _getApplicationWindow: function () {
  74. if (this.controlPanel.isMultiWindowMode()) {
  75. return this._getSeparateApplicationWindow();
  76. }
  77. return sel$('selenium_myiframe').contentWindow;
  78. },
  79. _getSeparateApplicationWindow: function () {
  80. if (this.appWindow == null) {
  81. this.appWindow = openSeparateApplicationWindow('TestRunner-splash.html', this.controlPanel.isAutomatedRun());
  82. }
  83. return this.appWindow;
  84. },
  85. _onloadTestSuite:function () {
  86. suiteFrame = new HtmlTestSuiteFrame(getSuiteFrame());
  87. if (! this.getTestSuite().isAvailable()) {
  88. return;
  89. }
  90. if (this.controlPanel.isAutomatedRun()) {
  91. this.startTestSuite();
  92. } else if (this.controlPanel.getAutoUrl()) {
  93. //todo what is the autourl doing, left to check it out
  94. addLoadListener(this._getApplicationWindow(), fnBind(this._startSingleTest, this));
  95. this._getApplicationWindow().src = this.controlPanel.getAutoUrl();
  96. } else {
  97. var testCaseLoaded = fnBind(function(){this.testCaseLoaded=true;},this);
  98. var testNumber = 0;
  99. if (this.controlPanel.getTestNumber() != null){
  100. var testNumber = this.controlPanel.getTestNumber() - 1;
  101. }
  102. this.getTestSuite().getSuiteRows()[testNumber].loadTestCase(testCaseLoaded);
  103. }
  104. },
  105. _startSingleTest:function () {
  106. removeLoadListener(getApplicationWindow(), fnBind(this._startSingleTest, this));
  107. var singleTestName = this.controlPanel.getSingleTestName();
  108. testFrame.load(singleTestName, fnBind(this.startTest, this));
  109. },
  110. _registerCommandHandlers: function () {
  111. this.commandFactory = new CommandHandlerFactory();
  112. this.commandFactory.registerAll(selenium);
  113. },
  114. startTestSuite: function() {
  115. this.controlPanel.reset();
  116. this.metrics.resetMetrics();
  117. this.getTestSuite().reset();
  118. this.runAllTests = true;
  119. this.runNextTest();
  120. },
  121. runNextTest: function () {
  122. this.getTestSuite().updateSuiteWithResultOfPreviousTest();
  123. if (!this.runAllTests) {
  124. return;
  125. }
  126. this.getTestSuite().runNextTestInSuite();
  127. },
  128. startTest: function () {
  129. this.controlPanel.reset();
  130. testFrame.scrollToTop();
  131. //todo: move testFailed and storedVars to TestCase
  132. this.testFailed = false;
  133. storedVars = new Object();
  134. storedVars.nbsp = String.fromCharCode(160);
  135. storedVars.space = ' ';
  136. this.currentTest = new HtmlRunnerTestLoop(testFrame.getCurrentTestCase(), this.metrics, this.commandFactory);
  137. currentTest = this.currentTest;
  138. this.currentTest.start();
  139. },
  140. runSingleTest:function() {
  141. this.runAllTests = false;
  142. this.metrics.resetMetrics();
  143. this.startTest();
  144. }
  145. });
  146. var runInterval = 0;
  147. /** SeleniumFrame encapsulates an iframe element */
  148. var SeleniumFrame = classCreate();
  149. objectExtend(SeleniumFrame.prototype, {
  150. initialize : function(frame) {
  151. this.frame = frame;
  152. addLoadListener(this.frame, fnBind(this._handleLoad, this));
  153. },
  154. getWindow : function() {
  155. return this.frame.contentWindow;
  156. },
  157. getDocument : function() {
  158. return this.frame.contentWindow.document;
  159. },
  160. _handleLoad: function() {
  161. this._attachStylesheet();
  162. this._onLoad();
  163. if (this.loadCallback) {
  164. this.loadCallback();
  165. }
  166. },
  167. _attachStylesheet: function() {
  168. var d = this.getDocument();
  169. var head = d.getElementsByTagName('head').item(0);
  170. var styleLink = d.createElement("link");
  171. styleLink.rel = "stylesheet";
  172. styleLink.type = "text/css";
  173. if (browserVersion && browserVersion.isChrome) {
  174. // DGF We have to play a clever trick to get the right absolute path.
  175. // This trick works on most browsers, (not IE), but is only needed in
  176. // chrome
  177. var tempLink = window.document.createElement("link");
  178. tempLink.href = "selenium-test.css"; // this will become an absolute href
  179. styleLink.href = tempLink.href;
  180. } else {
  181. // this works in every browser (except Firefox in chrome mode)
  182. var styleSheetPath = window.location.pathname.replace(/[^\/\\]+$/, "selenium-test.css");
  183. if (browserVersion.isIE && window.location.protocol == "file:") {
  184. styleSheetPath = "file:///" + styleSheetPath;
  185. }
  186. styleLink.href = styleSheetPath;
  187. }
  188. // DGF You're only going to see this log message if you set defaultLogLevel=debug
  189. LOG.debug("styleLink.href="+styleLink.href);
  190. head.appendChild(styleLink);
  191. },
  192. _onLoad: function() {
  193. },
  194. scrollToTop : function() {
  195. this.frame.contentWindow.scrollTo(0, 0);
  196. },
  197. _setLocation: function(location) {
  198. var isChrome = browserVersion.isChrome || false;
  199. var isHTA = browserVersion.isHTA || false;
  200. // DGF TODO multiWindow
  201. location += (location.indexOf("?") == -1 ? "?" : "&");
  202. location += "thisIsChrome=" + isChrome + "&thisIsHTA=" + isHTA;
  203. if (browserVersion.isSafari) {
  204. // safari doesn't reload the page when the location equals to current location.
  205. // hence, set the location to blank so that the page will reload automatically.
  206. this.frame.src = "about:blank";
  207. this.frame.src = location;
  208. } else {
  209. this.frame.contentWindow.location.replace(location);
  210. }
  211. },
  212. load: function(/* url, [callback] */) {
  213. if (arguments.length > 1) {
  214. this.loadCallback = arguments[1];
  215. }
  216. this._setLocation(arguments[0]);
  217. }
  218. });
  219. /** HtmlTestSuiteFrame - encapsulates the suite iframe element */
  220. var HtmlTestSuiteFrame = classCreate();
  221. objectExtend(HtmlTestSuiteFrame.prototype, SeleniumFrame.prototype);
  222. objectExtend(HtmlTestSuiteFrame.prototype, {
  223. getCurrentTestSuite: function() {
  224. if (!this.currentTestSuite) {
  225. this.currentTestSuite = new HtmlTestSuite(this.getDocument());
  226. }
  227. return this.currentTestSuite;
  228. }
  229. });
  230. /** HtmlTestFrame - encapsulates the test-case iframe element */
  231. var HtmlTestFrame = classCreate();
  232. objectExtend(HtmlTestFrame.prototype, SeleniumFrame.prototype);
  233. objectExtend(HtmlTestFrame.prototype, {
  234. _onLoad: function() {
  235. this.currentTestCase = new HtmlTestCase(this.getWindow(), htmlTestRunner.getTestSuite().getCurrentRow());
  236. },
  237. getCurrentTestCase: function() {
  238. return this.currentTestCase;
  239. }
  240. });
  241. function onSeleniumLoad() {
  242. suiteFrame = new HtmlTestSuiteFrame(getSuiteFrame());
  243. testFrame = new HtmlTestFrame(getTestFrame());
  244. htmlTestRunner = new HtmlTestRunner();
  245. }
  246. var suiteFrame;
  247. var testFrame;
  248. function getSuiteFrame() {
  249. var f = sel$('testSuiteFrame');
  250. if (f == null) {
  251. f = top;
  252. // proxyInjection mode does not set selenium_myiframe
  253. }
  254. return f;
  255. }
  256. function getTestFrame() {
  257. var f = sel$('testFrame');
  258. if (f == null) {
  259. f = top;
  260. // proxyInjection mode does not set selenium_myiframe
  261. }
  262. return f;
  263. }
  264. var HtmlTestRunnerControlPanel = classCreate();
  265. objectExtend(HtmlTestRunnerControlPanel.prototype, URLConfiguration.prototype);
  266. objectExtend(HtmlTestRunnerControlPanel.prototype, {
  267. initialize: function() {
  268. this._acquireQueryString();
  269. this.runInterval = 0;
  270. this.highlightOption = sel$('highlightOption');
  271. this.pauseButton = sel$('pauseTest');
  272. this.stepButton = sel$('stepTest');
  273. this.highlightOption.onclick = fnBindAsEventListener((function() {
  274. this.setHighlightOption();
  275. }), this);
  276. this.pauseButton.onclick = fnBindAsEventListener(this.pauseCurrentTest, this);
  277. this.stepButton.onclick = fnBindAsEventListener(this.stepCurrentTest, this);
  278. this.speedController = new Control.Slider('speedHandle', 'speedTrack', {
  279. range: $R(0, 1000),
  280. onSlide: fnBindAsEventListener(this.setRunInterval, this),
  281. onChange: fnBindAsEventListener(this.setRunInterval, this)
  282. });
  283. this._parseQueryParameter();
  284. },
  285. setHighlightOption: function () {
  286. var isHighlight = this.highlightOption.checked;
  287. selenium.browserbot.setShouldHighlightElement(isHighlight);
  288. },
  289. _parseQueryParameter: function() {
  290. var tempRunInterval = this._getQueryParameter("runInterval");
  291. if (tempRunInterval) {
  292. this.setRunInterval(tempRunInterval);
  293. }
  294. this.highlightOption.checked = this._getQueryParameter("highlight");
  295. },
  296. setRunInterval: function(runInterval) {
  297. this.runInterval = runInterval;
  298. },
  299. setToPauseAtNextCommand: function() {
  300. this.runInterval = -1;
  301. },
  302. pauseCurrentTest: function () {
  303. this.setToPauseAtNextCommand();
  304. this._switchPauseButtonToContinue();
  305. },
  306. continueCurrentTest: function () {
  307. this.reset();
  308. currentTest.resume();
  309. },
  310. reset: function() {
  311. this.runInterval = this.speedController.value;
  312. this._switchContinueButtonToPause();
  313. },
  314. _switchContinueButtonToPause: function() {
  315. this.pauseButton.className = "cssPauseTest";
  316. this.pauseButton.onclick = fnBindAsEventListener(this.pauseCurrentTest, this);
  317. },
  318. _switchPauseButtonToContinue: function() {
  319. sel$('stepTest').disabled = false;
  320. this.pauseButton.className = "cssContinueTest";
  321. this.pauseButton.onclick = fnBindAsEventListener(this.continueCurrentTest, this);
  322. },
  323. stepCurrentTest: function () {
  324. this.setToPauseAtNextCommand();
  325. currentTest.resume();
  326. },
  327. isAutomatedRun: function() {
  328. return this._isQueryParameterTrue("auto");
  329. },
  330. shouldSaveResultsToFile: function() {
  331. return this._isQueryParameterTrue("save");
  332. },
  333. closeAfterTests: function() {
  334. return this._isQueryParameterTrue("close");
  335. },
  336. getTestSuiteName: function() {
  337. return this._getQueryParameter("test");
  338. },
  339. getTestNumber: function() {
  340. return this._getQueryParameter("testNumber");
  341. },
  342. getSingleTestName: function() {
  343. return this._getQueryParameter("singletest");
  344. },
  345. getAutoUrl: function() {
  346. return this._getQueryParameter("autoURL");
  347. },
  348. getDefaultLogLevel: function() {
  349. return this._getQueryParameter("defaultLogLevel");
  350. },
  351. getResultsUrl: function() {
  352. return this._getQueryParameter("resultsUrl");
  353. },
  354. _acquireQueryString: function() {
  355. if (this.queryString) return;
  356. if (browserVersion.isHTA) {
  357. var args = this._extractArgs();
  358. if (args.length < 2) return null;
  359. this.queryString = args[1];
  360. } else {
  361. this.queryString = location.search.substr(1);
  362. }
  363. }
  364. });
  365. var AbstractResultAwareRow = classCreate();
  366. objectExtend(AbstractResultAwareRow.prototype, {
  367. initialize: function(trElement) {
  368. this.trElement = trElement;
  369. },
  370. setStatus: function(status) {
  371. this.unselect();
  372. this.trElement.className = this.trElement.className.replace(/status_[a-z]+/, "");
  373. if (status) {
  374. addClassName(this.trElement, "status_" + status);
  375. }
  376. },
  377. select: function() {
  378. addClassName(this.trElement, "selected");
  379. safeScrollIntoView(this.trElement);
  380. },
  381. unselect: function() {
  382. removeClassName(this.trElement, "selected");
  383. },
  384. markPassed: function() {
  385. this.setStatus("passed");
  386. },
  387. markDone: function() {
  388. this.setStatus("done");
  389. },
  390. markFailed: function() {
  391. this.setStatus("failed");
  392. }
  393. });
  394. var TitleRow = classCreate();
  395. objectExtend(TitleRow.prototype, AbstractResultAwareRow.prototype);
  396. objectExtend(TitleRow.prototype, {
  397. initialize: function(trElement) {
  398. this.trElement = trElement;
  399. trElement.className = "title";
  400. }
  401. });
  402. var HtmlTestCaseRow = classCreate();
  403. objectExtend(HtmlTestCaseRow.prototype, AbstractResultAwareRow.prototype);
  404. objectExtend(HtmlTestCaseRow.prototype, {
  405. getCommand: function () {
  406. return new SeleniumCommand(getText(this.trElement.cells[0]),
  407. getText(this.trElement.cells[1]),
  408. getText(this.trElement.cells[2]),
  409. this.isBreakpoint());
  410. },
  411. markFailed: function(errorMsg) {
  412. AbstractResultAwareRow.prototype.markFailed.call(this, errorMsg);
  413. this.setMessage(errorMsg);
  414. },
  415. setMessage: function(message) {
  416. setText(this.trElement.cells[2], message);
  417. },
  418. reset: function() {
  419. this.setStatus(null);
  420. var thirdCell = this.trElement.cells[2];
  421. if (thirdCell) {
  422. if (thirdCell.originalHTML) {
  423. thirdCell.innerHTML = thirdCell.originalHTML;
  424. } else {
  425. thirdCell.originalHTML = thirdCell.innerHTML;
  426. }
  427. }
  428. },
  429. onClick: function() {
  430. if (this.trElement.isBreakpoint == undefined) {
  431. this.trElement.isBreakpoint = true;
  432. addClassName(this.trElement, "breakpoint");
  433. } else {
  434. this.trElement.isBreakpoint = undefined;
  435. removeClassName(this.trElement, "breakpoint");
  436. }
  437. },
  438. addBreakpointSupport: function() {
  439. elementSetStyle(this.trElement, {"cursor" : "pointer"});
  440. this.trElement.onclick = fnBindAsEventListener(function() {
  441. this.onClick();
  442. }, this);
  443. },
  444. isBreakpoint: function() {
  445. if (this.trElement.isBreakpoint == undefined || this.trElement.isBreakpoint == null) {
  446. return false
  447. }
  448. return this.trElement.isBreakpoint;
  449. }
  450. });
  451. var HtmlTestSuiteRow = classCreate();
  452. objectExtend(HtmlTestSuiteRow.prototype, AbstractResultAwareRow.prototype);
  453. objectExtend(HtmlTestSuiteRow.prototype, {
  454. initialize: function(trElement, testFrame, htmlTestSuite) {
  455. this.trElement = trElement;
  456. this.testFrame = testFrame;
  457. this.htmlTestSuite = htmlTestSuite;
  458. this.link = trElement.getElementsByTagName("a")[0];
  459. this.link.onclick = fnBindAsEventListener(this._onClick, this);
  460. },
  461. reset: function() {
  462. this.setStatus(null);
  463. },
  464. _onClick: function() {
  465. this.loadTestCase(null);
  466. return false;
  467. },
  468. loadTestCase: function(onloadFunction) {
  469. this.htmlTestSuite.unselectCurrentRow();
  470. this.select();
  471. this.htmlTestSuite.currentRowInSuite = this.trElement.rowIndex - 1;
  472. // If the row has a stored results table, use that
  473. var resultsFromPreviousRun = this.trElement.cells[1];
  474. if (resultsFromPreviousRun) {
  475. // todo: delegate to TestFrame, e.g.
  476. // this.testFrame.restoreTestCase(resultsFromPreviousRun.innerHTML);
  477. var testBody = this.testFrame.getDocument().body;
  478. testBody.innerHTML = resultsFromPreviousRun.innerHTML;
  479. this.testFrame._onLoad();
  480. if (onloadFunction) {
  481. onloadFunction();
  482. }
  483. } else {
  484. this.testFrame.load(this.link.href, onloadFunction);
  485. }
  486. },
  487. saveTestResults: function() {
  488. // todo: GLOBAL ACCESS!
  489. var resultHTML = this.testFrame.getDocument().body.innerHTML;
  490. if (!resultHTML) return;
  491. // todo: why create this div?
  492. var divElement = this.trElement.ownerDocument.createElement("div");
  493. divElement.innerHTML = resultHTML;
  494. var hiddenCell = this.trElement.ownerDocument.createElement("td");
  495. hiddenCell.appendChild(divElement);
  496. hiddenCell.style.display = "none";
  497. this.trElement.appendChild(hiddenCell);
  498. }
  499. });
  500. var HtmlTestSuite = classCreate();
  501. objectExtend(HtmlTestSuite.prototype, {
  502. initialize: function(suiteDocument) {
  503. this.suiteDocument = suiteDocument;
  504. this.suiteRows = this._collectSuiteRows();
  505. var testTable = this.getTestTable();
  506. if (!testTable) return;
  507. this.titleRow = new TitleRow(testTable.rows[0]);
  508. this.reset();
  509. },
  510. reset: function() {
  511. this.failed = false;
  512. this.currentRowInSuite = -1;
  513. this.titleRow.setStatus(null);
  514. for (var i = 0; i < this.suiteRows.length; i++) {
  515. var row = this.suiteRows[i];
  516. row.reset();
  517. }
  518. },
  519. getSuiteRows: function() {
  520. return this.suiteRows;
  521. },
  522. getTestTable: function() {
  523. var tables = sel$A(this.suiteDocument.getElementsByTagName("table"));
  524. return tables[0];
  525. },
  526. isAvailable: function() {
  527. return this.getTestTable() != null;
  528. },
  529. _collectSuiteRows: function () {
  530. var result = [];
  531. var tables = sel$A(this.suiteDocument.getElementsByTagName("table"));
  532. var testTable = tables[0];
  533. if (!testTable) return;
  534. for (rowNum = 1; rowNum < testTable.rows.length; rowNum++) {
  535. var rowElement = testTable.rows[rowNum];
  536. result.push(new HtmlTestSuiteRow(rowElement, testFrame, this));
  537. }
  538. // process the unsuited rows as well
  539. for (var tableNum = 1; tableNum < sel$A(this.suiteDocument.getElementsByTagName("table")).length; tableNum++) {
  540. testTable = tables[tableNum];
  541. for (rowNum = 1; rowNum < testTable.rows.length; rowNum++) {
  542. var rowElement = testTable.rows[rowNum];
  543. new HtmlTestSuiteRow(rowElement, testFrame, this);
  544. }
  545. }
  546. return result;
  547. },
  548. getCurrentRow: function() {
  549. if (this.currentRowInSuite == -1) {
  550. return null;
  551. }
  552. return this.suiteRows[this.currentRowInSuite];
  553. },
  554. unselectCurrentRow: function() {
  555. var currentRow = this.getCurrentRow()
  556. if (currentRow) {
  557. currentRow.unselect();
  558. }
  559. },
  560. markFailed: function() {
  561. this.failed = true;
  562. this.titleRow.markFailed();
  563. },
  564. markDone: function() {
  565. if (!this.failed) {
  566. this.titleRow.markPassed();
  567. }
  568. },
  569. _startCurrentTestCase: function() {
  570. this.getCurrentRow().loadTestCase(fnBind(htmlTestRunner.startTest, htmlTestRunner));
  571. },
  572. _onTestSuiteComplete: function() {
  573. this.markDone();
  574. new SeleniumTestResult(this.failed, this.getTestTable()).post();
  575. },
  576. updateSuiteWithResultOfPreviousTest: function() {
  577. if (this.currentRowInSuite >= 0) {
  578. this.getCurrentRow().saveTestResults();
  579. }
  580. },
  581. runNextTestInSuite: function() {
  582. this.currentRowInSuite++;
  583. // If we are done with all of the tests, set the title bar as pass or fail
  584. if (this.currentRowInSuite >= this.suiteRows.length) {
  585. this._onTestSuiteComplete();
  586. } else {
  587. this._startCurrentTestCase();
  588. }
  589. }
  590. });
  591. var SeleniumTestResult = classCreate();
  592. objectExtend(SeleniumTestResult.prototype, {
  593. // Post the results to a servlet, CGI-script, etc. The URL of the
  594. // results-handler defaults to "/postResults", but an alternative location
  595. // can be specified by providing a "resultsUrl" query parameter.
  596. //
  597. // Parameters passed to the results-handler are:
  598. // result: passed/failed depending on whether the suite passed or failed
  599. // totalTime: the total running time in seconds for the suite.
  600. //
  601. // numTestPasses: the total number of tests which passed.
  602. // numTestFailures: the total number of tests which failed.
  603. //
  604. // numCommandPasses: the total number of commands which passed.
  605. // numCommandFailures: the total number of commands which failed.
  606. // numCommandErrors: the total number of commands which errored.
  607. //
  608. // suite: the suite table, including the hidden column of test results
  609. // testTable.1 to testTable.N: the individual test tables
  610. //
  611. initialize: function (suiteFailed, suiteTable) {
  612. this.controlPanel = htmlTestRunner.controlPanel;
  613. this.metrics = htmlTestRunner.metrics;
  614. this.suiteFailed = suiteFailed;
  615. this.suiteTable = suiteTable;
  616. },
  617. post: function () {
  618. if (!this.controlPanel.isAutomatedRun()) {
  619. return;
  620. }
  621. var form = document.createElement("form");
  622. document.body.appendChild(form);
  623. form.id = "resultsForm";
  624. form.method = "post";
  625. form.target = "selenium_myiframe";
  626. var resultsUrl = this.controlPanel.getResultsUrl();
  627. if (!resultsUrl) {
  628. resultsUrl = "./postResults";
  629. }
  630. var actionAndParameters = resultsUrl.split('?', 2);
  631. form.action = actionAndParameters[0];
  632. var resultsUrlQueryString = actionAndParameters[1];
  633. form.createHiddenField = function(name, value) {
  634. input = document.createElement("input");
  635. input.type = "hidden";
  636. input.name = name;
  637. input.value = value;
  638. this.appendChild(input);
  639. };
  640. if (resultsUrlQueryString) {
  641. var clauses = resultsUrlQueryString.split('&');
  642. for (var i = 0; i < clauses.length; i++) {
  643. var keyValuePair = clauses[i].split('=', 2);
  644. var key = unescape(keyValuePair[0]);
  645. var value = unescape(keyValuePair[1]);
  646. form.createHiddenField(key, value);
  647. }
  648. }
  649. form.createHiddenField("selenium.version", Selenium.version);
  650. form.createHiddenField("selenium.revision", Selenium.revision);
  651. form.createHiddenField("result", this.suiteFailed ? "failed" : "passed");
  652. form.createHiddenField("totalTime", Math.floor((this.metrics.currentTime - this.metrics.startTime) / 1000));
  653. form.createHiddenField("numTestPasses", this.metrics.numTestPasses);
  654. form.createHiddenField("numTestFailures", this.metrics.numTestFailures);
  655. form.createHiddenField("numCommandPasses", this.metrics.numCommandPasses);
  656. form.createHiddenField("numCommandFailures", this.metrics.numCommandFailures);
  657. form.createHiddenField("numCommandErrors", this.metrics.numCommandErrors);
  658. // Create an input for each test table. The inputs are named
  659. // testTable.1, testTable.2, etc.
  660. for (rowNum = 1; rowNum < this.suiteTable.rows.length; rowNum++) {
  661. // If there is a second column, then add a new input
  662. if (this.suiteTable.rows[rowNum].cells.length > 1) {
  663. var resultCell = this.suiteTable.rows[rowNum].cells[1];
  664. form.createHiddenField("testTable." + rowNum, resultCell.innerHTML);
  665. // remove the resultCell, so it's not included in the suite HTML
  666. resultCell.parentNode.removeChild(resultCell);
  667. }
  668. }
  669. form.createHiddenField("numTestTotal", rowNum-1);
  670. // Add HTML for the suite itself
  671. form.createHiddenField("suite", this.suiteTable.parentNode.innerHTML);
  672. var logMessages = [];
  673. while (LOG.pendingMessages.length > 0) {
  674. var msg = LOG.pendingMessages.shift();
  675. logMessages.push(msg.type);
  676. logMessages.push(": ");
  677. logMessages.push(msg.msg);
  678. logMessages.push('\n');
  679. }
  680. var logOutput = logMessages.join("");
  681. form.createHiddenField("log", logOutput);
  682. if (this.controlPanel.shouldSaveResultsToFile()) {
  683. this._saveToFile(resultsUrl, form);
  684. } else {
  685. form.submit();
  686. }
  687. document.body.removeChild(form);
  688. if (this.controlPanel.closeAfterTests()) {
  689. window.top.close();
  690. }
  691. },
  692. _saveToFile: function (fileName, form) {
  693. // This only works when run as an IE HTA
  694. var inputs = new Object();
  695. for (var i = 0; i < form.elements.length; i++) {
  696. inputs[form.elements[i].name] = form.elements[i].value;
  697. }
  698. var objFSO = new ActiveXObject("Scripting.FileSystemObject")
  699. // DGF get CSS
  700. var styles = "";
  701. try {
  702. var styleSheetPath = window.location.pathname.replace(/[^\/\\]+$/, "selenium-test.css");
  703. if (window.location.protocol == "file:") {
  704. var stylesFile = objFSO.OpenTextFile(styleSheetPath, 1);
  705. styles = stylesFile.ReadAll();
  706. } else {
  707. var xhr = XmlHttp.create();
  708. xhr.open("GET", styleSheetPath, false);
  709. xhr.send("");
  710. styles = xhr.responseText;
  711. }
  712. } catch (e) {}
  713. var scriptFile = objFSO.CreateTextFile(fileName);
  714. scriptFile.WriteLine("<html><head><title>Test suite results</title><style>");
  715. scriptFile.WriteLine(styles);
  716. scriptFile.WriteLine("</style>");
  717. scriptFile.WriteLine("<body>\n<h1>Test suite results</h1>" +
  718. "\n\n<table>\n<tr>\n<td>result:</td>\n<td>" + inputs["result"] + "</td>\n" +
  719. "</tr>\n<tr>\n<td>totalTime:</td>\n<td>" + inputs["totalTime"] + "</td>\n</tr>\n" +
  720. "<tr>\n<td>numTestTotal:</td>\n<td>" + inputs["numTestTotal"] + "</td>\n</tr>\n" +
  721. "<tr>\n<td>numTestPasses:</td>\n<td>" + inputs["numTestPasses"] + "</td>\n</tr>\n" +
  722. "<tr>\n<td>numTestFailures:</td>\n<td>" + inputs["numTestFailures"] + "</td>\n</tr>\n" +
  723. "<tr>\n<td>numCommandPasses:</td>\n<td>" + inputs["numCommandPasses"] + "</td>\n</tr>\n" +
  724. "<tr>\n<td>numCommandFailures:</td>\n<td>" + inputs["numCommandFailures"] + "</td>\n</tr>\n" +
  725. "<tr>\n<td>numCommandErrors:</td>\n<td>" + inputs["numCommandErrors"] + "</td>\n</tr>\n" +
  726. "<tr>\n<td>" + inputs["suite"] + "</td>\n<td>&nbsp;</td>\n</tr></table><table>");
  727. var testNum = inputs["numTestTotal"];
  728. for (var rowNum = 1; rowNum <= testNum; rowNum++) {
  729. scriptFile.WriteLine("<tr>\n<td>" + inputs["testTable." + rowNum] + "</td>\n<td>&nbsp;</td>\n</tr>");
  730. }
  731. scriptFile.WriteLine("</table><pre>");
  732. var log = inputs["log"];
  733. log=log.replace(/&/gm,"&amp;").replace(/</gm,"&lt;").replace(/>/gm,"&gt;").replace(/"/gm,"&quot;").replace(/'/gm,"&apos;");
  734. scriptFile.WriteLine(log);
  735. scriptFile.WriteLine("</pre></body></html>");
  736. scriptFile.Close();
  737. }
  738. });
  739. /** HtmlTestCase encapsulates an HTML test document */
  740. var HtmlTestCase = classCreate();
  741. objectExtend(HtmlTestCase.prototype, {
  742. initialize: function(testWindow, htmlTestSuiteRow) {
  743. if (testWindow == null) {
  744. throw "testWindow should not be null";
  745. }
  746. if (htmlTestSuiteRow == null) {
  747. throw "htmlTestSuiteRow should not be null";
  748. }
  749. this.testWindow = testWindow;
  750. this.testDocument = testWindow.document;
  751. this.pathname = "'unknown'";
  752. try {
  753. if (this.testWindow.location) {
  754. this.pathname = this.testWindow.location.pathname;
  755. }
  756. } catch (e) {}
  757. this.htmlTestSuiteRow = htmlTestSuiteRow;
  758. this.headerRow = new TitleRow(this.testDocument.getElementsByTagName("tr")[0]);
  759. this.commandRows = this._collectCommandRows();
  760. this.nextCommandRowIndex = 0;
  761. this._addBreakpointSupport();
  762. },
  763. _collectCommandRows: function () {
  764. var commandRows = [];
  765. var tables = sel$A(this.testDocument.getElementsByTagName("table"));
  766. var self = this;
  767. for (var i = 0; i < tables.length; i++) {
  768. var table = tables[i];
  769. var tableRows = sel$A(table.rows);
  770. for (var j = 0; j < tableRows.length; j++) {
  771. var candidateRow = tableRows[j];
  772. if (self.isCommandRow(candidateRow)) {
  773. commandRows.push(new HtmlTestCaseRow(candidateRow));
  774. }
  775. }
  776. }
  777. return commandRows;
  778. },
  779. isCommandRow: function (row) {
  780. return row.cells.length >= 3;
  781. },
  782. reset: function() {
  783. /**
  784. * reset the test to runnable state
  785. */
  786. this.nextCommandRowIndex = 0;
  787. this.setStatus('');
  788. for (var i = 0; i < this.commandRows.length; i++) {
  789. var row = this.commandRows[i];
  790. row.reset();
  791. }
  792. // remove any additional fake "error" row added to the end of the document
  793. var errorElement = this.testDocument.getElementById('error');
  794. if (errorElement) {
  795. errorElement.parentNode.removeChild(errorElement);
  796. }
  797. },
  798. getCommandRows: function () {
  799. return this.commandRows;
  800. },
  801. setStatus: function(status) {
  802. this.headerRow.setStatus(status);
  803. },
  804. markFailed: function() {
  805. this.setStatus("failed");
  806. this.htmlTestSuiteRow.markFailed();
  807. },
  808. markPassed: function() {
  809. this.setStatus("passed");
  810. this.htmlTestSuiteRow.markPassed();
  811. },
  812. addErrorMessage: function(errorMsg, currentRow) {
  813. errorMsg = errorMsg.replace(/ /g, String.fromCharCode(160)).replace("\n", '\\n');
  814. if (currentRow) {
  815. currentRow.markFailed(errorMsg);
  816. } else {
  817. var errorElement = this.testDocument.createElement("p");
  818. errorElement.id = "error";
  819. setText(errorElement, errorMsg);
  820. this.testDocument.body.appendChild(errorElement);
  821. errorElement.className = "status_failed";
  822. }
  823. },
  824. _addBreakpointSupport: function() {
  825. for (var i = 0; i < this.commandRows.length; i++) {
  826. var row = this.commandRows[i];
  827. row.addBreakpointSupport();
  828. }
  829. },
  830. hasMoreCommandRows: function() {
  831. return this.nextCommandRowIndex < this.commandRows.length;
  832. },
  833. getNextCommandRow: function() {
  834. if (this.hasMoreCommandRows()) {
  835. return this.commandRows[this.nextCommandRowIndex++];
  836. }
  837. return null;
  838. }
  839. });
  840. // TODO: split out an JavascriptTestCase class to handle the "sejs" stuff
  841. var get_new_rows = function() {
  842. var row_array = new Array();
  843. for (var i = 0; i < new_block.length; i++) {
  844. var new_source = (new_block[i][0].tokenizer.source.slice(new_block[i][0].start,
  845. new_block[i][0].end));
  846. var row = '<td style="display:none;" class="js">getEval</td>' +
  847. '<td style="display:none;">currentTest.doNextCommand()</td>' +
  848. '<td style="white-space: pre;">' + new_source + '</td>' +
  849. '<td></td>'
  850. row_array.push(row);
  851. }
  852. return row_array;
  853. };
  854. var Metrics = classCreate();
  855. objectExtend(Metrics.prototype, {
  856. initialize: function() {
  857. // The number of tests run
  858. this.numTestPasses = 0;
  859. // The number of tests that have failed
  860. this.numTestFailures = 0;
  861. // The number of commands which have passed
  862. this.numCommandPasses = 0;
  863. // The number of commands which have failed
  864. this.numCommandFailures = 0;
  865. // The number of commands which have caused errors (element not found)
  866. this.numCommandErrors = 0;
  867. // The time that the test was started.
  868. this.startTime = null;
  869. // The current time.
  870. this.currentTime = null;
  871. },
  872. printMetrics: function() {
  873. setText(sel$('commandPasses'), this.numCommandPasses);
  874. setText(sel$('commandFailures'), this.numCommandFailures);
  875. setText(sel$('commandErrors'), this.numCommandErrors);
  876. setText(sel$('testRuns'), this.numTestPasses + this.numTestFailures);
  877. setText(sel$('testFailures'), this.numTestFailures);
  878. this.currentTime = new Date().getTime();
  879. var timeDiff = this.currentTime - this.startTime;
  880. var totalSecs = Math.floor(timeDiff / 1000);
  881. var minutes = Math.floor(totalSecs / 60);
  882. var seconds = totalSecs % 60;
  883. setText(sel$('elapsedTime'), this._pad(minutes) + ":" + this._pad(seconds));
  884. },
  885. // Puts a leading 0 on num if it is less than 10
  886. _pad: function(num) {
  887. return (num > 9) ? num : "0" + num;
  888. },
  889. resetMetrics: function() {
  890. this.numTestPasses = 0;
  891. this.numTestFailures = 0;
  892. this.numCommandPasses = 0;
  893. this.numCommandFailures = 0;
  894. this.numCommandErrors = 0;
  895. this.startTime = new Date().getTime();
  896. }
  897. });
  898. var HtmlRunnerCommandFactory = classCreate();
  899. objectExtend(HtmlRunnerCommandFactory.prototype, {
  900. initialize: function(seleniumCommandFactory, testLoop) {
  901. this.seleniumCommandFactory = seleniumCommandFactory;
  902. this.testLoop = testLoop;
  903. this.handlers = {};
  904. //todo: register commands
  905. },
  906. getCommandHandler: function(command) {
  907. if (this.handlers[command]) {
  908. return this.handlers[command];
  909. }
  910. return this.seleniumCommandFactory.getCommandHandler(command);
  911. }
  912. });
  913. var HtmlRunnerTestLoop = classCreate();
  914. objectExtend(HtmlRunnerTestLoop.prototype, new TestLoop());
  915. objectExtend(HtmlRunnerTestLoop.prototype, {
  916. initialize: function(htmlTestCase, metrics, seleniumCommandFactory) {
  917. this.commandFactory = new HtmlRunnerCommandFactory(seleniumCommandFactory, this);
  918. this.metrics = metrics;
  919. this.htmlTestCase = htmlTestCase;
  920. LOG.info("Starting test " + htmlTestCase.pathname);
  921. this.currentRow = null;
  922. this.currentRowIndex = 0;
  923. // used for selenium tests in javascript
  924. this.currentItem = null;
  925. this.commandAgenda = new Array();
  926. this.expectedFailure = null;
  927. this.expectedFailureType = null;
  928. this.htmlTestCase.reset();
  929. },
  930. _advanceToNextRow: function() {
  931. if (this.htmlTestCase.hasMoreCommandRows()) {
  932. this.currentRow = this.htmlTestCase.getNextCommandRow();
  933. if (this.sejsElement) {
  934. this.currentItem = agenda.pop();
  935. this.currentRowIndex++;
  936. }
  937. } else {
  938. this.currentRow = null;
  939. this.currentItem = null;
  940. }
  941. },
  942. nextCommand : function() {
  943. this._advanceToNextRow();
  944. if (this.currentRow == null) {
  945. return null;
  946. }
  947. return this.currentRow.getCommand();
  948. },
  949. commandStarted : function() {
  950. sel$('pauseTest').disabled = false;
  951. this.currentRow.select();
  952. this.metrics.printMetrics();
  953. },
  954. commandComplete : function(result) {
  955. this._checkExpectedFailure(result);
  956. if (result.failed) {
  957. this.metrics.numCommandFailures += 1;
  958. this._recordFailure(result.failureMessage);
  959. } else if (result.passed) {
  960. this.metrics.numCommandPasses += 1;
  961. this.currentRow.markPassed();
  962. } else {
  963. this.currentRow.markDone();
  964. }
  965. },
  966. _checkExpectedFailure : function(result) {
  967. if (this.expectedFailure != null) {
  968. if (this.expectedFailureJustSet) {
  969. this.expectedFailureJustSet = false;
  970. return;
  971. }
  972. if (!result.failed) {
  973. result.passed = false;
  974. result.failed = true;
  975. result.failureMessage = "Expected " + this.expectedFailureType + " did not occur.";
  976. } else {
  977. if (PatternMatcher.matches(this.expectedFailure, result.failureMessage)) {
  978. var failureType = result.error ? "error" : "failure";
  979. if (failureType == this.expectedFailureType) {
  980. result.failed = false;
  981. result.passed = true;
  982. } else {
  983. result.failed = true;
  984. result.failureMessage = "Expected "+this.expectedFailureType+", but "+failureType+" occurred instead";
  985. }
  986. } else {
  987. result.failed = true;
  988. result.failureMessage = "Expected " + this.expectedFailureType + " message '" + this.expectedFailure
  989. + "' but was '" + result.failureMessage + "'";
  990. }
  991. }
  992. this.expectedFailure = null;
  993. this.expectedFailureType = null;
  994. }
  995. },
  996. commandError : function(errorMessage) {
  997. var tempResult = {};
  998. tempResult.passed = false;
  999. tempResult.failed = true;
  1000. tempResult.error = true;
  1001. tempResult.failureMessage = errorMessage;
  1002. this._checkExpectedFailure(tempResult);
  1003. if (tempResult.passed) {
  1004. this.currentRow.markDone();
  1005. return true;
  1006. }
  1007. errorMessage = tempResult.failureMessage;
  1008. this.metrics.numCommandErrors += 1;
  1009. this._recordFailure(errorMessage);
  1010. },
  1011. _recordFailure : function(errorMsg) {
  1012. LOG.warn("currentTest.recordFailure: " + errorMsg);
  1013. htmlTestRunner.markFailed();
  1014. this.htmlTestCase.addErrorMessage(errorMsg, this.currentRow);
  1015. },
  1016. testComplete : function() {
  1017. sel$('pauseTest').disabled = true;
  1018. sel$('stepTest').disabled = true;
  1019. if (htmlTestRunner.testFailed) {
  1020. this.htmlTestCase.markFailed();
  1021. this.metrics.numTestFailures += 1;
  1022. } else {
  1023. this.htmlTestCase.markPassed();
  1024. this.metrics.numTestPasses += 1;
  1025. }
  1026. this.metrics.printMetrics();
  1027. window.setTimeout(function() {
  1028. htmlTestRunner.runNextTest();
  1029. }, 1);
  1030. },
  1031. getCommandInterval : function() {
  1032. return htmlTestRunner.controlPanel.runInterval;
  1033. },
  1034. pause : function() {
  1035. htmlTestRunner.controlPanel.pauseCurrentTest();
  1036. },
  1037. doNextCommand: function() {
  1038. var _n = this.currentItem[0];
  1039. var _x = this.currentItem[1];
  1040. new_block = new Array()
  1041. execute(_n, _x);
  1042. if (new_block.length > 0) {
  1043. var the_table = this.htmlTestCase.testDocument.getElementById("se-js-table")
  1044. var loc = this.currentRowIndex
  1045. var new_rows = get_new_rows()
  1046. // make the new statements visible on screen...
  1047. for (var i = 0; i < new_rows.length; i++) {
  1048. the_table.insertRow(loc + 1);
  1049. the_table.rows[loc + 1].innerHTML = new_rows[i];
  1050. this.commandRows.unshift(the_table.rows[loc + 1])
  1051. }
  1052. }
  1053. }
  1054. });
  1055. Selenium.prototype.doPause = function(waitTime) {
  1056. /** Wait for the specified amount of time (in milliseconds)
  1057. * @param waitTime the amount of time to sleep (in milliseconds)
  1058. */
  1059. // todo: should not refer to currentTest directly
  1060. currentTest.pauseInterval = waitTime;
  1061. };
  1062. Selenium.prototype.doBreak = function() {
  1063. /** Halt the currently running test, and wait for the user to press the Continue button.
  1064. * This command is useful for debugging, but be careful when using it, because it will
  1065. * force automated tests to hang until a user intervenes manually.
  1066. */
  1067. // todo: should not refer to controlPanel directly
  1068. htmlTestRunner.controlPanel.setToPauseAtNextCommand();
  1069. };
  1070. Selenium.prototype.doStore = function(expression, variableName) {
  1071. /** This command is a synonym for storeExpression.
  1072. * @param expression the value to store
  1073. * @param variableName the name of a <a href="#storedVars">variable</a> in which the result is to be stored.
  1074. */
  1075. storedVars[variableName] = expression;
  1076. }
  1077. /*
  1078. * Click on the located element, and attach a callback to notify
  1079. * when the page is reloaded.
  1080. */
  1081. // DGF TODO this code has been broken for some time... what is it trying to accomplish?
  1082. Selenium.prototype.XXXdoModalDialogTest = function(returnValue) {
  1083. this.browserbot.doModalDialogTest(returnValue);
  1084. };
  1085. Selenium.prototype.doEcho = function(message) {
  1086. /** Prints the specified message into the third table cell in your Selenese tables.
  1087. * Useful for debugging.
  1088. * @param message the message to print
  1089. */
  1090. currentTest.currentRow.setMessage(message);
  1091. }
  1092. /*
  1093. * doSetSpeed and getSpeed are already defined in selenium-api.js,
  1094. * so we're defining these functions in a tricky way so that doc.js doesn't
  1095. * try to read API doc from the function definitions here.
  1096. */
  1097. Selenium.prototype._doSetSpeed = function(value) {
  1098. var milliseconds = parseInt(value);
  1099. if (milliseconds < 0) milliseconds = 0;
  1100. htmlTestRunner.controlPanel.speedController.setValue(milliseconds);
  1101. htmlTestRunner.controlPanel.setRunInterval(milliseconds);
  1102. }
  1103. Selenium.prototype.doSetSpeed = Selenium.prototype._doSetSpeed;
  1104. Selenium.prototype._getSpeed = function() {
  1105. return htmlTestRunner.controlPanel.runInterval;
  1106. }
  1107. Selenium.prototype.getSpeed = Selenium.prototype._getSpeed;
  1108. Selenium.prototype.assertSelected = function(selectLocator, optionLocator) {
  1109. /**
  1110. * Verifies that the selected option of a drop-down satisfies the optionSpecifier. <i>Note that this command is deprecated; you should use assertSelectedLabel, assertSelectedValue, assertSelectedIndex, or assertSelectedId instead.</i>
  1111. *
  1112. * <p>See the select command for more information about option locators.</p>
  1113. *
  1114. * @param selectLocator an <a href="#locators">element locator</a> identifying a drop-down menu
  1115. * @param optionLocator an option locator, typically just an option label (e.g. "John Smith")
  1116. */
  1117. var element = this.page().findElement(selectLocator);
  1118. var locator = this.optionLocatorFactory.fromLocatorString(optionLocator);
  1119. if (element.selectedIndex == -1)
  1120. {
  1121. Assert.fail("No option selected");
  1122. }
  1123. locator.assertSelected(element);
  1124. };
  1125. Selenium.prototype.assertFailureOnNext = function(message) {
  1126. /**
  1127. * Tell Selenium to expect a failure on the next command execution.
  1128. * @param message The failure message we should expect. This command will fail if the wrong failure message appears.
  1129. */
  1130. if (!message) {
  1131. throw new SeleniumError("Message must be provided");
  1132. }
  1133. currentTest.expectedFailure = message;
  1134. currentTest.expectedFailureType = "failure";
  1135. currentTest.expectedFailureJustSet = true;
  1136. };
  1137. Selenium.prototype.assertErrorOnNext = function(message) {
  1138. /**
  1139. * Tell Selenium to expect an error on the next command execution.
  1140. * @param message The error message we should expect. This command will fail if the wrong error message appears.
  1141. */
  1142. // This command temporarily installs a CommandFactory that generates
  1143. // CommandHandlers that expect an error.
  1144. if (!message) {
  1145. throw new SeleniumError("Message must be provided");
  1146. }
  1147. currentTest.expectedFailure = message;
  1148. currentTest.expectedFailureType = "error";
  1149. currentTest.expectedFailureJustSet = true;
  1150. };