##// END OF EJS Templates
Check for Python errors in js tests...
MinRK -
Show More
@@ -1,603 +1,627 b''
1 1 //
2 2 // Utility functions for the HTML notebook's CasperJS tests.
3 3 //
4 4 casper.get_notebook_server = function () {
5 5 // Get the URL of a notebook server on which to run tests.
6 6 port = casper.cli.get("port");
7 7 port = (typeof port === 'undefined') ? '8888' : port;
8 8 return 'http://127.0.0.1:' + port;
9 9 };
10 10
11 11 casper.open_new_notebook = function () {
12 12 // Create and open a new notebook.
13 13 var baseUrl = this.get_notebook_server();
14 14 this.start(baseUrl);
15 15 this.waitFor(this.page_loaded);
16 16 this.thenClick('button#new_notebook');
17 17 this.waitForPopup('');
18 18
19 19 this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
20 20 this.then(function () {
21 21 this.open(this.popups[0].url);
22 22 });
23 23 this.waitFor(this.page_loaded);
24 24
25 25 // Make sure the kernel has started
26 26 this.waitFor(this.kernel_running);
27 27 // track the IPython busy/idle state
28 28 this.thenEvaluate(function () {
29 29 require(['base/js/namespace', 'base/js/events'], function (IPython, events) {
30 30
31 31 events.on('status_idle.Kernel',function () {
32 32 IPython._status = 'idle';
33 33 });
34 34 events.on('status_busy.Kernel',function () {
35 35 IPython._status = 'busy';
36 36 });
37 37 });
38 38 });
39 39
40 40 // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
41 41 // sure the notebook has actually been loaded into the IPython namespace
42 42 // before running any tests.
43 43 this.waitFor(function() {
44 44 return this.evaluate(function () {
45 45 return IPython.notebook;
46 46 });
47 47 });
48 48 };
49 49
50 50 casper.page_loaded = function() {
51 51 // Return whether or not the kernel is running.
52 52 return this.evaluate(function() {
53 53 return typeof IPython !== "undefined" &&
54 54 IPython.page !== undefined;
55 55 });
56 56 };
57 57
58 58 casper.kernel_running = function() {
59 59 // Return whether or not the kernel is running.
60 60 return this.evaluate(function() {
61 61 return IPython.notebook.kernel.running;
62 62 });
63 63 };
64 64
65 65 casper.shutdown_current_kernel = function () {
66 66 // Shut down the current notebook's kernel.
67 67 this.thenEvaluate(function() {
68 68 IPython.notebook.session.delete();
69 69 });
70 70 // We close the page right after this so we need to give it time to complete.
71 71 this.wait(1000);
72 72 };
73 73
74 74 casper.delete_current_notebook = function () {
75 75 // Delete created notebook.
76 76
77 77 // For some unknown reason, this doesn't work?!?
78 78 this.thenEvaluate(function() {
79 79 IPython.notebook.delete();
80 80 });
81 81 };
82 82
83 83 casper.wait_for_busy = function () {
84 84 // Waits for the notebook to enter a busy state.
85 85 this.waitFor(function () {
86 86 return this.evaluate(function () {
87 87 return IPython._status == 'busy';
88 88 });
89 89 });
90 90 };
91 91
92 92 casper.wait_for_idle = function () {
93 93 // Waits for the notebook to idle.
94 94 this.waitFor(function () {
95 95 return this.evaluate(function () {
96 96 return IPython._status == 'idle';
97 97 });
98 98 });
99 99 };
100 100
101 101 casper.wait_for_output = function (cell_num, out_num) {
102 102 // wait for the nth output in a given cell
103 103 this.wait_for_idle();
104 104 out_num = out_num || 0;
105 105 this.then(function() {
106 106 this.waitFor(function (c, o) {
107 107 return this.evaluate(function get_output(c, o) {
108 108 var cell = IPython.notebook.get_cell(c);
109 109 return cell.output_area.outputs.length > o;
110 110 },
111 111 // pass parameter from the test suite js to the browser code js
112 112 {c : cell_num, o : out_num});
113 113 });
114 114 },
115 115 function then() { },
116 116 function timeout() {
117 117 this.echo("wait_for_output timed out!");
118 118 });
119 119 };
120 120
121 121 casper.wait_for_widget = function (widget_info) {
122 122 // wait for a widget msg que to reach 0
123 123 //
124 124 // Parameters
125 125 // ----------
126 126 // widget_info : object
127 127 // Object which contains info related to the widget. The model_id property
128 128 // is used to identify the widget.
129 129 this.waitFor(function () {
130 130 var pending = this.evaluate(function (m) {
131 131 return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
132 132 }, {m: widget_info.model_id});
133 133
134 134 if (pending === 0) {
135 135 return true;
136 136 } else {
137 137 return false;
138 138 }
139 139 });
140 140 };
141 141
142 142 casper.get_output_cell = function (cell_num, out_num) {
143 143 // return an output of a given cell
144 144 out_num = out_num || 0;
145 145 var result = casper.evaluate(function (c, o) {
146 146 var cell = IPython.notebook.get_cell(c);
147 147 return cell.output_area.outputs[o];
148 148 },
149 149 {c : cell_num, o : out_num});
150 150 if (!result) {
151 151 var num_outputs = casper.evaluate(function (c) {
152 152 var cell = IPython.notebook.get_cell(c);
153 153 return cell.output_area.outputs.length;
154 154 },
155 155 {c : cell_num});
156 156 this.test.assertTrue(false,
157 157 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
158 158 );
159 159 } else {
160 160 return result;
161 161 }
162 162 };
163 163
164 164 casper.get_cells_length = function () {
165 165 // return the number of cells in the notebook
166 166 var result = casper.evaluate(function () {
167 167 return IPython.notebook.get_cells().length;
168 168 });
169 169 return result;
170 170 };
171 171
172 172 casper.set_cell_text = function(index, text){
173 173 // Set the text content of a cell.
174 174 this.evaluate(function (index, text) {
175 175 var cell = IPython.notebook.get_cell(index);
176 176 cell.set_text(text);
177 177 }, index, text);
178 178 };
179 179
180 180 casper.get_cell_text = function(index){
181 181 // Get the text content of a cell.
182 182 return this.evaluate(function (index) {
183 183 var cell = IPython.notebook.get_cell(index);
184 184 return cell.get_text();
185 185 }, index);
186 186 };
187 187
188 188 casper.insert_cell_at_bottom = function(cell_type){
189 189 // Inserts a cell at the bottom of the notebook
190 190 // Returns the new cell's index.
191 191 return this.evaluate(function (cell_type) {
192 192 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
193 193 return IPython.notebook.find_cell_index(cell);
194 194 }, cell_type);
195 195 };
196 196
197 197 casper.append_cell = function(text, cell_type) {
198 198 // Insert a cell at the bottom of the notebook and set the cells text.
199 199 // Returns the new cell's index.
200 200 var index = this.insert_cell_at_bottom(cell_type);
201 201 if (text !== undefined) {
202 202 this.set_cell_text(index, text);
203 203 }
204 204 return index;
205 205 };
206 206
207 casper.execute_cell = function(index){
207 casper.execute_cell = function(index, expect_failure){
208 208 // Asynchronously executes a cell by index.
209 209 // Returns the cell's index.
210
211 if (expect_failure === undefined) expect_failure = false;
210 212 var that = this;
211 213 this.then(function(){
212 214 that.evaluate(function (index) {
213 215 var cell = IPython.notebook.get_cell(index);
214 216 cell.execute();
215 217 }, index);
216 218 });
219 this.wait_for_idle();
220
221 this.then(function () {
222 var error = that.evaluate(function (index) {
223 var cell = IPython.notebook.get_cell(index);
224 var outputs = cell.output_area.outputs;
225 for (var i = 0; i < outputs.length; i++) {
226 if (outputs[i].output_type == 'error') {
227 return outputs[i];
228 }
229 }
230 return false;
231 }, index);
232 if (error === null) {
233 this.test.fail("Failed to check for error output");
234 }
235 if (expect_failure && error === false) {
236 this.test.fail("Expected error while running cell");
237 } else if (!expect_failure && error !== false) {
238 this.test.fail("Error running cell:\n" + error.traceback.join('\n'));
239 }
240 });
217 241 return index;
218 242 };
219 243
220 casper.execute_cell_then = function(index, then_callback) {
244 casper.execute_cell_then = function(index, then_callback, expect_failure) {
221 245 // Synchronously executes a cell by index.
222 246 // Optionally accepts a then_callback parameter. then_callback will get called
223 247 // when the cell has finished executing.
224 248 // Returns the cell's index.
225 var return_val = this.execute_cell(index);
249 var return_val = this.execute_cell(index, expect_failure);
226 250
227 251 this.wait_for_idle();
228 252
229 253 var that = this;
230 254 this.then(function(){
231 255 if (then_callback!==undefined) {
232 256 then_callback.apply(that, [index]);
233 257 }
234 });
258 });
235 259
236 260 return return_val;
237 261 };
238 262
239 263 casper.cell_element_exists = function(index, selector){
240 264 // Utility function that allows us to easily check if an element exists
241 265 // within a cell. Uses JQuery selector to look for the element.
242 266 return casper.evaluate(function (index, selector) {
243 267 var $cell = IPython.notebook.get_cell(index).element;
244 268 return $cell.find(selector).length > 0;
245 269 }, index, selector);
246 270 };
247 271
248 272 casper.cell_element_function = function(index, selector, function_name, function_args){
249 273 // Utility function that allows us to execute a jQuery function on an
250 274 // element within a cell.
251 275 return casper.evaluate(function (index, selector, function_name, function_args) {
252 276 var $cell = IPython.notebook.get_cell(index).element;
253 277 var $el = $cell.find(selector);
254 278 return $el[function_name].apply($el, function_args);
255 279 }, index, selector, function_name, function_args);
256 280 };
257 281
258 282 casper.validate_notebook_state = function(message, mode, cell_index) {
259 283 // Validate the entire dual mode state of the notebook. Make sure no more than
260 284 // one cell is selected, focused, in edit mode, etc...
261 285
262 286 // General tests.
263 287 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
264 288 message + '; keyboard and notebook modes match');
265 289 // Is the selected cell the only cell that is selected?
266 290 if (cell_index!==undefined) {
267 291 this.test.assert(this.is_only_cell_selected(cell_index),
268 292 message + '; cell ' + cell_index + ' is the only cell selected');
269 293 }
270 294
271 295 // Mode specific tests.
272 296 if (mode==='command') {
273 297 // Are the notebook and keyboard manager in command mode?
274 298 this.test.assertEquals(this.get_keyboard_mode(), 'command',
275 299 message + '; in command mode');
276 300 // Make sure there isn't a single cell in edit mode.
277 301 this.test.assert(this.is_only_cell_edit(null),
278 302 message + '; all cells in command mode');
279 303 this.test.assert(this.is_cell_editor_focused(null),
280 304 message + '; no cell editors are focused while in command mode');
281 305
282 306 } else if (mode==='edit') {
283 307 // Are the notebook and keyboard manager in edit mode?
284 308 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
285 309 message + '; in edit mode');
286 310 if (cell_index!==undefined) {
287 311 // Is the specified cell the only cell in edit mode?
288 312 this.test.assert(this.is_only_cell_edit(cell_index),
289 313 message + '; cell ' + cell_index + ' is the only cell in edit mode');
290 314 // Is the specified cell the only cell with a focused code mirror?
291 315 this.test.assert(this.is_cell_editor_focused(cell_index),
292 316 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
293 317 }
294 318
295 319 } else {
296 320 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
297 321 }
298 322 };
299 323
300 324 casper.select_cell = function(index) {
301 325 // Select a cell in the notebook.
302 326 this.evaluate(function (i) {
303 327 IPython.notebook.select(i);
304 328 }, {i: index});
305 329 };
306 330
307 331 casper.click_cell_editor = function(index) {
308 332 // Emulate a click on a cell's editor.
309 333
310 334 // Code Mirror does not play nicely with emulated brower events.
311 335 // Instead of trying to emulate a click, here we run code similar to
312 336 // the code used in Code Mirror that handles the mousedown event on a
313 337 // region of codemirror that the user can focus.
314 338 this.evaluate(function (i) {
315 339 var cm = IPython.notebook.get_cell(i).code_mirror;
316 340 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
317 341 cm.display.input.focus();
318 342 }, {i: index});
319 343 };
320 344
321 345 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
322 346 // Set the Code Mirror instance cursor's location.
323 347 this.evaluate(function (i, l, c) {
324 348 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
325 349 }, {i: index, l: line_index, c: char_index});
326 350 };
327 351
328 352 casper.focus_notebook = function() {
329 353 // Focus the notebook div.
330 354 this.evaluate(function (){
331 355 $('#notebook').focus();
332 356 }, {});
333 357 };
334 358
335 359 casper.trigger_keydown = function() {
336 360 // Emulate a keydown in the notebook.
337 361 for (var i = 0; i < arguments.length; i++) {
338 362 this.evaluate(function (k) {
339 363 var element = $(document);
340 364 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
341 365 element.trigger(event);
342 366 }, {k: arguments[i]});
343 367 }
344 368 };
345 369
346 370 casper.get_keyboard_mode = function() {
347 371 // Get the mode of the keyboard manager.
348 372 return this.evaluate(function() {
349 373 return IPython.keyboard_manager.mode;
350 374 }, {});
351 375 };
352 376
353 377 casper.get_notebook_mode = function() {
354 378 // Get the mode of the notebook.
355 379 return this.evaluate(function() {
356 380 return IPython.notebook.mode;
357 381 }, {});
358 382 };
359 383
360 384 casper.get_cell = function(index) {
361 385 // Get a single cell.
362 386 //
363 387 // Note: Handles to DOM elements stored in the cell will be useless once in
364 388 // CasperJS context.
365 389 return this.evaluate(function(i) {
366 390 var cell = IPython.notebook.get_cell(i);
367 391 if (cell) {
368 392 return cell;
369 393 }
370 394 return null;
371 395 }, {i : index});
372 396 };
373 397
374 398 casper.is_cell_editor_focused = function(index) {
375 399 // Make sure a cell's editor is the only editor focused on the page.
376 400 return this.evaluate(function(i) {
377 401 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
378 402 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
379 403 if (i === null) {
380 404 return focused_textarea.length === 0;
381 405 } else {
382 406 var cell = IPython.notebook.get_cell(i);
383 407 if (cell) {
384 408 return cell.code_mirror.getInputField() == focused_textarea[0];
385 409 }
386 410 }
387 411 return false;
388 412 }, {i : index});
389 413 };
390 414
391 415 casper.is_only_cell_selected = function(index) {
392 416 // Check if a cell is the only cell selected.
393 417 // Pass null as the index to check if no cells are selected.
394 418 return this.is_only_cell_on(index, 'selected', 'unselected');
395 419 };
396 420
397 421 casper.is_only_cell_edit = function(index) {
398 422 // Check if a cell is the only cell in edit mode.
399 423 // Pass null as the index to check if all of the cells are in command mode.
400 424 return this.is_only_cell_on(index, 'edit_mode', 'command_mode');
401 425 };
402 426
403 427 casper.is_only_cell_on = function(i, on_class, off_class) {
404 428 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
405 429 // All of the other cells are checked for the `off_class` DOM class.
406 430 // Pass null as the index to check if all of the cells have the `off_class`.
407 431 var cells_length = this.get_cells_length();
408 432 for (var j = 0; j < cells_length; j++) {
409 433 if (j === i) {
410 434 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
411 435 return false;
412 436 }
413 437 } else {
414 438 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
415 439 return false;
416 440 }
417 441 }
418 442 }
419 443 return true;
420 444 };
421 445
422 446 casper.cell_has_class = function(index, classes) {
423 447 // Check if a cell has a class.
424 448 return this.evaluate(function(i, c) {
425 449 var cell = IPython.notebook.get_cell(i);
426 450 if (cell) {
427 451 return cell.element.hasClass(c);
428 452 }
429 453 return false;
430 454 }, {i : index, c: classes});
431 455 };
432 456
433 457 casper.is_cell_rendered = function (index) {
434 458 return this.evaluate(function(i) {
435 459 return !!IPython.notebook.get_cell(i).rendered;
436 460 }, {i:index});
437 461 };
438 462
439 463 casper.assert_colors_equal = function (hex_color, local_color, msg) {
440 464 // Tests to see if two colors are equal.
441 465 //
442 466 // Parameters
443 467 // hex_color: string
444 468 // Hexadecimal color code, with or without preceeding hash character.
445 469 // local_color: string
446 470 // Local color representation. Can either be hexadecimal (default for
447 471 // phantom) or rgb (default for slimer).
448 472
449 473 // Remove parentheses, hashes, semi-colons, and space characters.
450 474 hex_color = hex_color.replace(/[\(\); #]/, '');
451 475 local_color = local_color.replace(/[\(\); #]/, '');
452 476
453 477 // If the local color is rgb, clean it up and replace
454 478 if (local_color.substr(0,3).toLowerCase() == 'rgb') {
455 479 components = local_color.substr(3).split(',');
456 480 local_color = '';
457 481 for (var i = 0; i < components.length; i++) {
458 482 var part = parseInt(components[i]).toString(16);
459 483 while (part.length < 2) part = '0' + part;
460 484 local_color += part;
461 485 }
462 486 }
463 487
464 488 this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
465 489 };
466 490
467 491 casper.notebook_test = function(test) {
468 492 // Wrap a notebook test to reduce boilerplate.
469 493 this.open_new_notebook();
470 494
471 495 // Echo whether or not we are running this test using SlimerJS
472 496 if (this.evaluate(function(){
473 497 return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
474 498 })) {
475 499 console.log('This test is running in SlimerJS.');
476 500 this.slimerjs = true;
477 501 }
478 502
479 503 // Make sure to remove the onbeforeunload callback. This callback is
480 504 // responsible for the "Are you sure you want to quit?" type messages.
481 505 // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
482 506 this.then(function(){
483 507 this.evaluate(function(){
484 508 window.onbeforeunload = function(){};
485 509 });
486 510 });
487 511
488 512 this.then(test);
489 513
490 514 // Kill the kernel and delete the notebook.
491 515 this.shutdown_current_kernel();
492 516 // This is still broken but shouldn't be a problem for now.
493 517 // this.delete_current_notebook();
494 518
495 519 // This is required to clean up the page we just finished with. If we don't call this
496 520 // casperjs will leak file descriptors of all the open WebSockets in that page. We
497 521 // have to set this.page=null so that next time casper.start runs, it will create a
498 522 // new page from scratch.
499 523 this.then(function () {
500 524 this.page.close();
501 525 this.page = null;
502 526 });
503 527
504 528 // Run the browser automation.
505 529 this.run(function() {
506 530 this.test.done();
507 531 });
508 532 };
509 533
510 534 casper.wait_for_dashboard = function () {
511 535 // Wait for the dashboard list to load.
512 536 casper.waitForSelector('.list_item');
513 537 };
514 538
515 539 casper.open_dashboard = function () {
516 540 // Start casper by opening the dashboard page.
517 541 var baseUrl = this.get_notebook_server();
518 542 this.start(baseUrl);
519 543 this.waitFor(this.page_loaded);
520 544 this.wait_for_dashboard();
521 545 };
522 546
523 547 casper.dashboard_test = function (test) {
524 548 // Open the dashboard page and run a test.
525 549 this.open_dashboard();
526 550 this.then(test);
527 551
528 552 this.then(function () {
529 553 this.page.close();
530 554 this.page = null;
531 555 });
532 556
533 557 // Run the browser automation.
534 558 this.run(function() {
535 559 this.test.done();
536 560 });
537 561 };
538 562
539 563 casper.options.waitTimeout=10000;
540 564 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
541 565 this.echo("Timeout for " + casper.get_notebook_server());
542 566 this.echo("Is the notebook server running?");
543 567 });
544 568
545 569 casper.print_log = function () {
546 570 // Pass `console.log` calls from page JS to casper.
547 571 this.on('remote.message', function(msg) {
548 572 this.echo('Remote message caught: ' + msg);
549 573 });
550 574 };
551 575
552 576 casper.on("page.error", function onError(msg, trace) {
553 577 // show errors in the browser
554 578 this.echo("Page Error!");
555 579 for (var i = 0; i < trace.length; i++) {
556 580 var frame = trace[i];
557 581 var file = frame.file;
558 582 // shorten common phantomjs evaluate url
559 583 // this will have a different value on slimerjs
560 584 if (file === "phantomjs://webpage.evaluate()") {
561 585 file = "evaluate";
562 586 }
563 587 this.echo("line " + frame.line + " of " + file);
564 588 if (frame.function.length > 0) {
565 589 this.echo("in " + frame.function);
566 590 }
567 591 }
568 592 this.echo(msg);
569 593 });
570 594
571 595
572 596 casper.capture_log = function () {
573 597 // show captured errors
574 598 var captured_log = [];
575 599 var seen_errors = 0;
576 600 this.on('remote.message', function(msg) {
577 601 captured_log.push(msg);
578 602 });
579 603
580 604 this.test.on("test.done", function (result) {
581 605 // test.done runs per-file,
582 606 // but suiteResults is per-suite (directory)
583 607 var current_errors;
584 608 if (this.suiteResults) {
585 609 // casper 1.1 has suiteResults
586 610 current_errors = this.suiteResults.countErrors() + this.suiteResults.countFailed();
587 611 } else {
588 612 // casper 1.0 has testResults instead
589 613 current_errors = this.testResults.failed;
590 614 }
591 615
592 616 if (current_errors > seen_errors && captured_log.length > 0) {
593 617 casper.echo("\nCaptured console.log:");
594 618 for (var i = 0; i < captured_log.length; i++) {
595 619 casper.echo(" " + captured_log[i]);
596 620 }
597 621 }
598 622 seen_errors = current_errors;
599 623 captured_log = [];
600 624 });
601 625 };
602 626
603 627 casper.capture_log();
General Comments 0
You need to be logged in to leave comments. Login now