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 | |
28 | goog.provide('webdriver.testing.TestCase'); |
29 | |
30 | goog.require('goog.testing.TestCase'); |
31 | goog.require('webdriver.promise.ControlFlow'); |
32 | /** @suppress {extraRequire} Imported for user convenience. */ |
33 | goog.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 | */ |
48 | webdriver.testing.TestCase = function(client, opt_name) { |
49 | goog.base(this, opt_name); |
50 | |
51 | /** @private {!webdriver.testing.Client} */ |
52 | this.client_ = client; |
53 | }; |
54 | goog.inherits(webdriver.testing.TestCase, goog.testing.TestCase); |
55 | |
56 | |
57 | /** |
58 | * Executes the next test inside its own {@code webdriver.Application}. |
59 | * @override |
60 | */ |
61 | webdriver.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 */ |
96 | webdriver.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 | */ |
154 | webdriver.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 | }; |