Commit 4949424c 4949424c8e222143fc38fbeb1e95833ca29e9830 by Nicolas Perriault

Merge pull request #693 from nathanboktae/wait-for-details

Better log messages for waitFor.timeout events
2 parents 414f83d2 17d0f210
...@@ -437,7 +437,7 @@ Emitted when a navigation step has been started. ...@@ -437,7 +437,7 @@ Emitted when a navigation step has been started.
437 ``step.timeout`` 437 ``step.timeout``
438 ~~~~~~~~~~~~~~~~ 438 ~~~~~~~~~~~~~~~~
439 439
440 **Arguments:** ``None`` 440 **Arguments:** ``[step, timeout]``
441 441
442 Emitted when a navigation step has been executed. 442 Emitted when a navigation step has been executed.
443 443
...@@ -483,9 +483,11 @@ Emitted when a ``Casper.wait()`` operation starts. ...@@ -483,9 +483,11 @@ Emitted when a ``Casper.wait()`` operation starts.
483 ``waitFor.timeout`` 483 ``waitFor.timeout``
484 ~~~~~~~~~~~~~~~~~~~ 484 ~~~~~~~~~~~~~~~~~~~
485 485
486 **Arguments:** ``None`` 486 **Arguments:** ``[timeout, details]``
487
488 Emitted when the execution time of a ``Casper.wait*()`` operation has exceeded the value of ``timeout``.
487 489
488 Emitted when the execution time of a ``Casper.wait*()`` operation has exceeded the value of ``Casper.options.stepTimeout``. 490 ``deatils`` is a property bag describing what was being waited on. For example, if ``waitForSelector`` timed out, ``details`` will have a ``selector`` string property that was the selector that did not show up in time.
489 491
490 492
491 .. index:: filters 493 .. index:: filters
......
...@@ -1943,7 +1943,7 @@ You can also write the same thing like this:: ...@@ -1943,7 +1943,7 @@ You can also write the same thing like this::
1943 ``waitFor()`` 1943 ``waitFor()``
1944 ------------------------------------------------------------------------------- 1944 -------------------------------------------------------------------------------
1945 1945
1946 **Signature:** ``waitFor(Function testFx[, Function then, Function onTimeout, Number timeout])`` 1946 **Signature:** ``waitFor(Function testFx[, Function then, Function onTimeout, Number timeout, Object details])``
1947 1947
1948 Waits until a function returns true to process any next step. 1948 Waits until a function returns true to process any next step.
1949 1949
...@@ -1977,6 +1977,9 @@ Example using the ``onTimeout`` callback:: ...@@ -1977,6 +1977,9 @@ Example using the ``onTimeout`` callback::
1977 1977
1978 casper.run(); 1978 casper.run();
1979 1979
1980 ``details`` is a property bag of various information that will be passed to the ``waitFor.timeout`` event, if it is emitted.
1981 This can be used for better error messages or to conditionally ignore some timeout events.
1982
1980 .. _casper_waitforpopup: 1983 .. _casper_waitforpopup:
1981 1984
1982 .. index:: Popups, New window, window.open, Tabs 1985 .. index:: Popups, New window, window.open, Tabs
......
...@@ -122,7 +122,7 @@ var Casper = function Casper(options) { ...@@ -122,7 +122,7 @@ var Casper = function Casper(options) {
122 retryTimeout: 20, 122 retryTimeout: 20,
123 waitTimeout: 5000, 123 waitTimeout: 5000,
124 clipRect : null, 124 clipRect : null,
125 viewportSize : null, 125 viewportSize : null
126 }; 126 };
127 // options 127 // options
128 this.options = utils.mergeObjects(this.defaults, options); 128 this.options = utils.mergeObjects(this.defaults, options);
...@@ -1504,7 +1504,7 @@ Casper.prototype.runStep = function runStep(step) { ...@@ -1504,7 +1504,7 @@ Casper.prototype.runStep = function runStep(step) {
1504 var stepTimeoutCheckInterval = setInterval(function _check(self, start, stepNum) { 1504 var stepTimeoutCheckInterval = setInterval(function _check(self, start, stepNum) {
1505 if (new Date().getTime() - start > self.options.stepTimeout) { 1505 if (new Date().getTime() - start > self.options.stepTimeout) {
1506 if (getCurrentSuiteId(self) === stepNum) { 1506 if (getCurrentSuiteId(self) === stepNum) {
1507 self.emit('step.timeout'); 1507 self.emit('step.timeout', stepNum, self.options.onStepTimeout);
1508 if (utils.isFunction(self.options.onStepTimeout)) { 1508 if (utils.isFunction(self.options.onStepTimeout)) {
1509 self.options.onStepTimeout.call(self, self.options.stepTimeout, stepNum); 1509 self.options.onStepTimeout.call(self, self.options.stepTimeout, stepNum);
1510 } 1510 }
...@@ -2003,12 +2003,14 @@ Casper.prototype.waitDone = function waitDone() { ...@@ -2003,12 +2003,14 @@ Casper.prototype.waitDone = function waitDone() {
2003 * @param Function then The next step to perform (optional) 2003 * @param Function then The next step to perform (optional)
2004 * @param Function onTimeout A callback function to call on timeout (optional) 2004 * @param Function onTimeout A callback function to call on timeout (optional)
2005 * @param Number timeout The max amount of time to wait, in milliseconds (optional) 2005 * @param Number timeout The max amount of time to wait, in milliseconds (optional)
2006 * @param Object details A property bag of information about the condition being waited on (optional)
2006 * @return Casper 2007 * @return Casper
2007 */ 2008 */
2008 Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) { 2009 Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout, details) {
2009 "use strict"; 2010 "use strict";
2010 this.checkStarted(); 2011 this.checkStarted();
2011 timeout = timeout ? timeout : this.options.waitTimeout; 2012 timeout = timeout || this.options.waitTimeout;
2013 details = details || { testFx: testFx };
2012 if (!utils.isFunction(testFx)) { 2014 if (!utils.isFunction(testFx)) {
2013 throw new CasperError("waitFor() needs a test function"); 2015 throw new CasperError("waitFor() needs a test function");
2014 } 2016 }
...@@ -2019,7 +2021,7 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) { ...@@ -2019,7 +2021,7 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
2019 this.waitStart(); 2021 this.waitStart();
2020 var start = new Date().getTime(); 2022 var start = new Date().getTime();
2021 var condition = false; 2023 var condition = false;
2022 var interval = setInterval(function _check(self, testFx, timeout, onTimeout) { 2024 var interval = setInterval(function _check(self) {
2023 /*jshint maxstatements:20*/ 2025 /*jshint maxstatements:20*/
2024 if ((new Date().getTime() - start < timeout) && !condition) { 2026 if ((new Date().getTime() - start < timeout) && !condition) {
2025 condition = testFx.call(self, self); 2027 condition = testFx.call(self, self);
...@@ -2029,13 +2031,13 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) { ...@@ -2029,13 +2031,13 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
2029 if (!condition) { 2031 if (!condition) {
2030 self.log("Casper.waitFor() timeout", "warning"); 2032 self.log("Casper.waitFor() timeout", "warning");
2031 var onWaitTimeout = onTimeout ? onTimeout : self.options.onWaitTimeout; 2033 var onWaitTimeout = onTimeout ? onTimeout : self.options.onWaitTimeout;
2032 self.emit('waitFor.timeout', timeout, onWaitTimeout); 2034 self.emit('waitFor.timeout', timeout, details);
2033 clearInterval(interval); // refs #383 2035 clearInterval(interval); // refs #383
2034 if (!utils.isFunction(onWaitTimeout)) { 2036 if (!utils.isFunction(onWaitTimeout)) {
2035 throw new CasperError('Invalid timeout function'); 2037 throw new CasperError('Invalid timeout function');
2036 } 2038 }
2037 try { 2039 try {
2038 return onWaitTimeout.call(self, timeout); 2040 return onWaitTimeout.call(self, timeout, details);
2039 } catch (error) { 2041 } catch (error) {
2040 self.emit('waitFor.timeout.error', error); 2042 self.emit('waitFor.timeout.error', error);
2041 } finally { 2043 } finally {
...@@ -2047,7 +2049,7 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) { ...@@ -2047,7 +2049,7 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
2047 if (then) { 2049 if (then) {
2048 self.then(then); 2050 self.then(then);
2049 } 2051 }
2050 }, this.options.retryTimeout, this, testFx, timeout, onTimeout); 2052 }, this.options.retryTimeout, this);
2051 this.waiters.push(interval); 2053 this.waiters.push(interval);
2052 }); 2054 });
2053 }; 2055 };
...@@ -2071,7 +2073,7 @@ Casper.prototype.waitForPopup = function waitForPopup(urlPattern, then, onTimeou ...@@ -2071,7 +2073,7 @@ Casper.prototype.waitForPopup = function waitForPopup(urlPattern, then, onTimeou
2071 } catch (e) { 2073 } catch (e) {
2072 return false; 2074 return false;
2073 } 2075 }
2074 }, then, onTimeout, timeout); 2076 }, then, onTimeout, timeout, { popup: urlPattern });
2075 }; 2077 };
2076 2078
2077 /** 2079 /**
...@@ -2090,7 +2092,7 @@ Casper.prototype.waitForResource = function waitForResource(test, then, onTimeou ...@@ -2090,7 +2092,7 @@ Casper.prototype.waitForResource = function waitForResource(test, then, onTimeou
2090 timeout = timeout ? timeout : this.options.waitTimeout; 2092 timeout = timeout ? timeout : this.options.waitTimeout;
2091 return this.waitFor(function _check() { 2093 return this.waitFor(function _check() {
2092 return this.resourceExists(test); 2094 return this.resourceExists(test);
2093 }, then, onTimeout, timeout); 2095 }, then, onTimeout, timeout, { resource: test });
2094 }; 2096 };
2095 2097
2096 /** 2098 /**
...@@ -2112,7 +2114,7 @@ Casper.prototype.waitForUrl = function waitForUrl(url, then, onTimeout, timeout) ...@@ -2112,7 +2114,7 @@ Casper.prototype.waitForUrl = function waitForUrl(url, then, onTimeout, timeout)
2112 return url.test(this.getCurrentUrl()); 2114 return url.test(this.getCurrentUrl());
2113 } 2115 }
2114 throw new CasperError('invalid url argument'); 2116 throw new CasperError('invalid url argument');
2115 }, then, onTimeout, timeout); 2117 }, then, onTimeout, timeout, { url: url });
2116 }; 2118 };
2117 2119
2118 /** 2120 /**
...@@ -2131,7 +2133,7 @@ Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTi ...@@ -2131,7 +2133,7 @@ Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTi
2131 timeout = timeout ? timeout : this.options.waitTimeout; 2133 timeout = timeout ? timeout : this.options.waitTimeout;
2132 return this.waitFor(function _check() { 2134 return this.waitFor(function _check() {
2133 return this.exists(selector); 2135 return this.exists(selector);
2134 }, then, onTimeout, timeout); 2136 }, then, onTimeout, timeout, { selector: selector });
2135 }; 2137 };
2136 2138
2137 /** 2139 /**
...@@ -2153,7 +2155,7 @@ Casper.prototype.waitForText = function(pattern, then, onTimeout, timeout) { ...@@ -2153,7 +2155,7 @@ Casper.prototype.waitForText = function(pattern, then, onTimeout, timeout) {
2153 return pattern.test(content); 2155 return pattern.test(content);
2154 } 2156 }
2155 return content.indexOf(pattern) !== -1; 2157 return content.indexOf(pattern) !== -1;
2156 }, then, onTimeout, timeout); 2158 }, then, onTimeout, timeout, { text: pattern });
2157 }; 2159 };
2158 2160
2159 /** 2161 /**
...@@ -2173,7 +2175,7 @@ Casper.prototype.waitForSelectorTextChange = function(selector, then, onTimeout, ...@@ -2173,7 +2175,7 @@ Casper.prototype.waitForSelectorTextChange = function(selector, then, onTimeout,
2173 var currentSelectorText = this.fetchText(selector); 2175 var currentSelectorText = this.fetchText(selector);
2174 return this.waitFor(function _check() { 2176 return this.waitFor(function _check() {
2175 return currentSelectorText !== this.fetchText(selector); 2177 return currentSelectorText !== this.fetchText(selector);
2176 }, then, onTimeout, timeout); 2178 }, then, onTimeout, timeout, { selectorTextChange: selector });
2177 }; 2179 };
2178 2180
2179 /** 2181 /**
...@@ -2192,7 +2194,10 @@ Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then, ...@@ -2192,7 +2194,10 @@ Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then,
2192 timeout = timeout ? timeout : this.options.waitTimeout; 2194 timeout = timeout ? timeout : this.options.waitTimeout;
2193 return this.waitFor(function _check() { 2195 return this.waitFor(function _check() {
2194 return !this.exists(selector); 2196 return !this.exists(selector);
2195 }, then, onTimeout, timeout); 2197 }, then, onTimeout, timeout, {
2198 selector: selector,
2199 waitWhile: true
2200 });
2196 }; 2201 };
2197 2202
2198 /** 2203 /**
...@@ -2211,7 +2216,7 @@ Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, on ...@@ -2211,7 +2216,7 @@ Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, on
2211 timeout = timeout ? timeout : this.options.waitTimeout; 2216 timeout = timeout ? timeout : this.options.waitTimeout;
2212 return this.waitFor(function _check() { 2217 return this.waitFor(function _check() {
2213 return this.visible(selector); 2218 return this.visible(selector);
2214 }, then, onTimeout, timeout); 2219 }, then, onTimeout, timeout, { visible: selector });
2215 }; 2220 };
2216 2221
2217 /** 2222 /**
...@@ -2230,7 +2235,10 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on ...@@ -2230,7 +2235,10 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on
2230 timeout = timeout ? timeout : this.options.waitTimeout; 2235 timeout = timeout ? timeout : this.options.waitTimeout;
2231 return this.waitFor(function _check() { 2236 return this.waitFor(function _check() {
2232 return !this.visible(selector); 2237 return !this.visible(selector);
2233 }, then, onTimeout, timeout); 2238 }, then, onTimeout, timeout, {
2239 visible: selector,
2240 waitWhile: true
2241 });
2234 }; 2242 };
2235 2243
2236 /** 2244 /**
......
...@@ -164,15 +164,6 @@ var Tester = function Tester(casper, options) { ...@@ -164,15 +164,6 @@ var Tester = function Tester(casper, options) {
164 } 164 }
165 }); 165 });
166 166
167 // casper events
168 this.casper.on('error', function onCasperError(msg, backtrace) {
169 self.processPhantomError(msg, backtrace);
170 });
171
172 this.casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
173 this.warn(f('wait timeout of %dms reached', timeout));
174 });
175
176 function errorHandler(error, backtrace) { 167 function errorHandler(error, backtrace) {
177 self.casper.unwait(); 168 self.casper.unwait();
178 if (error instanceof Error) { 169 if (error instanceof Error) {
...@@ -190,12 +181,17 @@ var Tester = function Tester(casper, options) { ...@@ -190,12 +181,17 @@ var Tester = function Tester(casper, options) {
190 } catch (e) {} 181 } catch (e) {}
191 self.uncaughtError(error, self.currentTestFile, line, backtrace); 182 self.uncaughtError(error, self.currentTestFile, line, backtrace);
192 } 183 }
193 184
194 function errorHandlerAndDone(error, backtrace) { 185 function errorHandlerAndDone(error, backtrace) {
195 errorHandler(error, backtrace); 186 errorHandler(error, backtrace);
196 self.done(); 187 self.done();
197 } 188 }
198 189
190 // casper events
191 this.casper.on('error', function onCasperError(msg, backtrace) {
192 self.processPhantomError(msg, backtrace);
193 });
194
199 [ 195 [
200 'wait.error', 196 'wait.error',
201 'waitFor.timeout.error', 197 'waitFor.timeout.error',
...@@ -227,8 +223,34 @@ var Tester = function Tester(casper, options) { ...@@ -227,8 +223,34 @@ var Tester = function Tester(casper, options) {
227 throw new TimedOutError(f("Timeout occured (%dms)", timeout)); 223 throw new TimedOutError(f("Timeout occured (%dms)", timeout));
228 }; 224 };
229 225
230 this.casper.options.onWaitTimeout = function test_onWaitTimeout(timeout) { 226 this.casper.options.onWaitTimeout = function test_onWaitTimeout(timeout, details) {
231 throw new TimedOutError(f("Wait timeout occured (%dms)", timeout)); 227 /*jshint maxcomplexity:10*/
228 var message = f("Wait timeout occured (%dms)", timeout);
229 details = details || {};
230
231 if (details.selector) {
232 message = f(details.waitWhile ? '"%s" never went away in %dms' : '"%s" still did not exist in %dms', details.selector, timeout);
233 }
234 else if (details.visible) {
235 message = f(details.waitWhile ? '"%s" never disappeared in %dms' : '"%s" never appeared in %dms', details.visible, timeout);
236 }
237 else if (details.url || details.resource) {
238 message = f('%s did not load in %dms', details.url || details.resource, timeout);
239 }
240 else if (details.popup) {
241 message = f('%s did not pop up in %dms', details.popup, timeout);
242 }
243 else if (details.text) {
244 message = f('"%s" did not appear in the page in %dms', details.text, timeout);
245 }
246 else if (details.selectorTextChange) {
247 message = f('"%s" did not have a text change in %dms', details.selectorTextChange, timeout);
248 }
249 else if (utils.isFunction(details.testFx)) {
250 message = f('"%s" did not evaluate to something truthy in %dms', details.testFx.toString(), timeout);
251 }
252
253 errorHandlerAndDone(new TimedOutError(message));
232 }; 254 };
233 }; 255 };
234 256
...@@ -857,7 +879,7 @@ Tester.prototype.assertInstanceOf = function assertInstanceOf(subject, construct ...@@ -857,7 +879,7 @@ Tester.prototype.assertInstanceOf = function assertInstanceOf(subject, construct
857 standard: f('Subject is instance of: "%s"', constructor.name), 879 standard: f('Subject is instance of: "%s"', constructor.name),
858 values: { 880 values: {
859 subject: subject, 881 subject: subject,
860 constructorName: constructor.name, 882 constructorName: constructor.name
861 } 883 }
862 }); 884 });
863 }; 885 };
......
...@@ -64,8 +64,9 @@ class CasperExecTestBase(unittest.TestCase): ...@@ -64,8 +64,9 @@ class CasperExecTestBase(unittest.TestCase):
64 if not what: 64 if not what:
65 raise AssertionError('Empty lookup') 65 raise AssertionError('Empty lookup')
66 if isinstance(what, (list, tuple)): 66 if isinstance(what, (list, tuple)):
67 output = self.runCommand(cmd, **kwargs)
67 for entry in what: 68 for entry in what:
68 self.assertIn(entry, self.runCommand(cmd, **kwargs)) 69 self.assertIn(entry, output)
69 else: 70 else:
70 self.assertIn(what, self.runCommand(cmd)) 71 self.assertIn(what, self.runCommand(cmd))
71 72
...@@ -287,6 +288,21 @@ class TestCommandOutputTest(CasperExecTestBase): ...@@ -287,6 +288,21 @@ class TestCommandOutputTest(CasperExecTestBase):
287 ], failing=True) 288 ], failing=True)
288 289
289 @timeout(20) 290 @timeout(20)
291 def test_waitFor_timeout(self):
292 # using begin()
293 script_path = os.path.join(TEST_ROOT, 'tester', 'waitFor_timeout.js')
294 self.assertCommandOutputContains('test ' + script_path, [
295 '"p.nonexistent" still did not exist in',
296 '"#encoded" did not have a text change in',
297 '"p[style]" never appeared in',
298 '/github\.com/ did not load in',
299 '/foobar/ did not pop up in',
300 '"Lorem ipsum" did not appear in the page in',
301 'return false',
302 'did not evaluate to something truthy in'
303 ], failing=True)
304
305 @timeout(20)
290 def test_dubious_test(self): 306 def test_dubious_test(self):
291 script_path = os.path.join(TEST_ROOT, 'tester', 'dubious.js') 307 script_path = os.path.join(TEST_ROOT, 'tester', 'dubious.js')
292 self.assertCommandOutputContains('test ' + script_path, [ 308 self.assertCommandOutputContains('test ' + script_path, [
......
1 /*global casper*/
2 /*jshint strict:false*/
3
4 casper.options.waitTimeout = 500;
5
6 casper.test.begin('waitForSelector fails with expected message', 1, function(test) {
7 casper.start('../site/waitFor.html');
8
9 casper.waitForSelector('p.nonexistent', function() {
10 throw new Error('waitForSelector found something it should not have');
11 });
12
13 casper.run(function() {
14 test.done();
15 });
16 });
17
18 casper.test.begin('waitWhileSelector fails with expected message', 1, function(test) {
19 casper.start('../site/waitFor.html');
20
21 casper.waitForSelector('#encoded');
22
23 casper.waitWhileSelector('#encoded', function() {
24 throw new Error('waitWhileSelector thought something got removed when it did not');
25 });
26
27 casper.run(function() {
28 test.done();
29 });
30 });
31
32 casper.test.begin('waitForSelectorTextChange fails with expected message', 1, function(test) {
33 casper.start('../site/waitFor.html');
34
35 casper.waitForSelectorTextChange('#encoded', function() {
36 throw new Error('waitForSelectorTextChange thought text changed when it did not');
37 });
38
39 casper.run(function() {
40 test.done();
41 });
42 });
43
44 casper.test.begin('waitUntilVisible fails with expected message', 1, function(test) {
45 casper.start('../site/waitFor.html');
46
47 casper.waitUntilVisible('p[style]', function() {
48 throw new Error('waitUntilVisible falsely identified a hidden paragraph');
49 });
50
51 casper.run(function() {
52 test.done();
53 });
54 });
55
56 casper.test.begin('waitWhileVisible fails with expected message', 1, function(test) {
57 casper.start('../site/waitFor.html');
58
59 casper.waitWhileVisible('img', function() {
60 throw new Error('waitWhileVisible thought something disappeared when it did not');
61 });
62
63 casper.run(function() {
64 test.done();
65 });
66 });
67
68 casper.test.begin('waitForUrl fails with expected message', 1, function(test) {
69 casper.start('../site/waitFor.html');
70
71 casper.waitForUrl(/github\.com/, function() {
72 throw new Error('waitForUrl thought we actually navigated to GitHub');
73 });
74
75 casper.run(function() {
76 test.done();
77 });
78 });
79
80 casper.test.begin('waitForPopup fails with expected message', 1, function(test) {
81 casper.start('../site/waitFor.html');
82
83 casper.waitForPopup(/foobar/, function() {
84 throw new Error('waitForPopup found something it should not have');
85 });
86
87 casper.run(function() {
88 test.done();
89 });
90 });
91
92 casper.test.begin('waitForText fails with expected message', 1, function(test) {
93 casper.start('../site/waitFor.html');
94
95 casper.waitForText("Lorem ipsum", function() {
96 throw new Error('waitForText found something it should not have');
97 });
98
99 casper.run(function() {
100 test.done();
101 });
102 });
103
104 casper.test.begin('waitFor fails with expected message', 1, function(test) {
105 casper.start('../site/waitFor.html');
106
107 casper.waitFor(function() {
108 return false
109 }, function() {
110 throw new Error('waitFor fasely succeeded');
111 });
112
113 casper.run(function() {
114 test.done();
115 });
116 });
...\ No newline at end of file ...\ No newline at end of file