browser_test_harness.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. /**
  5. * @fileoverview
  6. * Integration module for QUnit tests running in browser tests.
  7. * Specifically it:
  8. * - Sets QUnit.autostart to false, so that the browser test can hook the test
  9. * results callback before the test starts.
  10. * - Implements a text-based test reporter to report test results back to the
  11. * browser test.
  12. */
  13. (function(QUnit, automationController, exports) {
  14. 'use strict';
  15. var TEST_TIMEOUT_IN_MS = 5000;
  16. var TestReporter = function() {
  17. this.errorMessage_ = '';
  18. this.failedTestsCount_ = 0;
  19. this.failedAssertions_ = [];
  20. };
  21. TestReporter.prototype.init = function(qunit) {
  22. qunit.testStart(this.onTestStart_.bind(this));
  23. qunit.testDone(this.onTestDone_.bind(this));
  24. qunit.log(this.onAssertion_.bind(this));
  25. };
  26. /**
  27. * @param {{ module:string, name: string }} details
  28. */
  29. TestReporter.prototype.onTestStart_ = function(details) {};
  30. /**
  31. * @param {{ module:string, name: string }} details
  32. */
  33. TestReporter.prototype.onTestDone_ = function(details) {
  34. if (this.failedAssertions_.length > 0) {
  35. this.errorMessage_ += ' ' + details.module + '.' + details.name + '\n';
  36. this.errorMessage_ += this.failedAssertions_.map(
  37. function(assertion, index){
  38. return ' ' + (index + 1) + '. ' + assertion.message + '\n' +
  39. ' ' + assertion.source;
  40. }).join('\n') + '\n';
  41. this.failedAssertions_ = [];
  42. this.failedTestsCount_++;
  43. }
  44. };
  45. TestReporter.prototype.onAssertion_ = function(details) {
  46. if (!details.result) {
  47. this.failedAssertions_.push(details);
  48. }
  49. };
  50. TestReporter.prototype.getErrorMessage = function(){
  51. var errorMessage = '';
  52. if (this.failedTestsCount_ > 0) {
  53. var test = (this.failedTestsCount_ > 1) ? 'tests' : 'test';
  54. errorMessage = this.failedTestsCount_ + ' ' + test + ' failed:\n';
  55. errorMessage += this.errorMessage_;
  56. }
  57. return errorMessage;
  58. };
  59. var BrowserTestHarness = function(qunit, domAutomationController, reporter) {
  60. this.qunit_ = qunit;
  61. this.automationController_ = domAutomationController;
  62. this.reporter_ = reporter;
  63. };
  64. BrowserTestHarness.prototype.init = function() {
  65. this.qunit_.config.autostart = false;
  66. };
  67. BrowserTestHarness.prototype.run = function() {
  68. this.reporter_.init(this.qunit_);
  69. this.qunit_.start();
  70. this.qunit_.done(function(details){
  71. this.automationController_.send(JSON.stringify({
  72. passed: details.passed == details.total,
  73. errorMessage: this.reporter_.getErrorMessage()
  74. }));
  75. }.bind(this));
  76. };
  77. // The browser test runs chrome with the flag --dom-automation, which creates
  78. // the window.domAutomationController object. This allows the test suite to
  79. // JS-encoded data back to the browser test.
  80. if (automationController) {
  81. if (!QUnit) {
  82. console.error('browser_test_harness.js must be included after QUnit.js.');
  83. return;
  84. }
  85. var testHarness = new BrowserTestHarness(
  86. QUnit,
  87. automationController,
  88. new TestReporter());
  89. testHarness.init();
  90. exports.browserTestHarness = testHarness;
  91. }
  92. var qunitTest = QUnit.test;
  93. var reasonTimeout = {};
  94. /**
  95. * Returns a promise that resolves after |delay| along with a timerId
  96. * for cancellation.
  97. *
  98. * @return {promise: !Promise, timerId: number}
  99. */
  100. BrowserTestHarness.timeout = function(delay) {
  101. var timerId = 0;
  102. var promise = new Promise(function(resolve) {
  103. timerId = window.setTimeout(function() {
  104. resolve();
  105. }, delay);
  106. });
  107. return {
  108. timerId: timerId,
  109. promise: promise
  110. };
  111. };
  112. QUnit.config.urlConfig.push({
  113. id: "disableTestTimeout",
  114. label: "disable test timeout",
  115. tooltip: "Check this when debugging locally to disable test timeout.",
  116. });
  117. /**
  118. * Forces the test to fail after |TEST_TIMEOUT_IN_MS|.
  119. *
  120. * @param {function(QUnit.Assert)} testCallback
  121. */
  122. BrowserTestHarness.test = function(testCallback) {
  123. return function() {
  124. var args = Array.prototype.slice.call(arguments);
  125. var timeout = BrowserTestHarness.timeout(TEST_TIMEOUT_IN_MS);
  126. var testPromise = Promise.resolve(testCallback.apply(this, args))
  127. .then(function() {
  128. window.clearTimeout(timeout.timerId);
  129. });
  130. var asserts = args[0];
  131. var timeoutPromise = timeout.promise.then(function(){
  132. asserts.ok(false, 'Test timed out after ' + TEST_TIMEOUT_IN_MS + ' ms')
  133. })
  134. return Promise.race([testPromise, timeoutPromise]);
  135. };
  136. };
  137. if (!QUnit.urlParams.disableTestTimeout) {
  138. QUnit.test = function(name, expected, testCallback, async) {
  139. qunitTest(name, expected, BrowserTestHarness.test(testCallback), async);
  140. };
  141. }
  142. })(window.QUnit, window.domAutomationController, window);