|
1 /* |
|
2 * Copyright (C) 2010 Google Inc. All rights reserved. |
|
3 * |
|
4 * Redistribution and use in source and binary forms, with or without |
|
5 * modification, are permitted provided that the following conditions are |
|
6 * met: |
|
7 * |
|
8 * * Redistributions of source code must retain the above copyright |
|
9 * notice, this list of conditions and the following disclaimer. |
|
10 * * Redistributions in binary form must reproduce the above |
|
11 * copyright notice, this list of conditions and the following disclaimer |
|
12 * in the documentation and/or other materials provided with the |
|
13 * distribution. |
|
14 * * Neither the name of Google Inc. nor the names of its |
|
15 * contributors may be used to endorse or promote products derived from |
|
16 * this software without specific prior written permission. |
|
17 * |
|
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
29 */ |
|
30 |
|
31 |
|
32 /** |
|
33 * @fileoverview This file contains small testing framework along with the |
|
34 * test suite for the frontend. These tests are a part of the continues build |
|
35 * and are executed by the devtools_sanity_unittest.cc as a part of the |
|
36 * Interactive UI Test suite. |
|
37 * FIXME: change field naming style to use trailing underscore. |
|
38 */ |
|
39 |
|
40 if (window.domAutomationController) { |
|
41 |
|
42 var ___interactiveUiTestsMode = true; |
|
43 |
|
44 /** |
|
45 * Test suite for interactive UI tests. |
|
46 * @constructor |
|
47 */ |
|
48 TestSuite = function() |
|
49 { |
|
50 this.controlTaken_ = false; |
|
51 this.timerId_ = -1; |
|
52 }; |
|
53 |
|
54 |
|
55 /** |
|
56 * Reports test failure. |
|
57 * @param {string} message Failure description. |
|
58 */ |
|
59 TestSuite.prototype.fail = function(message) |
|
60 { |
|
61 if (this.controlTaken_) |
|
62 this.reportFailure_(message); |
|
63 else |
|
64 throw message; |
|
65 }; |
|
66 |
|
67 |
|
68 /** |
|
69 * Equals assertion tests that expected === actual. |
|
70 * @param {Object} expected Expected object. |
|
71 * @param {Object} actual Actual object. |
|
72 * @param {string} opt_message User message to print if the test fails. |
|
73 */ |
|
74 TestSuite.prototype.assertEquals = function(expected, actual, opt_message) |
|
75 { |
|
76 if (expected !== actual) { |
|
77 var message = "Expected: '" + expected + "', but was '" + actual + "'"; |
|
78 if (opt_message) |
|
79 message = opt_message + "(" + message + ")"; |
|
80 this.fail(message); |
|
81 } |
|
82 }; |
|
83 |
|
84 |
|
85 /** |
|
86 * True assertion tests that value == true. |
|
87 * @param {Object} value Actual object. |
|
88 * @param {string} opt_message User message to print if the test fails. |
|
89 */ |
|
90 TestSuite.prototype.assertTrue = function(value, opt_message) |
|
91 { |
|
92 this.assertEquals(true, !!value, opt_message); |
|
93 }; |
|
94 |
|
95 |
|
96 /** |
|
97 * Contains assertion tests that string contains substring. |
|
98 * @param {string} string Outer. |
|
99 * @param {string} substring Inner. |
|
100 */ |
|
101 TestSuite.prototype.assertContains = function(string, substring) |
|
102 { |
|
103 if (string.indexOf(substring) === -1) |
|
104 this.fail("Expected to: '" + string + "' to contain '" + substring + "'"); |
|
105 }; |
|
106 |
|
107 |
|
108 /** |
|
109 * Takes control over execution. |
|
110 */ |
|
111 TestSuite.prototype.takeControl = function() |
|
112 { |
|
113 this.controlTaken_ = true; |
|
114 // Set up guard timer. |
|
115 var self = this; |
|
116 this.timerId_ = setTimeout(function() { |
|
117 self.reportFailure_("Timeout exceeded: 20 sec"); |
|
118 }, 20000); |
|
119 }; |
|
120 |
|
121 |
|
122 /** |
|
123 * Releases control over execution. |
|
124 */ |
|
125 TestSuite.prototype.releaseControl = function() |
|
126 { |
|
127 if (this.timerId_ !== -1) { |
|
128 clearTimeout(this.timerId_); |
|
129 this.timerId_ = -1; |
|
130 } |
|
131 this.reportOk_(); |
|
132 }; |
|
133 |
|
134 |
|
135 /** |
|
136 * Async tests use this one to report that they are completed. |
|
137 */ |
|
138 TestSuite.prototype.reportOk_ = function() |
|
139 { |
|
140 window.domAutomationController.send("[OK]"); |
|
141 }; |
|
142 |
|
143 |
|
144 /** |
|
145 * Async tests use this one to report failures. |
|
146 */ |
|
147 TestSuite.prototype.reportFailure_ = function(error) |
|
148 { |
|
149 if (this.timerId_ !== -1) { |
|
150 clearTimeout(this.timerId_); |
|
151 this.timerId_ = -1; |
|
152 } |
|
153 window.domAutomationController.send("[FAILED] " + error); |
|
154 }; |
|
155 |
|
156 |
|
157 /** |
|
158 * Runs all global functions starting with "test" as unit tests. |
|
159 */ |
|
160 TestSuite.prototype.runTest = function(testName) |
|
161 { |
|
162 try { |
|
163 this[testName](); |
|
164 if (!this.controlTaken_) |
|
165 this.reportOk_(); |
|
166 } catch (e) { |
|
167 this.reportFailure_(e); |
|
168 } |
|
169 }; |
|
170 |
|
171 |
|
172 /** |
|
173 * @param {string} panelName Name of the panel to show. |
|
174 */ |
|
175 TestSuite.prototype.showPanel = function(panelName) |
|
176 { |
|
177 // Open Scripts panel. |
|
178 var toolbar = document.getElementById("toolbar"); |
|
179 var button = toolbar.getElementsByClassName(panelName)[0]; |
|
180 button.click(); |
|
181 this.assertEquals(WebInspector.panels[panelName], WebInspector.currentPanel); |
|
182 }; |
|
183 |
|
184 |
|
185 /** |
|
186 * Overrides the method with specified name until it's called first time. |
|
187 * @param {Object} receiver An object whose method to override. |
|
188 * @param {string} methodName Name of the method to override. |
|
189 * @param {Function} override A function that should be called right after the |
|
190 * overriden method returns. |
|
191 * @param {boolean} opt_sticky Whether restore original method after first run |
|
192 * or not. |
|
193 */ |
|
194 TestSuite.prototype.addSniffer = function(receiver, methodName, override, opt_sticky) |
|
195 { |
|
196 var orig = receiver[methodName]; |
|
197 if (typeof orig !== "function") |
|
198 this.fail("Cannot find method to override: " + methodName); |
|
199 var test = this; |
|
200 receiver[methodName] = function(var_args) { |
|
201 try { |
|
202 var result = orig.apply(this, arguments); |
|
203 } finally { |
|
204 if (!opt_sticky) |
|
205 receiver[methodName] = orig; |
|
206 } |
|
207 // In case of exception the override won't be called. |
|
208 try { |
|
209 override.apply(this, arguments); |
|
210 } catch (e) { |
|
211 test.fail("Exception in overriden method '" + methodName + "': " + e); |
|
212 } |
|
213 return result; |
|
214 }; |
|
215 }; |
|
216 |
|
217 |
|
218 // UI Tests |
|
219 |
|
220 |
|
221 /** |
|
222 * Tests that the real injected host is present in the context. |
|
223 */ |
|
224 TestSuite.prototype.testHostIsPresent = function() |
|
225 { |
|
226 this.assertTrue(typeof InspectorFrontendHost === "object" && !InspectorFrontendHost.isStub); |
|
227 }; |
|
228 |
|
229 |
|
230 /** |
|
231 * Tests elements tree has an "HTML" root. |
|
232 */ |
|
233 TestSuite.prototype.testElementsTreeRoot = function() |
|
234 { |
|
235 var doc = WebInspector.domAgent.document; |
|
236 this.assertEquals("HTML", doc.documentElement.nodeName); |
|
237 this.assertTrue(doc.documentElement.hasChildNodes()); |
|
238 }; |
|
239 |
|
240 |
|
241 /** |
|
242 * Tests that main resource is present in the system and that it is |
|
243 * the only resource. |
|
244 */ |
|
245 TestSuite.prototype.testMainResource = function() |
|
246 { |
|
247 var tokens = []; |
|
248 var resources = WebInspector.resources; |
|
249 for (var id in resources) |
|
250 tokens.push(resources[id].lastPathComponent); |
|
251 this.assertEquals("simple_page.html", tokens.join(",")); |
|
252 }; |
|
253 |
|
254 |
|
255 /** |
|
256 * Tests that resources tab is enabled when corresponding item is selected. |
|
257 */ |
|
258 TestSuite.prototype.testEnableResourcesTab = function() |
|
259 { |
|
260 this.showPanel("resources"); |
|
261 |
|
262 var test = this; |
|
263 this.addSniffer(WebInspector, "updateResource", |
|
264 function(identifier, payload) { |
|
265 test.assertEquals("simple_page.html", payload.lastPathComponent); |
|
266 WebInspector.panels.resources.refresh(); |
|
267 WebInspector.panels.resources.revealAndSelectItem(WebInspector.resources[identifier]); |
|
268 |
|
269 test.releaseControl(); |
|
270 }); |
|
271 |
|
272 // Following call should lead to reload that we capture in the |
|
273 // addResource override. |
|
274 WebInspector.panels.resources._enableResourceTracking(); |
|
275 |
|
276 // We now have some time to report results to controller. |
|
277 this.takeControl(); |
|
278 }; |
|
279 |
|
280 |
|
281 /** |
|
282 * Tests that correct content length is reported for resources. |
|
283 */ |
|
284 TestSuite.prototype.testResourceContentLength = function() |
|
285 { |
|
286 this.showPanel("resources"); |
|
287 var test = this; |
|
288 |
|
289 var png = false; |
|
290 var html = false; |
|
291 this.addSniffer(WebInspector, "updateResource", |
|
292 function(identifier, payload) { |
|
293 if (!payload.didLengthChange) |
|
294 return; |
|
295 var resource = WebInspector.resources[identifier]; |
|
296 if (!resource || !resource.url) |
|
297 return; |
|
298 if (resource.url.search("image.html") !== -1) { |
|
299 var expectedLength = 87; |
|
300 test.assertTrue( |
|
301 resource.resourceSize <= expectedLength, |
|
302 "image.html content length is greater thatn expected."); |
|
303 if (expectedLength === resource.resourceSize) |
|
304 html = true; |
|
305 } else if (resource.url.search("image.png") !== -1) { |
|
306 var expectedLength = 257796; |
|
307 test.assertTrue( |
|
308 resource.resourceSize <= expectedLength, |
|
309 "image.png content length is greater than expected."); |
|
310 if (expectedLength === resource.resourceSize) |
|
311 png = true; |
|
312 } |
|
313 if (html && png) { |
|
314 // Wait 1 second before releasing control to check that the content |
|
315 // lengths are not updated anymore. |
|
316 setTimeout(function() { |
|
317 test.releaseControl(); |
|
318 }, 1000); |
|
319 } |
|
320 }, true); |
|
321 |
|
322 // Make sure resource tracking is on. |
|
323 WebInspector.panels.resources._enableResourceTracking(); |
|
324 // Reload inspected page to update all resources. |
|
325 test.evaluateInConsole_( |
|
326 "window.location.reload(true);", |
|
327 function(resultText) { |
|
328 test.assertEquals("undefined", resultText, "Unexpected result of reload()."); |
|
329 }); |
|
330 |
|
331 // We now have some time to report results to controller. |
|
332 this.takeControl(); |
|
333 }; |
|
334 |
|
335 |
|
336 /** |
|
337 * Tests resource headers. |
|
338 */ |
|
339 TestSuite.prototype.testResourceHeaders = function() |
|
340 { |
|
341 this.showPanel("resources"); |
|
342 |
|
343 var test = this; |
|
344 |
|
345 var responseOk = false; |
|
346 var timingOk = false; |
|
347 |
|
348 this.addSniffer(WebInspector, "updateResource", |
|
349 function(identifier, payload) { |
|
350 var resource = this.resources[identifier]; |
|
351 if (!resource || resource.mainResource) { |
|
352 // We are only interested in secondary resources in this test. |
|
353 return; |
|
354 } |
|
355 |
|
356 var requestHeaders = JSON.stringify(resource.requestHeaders); |
|
357 test.assertContains(requestHeaders, "Accept"); |
|
358 |
|
359 if (payload.didResponseChange) { |
|
360 var responseHeaders = JSON.stringify(resource.responseHeaders); |
|
361 test.assertContains(responseHeaders, "Content-type"); |
|
362 test.assertContains(responseHeaders, "Content-Length"); |
|
363 test.assertTrue(typeof resource.responseReceivedTime !== "undefined"); |
|
364 responseOk = true; |
|
365 } |
|
366 |
|
367 if (payload.didTimingChange) { |
|
368 test.assertTrue(typeof resource.startTime !== "undefined"); |
|
369 timingOk = true; |
|
370 } |
|
371 |
|
372 if (payload.didCompletionChange) { |
|
373 test.assertTrue(responseOk); |
|
374 test.assertTrue(timingOk); |
|
375 test.assertTrue(typeof resource.endTime !== "undefined"); |
|
376 test.releaseControl(); |
|
377 } |
|
378 }, true); |
|
379 |
|
380 WebInspector.panels.resources._enableResourceTracking(); |
|
381 this.takeControl(); |
|
382 }; |
|
383 |
|
384 |
|
385 /** |
|
386 * Tests the mime type of a cached (HTTP 304) resource. |
|
387 */ |
|
388 TestSuite.prototype.testCachedResourceMimeType = function() |
|
389 { |
|
390 this.showPanel("resources"); |
|
391 |
|
392 var test = this; |
|
393 var hasReloaded = false; |
|
394 |
|
395 this.addSniffer(WebInspector, "updateResource", |
|
396 function(identifier, payload) { |
|
397 var resource = this.resources[identifier]; |
|
398 if (!resource || resource.mainResource) { |
|
399 // We are only interested in secondary resources in this test. |
|
400 return; |
|
401 } |
|
402 |
|
403 if (payload.didResponseChange) { |
|
404 // Test server uses a default mime type for JavaScript files. |
|
405 test.assertEquals("text/html", payload.mimeType); |
|
406 if (!hasReloaded) { |
|
407 hasReloaded = true; |
|
408 // Reload inspected page to update all resources. |
|
409 test.evaluateInConsole_("window.location.reload(true);", function() {}); |
|
410 } else |
|
411 test.releaseControl(); |
|
412 } |
|
413 |
|
414 }, true); |
|
415 |
|
416 WebInspector.panels.resources._enableResourceTracking(); |
|
417 this.takeControl(); |
|
418 }; |
|
419 |
|
420 |
|
421 /** |
|
422 * Tests that profiler works. |
|
423 */ |
|
424 TestSuite.prototype.testProfilerTab = function() |
|
425 { |
|
426 this.showPanel("profiles"); |
|
427 |
|
428 var panel = WebInspector.panels.profiles; |
|
429 var test = this; |
|
430 |
|
431 function findDisplayedNode() { |
|
432 var node = panel.visibleView.profileDataGridTree.children[0]; |
|
433 if (!node) { |
|
434 // Profile hadn't been queried yet, re-schedule. |
|
435 window.setTimeout(findDisplayedNode, 100); |
|
436 return; |
|
437 } |
|
438 |
|
439 // Iterate over displayed functions and search for a function |
|
440 // that is called "fib" or "eternal_fib". If found, this will mean |
|
441 // that we actually have profiled page's code. |
|
442 while (node) { |
|
443 if (node.functionName.indexOf("fib") !== -1) |
|
444 test.releaseControl(); |
|
445 node = node.traverseNextNode(true, null, true); |
|
446 } |
|
447 |
|
448 test.fail(); |
|
449 } |
|
450 |
|
451 function findVisibleView() { |
|
452 if (!panel.visibleView) { |
|
453 setTimeout(findVisibleView, 0); |
|
454 return; |
|
455 } |
|
456 setTimeout(findDisplayedNode, 0); |
|
457 } |
|
458 |
|
459 findVisibleView(); |
|
460 this.takeControl(); |
|
461 }; |
|
462 |
|
463 |
|
464 /** |
|
465 * Tests that heap profiler works. |
|
466 */ |
|
467 TestSuite.prototype.testHeapProfiler = function() |
|
468 { |
|
469 this.showPanel("profiles"); |
|
470 |
|
471 var panel = WebInspector.panels.profiles; |
|
472 var test = this; |
|
473 |
|
474 function findDisplayedNode() { |
|
475 var node = panel.visibleView.dataGrid.children[0]; |
|
476 if (!node) { |
|
477 // Profile hadn't been queried yet, re-schedule. |
|
478 window.setTimeout(findDisplayedNode, 100); |
|
479 return; |
|
480 } |
|
481 |
|
482 // Iterate over displayed functions and find node called "A" |
|
483 // If found, this will mean that we actually have taken heap snapshot. |
|
484 while (node) { |
|
485 if (node.constructorName.indexOf("A") !== -1) { |
|
486 test.releaseControl(); |
|
487 return; |
|
488 } |
|
489 node = node.traverseNextNode(false, null, true); |
|
490 } |
|
491 |
|
492 test.fail(); |
|
493 } |
|
494 |
|
495 function findVisibleView() { |
|
496 if (!panel.visibleView) { |
|
497 setTimeout(findVisibleView, 0); |
|
498 return; |
|
499 } |
|
500 setTimeout(findDisplayedNode, 0); |
|
501 } |
|
502 |
|
503 WebInspector.HeapSnapshotProfileType.prototype.buttonClicked(); |
|
504 findVisibleView(); |
|
505 this.takeControl(); |
|
506 }; |
|
507 |
|
508 |
|
509 /** |
|
510 * Tests that scripts tab can be open and populated with inspected scripts. |
|
511 */ |
|
512 TestSuite.prototype.testShowScriptsTab = function() |
|
513 { |
|
514 this.showPanel("scripts"); |
|
515 var test = this; |
|
516 // There should be at least main page script. |
|
517 this._waitUntilScriptsAreParsed(["debugger_test_page.html"], |
|
518 function() { |
|
519 test.releaseControl(); |
|
520 }); |
|
521 // Wait until all scripts are added to the debugger. |
|
522 this.takeControl(); |
|
523 }; |
|
524 |
|
525 |
|
526 /** |
|
527 * Tests that scripts tab is populated with inspected scripts even if it |
|
528 * hadn't been shown by the moment inspected paged refreshed. |
|
529 * @see http://crbug.com/26312 |
|
530 */ |
|
531 TestSuite.prototype.testScriptsTabIsPopulatedOnInspectedPageRefresh = function() |
|
532 { |
|
533 var test = this; |
|
534 this.assertEquals(WebInspector.panels.elements, WebInspector.currentPanel, "Elements panel should be current one."); |
|
535 |
|
536 this.addSniffer(WebInspector.panels.scripts, "reset", waitUntilScriptIsParsed); |
|
537 |
|
538 // Reload inspected page. It will reset the debugger agent. |
|
539 test.evaluateInConsole_( |
|
540 "window.location.reload(true);", |
|
541 function(resultText) {}); |
|
542 |
|
543 function waitUntilScriptIsParsed() { |
|
544 test.showPanel("scripts"); |
|
545 test._waitUntilScriptsAreParsed(["debugger_test_page.html"], |
|
546 function() { |
|
547 test.releaseControl(); |
|
548 }); |
|
549 } |
|
550 |
|
551 // Wait until all scripts are added to the debugger. |
|
552 this.takeControl(); |
|
553 }; |
|
554 |
|
555 |
|
556 /** |
|
557 * Tests that scripts list contains content scripts. |
|
558 */ |
|
559 TestSuite.prototype.testContentScriptIsPresent = function() |
|
560 { |
|
561 this.showPanel("scripts"); |
|
562 var test = this; |
|
563 |
|
564 test._waitUntilScriptsAreParsed( |
|
565 ["page_with_content_script.html", "simple_content_script.js"], |
|
566 function() { |
|
567 test.releaseControl(); |
|
568 }); |
|
569 |
|
570 // Wait until all scripts are added to the debugger. |
|
571 this.takeControl(); |
|
572 }; |
|
573 |
|
574 |
|
575 /** |
|
576 * Tests that scripts are not duplicaed on Scripts tab switch. |
|
577 */ |
|
578 TestSuite.prototype.testNoScriptDuplicatesOnPanelSwitch = function() |
|
579 { |
|
580 var test = this; |
|
581 |
|
582 // There should be two scripts: one for the main page and another |
|
583 // one which is source of console API(see |
|
584 // InjectedScript._ensureCommandLineAPIInstalled). |
|
585 var expectedScriptsCount = 2; |
|
586 var parsedScripts = []; |
|
587 |
|
588 this.showPanel("scripts"); |
|
589 |
|
590 |
|
591 function switchToElementsTab() { |
|
592 test.showPanel("elements"); |
|
593 setTimeout(switchToScriptsTab, 0); |
|
594 } |
|
595 |
|
596 function switchToScriptsTab() { |
|
597 test.showPanel("scripts"); |
|
598 setTimeout(checkScriptsPanel, 0); |
|
599 } |
|
600 |
|
601 function checkScriptsPanel() { |
|
602 test.assertTrue(!!WebInspector.panels.scripts.visibleView, "No visible script view."); |
|
603 test.assertTrue(test._scriptsAreParsed(["debugger_test_page.html"]), "Some scripts are missing."); |
|
604 checkNoDuplicates(); |
|
605 test.releaseControl(); |
|
606 } |
|
607 |
|
608 function checkNoDuplicates() { |
|
609 var scriptSelect = document.getElementById("scripts-files"); |
|
610 var options = scriptSelect.options; |
|
611 for (var i = 0; i < options.length; i++) { |
|
612 var scriptName = options[i].text; |
|
613 for (var j = i + 1; j < options.length; j++) |
|
614 test.assertTrue(scriptName !== options[j].text, "Found script duplicates: " + test.optionsToString_(options)); |
|
615 } |
|
616 } |
|
617 |
|
618 test._waitUntilScriptsAreParsed( |
|
619 ["debugger_test_page.html"], |
|
620 function() { |
|
621 checkNoDuplicates(); |
|
622 setTimeout(switchToElementsTab, 0); |
|
623 }); |
|
624 |
|
625 |
|
626 // Wait until all scripts are added to the debugger. |
|
627 this.takeControl(); |
|
628 }; |
|
629 |
|
630 |
|
631 /** |
|
632 * Tests that a breakpoint can be set. |
|
633 */ |
|
634 TestSuite.prototype.testSetBreakpoint = function() |
|
635 { |
|
636 var test = this; |
|
637 this.showPanel("scripts"); |
|
638 |
|
639 var breakpointLine = 16 |
|
640 |
|
641 this._waitUntilScriptsAreParsed(["debugger_test_page.html"], |
|
642 function() { |
|
643 test.showMainPageScriptSource_( |
|
644 "debugger_test_page.html", |
|
645 function(view, url) { |
|
646 view._addBreakpoint(breakpointLine); |
|
647 |
|
648 test.evaluateInConsole_( |
|
649 'setTimeout("calculate()" , 0)', |
|
650 function(resultText) { |
|
651 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText); |
|
652 }); |
|
653 }); |
|
654 }); |
|
655 |
|
656 this._waitForScriptPause( |
|
657 { |
|
658 functionsOnStack: ["calculate", ""], |
|
659 lineNumber: breakpointLine, |
|
660 lineText: " result = fib(lastVal++);" |
|
661 }, |
|
662 function() { |
|
663 test.releaseControl(); |
|
664 }); |
|
665 |
|
666 this.takeControl(); |
|
667 }; |
|
668 |
|
669 |
|
670 /** |
|
671 * Tests that pause on exception works. |
|
672 */ |
|
673 TestSuite.prototype.testPauseOnException = function() |
|
674 { |
|
675 this.showPanel("scripts"); |
|
676 var test = this; |
|
677 |
|
678 InspectorBackend.setPauseOnExceptionsState(WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnUncaughtExceptions); |
|
679 |
|
680 this._executeCodeWhenScriptsAreParsed("handleClick()", ["pause_on_exception.html"]); |
|
681 |
|
682 this._waitForScriptPause( |
|
683 { |
|
684 functionsOnStack: ["throwAnException", "handleClick", ""], |
|
685 lineNumber: 6, |
|
686 lineText: " return unknown_var;" |
|
687 }, |
|
688 function() { |
|
689 test.releaseControl(); |
|
690 }); |
|
691 |
|
692 this.takeControl(); |
|
693 }; |
|
694 |
|
695 |
|
696 // Tests that debugger works correctly if pause event occurs when DevTools |
|
697 // frontend is being loaded. |
|
698 TestSuite.prototype.testPauseWhenLoadingDevTools = function() |
|
699 { |
|
700 this.showPanel("scripts"); |
|
701 var test = this; |
|
702 |
|
703 var expectations = { |
|
704 functionsOnStack: ["callDebugger"], |
|
705 lineNumber: 8, |
|
706 lineText: " debugger;" |
|
707 }; |
|
708 |
|
709 |
|
710 // Script execution can already be paused. |
|
711 if (WebInspector.currentPanel.paused) { |
|
712 var callFrame = WebInspector.currentPanel.sidebarPanes.callstack.selectedCallFrame; |
|
713 this.assertEquals(expectations.functionsOnStack[0], callFrame.functionName); |
|
714 var callbackInvoked = false; |
|
715 this._checkSourceFrameWhenLoaded(expectations, function() { |
|
716 callbackInvoked = true; |
|
717 if (test.controlTaken_) |
|
718 test.releaseControl(); |
|
719 }); |
|
720 if (!callbackInvoked) { |
|
721 test.takeControl(); |
|
722 } |
|
723 return; |
|
724 } |
|
725 |
|
726 this._waitForScriptPause( |
|
727 { |
|
728 functionsOnStack: ["callDebugger"], |
|
729 lineNumber: 8, |
|
730 lineText: " debugger;" |
|
731 }, |
|
732 function() { |
|
733 test.releaseControl(); |
|
734 }); |
|
735 this.takeControl(); |
|
736 }; |
|
737 |
|
738 |
|
739 // Tests that pressing "Pause" will pause script execution if the script |
|
740 // is already running. |
|
741 TestSuite.prototype.testPauseWhenScriptIsRunning = function() |
|
742 { |
|
743 this.showPanel("scripts"); |
|
744 var test = this; |
|
745 |
|
746 test.evaluateInConsole_( |
|
747 'setTimeout("handleClick()" , 0)', |
|
748 function(resultText) { |
|
749 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText); |
|
750 testScriptPauseAfterDelay(); |
|
751 }); |
|
752 |
|
753 // Wait for some time to make sure that inspected page is running the |
|
754 // infinite loop. |
|
755 function testScriptPauseAfterDelay() { |
|
756 setTimeout(testScriptPause, 300); |
|
757 } |
|
758 |
|
759 function testScriptPause() { |
|
760 // The script should be in infinite loop. Click "Pause" button to |
|
761 // pause it and wait for the result. |
|
762 WebInspector.panels.scripts.pauseButton.click(); |
|
763 |
|
764 test._waitForScriptPause( |
|
765 { |
|
766 functionsOnStack: ["handleClick", ""], |
|
767 lineNumber: 5, |
|
768 lineText: " while(true) {" |
|
769 }, |
|
770 function() { |
|
771 test.releaseControl(); |
|
772 }); |
|
773 } |
|
774 |
|
775 this.takeControl(); |
|
776 }; |
|
777 |
|
778 |
|
779 /** |
|
780 * Serializes options collection to string. |
|
781 * @param {HTMLOptionsCollection} options |
|
782 * @return {string} |
|
783 */ |
|
784 TestSuite.prototype.optionsToString_ = function(options) |
|
785 { |
|
786 var names = []; |
|
787 for (var i = 0; i < options.length; i++) |
|
788 names.push('"' + options[i].text + '"'); |
|
789 return names.join(","); |
|
790 }; |
|
791 |
|
792 |
|
793 /** |
|
794 * Ensures that main HTML resource is selected in Scripts panel and that its |
|
795 * source frame is setup. Invokes the callback when the condition is satisfied. |
|
796 * @param {HTMLOptionsCollection} options |
|
797 * @param {function(WebInspector.SourceView,string)} callback |
|
798 */ |
|
799 TestSuite.prototype.showMainPageScriptSource_ = function(scriptName, callback) |
|
800 { |
|
801 var test = this; |
|
802 |
|
803 var scriptSelect = document.getElementById("scripts-files"); |
|
804 var options = scriptSelect.options; |
|
805 |
|
806 test.assertTrue(options.length, "Scripts list is empty"); |
|
807 |
|
808 // Select page's script if it's not current option. |
|
809 var scriptResource; |
|
810 if (options[scriptSelect.selectedIndex].text === scriptName) |
|
811 scriptResource = options[scriptSelect.selectedIndex].representedObject; |
|
812 else { |
|
813 var pageScriptIndex = -1; |
|
814 for (var i = 0; i < options.length; i++) { |
|
815 if (options[i].text === scriptName) { |
|
816 pageScriptIndex = i; |
|
817 break; |
|
818 } |
|
819 } |
|
820 test.assertTrue(-1 !== pageScriptIndex, "Script with url " + scriptName + " not found among " + test.optionsToString_(options)); |
|
821 scriptResource = options[pageScriptIndex].representedObject; |
|
822 |
|
823 // Current panel is "Scripts". |
|
824 WebInspector.currentPanel._showScriptOrResource(scriptResource); |
|
825 test.assertEquals(pageScriptIndex, scriptSelect.selectedIndex, "Unexpected selected option index."); |
|
826 } |
|
827 |
|
828 test.assertTrue(scriptResource instanceof WebInspector.Resource, |
|
829 "Unexpected resource class."); |
|
830 test.assertTrue(!!scriptResource.url, "Resource URL is null."); |
|
831 test.assertTrue(scriptResource.url.search(scriptName + "$") !== -1, "Main HTML resource should be selected."); |
|
832 |
|
833 var scriptsPanel = WebInspector.panels.scripts; |
|
834 |
|
835 var view = scriptsPanel.visibleView; |
|
836 test.assertTrue(view instanceof WebInspector.SourceView); |
|
837 |
|
838 if (!view.sourceFrame._loaded) { |
|
839 test.addSniffer(view, "_sourceFrameSetupFinished", function(event) { |
|
840 callback(view, scriptResource.url); |
|
841 }); |
|
842 } else |
|
843 callback(view, scriptResource.url); |
|
844 }; |
|
845 |
|
846 |
|
847 /* |
|
848 * Evaluates the code in the console as if user typed it manually and invokes |
|
849 * the callback when the result message is received and added to the console. |
|
850 * @param {string} code |
|
851 * @param {function(string)} callback |
|
852 */ |
|
853 TestSuite.prototype.evaluateInConsole_ = function(code, callback) |
|
854 { |
|
855 WebInspector.showConsole(); |
|
856 WebInspector.console.prompt.text = code; |
|
857 WebInspector.console.promptElement.dispatchEvent( TestSuite.createKeyEvent("Enter")); |
|
858 |
|
859 this.addSniffer(WebInspector.ConsoleView.prototype, "addMessage", |
|
860 function(commandResult) { |
|
861 callback(commandResult.toMessageElement().textContent); |
|
862 }); |
|
863 }; |
|
864 |
|
865 |
|
866 /** |
|
867 * Tests eval on call frame. |
|
868 */ |
|
869 TestSuite.prototype.testEvalOnCallFrame = function() |
|
870 { |
|
871 this.showPanel("scripts"); |
|
872 |
|
873 var breakpointLine = 16; |
|
874 |
|
875 var test = this; |
|
876 this._waitUntilScriptsAreParsed(["debugger_test_page.html"], |
|
877 function() { |
|
878 test.showMainPageScriptSource_( |
|
879 "debugger_test_page.html", |
|
880 function(view, url) { |
|
881 view._addBreakpoint(breakpointLine); |
|
882 |
|
883 // Since breakpoints are ignored in evals' calculate() function is |
|
884 // execute after zero-timeout so that the breakpoint is hit. |
|
885 test.evaluateInConsole_( |
|
886 'setTimeout("calculate(123)" , 0)', |
|
887 function(resultText) { |
|
888 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText); |
|
889 waitForBreakpointHit(); |
|
890 }); |
|
891 }); |
|
892 }); |
|
893 |
|
894 function waitForBreakpointHit() { |
|
895 test.addSniffer(WebInspector, |
|
896 "pausedScript", |
|
897 function(callFrames) { |
|
898 test.assertEquals(2, callFrames.length, "Unexpected stack depth on the breakpoint. " + JSON.stringify(callFrames, null, 4)); |
|
899 test.assertEquals("calculate", callFrames[0].functionName, "Unexpected top frame function."); |
|
900 // Evaluate "e+1" where "e" is an argument of "calculate" function. |
|
901 test.evaluateInConsole_( |
|
902 "e+1", |
|
903 function(resultText) { |
|
904 test.assertEquals("124", resultText, 'Unexpected "e+1" value.'); |
|
905 test.releaseControl(); |
|
906 }); |
|
907 }); |
|
908 } |
|
909 |
|
910 this.takeControl(); |
|
911 }; |
|
912 |
|
913 |
|
914 /** |
|
915 * Tests that console auto completion works when script execution is paused. |
|
916 */ |
|
917 TestSuite.prototype.testCompletionOnPause = function() |
|
918 { |
|
919 this.showPanel("scripts"); |
|
920 var test = this; |
|
921 this._executeCodeWhenScriptsAreParsed("handleClick()", ["completion_on_pause.html"]); |
|
922 |
|
923 this._waitForScriptPause( |
|
924 { |
|
925 functionsOnStack: ["innerFunction", "handleClick", ""], |
|
926 lineNumber: 9, |
|
927 lineText: " debugger;" |
|
928 }, |
|
929 showConsole); |
|
930 |
|
931 function showConsole() { |
|
932 if (WebInspector.currentFocusElement === WebInspector.console.promptElement) |
|
933 testLocalsCompletion(); |
|
934 else { |
|
935 test.addSniffer(WebInspector.console, "afterShow", testLocalsCompletion); |
|
936 WebInspector.showConsole(); |
|
937 } |
|
938 } |
|
939 |
|
940 function testLocalsCompletion() { |
|
941 checkCompletions("th", ["parameter1", "closureLocal", "p", "createClosureLocal"], testThisCompletion); |
|
942 } |
|
943 |
|
944 function testThisCompletion() { |
|
945 checkCompletions("this.", ["field1", "field2", "m"], testFieldCompletion); |
|
946 } |
|
947 |
|
948 function testFieldCompletion() { |
|
949 checkCompletions("this.field1.", ["id", "name"], function() { test.releaseControl(); }); |
|
950 } |
|
951 |
|
952 function checkCompletions(expression, expectedProperties, callback) { |
|
953 test.addSniffer(WebInspector.console, "_reportCompletions", |
|
954 function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) { |
|
955 test.assertTrue(!isException, "Exception while collecting completions"); |
|
956 for (var i = 0; i < expectedProperties.length; i++) { |
|
957 var name = expectedProperties[i]; |
|
958 test.assertTrue(result[name], "Name " + name + " not found among the completions: " + JSON.stringify(result)); |
|
959 } |
|
960 setTimeout(callback, 0); |
|
961 }); |
|
962 WebInspector.console.prompt.text = expression; |
|
963 WebInspector.console.prompt.autoCompleteSoon(); |
|
964 } |
|
965 |
|
966 this.takeControl(); |
|
967 }; |
|
968 |
|
969 |
|
970 /** |
|
971 * Tests that inspected page doesn't hang on reload if it contains a syntax |
|
972 * error and DevTools window is open. |
|
973 */ |
|
974 TestSuite.prototype.testAutoContinueOnSyntaxError = function() |
|
975 { |
|
976 // TODO(yurys): provide an implementation that works with ScriptDebugServer. |
|
977 }; |
|
978 |
|
979 |
|
980 /** |
|
981 * Checks current execution line against expectations. |
|
982 * @param {WebInspector.SourceFrame} sourceFrame |
|
983 * @param {number} lineNumber Expected line number |
|
984 * @param {string} lineContent Expected line text |
|
985 */ |
|
986 TestSuite.prototype._checkExecutionLine = function(sourceFrame, lineNumber, lineContent) |
|
987 { |
|
988 this.assertEquals(lineNumber, sourceFrame.executionLine, "Unexpected execution line number."); |
|
989 this.assertEquals(lineContent, sourceFrame._textModel.line(lineNumber - 1), "Unexpected execution line text."); |
|
990 } |
|
991 |
|
992 |
|
993 /** |
|
994 * Checks that all expected scripts are present in the scripts list |
|
995 * in the Scripts panel. |
|
996 * @param {Array.<string>} expected Regular expressions describing |
|
997 * expected script names. |
|
998 * @return {boolean} Whether all the scripts are in "scripts-files" select |
|
999 * box |
|
1000 */ |
|
1001 TestSuite.prototype._scriptsAreParsed = function(expected) |
|
1002 { |
|
1003 var scriptSelect = document.getElementById("scripts-files"); |
|
1004 var options = scriptSelect.options; |
|
1005 |
|
1006 // Check that at least all the expected scripts are present. |
|
1007 var missing = expected.slice(0); |
|
1008 for (var i = 0 ; i < options.length; i++) { |
|
1009 for (var j = 0; j < missing.length; j++) { |
|
1010 if (options[i].text.search(missing[j]) !== -1) { |
|
1011 missing.splice(j, 1); |
|
1012 break; |
|
1013 } |
|
1014 } |
|
1015 } |
|
1016 return missing.length === 0; |
|
1017 }; |
|
1018 |
|
1019 |
|
1020 /** |
|
1021 * Waits for script pause, checks expectations, and invokes the callback. |
|
1022 * @param {Object} expectations Dictionary of expectations |
|
1023 * @param {function():void} callback |
|
1024 */ |
|
1025 TestSuite.prototype._waitForScriptPause = function(expectations, callback) |
|
1026 { |
|
1027 var test = this; |
|
1028 // Wait until script is paused. |
|
1029 test.addSniffer( |
|
1030 WebInspector, |
|
1031 "pausedScript", |
|
1032 function(callFrames) { |
|
1033 var functionsOnStack = []; |
|
1034 for (var i = 0; i < callFrames.length; i++) |
|
1035 functionsOnStack.push(callFrames[i].functionName); |
|
1036 |
|
1037 test.assertEquals(expectations.functionsOnStack.join(","), functionsOnStack.join(","), "Unexpected stack."); |
|
1038 |
|
1039 // Check that execution line where the script is paused is |
|
1040 // expected one. |
|
1041 test._checkSourceFrameWhenLoaded(expectations, callback); |
|
1042 }); |
|
1043 }; |
|
1044 |
|
1045 |
|
1046 /** |
|
1047 * Waits for current source frame to load, checks expectations, and invokes |
|
1048 * the callback. |
|
1049 * @param {Object} expectations Dictionary of expectations |
|
1050 * @param {function():void} callback |
|
1051 */ |
|
1052 TestSuite.prototype._checkSourceFrameWhenLoaded = function(expectations, callback) |
|
1053 { |
|
1054 var test = this; |
|
1055 |
|
1056 var frame = WebInspector.currentPanel.visibleView.sourceFrame; |
|
1057 if (frame._loaded) |
|
1058 checkExecLine(); |
|
1059 else { |
|
1060 setTimeout(function() { |
|
1061 test._checkSourceFrameWhenLoaded(expectations, callback); |
|
1062 }, 100); |
|
1063 } |
|
1064 function checkExecLine() { |
|
1065 test._checkExecutionLine(frame, expectations.lineNumber, expectations.lineText); |
|
1066 callback(); |
|
1067 } |
|
1068 }; |
|
1069 |
|
1070 |
|
1071 /** |
|
1072 * Performs sequence of steps. |
|
1073 * @param {Array.<Object|Function>} Array [expectations1,action1,expectations2, |
|
1074 * action2,...,actionN]. |
|
1075 */ |
|
1076 TestSuite.prototype._performSteps = function(actions) |
|
1077 { |
|
1078 var test = this; |
|
1079 var i = 0; |
|
1080 function doNextAction() { |
|
1081 if (i > 0) |
|
1082 actions[i++](); |
|
1083 if (i < actions.length - 1) |
|
1084 test._waitForScriptPause(actions[i++], doNextAction); |
|
1085 } |
|
1086 doNextAction(); |
|
1087 }; |
|
1088 |
|
1089 |
|
1090 /** |
|
1091 * Waits until all the scripts are parsed and asynchronously executes the code |
|
1092 * in the inspected page. |
|
1093 */ |
|
1094 TestSuite.prototype._executeCodeWhenScriptsAreParsed = function(code, expectedScripts) |
|
1095 { |
|
1096 var test = this; |
|
1097 |
|
1098 function executeFunctionInInspectedPage() { |
|
1099 // Since breakpoints are ignored in evals' calculate() function is |
|
1100 // execute after zero-timeout so that the breakpoint is hit. |
|
1101 test.evaluateInConsole_( |
|
1102 'setTimeout("' + code + '" , 0)', |
|
1103 function(resultText) { |
|
1104 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText + ". Code: " + code); |
|
1105 }); |
|
1106 } |
|
1107 |
|
1108 test._waitUntilScriptsAreParsed(expectedScripts, executeFunctionInInspectedPage); |
|
1109 }; |
|
1110 |
|
1111 |
|
1112 /** |
|
1113 * Waits until all the scripts are parsed and invokes the callback. |
|
1114 */ |
|
1115 TestSuite.prototype._waitUntilScriptsAreParsed = function(expectedScripts, callback) |
|
1116 { |
|
1117 var test = this; |
|
1118 |
|
1119 function waitForAllScripts() { |
|
1120 if (test._scriptsAreParsed(expectedScripts)) |
|
1121 callback(); |
|
1122 else |
|
1123 test.addSniffer(WebInspector, "parsedScriptSource", waitForAllScripts); |
|
1124 } |
|
1125 |
|
1126 waitForAllScripts(); |
|
1127 }; |
|
1128 |
|
1129 |
|
1130 /** |
|
1131 * Waits until all debugger scripts are parsed and executes "a()" in the |
|
1132 * inspected page. |
|
1133 */ |
|
1134 TestSuite.prototype._executeFunctionForStepTest = function() |
|
1135 { |
|
1136 this._executeCodeWhenScriptsAreParsed("a()", ["debugger_step.html", "debugger_step.js"]); |
|
1137 }; |
|
1138 |
|
1139 |
|
1140 /** |
|
1141 * Tests step over in the debugger. |
|
1142 */ |
|
1143 TestSuite.prototype.testStepOver = function() |
|
1144 { |
|
1145 this.showPanel("scripts"); |
|
1146 var test = this; |
|
1147 |
|
1148 this._executeFunctionForStepTest(); |
|
1149 |
|
1150 this._performSteps([ |
|
1151 { |
|
1152 functionsOnStack: ["d","a",""], |
|
1153 lineNumber: 3, |
|
1154 lineText: " debugger;" |
|
1155 }, |
|
1156 function() { |
|
1157 document.getElementById("scripts-step-over").click(); |
|
1158 }, |
|
1159 { |
|
1160 functionsOnStack: ["d","a",""], |
|
1161 lineNumber: 5, |
|
1162 lineText: " var y = fact(10);" |
|
1163 }, |
|
1164 function() { |
|
1165 document.getElementById("scripts-step-over").click(); |
|
1166 }, |
|
1167 { |
|
1168 functionsOnStack: ["d","a",""], |
|
1169 lineNumber: 6, |
|
1170 lineText: " return y;" |
|
1171 }, |
|
1172 function() { |
|
1173 test.releaseControl(); |
|
1174 } |
|
1175 ]); |
|
1176 |
|
1177 test.takeControl(); |
|
1178 }; |
|
1179 |
|
1180 |
|
1181 /** |
|
1182 * Tests step out in the debugger. |
|
1183 */ |
|
1184 TestSuite.prototype.testStepOut = function() |
|
1185 { |
|
1186 this.showPanel("scripts"); |
|
1187 var test = this; |
|
1188 |
|
1189 this._executeFunctionForStepTest(); |
|
1190 |
|
1191 this._performSteps([ |
|
1192 { |
|
1193 functionsOnStack: ["d","a",""], |
|
1194 lineNumber: 3, |
|
1195 lineText: " debugger;" |
|
1196 }, |
|
1197 function() { |
|
1198 document.getElementById("scripts-step-out").click(); |
|
1199 }, |
|
1200 { |
|
1201 functionsOnStack: ["a",""], |
|
1202 lineNumber: 8, |
|
1203 lineText: " printResult(result);" |
|
1204 }, |
|
1205 function() { |
|
1206 test.releaseControl(); |
|
1207 } |
|
1208 ]); |
|
1209 |
|
1210 test.takeControl(); |
|
1211 }; |
|
1212 |
|
1213 |
|
1214 /** |
|
1215 * Tests step in in the debugger. |
|
1216 */ |
|
1217 TestSuite.prototype.testStepIn = function() |
|
1218 { |
|
1219 this.showPanel("scripts"); |
|
1220 var test = this; |
|
1221 |
|
1222 this._executeFunctionForStepTest(); |
|
1223 |
|
1224 this._performSteps([ |
|
1225 { |
|
1226 functionsOnStack: ["d","a",""], |
|
1227 lineNumber: 3, |
|
1228 lineText: " debugger;" |
|
1229 }, |
|
1230 function() { |
|
1231 document.getElementById("scripts-step-over").click(); |
|
1232 }, |
|
1233 { |
|
1234 functionsOnStack: ["d","a",""], |
|
1235 lineNumber: 5, |
|
1236 lineText: " var y = fact(10);" |
|
1237 }, |
|
1238 function() { |
|
1239 document.getElementById("scripts-step-into").click(); |
|
1240 }, |
|
1241 { |
|
1242 functionsOnStack: ["fact","d","a",""], |
|
1243 lineNumber: 10, |
|
1244 lineText: " var r = 1;" |
|
1245 }, |
|
1246 function() { |
|
1247 test.releaseControl(); |
|
1248 } |
|
1249 ]); |
|
1250 |
|
1251 test.takeControl(); |
|
1252 }; |
|
1253 |
|
1254 |
|
1255 /** |
|
1256 * Gets a XPathResult matching given xpath. |
|
1257 * @param {string} xpath |
|
1258 * @param {number} resultType |
|
1259 * @param {Node} opt_ancestor Context node. If not specified documentElement |
|
1260 * will be used |
|
1261 * @return {XPathResult} Type of returned value is determined by "resultType" parameter |
|
1262 */ |
|
1263 |
|
1264 TestSuite.prototype._evaluateXpath = function(xpath, resultType, opt_ancestor) |
|
1265 { |
|
1266 if (!opt_ancestor) |
|
1267 opt_ancestor = document.documentElement; |
|
1268 try { |
|
1269 return document.evaluate(xpath, opt_ancestor, null, resultType, null); |
|
1270 } catch(e) { |
|
1271 this.fail('Error in expression: "' + xpath + '".' + e); |
|
1272 } |
|
1273 }; |
|
1274 |
|
1275 |
|
1276 /** |
|
1277 * Gets first Node matching given xpath. |
|
1278 * @param {string} xpath |
|
1279 * @param {Node} opt_ancestor Context node. If not specified documentElement |
|
1280 * will be used |
|
1281 * @return {?Node} |
|
1282 */ |
|
1283 TestSuite.prototype._findNode = function(xpath, opt_ancestor) |
|
1284 { |
|
1285 var result = this._evaluateXpath(xpath, XPathResult.FIRST_ORDERED_NODE_TYPE, opt_ancestor).singleNodeValue; |
|
1286 this.assertTrue(!!result, "Cannot find node on path: " + xpath); |
|
1287 return result; |
|
1288 }; |
|
1289 |
|
1290 |
|
1291 /** |
|
1292 * Gets a text matching given xpath. |
|
1293 * @param {string} xpath |
|
1294 * @param {Node} opt_ancestor Context node. If not specified documentElement |
|
1295 * will be used |
|
1296 * @return {?string} |
|
1297 */ |
|
1298 TestSuite.prototype._findText = function(xpath, opt_ancestor) |
|
1299 { |
|
1300 var result = this._evaluateXpath(xpath, XPathResult.STRING_TYPE, opt_ancestor).stringValue; |
|
1301 this.assertTrue(!!result, "Cannot find text on path: " + xpath); |
|
1302 return result; |
|
1303 }; |
|
1304 |
|
1305 |
|
1306 /** |
|
1307 * Gets an iterator over nodes matching given xpath. |
|
1308 * @param {string} xpath |
|
1309 * @param {Node} opt_ancestor Context node. If not specified, documentElement |
|
1310 * will be used |
|
1311 * @return {XPathResult} Iterator over the nodes |
|
1312 */ |
|
1313 TestSuite.prototype._nodeIterator = function(xpath, opt_ancestor) |
|
1314 { |
|
1315 return this._evaluateXpath(xpath, XPathResult.ORDERED_NODE_ITERATOR_TYPE, opt_ancestor); |
|
1316 }; |
|
1317 |
|
1318 |
|
1319 /** |
|
1320 * Checks the scopeSectionDiv against the expectations. |
|
1321 * @param {Node} scopeSectionDiv The section div |
|
1322 * @param {Object} expectations Expectations dictionary |
|
1323 */ |
|
1324 TestSuite.prototype._checkScopeSectionDiv = function(scopeSectionDiv, expectations) |
|
1325 { |
|
1326 var scopeTitle = this._findText('./div[@class="header"]/div[@class="title"]/text()', scopeSectionDiv); |
|
1327 this.assertEquals(expectations.title, scopeTitle, "Unexpected scope section title."); |
|
1328 if (!expectations.properties) |
|
1329 return; |
|
1330 this.assertTrue(scopeSectionDiv.hasStyleClass("expanded"), 'Section "' + scopeTitle + '" is collapsed.'); |
|
1331 |
|
1332 var propertyIt = this._nodeIterator("./ol/li", scopeSectionDiv); |
|
1333 var propertyLi; |
|
1334 var foundProps = []; |
|
1335 while (propertyLi = propertyIt.iterateNext()) { |
|
1336 var name = this._findText('./span[@class="name"]/text()', propertyLi); |
|
1337 var value = this._findText('./span[@class="value"]/text()', propertyLi); |
|
1338 this.assertTrue(!!name, 'Invalid variable name: "' + name + '"'); |
|
1339 this.assertTrue(name in expectations.properties, "Unexpected property: " + name); |
|
1340 this.assertEquals(expectations.properties[name], value, 'Unexpected "' + name + '" property value.'); |
|
1341 delete expectations.properties[name]; |
|
1342 foundProps.push(name + " = " + value); |
|
1343 } |
|
1344 |
|
1345 // Check that all expected properties were found. |
|
1346 for (var p in expectations.properties) |
|
1347 this.fail('Property "' + p + '" was not found in scope "' + scopeTitle + '". Found properties: "' + foundProps.join(",") + '"'); |
|
1348 }; |
|
1349 |
|
1350 |
|
1351 /** |
|
1352 * Expands scope sections matching the filter and invokes the callback on |
|
1353 * success. |
|
1354 * @param {function(WebInspector.ObjectPropertiesSection, number):boolean} |
|
1355 * filter |
|
1356 * @param {Function} callback |
|
1357 */ |
|
1358 TestSuite.prototype._expandScopeSections = function(filter, callback) |
|
1359 { |
|
1360 var sections = WebInspector.currentPanel.sidebarPanes.scopechain.sections; |
|
1361 |
|
1362 var toBeUpdatedCount = 0; |
|
1363 function updateListener() { |
|
1364 --toBeUpdatedCount; |
|
1365 if (toBeUpdatedCount === 0) { |
|
1366 // Report when all scopes are expanded and populated. |
|
1367 callback(); |
|
1368 } |
|
1369 } |
|
1370 |
|
1371 // Global scope is always the last one. |
|
1372 for (var i = 0; i < sections.length - 1; i++) { |
|
1373 var section = sections[i]; |
|
1374 if (!filter(sections, i)) |
|
1375 continue; |
|
1376 ++toBeUpdatedCount; |
|
1377 var populated = section.populated; |
|
1378 |
|
1379 this._hookGetPropertiesCallback(updateListener, |
|
1380 function() { |
|
1381 section.expand(); |
|
1382 if (populated) { |
|
1383 // Make sure "updateProperties" callback will be called at least once |
|
1384 // after it was overridden. |
|
1385 section.update(); |
|
1386 } |
|
1387 }); |
|
1388 } |
|
1389 }; |
|
1390 |
|
1391 |
|
1392 /** |
|
1393 * Tests that scopes can be expanded and contain expected data. |
|
1394 */ |
|
1395 TestSuite.prototype.testExpandScope = function() |
|
1396 { |
|
1397 this.showPanel("scripts"); |
|
1398 var test = this; |
|
1399 |
|
1400 this._executeCodeWhenScriptsAreParsed("handleClick()", ["debugger_closure.html"]); |
|
1401 |
|
1402 this._waitForScriptPause( |
|
1403 { |
|
1404 functionsOnStack: ["innerFunction", "handleClick", ""], |
|
1405 lineNumber: 8, |
|
1406 lineText: " debugger;" |
|
1407 }, |
|
1408 expandAllSectionsExceptGlobal); |
|
1409 |
|
1410 // Expanding Global scope takes for too long so we skeep it. |
|
1411 function expandAllSectionsExceptGlobal() { |
|
1412 test._expandScopeSections(function(sections, i) { |
|
1413 return i < sections.length - 1; |
|
1414 }, |
|
1415 examineScopes /* When all scopes are expanded and populated check them. */); |
|
1416 } |
|
1417 |
|
1418 // Check scope sections contents. |
|
1419 function examineScopes() { |
|
1420 var scopeVariablesSection = test._findNode('//div[@id="scripts-sidebar"]/div[div[@class="title"]/text()="Scope Variables"]'); |
|
1421 var expectedScopes = [ |
|
1422 { |
|
1423 title: "Local", |
|
1424 properties: { |
|
1425 x:"2009", |
|
1426 innerFunctionLocalVar:"2011", |
|
1427 "this": "DOMWindow", |
|
1428 } |
|
1429 }, |
|
1430 { |
|
1431 title: "Closure", |
|
1432 properties: { |
|
1433 n: '"TextParam"', |
|
1434 makeClosureLocalVar: '"local.TextParam"', |
|
1435 } |
|
1436 }, |
|
1437 { |
|
1438 title: "Global", |
|
1439 }, |
|
1440 ]; |
|
1441 var it = test._nodeIterator('./div[@class="body"]/div', scopeVariablesSection); |
|
1442 var scopeIndex = 0; |
|
1443 var scopeDiv; |
|
1444 while (scopeDiv = it.iterateNext()) { |
|
1445 test.assertTrue(scopeIndex < expectedScopes.length, "Too many scopes."); |
|
1446 test._checkScopeSectionDiv(scopeDiv, expectedScopes[scopeIndex]); |
|
1447 ++scopeIndex; |
|
1448 } |
|
1449 test.assertEquals(expectedScopes.length, scopeIndex, "Unexpected number of scopes."); |
|
1450 |
|
1451 test.releaseControl(); |
|
1452 } |
|
1453 |
|
1454 test.takeControl(); |
|
1455 }; |
|
1456 |
|
1457 |
|
1458 /** |
|
1459 * Returns child tree element for a property with given name. |
|
1460 * @param {TreeElement} parent Parent tree element. |
|
1461 * @param {string} childName |
|
1462 * @param {string} objectPath Path to the object. Will be printed in the case |
|
1463 * of failure. |
|
1464 * @return {TreeElement} |
|
1465 */ |
|
1466 TestSuite.prototype._findChildProperty = function(parent, childName, objectPath) |
|
1467 { |
|
1468 var children = parent.children; |
|
1469 for (var i = 0; i < children.length; i++) { |
|
1470 var treeElement = children[i]; |
|
1471 var property = treeElement.property; |
|
1472 if (property.name === childName) |
|
1473 return treeElement; |
|
1474 } |
|
1475 this.fail('Cannot find property "' + childName + '" in ' + objectPath); |
|
1476 }; |
|
1477 |
|
1478 |
|
1479 /** |
|
1480 * Executes the 'code' with InjectedScriptAccess.getProperties overriden |
|
1481 * so that all callbacks passed to InjectedScriptAccess.getProperties are |
|
1482 * extended with the "hook". |
|
1483 * @param {Function} hook The hook function. |
|
1484 * @param {Function} code A code snippet to be executed. |
|
1485 */ |
|
1486 TestSuite.prototype._hookGetPropertiesCallback = function(hook, code) |
|
1487 { |
|
1488 var accessor = InjectedScriptAccess.prototype; |
|
1489 var orig = accessor.getProperties; |
|
1490 accessor.getProperties = function(objectProxy, ignoreHasOwnProperty, abbreviate, callback) { |
|
1491 orig.call(this, objectProxy, ignoreHasOwnProperty, abbreviate, |
|
1492 function() { |
|
1493 callback.apply(this, arguments); |
|
1494 hook(); |
|
1495 }); |
|
1496 }; |
|
1497 try { |
|
1498 code(); |
|
1499 } finally { |
|
1500 accessor.getProperties = orig; |
|
1501 } |
|
1502 }; |
|
1503 |
|
1504 |
|
1505 /** |
|
1506 * Tests that all elements in prototype chain of an object have expected |
|
1507 * intrinic proprties(__proto__, constructor, prototype). |
|
1508 */ |
|
1509 TestSuite.prototype.testDebugIntrinsicProperties = function() |
|
1510 { |
|
1511 this.showPanel("scripts"); |
|
1512 var test = this; |
|
1513 |
|
1514 this._executeCodeWhenScriptsAreParsed("handleClick()", ["debugger_intrinsic_properties.html"]); |
|
1515 |
|
1516 this._waitForScriptPause( |
|
1517 { |
|
1518 functionsOnStack: ["callDebugger", "handleClick", ""], |
|
1519 lineNumber: 29, |
|
1520 lineText: " debugger;" |
|
1521 }, |
|
1522 expandLocalScope); |
|
1523 |
|
1524 var localScopeSection = null; |
|
1525 function expandLocalScope() { |
|
1526 test._expandScopeSections(function(sections, i) { |
|
1527 if (i === 0) { |
|
1528 test.assertTrue(sections[i].object.isLocal, "Scope #0 is not Local."); |
|
1529 localScopeSection = sections[i]; |
|
1530 return true; |
|
1531 } |
|
1532 return false; |
|
1533 }, |
|
1534 examineLocalScope); |
|
1535 } |
|
1536 |
|
1537 function examineLocalScope() { |
|
1538 var scopeExpectations = [ |
|
1539 "a", "Child", [ |
|
1540 "__proto__", "Child", [ |
|
1541 "__proto__", "Parent", [ |
|
1542 "__proto__", "Object", null, |
|
1543 "constructor", "function Parent(n) {", [ |
|
1544 "name", '"Parent"', null, |
|
1545 "prototype", 'Parent', [ |
|
1546 "parentProtoField", "11", null, |
|
1547 ] |
|
1548 ], |
|
1549 "parentProtoField", "11", null, |
|
1550 ], |
|
1551 "constructor", "function Child(n) {", null, |
|
1552 "childProtoField", "21", null, |
|
1553 ], |
|
1554 |
|
1555 "parentField", "10", null, |
|
1556 "childField", "20", null, |
|
1557 ] |
|
1558 ]; |
|
1559 checkProperty(localScopeSection.propertiesTreeOutline, "<Local Scope>", scopeExpectations); |
|
1560 } |
|
1561 |
|
1562 var propQueue = []; |
|
1563 var index = 0; |
|
1564 var expectedFinalIndex = 5; |
|
1565 |
|
1566 function expandAndCheckNextProperty() { |
|
1567 if (index === propQueue.length) { |
|
1568 test.assertEquals(expectedFinalIndex, index, "Unexpected number of expanded objects."); |
|
1569 test.releaseControl(); |
|
1570 return; |
|
1571 } |
|
1572 |
|
1573 // Read next property data from the queue. |
|
1574 var treeElement = propQueue[index].treeElement; |
|
1575 var path = propQueue[index].path; |
|
1576 var expectations = propQueue[index].expectations; |
|
1577 index++; |
|
1578 |
|
1579 // Expand the property. |
|
1580 test._hookGetPropertiesCallback(function() { |
|
1581 checkProperty(treeElement, path, expectations); |
|
1582 }, |
|
1583 function() { |
|
1584 treeElement.expand(); |
|
1585 }); |
|
1586 } |
|
1587 |
|
1588 function checkProperty(treeElement, path, expectations) { |
|
1589 for (var i = 0; i < expectations.length; i += 3) { |
|
1590 var name = expectations[i]; |
|
1591 var description = expectations[i+1]; |
|
1592 var value = expectations[i+2]; |
|
1593 |
|
1594 var propertyPath = path + "." + name; |
|
1595 var propertyTreeElement = test._findChildProperty(treeElement, name, path); |
|
1596 test.assertTrue(propertyTreeElement, 'Property "' + propertyPath + '" not found.'); |
|
1597 test.assertEquals(description, propertyTreeElement.property.value.description, 'Unexpected "' + propertyPath + '" description.'); |
|
1598 if (value) { |
|
1599 // Schedule property content check. |
|
1600 propQueue.push({ |
|
1601 treeElement: propertyTreeElement, |
|
1602 path: propertyPath, |
|
1603 expectations: value, |
|
1604 }); |
|
1605 } |
|
1606 } |
|
1607 // Check next property in the queue. |
|
1608 expandAndCheckNextProperty(); |
|
1609 } |
|
1610 |
|
1611 test.takeControl(); |
|
1612 }; |
|
1613 |
|
1614 |
|
1615 /** |
|
1616 * Tests "Pause" button will pause debugger when a snippet is evaluated. |
|
1617 */ |
|
1618 TestSuite.prototype.testPauseInEval = function() |
|
1619 { |
|
1620 this.showPanel("scripts"); |
|
1621 |
|
1622 var test = this; |
|
1623 |
|
1624 var pauseButton = document.getElementById("scripts-pause"); |
|
1625 pauseButton.click(); |
|
1626 |
|
1627 devtools.tools.evaluateJavaScript("fib(10)"); |
|
1628 |
|
1629 this.addSniffer(WebInspector, "pausedScript", |
|
1630 function() { |
|
1631 test.releaseControl(); |
|
1632 }); |
|
1633 |
|
1634 test.takeControl(); |
|
1635 }; |
|
1636 |
|
1637 |
|
1638 /** |
|
1639 * Key event with given key identifier. |
|
1640 */ |
|
1641 TestSuite.createKeyEvent = function(keyIdentifier) |
|
1642 { |
|
1643 var evt = document.createEvent("KeyboardEvent"); |
|
1644 evt.initKeyboardEvent("keydown", true /* can bubble */, true /* can cancel */, null /* view */, keyIdentifier, ""); |
|
1645 return evt; |
|
1646 }; |
|
1647 |
|
1648 |
|
1649 /** |
|
1650 * Tests console eval. |
|
1651 */ |
|
1652 TestSuite.prototype.testConsoleEval = function() |
|
1653 { |
|
1654 var test = this; |
|
1655 this.evaluateInConsole_("123", |
|
1656 function(resultText) { |
|
1657 test.assertEquals("123", resultText); |
|
1658 test.releaseControl(); |
|
1659 }); |
|
1660 |
|
1661 this.takeControl(); |
|
1662 }; |
|
1663 |
|
1664 |
|
1665 /** |
|
1666 * Tests console log. |
|
1667 */ |
|
1668 TestSuite.prototype.testConsoleLog = function() |
|
1669 { |
|
1670 WebInspector.showConsole(); |
|
1671 var messages = WebInspector.console.messages; |
|
1672 var index = 0; |
|
1673 |
|
1674 var test = this; |
|
1675 var assertNext = function(line, message, opt_class, opt_count, opt_substr) { |
|
1676 var elem = messages[index++].toMessageElement(); |
|
1677 var clazz = elem.getAttribute("class"); |
|
1678 var expectation = (opt_count || '') + 'console_test_page.html:' + line + message; |
|
1679 if (opt_substr) |
|
1680 test.assertContains(elem.textContent, expectation); |
|
1681 else |
|
1682 test.assertEquals(expectation, elem.textContent); |
|
1683 if (opt_class) |
|
1684 test.assertContains(clazz, "console-" + opt_class); |
|
1685 }; |
|
1686 |
|
1687 assertNext("5", "log", "log-level"); |
|
1688 assertNext("7", "debug", "log-level"); |
|
1689 assertNext("9", "info", "log-level"); |
|
1690 assertNext("11", "warn", "warning-level"); |
|
1691 assertNext("13", "error", "error-level"); |
|
1692 assertNext("15", "Message format number 1, 2 and 3.5"); |
|
1693 assertNext("17", "Message format for string"); |
|
1694 assertNext("19", "Object Object"); |
|
1695 assertNext("22", "repeated", "log-level", 5); |
|
1696 assertNext("26", "count: 1"); |
|
1697 assertNext("26", "count: 2"); |
|
1698 assertNext("29", "group", "group-title"); |
|
1699 index++; |
|
1700 assertNext("33", "timer:", "log-level", "", true); |
|
1701 assertNext("35", "1 2 3", "log-level"); |
|
1702 assertNext("37", "HTMLDocument", "log-level"); |
|
1703 assertNext("39", "<html>", "log-level", "", true); |
|
1704 }; |
|
1705 |
|
1706 |
|
1707 /** |
|
1708 * Tests eval of global objects. |
|
1709 */ |
|
1710 TestSuite.prototype.testEvalGlobal = function() |
|
1711 { |
|
1712 WebInspector.showConsole(); |
|
1713 |
|
1714 var inputs = ["foo", "foobar"]; |
|
1715 var expectations = ["foo", "fooValue", "foobar", "ReferenceError: foobar is not defined"]; |
|
1716 |
|
1717 // Do not change code below - simply add inputs and expectations above. |
|
1718 var initEval = function(input) { |
|
1719 WebInspector.console.prompt.text = input; |
|
1720 WebInspector.console.promptElement.dispatchEvent( TestSuite.createKeyEvent("Enter")); |
|
1721 }; |
|
1722 var test = this; |
|
1723 var messagesCount = 0; |
|
1724 var inputIndex = 0; |
|
1725 this.addSniffer(WebInspector.ConsoleView.prototype, "addMessage", |
|
1726 function(commandResult) { |
|
1727 messagesCount++; |
|
1728 if (messagesCount === expectations.length) { |
|
1729 var messages = WebInspector.console.messages; |
|
1730 for (var i = 0; i < expectations; ++i) { |
|
1731 var elem = messages[i++].toMessageElement(); |
|
1732 test.assertEquals(elem.textContent, expectations[i]); |
|
1733 } |
|
1734 test.releaseControl(); |
|
1735 } else if (messagesCount % 2 === 0) |
|
1736 initEval(inputs[inputIndex++]); |
|
1737 }, true); |
|
1738 |
|
1739 initEval(inputs[inputIndex++]); |
|
1740 this.takeControl(); |
|
1741 }; |
|
1742 |
|
1743 |
|
1744 /** |
|
1745 * Tests the message loop re-entrancy. |
|
1746 */ |
|
1747 TestSuite.prototype.testMessageLoopReentrant = function() |
|
1748 { |
|
1749 var test = this; |
|
1750 this.showPanel("scripts"); |
|
1751 |
|
1752 var breakpointLine = 16; |
|
1753 |
|
1754 WebInspector.showConsole(); |
|
1755 |
|
1756 this._waitUntilScriptsAreParsed(["debugger_test_page.html"], |
|
1757 function() { |
|
1758 test.showMainPageScriptSource_( |
|
1759 "debugger_test_page.html", |
|
1760 function(view, url) { |
|
1761 view._addBreakpoint(breakpointLine); |
|
1762 |
|
1763 test.evaluateInConsole_( |
|
1764 'setTimeout("calculate()", 0)', |
|
1765 function(resultText) { |
|
1766 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText); |
|
1767 }); |
|
1768 |
|
1769 }); |
|
1770 }); |
|
1771 |
|
1772 // Wait until script is paused. |
|
1773 this.addSniffer( |
|
1774 WebInspector, |
|
1775 "pausedScript", |
|
1776 function(callFrames) { |
|
1777 test.evaluateInConsole_( |
|
1778 'document.cookie', |
|
1779 test.releaseControl.bind(test)); // This callback will be invoked only if the test succeeds (i.e. no crash). |
|
1780 }); |
|
1781 |
|
1782 this.takeControl(); |
|
1783 }; |
|
1784 |
|
1785 |
|
1786 /** |
|
1787 * Tests that Storage panel can be open and that local DOM storage is added |
|
1788 * to the panel. |
|
1789 */ |
|
1790 TestSuite.prototype.testShowStoragePanel = function() |
|
1791 { |
|
1792 var test = this; |
|
1793 this.addSniffer(WebInspector.panels.storage, "addDOMStorage", |
|
1794 function(storage) { |
|
1795 var orig = storage.getEntries; |
|
1796 storage.getEntries = function(callback) { |
|
1797 orig.call(this, function(entries) { |
|
1798 callback(entries); |
|
1799 test.releaseControl(); |
|
1800 }); |
|
1801 }; |
|
1802 try { |
|
1803 WebInspector.currentPanel.selectDOMStorage(storage.id); |
|
1804 storage.getEntries = orig; |
|
1805 } catch (e) { |
|
1806 test.fail("Exception in selectDOMStorage: " + e); |
|
1807 } |
|
1808 }); |
|
1809 this.showPanel("storage"); |
|
1810 |
|
1811 // Access localStorage so that it's pushed to the frontend. |
|
1812 this.evaluateInConsole_( |
|
1813 'setTimeout("localStorage.x = 10" , 0)', |
|
1814 function(resultText) { |
|
1815 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText); |
|
1816 }); |
|
1817 |
|
1818 // Wait until DOM storage is added to the panel. |
|
1819 this.takeControl(); |
|
1820 }; |
|
1821 |
|
1822 |
|
1823 /** |
|
1824 * Test runner for the test suite. |
|
1825 */ |
|
1826 var uiTests = {}; |
|
1827 |
|
1828 |
|
1829 /** |
|
1830 * Run each test from the test suit on a fresh instance of the suite. |
|
1831 */ |
|
1832 uiTests.runAllTests = function() |
|
1833 { |
|
1834 // For debugging purposes. |
|
1835 for (var name in TestSuite.prototype) { |
|
1836 if (name.substring(0, 4) === "test" && typeof TestSuite.prototype[name] === "function") |
|
1837 uiTests.runTest(name); |
|
1838 } |
|
1839 }; |
|
1840 |
|
1841 |
|
1842 /** |
|
1843 * Run specified test on a fresh instance of the test suite. |
|
1844 * @param {string} name Name of a test method from TestSuite class. |
|
1845 */ |
|
1846 uiTests.runTest = function(name) |
|
1847 { |
|
1848 new TestSuite().runTest(name); |
|
1849 }; |
|
1850 |
|
1851 |
|
1852 } |