lib/webdriver/testing/testcase.js

1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/**
16 * @fileoverview Defines a special test case that runs each test inside of a
17 * {@code webdriver.Application}. This allows each phase to schedule
18 * asynchronous actions that run to completion before the next phase of the
19 * test.
20 *
21 * This file requires the global {@code G_testRunner} to be initialized before
22 * use. This can be accomplished by also importing
23 * {@link webdriver.testing.jsunit}. This namespace is not required by default
24 * to improve interoperability with other namespaces that may initialize
25 * G_testRunner.
26 */
27
28goog.provide('webdriver.testing.TestCase');
29
30goog.require('goog.testing.TestCase');
31goog.require('webdriver.promise.ControlFlow');
32/** @suppress {extraRequire} Imported for user convenience. */
33goog.require('webdriver.testing.asserts');
34
35
36
37/**
38 * Constructs a test case that synchronizes each test case with the singleton
39 * {@code webdriver.promise.ControlFlow}.
40 *
41 * @param {!webdriver.testing.Client} client The test client to use for
42 * reporting test results.
43 * @param {string=} opt_name The name of the test case, defaults to
44 * 'Untitled Test Case'.
45 * @constructor
46 * @extends {goog.testing.TestCase}
47 */
48webdriver.testing.TestCase = function(client, opt_name) {
49 goog.base(this, opt_name);
50
51 /** @private {!webdriver.testing.Client} */
52 this.client_ = client;
53};
54goog.inherits(webdriver.testing.TestCase, goog.testing.TestCase);
55
56
57/**
58 * Executes the next test inside its own {@code webdriver.Application}.
59 * @override
60 */
61webdriver.testing.TestCase.prototype.cycleTests = function() {
62 var test = this.next();
63 if (!test) {
64 this.finalize();
65 return;
66 }
67
68 goog.testing.TestCase.currentTestName = test.name;
69 this.result_.runCount++;
70 this.log('Running test: ' + test.name);
71 this.client_.sendTestStartedEvent(test.name);
72
73 var self = this;
74 var hadError = false;
75 var app = webdriver.promise.controlFlow();
76
77 this.runSingleTest_(test, onError).then(function() {
78 hadError || self.doSuccess(test);
79 self.timeout(function() {
80 self.cycleTests();
81 }, 100);
82 });
83
84 function onError(e) {
85 hadError = true;
86 self.doError(test, app.annotateError(e));
87 // Note: result_ is a @protected field but still uses the trailing
88 // underscore.
89 var err = self.result_.errors[self.result_.errors.length - 1];
90 self.client_.sendErrorEvent(err.toString());
91 }
92};
93
94
95/** @override */
96webdriver.testing.TestCase.prototype.logError = function(name, opt_e) {
97 var errMsg = null;
98 var stack = null;
99 if (opt_e) {
100 this.log(opt_e);
101 if (goog.isString(opt_e)) {
102 errMsg = opt_e;
103 } else {
104 // In case someone calls this function directly, make sure we have a
105 // properly annotated error.
106 webdriver.promise.controlFlow().annotateError(opt_e);
107 errMsg = opt_e.toString();
108 stack = opt_e.stack.substring(errMsg.length + 1);
109 }
110 } else {
111 errMsg = 'An unknown error occurred';
112 }
113 var err = new goog.testing.TestCase.Error(name, errMsg, stack);
114
115 // Avoid double logging.
116 if (!opt_e || !opt_e['isJsUnitException'] ||
117 !opt_e['loggedJsUnitException']) {
118 this.saveMessage(err.toString());
119 }
120
121 if (opt_e && opt_e['isJsUnitException']) {
122 opt_e['loggedJsUnitException'] = true;
123 }
124
125 return err;
126};
127
128
129/**
130 * Executes a single test, scheduling each phase with the global application.
131 * Each phase will wait for the application to go idle before moving on to the
132 * next test phase. This function models the follow basic test flow:
133 *
134 * try {
135 * this.setUp.call(test.scope);
136 * test.ref.call(test.scope);
137 * } catch (ex) {
138 * onError(ex);
139 * } finally {
140 * try {
141 * this.tearDown.call(test.scope);
142 * } catch (e) {
143 * onError(e);
144 * }
145 * }
146 *
147 * @param {!goog.testing.TestCase.Test} test The test to run.
148 * @param {function(*)} onError The function to call each time an error is
149 * detected.
150 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
151 * test has finished running.
152 * @private
153 */
154webdriver.testing.TestCase.prototype.runSingleTest_ = function(test, onError) {
155 var flow = webdriver.promise.controlFlow();
156 flow.clearHistory();
157
158 return execute(test.name + '.setUp()', this.setUp)().
159 then(execute(test.name + '()', test.ref)).
160 thenCatch(onError).
161 then(execute(test.name + '.tearDown()', this.tearDown)).
162 thenCatch(onError);
163
164 function execute(description, fn) {
165 return function() {
166 return flow.execute(goog.bind(fn, test.scope), description);
167 }
168 }
169};