Web Inspector: Add more diagnostics output to testRendererProcessNativeMemorySize
[WebKit-https.git] / Source / WebKit / chromium / src / js / Tests.js
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  * True assertion tests that value == true.
86  * @param {Object} value Actual object.
87  * @param {string} opt_message User message to print if the test fails.
88  */
89 TestSuite.prototype.assertTrue = function(value, opt_message)
90 {
91     this.assertEquals(true, !!value, opt_message);
92 };
93
94
95 /**
96  * HasKey assertion tests that object has given key.
97  * @param {Object} object
98  * @param {string} key
99  */
100 TestSuite.prototype.assertHasKey = function(object, key)
101 {
102     if (!object.hasOwnProperty(key))
103         this.fail("Expected object to contain key '" + key + "'");
104 };
105
106
107 /**
108  * Contains assertion tests that string contains substring.
109  * @param {string} string Outer.
110  * @param {string} substring Inner.
111  */
112 TestSuite.prototype.assertContains = function(string, substring)
113 {
114     if (string.indexOf(substring) === -1)
115         this.fail("Expected to: '" + string + "' to contain '" + substring + "'");
116 };
117
118
119 /**
120  * Takes control over execution.
121  */
122 TestSuite.prototype.takeControl = function()
123 {
124     this.controlTaken_ = true;
125     // Set up guard timer.
126     var self = this;
127     this.timerId_ = setTimeout(function() {
128         self.reportFailure_("Timeout exceeded: 20 sec");
129     }, 20000);
130 };
131
132
133 /**
134  * Releases control over execution.
135  */
136 TestSuite.prototype.releaseControl = function()
137 {
138     if (this.timerId_ !== -1) {
139         clearTimeout(this.timerId_);
140         this.timerId_ = -1;
141     }
142     this.reportOk_();
143 };
144
145
146 /**
147  * Async tests use this one to report that they are completed.
148  */
149 TestSuite.prototype.reportOk_ = function()
150 {
151     window.domAutomationController.send("[OK]");
152 };
153
154
155 /**
156  * Async tests use this one to report failures.
157  */
158 TestSuite.prototype.reportFailure_ = function(error)
159 {
160     if (this.timerId_ !== -1) {
161         clearTimeout(this.timerId_);
162         this.timerId_ = -1;
163     }
164     window.domAutomationController.send("[FAILED] " + error);
165 };
166
167
168 /**
169  * Runs all global functions starting with "test" as unit tests.
170  */
171 TestSuite.prototype.runTest = function(testName)
172 {
173     try {
174         this[testName]();
175         if (!this.controlTaken_)
176             this.reportOk_();
177     } catch (e) {
178         this.reportFailure_(e);
179     }
180 };
181
182
183 /**
184  * @param {string} panelName Name of the panel to show.
185  */
186 TestSuite.prototype.showPanel = function(panelName)
187 {
188     // Open Scripts panel.
189     var toolbar = document.getElementById("toolbar");
190     var button = toolbar.getElementsByClassName(panelName)[0];
191     button.click();
192     this.assertEquals(WebInspector.panels[panelName], WebInspector.inspectorView.currentPanel());
193 };
194
195
196 /**
197  * Overrides the method with specified name until it's called first time.
198  * @param {Object} receiver An object whose method to override.
199  * @param {string} methodName Name of the method to override.
200  * @param {Function} override A function that should be called right after the
201  *     overriden method returns.
202  * @param {boolean} opt_sticky Whether restore original method after first run
203  *     or not.
204  */
205 TestSuite.prototype.addSniffer = function(receiver, methodName, override, opt_sticky)
206 {
207     var orig = receiver[methodName];
208     if (typeof orig !== "function")
209         this.fail("Cannot find method to override: " + methodName);
210     var test = this;
211     receiver[methodName] = function(var_args) {
212         try {
213             var result = orig.apply(this, arguments);
214         } finally {
215             if (!opt_sticky)
216                 receiver[methodName] = orig;
217         }
218         // In case of exception the override won't be called.
219         try {
220             override.apply(this, arguments);
221         } catch (e) {
222             test.fail("Exception in overriden method '" + methodName + "': " + e);
223         }
224         return result;
225     };
226 };
227
228
229 TestSuite.prototype.testEnableResourcesTab = function()
230 {
231     // FIXME once reference is removed downstream.
232 }
233
234 TestSuite.prototype.testCompletionOnPause = function()
235 {
236     // FIXME once reference is removed downstream.
237 }
238
239 // UI Tests
240
241
242 /**
243  * Tests that scripts tab can be open and populated with inspected scripts.
244  */
245 TestSuite.prototype.testShowScriptsTab = function()
246 {
247     this.showPanel("scripts");
248     var test = this;
249     // There should be at least main page script.
250     this._waitUntilScriptsAreParsed(["debugger_test_page.html"],
251         function() {
252             test.releaseControl();
253         });
254     // Wait until all scripts are added to the debugger.
255     this.takeControl();
256 };
257
258
259 /**
260  * Tests that scripts tab is populated with inspected scripts even if it
261  * hadn't been shown by the moment inspected paged refreshed.
262  * @see http://crbug.com/26312
263  */
264 TestSuite.prototype.testScriptsTabIsPopulatedOnInspectedPageRefresh = function()
265 {
266     var test = this;
267     this.assertEquals(WebInspector.panels.elements, WebInspector.inspectorView.currentPanel(), "Elements panel should be current one.");
268
269     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, waitUntilScriptIsParsed);
270
271     // Reload inspected page. It will reset the debugger agent.
272     test.evaluateInConsole_("window.location.reload(true);", function(resultText) {});
273
274     function waitUntilScriptIsParsed()
275     {
276         WebInspector.debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, waitUntilScriptIsParsed);
277         test.showPanel("scripts");
278         test._waitUntilScriptsAreParsed(["debugger_test_page.html"],
279             function() {
280                 test.releaseControl();
281             });
282     }
283
284     // Wait until all scripts are added to the debugger.
285     this.takeControl();
286 };
287
288
289 /**
290  * Tests that scripts list contains content scripts.
291  */
292 TestSuite.prototype.testContentScriptIsPresent = function()
293 {
294     this.showPanel("scripts");
295     var test = this;
296
297     test._waitUntilScriptsAreParsed(
298         ["page_with_content_script.html", "simple_content_script.js"],
299         function() {
300           test.releaseControl();
301         });
302
303     // Wait until all scripts are added to the debugger.
304     this.takeControl();
305 };
306
307
308 /**
309  * Tests renderer process memory size obtained and passed to inspector
310  * successfully.
311  */
312 TestSuite.prototype.testRendererProcessNativeMemorySize = function()
313 {
314     var test = this;
315     var KB = 1024;
316     var MB = KB * KB;
317     var arraySize = 20000000;
318     var initialSize;
319
320     function checkFuzzyValue(value, expected, allowedDelta)
321     {
322         var relativeDiff = Math.abs(value - expected) / expected;
323         if (relativeDiff > allowedDelta)
324             test.fail("Value (" + value + ") differs from expected (" + expected + ") by more than " + (allowedDelta * 100) + "%.");
325     }
326
327     function step1(error, memoryBlock)
328     {
329         test.assertTrue(!error, "An error has occurred: " + error);
330         test.assertTrue(memoryBlock.size > 1 * MB && memoryBlock.size < 1500 * MB, "Unfeasible process size: " + memoryBlock.size + " bytes.");
331
332         initialSize = memoryBlock.size;
333
334         test.evaluateInConsole_("var a = new Uint8Array(" + arraySize + ");", function() {});
335
336         MemoryAgent.getProcessMemoryDistribution(false, step2);
337     }
338
339     function step2(error, memoryBlock)
340     {
341         test.assertTrue(!error, "An error has occurred: " + error);
342         var deltaBytes = memoryBlock.size - initialSize;
343         // Checks that the process size has grown approximately by
344         // the size of the allocated array (within 10% confidence interval).
345         checkFuzzyValue(deltaBytes, arraySize, 0.1);
346
347         test.releaseControl();
348     }
349
350     MemoryAgent.getProcessMemoryDistribution(false, step1);
351
352     this.takeControl();
353 };
354
355
356 /**
357  * Tests that scripts are not duplicaed on Scripts tab switch.
358  */
359 TestSuite.prototype.testNoScriptDuplicatesOnPanelSwitch = function()
360 {
361     var test = this;
362
363     // There should be two scripts: one for the main page and another
364     // one which is source of console API(see
365     // InjectedScript._ensureCommandLineAPIInstalled).
366     var expectedScriptsCount = 2;
367     var parsedScripts = [];
368
369     this.showPanel("scripts");
370
371     function switchToElementsTab() {
372         test.showPanel("elements");
373         setTimeout(switchToScriptsTab, 0);
374     }
375
376     function switchToScriptsTab() {
377         test.showPanel("scripts");
378         setTimeout(checkScriptsPanel, 0);
379     }
380
381     function checkScriptsPanel() {
382         test.assertTrue(test._scriptsAreParsed(["debugger_test_page.html"]), "Some scripts are missing.");
383         checkNoDuplicates();
384         test.releaseControl();
385     }
386
387     function checkNoDuplicates() {
388         var uiSourceCodes = test.nonAnonymousUISourceCodes_();
389         for (var i = 0; i < uiSourceCodes.length; i++) {
390             var scriptName = uiSourceCodes[i].url;
391             for (var j = i + 1; j < uiSourceCodes.length; j++)
392                 test.assertTrue(scriptName !== uiSourceCodes[j].url, "Found script duplicates: " + test.uiSourceCodesToString_(uiSourceCodes));
393         }
394     }
395
396     test._waitUntilScriptsAreParsed(
397         ["debugger_test_page.html"],
398         function() {
399             checkNoDuplicates();
400             setTimeout(switchToElementsTab, 0);
401         });
402
403
404     // Wait until all scripts are added to the debugger.
405     this.takeControl();
406 };
407
408
409 // Tests that debugger works correctly if pause event occurs when DevTools
410 // frontend is being loaded.
411 TestSuite.prototype.testPauseWhenLoadingDevTools = function()
412 {
413     this.showPanel("scripts");
414
415     // Script execution can already be paused.
416     if (WebInspector.debuggerModel.debuggerPausedDetails)
417         return;
418
419     this._waitForScriptPause(this.releaseControl.bind(this));
420     this.takeControl();
421 };
422
423
424 // Tests that pressing "Pause" will pause script execution if the script
425 // is already running.
426 TestSuite.prototype.testPauseWhenScriptIsRunning = function()
427 {
428     this.showPanel("scripts");
429
430     this.evaluateInConsole_(
431         'setTimeout("handleClick()" , 0)',
432         didEvaluateInConsole.bind(this));
433
434     function didEvaluateInConsole(resultText) {
435         this.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText);
436         // Wait for some time to make sure that inspected page is running the
437         // infinite loop.
438         setTimeout(testScriptPause.bind(this), 300);
439     }
440
441     function testScriptPause() {
442         // The script should be in infinite loop. Click "Pause" button to
443         // pause it and wait for the result.
444         WebInspector.panels.scripts.pauseButton.click();
445
446         this._waitForScriptPause(this.releaseControl.bind(this));
447     }
448
449     this.takeControl();
450 };
451
452
453 /**
454  * Tests network size.
455  */
456 TestSuite.prototype.testNetworkSize = function()
457 {
458     var test = this;
459
460     function finishResource(resource, finishTime)
461     {
462         test.assertEquals(219, resource.transferSize, "Incorrect total encoded data length");
463         test.assertEquals(25, resource.resourceSize, "Incorrect total data length");
464         test.releaseControl();
465     }
466     
467     this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishNetworkRequest", finishResource);
468
469     // Reload inspected page to sniff network events
470     test.evaluateInConsole_("window.location.reload(true);", function(resultText) {});
471     
472     this.takeControl();
473 };
474
475
476 /**
477  * Tests network sync size.
478  */
479 TestSuite.prototype.testNetworkSyncSize = function()
480 {
481     var test = this;
482
483     function finishResource(resource, finishTime)
484     {
485         test.assertEquals(219, resource.transferSize, "Incorrect total encoded data length");
486         test.assertEquals(25, resource.resourceSize, "Incorrect total data length");
487         test.releaseControl();
488     }
489     
490     this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishNetworkRequest", finishResource);
491
492     // Send synchronous XHR to sniff network events
493     test.evaluateInConsole_("var xhr = new XMLHttpRequest(); xhr.open(\"GET\", \"chunked\", false); xhr.send(null);", function() {});
494     
495     this.takeControl();
496 };
497
498
499 /**
500  * Tests network raw headers text.
501  */
502 TestSuite.prototype.testNetworkRawHeadersText = function()
503 {
504     var test = this;
505     
506     function finishResource(resource, finishTime)
507     {
508         if (!resource.responseHeadersText)
509             test.fail("Failure: resource does not have response headers text");
510         test.assertEquals(164, resource.responseHeadersText.length, "Incorrect response headers text length");
511         test.releaseControl();
512     }
513     
514     this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishNetworkRequest", finishResource);
515
516     // Reload inspected page to sniff network events
517     test.evaluateInConsole_("window.location.reload(true);", function(resultText) {});
518     
519     this.takeControl();
520 };
521
522
523 /**
524  * Tests network timing.
525  */
526 TestSuite.prototype.testNetworkTiming = function()
527 {
528     var test = this;
529
530     function finishResource(resource, finishTime)
531     {
532         // Setting relaxed expectations to reduce flakiness. 
533         // Server sends headers after 100ms, then sends data during another 100ms.
534         // We expect these times to be measured at least as 70ms.  
535         test.assertTrue(resource.timing.receiveHeadersEnd - resource.timing.connectStart >= 70, 
536                         "Time between receiveHeadersEnd and connectStart should be >=70ms, but was " +
537                         "receiveHeadersEnd=" + resource.timing.receiveHeadersEnd + ", connectStart=" + resource.timing.connectStart + ".");
538         test.assertTrue(resource.responseReceivedTime - resource.startTime >= 0.07, 
539                 "Time between responseReceivedTime and startTime should be >=0.07s, but was " +
540                 "responseReceivedTime=" + resource.responseReceivedTime + ", startTime=" + resource.startTime + ".");
541         test.assertTrue(resource.endTime - resource.startTime >= 0.14, 
542                 "Time between endTime and startTime should be >=0.14s, but was " +
543                 "endtime=" + resource.endTime + ", startTime=" + resource.startTime + ".");
544         
545         test.releaseControl();
546     }
547     
548     this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishNetworkRequest", finishResource);
549     
550     // Reload inspected page to sniff network events
551     test.evaluateInConsole_("window.location.reload(true);", function(resultText) {});
552
553     this.takeControl();
554 };
555
556
557 TestSuite.prototype.testConsoleOnNavigateBack = function()
558 {
559     if (WebInspector.console.messages.length === 1)
560         firstConsoleMessageReceived.call(this);
561     else
562         WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, firstConsoleMessageReceived, this);
563
564     function firstConsoleMessageReceived() {
565         this.evaluateInConsole_("clickLink();", didClickLink.bind(this));
566     }
567
568     function didClickLink() {
569         // Check that there are no new messages(command is not a message).
570         this.assertEquals(1, WebInspector.console.messages.length);
571         this.assertEquals(1, WebInspector.console.messages[0].totalRepeatCount);
572         this.evaluateInConsole_("history.back();", didNavigateBack.bind(this));
573     }
574
575     function didNavigateBack()
576     {
577         // Make sure navigation completed and possible console messages were pushed.
578         this.evaluateInConsole_("void 0;", didCompleteNavigation.bind(this));
579     }
580
581     function didCompleteNavigation() {
582         this.assertEquals(1, WebInspector.console.messages.length);
583         this.assertEquals(1, WebInspector.console.messages[0].totalRepeatCount);
584         this.releaseControl();
585     }
586
587     this.takeControl();
588 };
589
590
591 TestSuite.prototype.testReattachAfterCrash = function()
592 {
593     this.evaluateInConsole_("1+1;", this.releaseControl.bind(this));
594     this.takeControl();
595 };
596
597
598 TestSuite.prototype.testSharedWorker = function()
599 {
600     function didEvaluateInConsole(resultText) {
601         this.assertEquals("2011", resultText);
602         this.releaseControl();
603     }
604     this.evaluateInConsole_("globalVar", didEvaluateInConsole.bind(this));
605     this.takeControl();
606 };
607
608
609 TestSuite.prototype.testPauseInSharedWorkerInitialization = function()
610 {
611     if (WebInspector.debuggerModel.debuggerPausedDetails)
612         return;
613     this._waitForScriptPause(this.releaseControl.bind(this));
614     this.takeControl();
615 };
616
617 /**
618  * Tests that timeline receives frame signals.
619  */
620 TestSuite.prototype.testTimelineFrames = function()
621 {
622     var test = this;
623
624     function step1()
625     {
626         test.recordTimeline(onTimelineRecorded);
627         test.evaluateInConsole_("runTest()", function(){});
628     }
629
630     function onTimelineRecorded(records)
631     {
632         var frameCount = 0;
633         var recordsInFrame = {};
634
635         for (var i = 0; i < records.length; ++i) {
636             var record = records[i];
637             if (record.type !== "BeginFrame") {
638                 recordsInFrame[record.type] = (recordsInFrame[record.type] || 0) + 1;
639                 continue;
640             }
641             if (!frameCount++)
642                 continue;
643             
644             test.assertHasKey(recordsInFrame, "FireAnimationFrame");
645             test.assertHasKey(recordsInFrame, "Layout");
646             test.assertHasKey(recordsInFrame, "RecalculateStyles");
647             test.assertHasKey(recordsInFrame, "Paint");
648             recordsInFrame = {};
649         }
650         test.assertTrue(frameCount >= 5, "Not enough frames");
651         test.releaseControl();
652     }
653
654     step1();
655     test.takeControl();
656 }
657
658 // Regression test for http://webk.it/97466
659 TestSuite.prototype.testPageOverlayUpdate = function()
660 {
661     var test = this;
662
663     function populatePage()
664     {
665         var div1 = document.createElement("div");
666         div1.id = "div1";
667         // Force accelerated compositing.
668         div1.style.webkitTransform = "translateZ(0)";
669         document.body.appendChild(div1);
670         var div2 = document.createElement("div");
671         div2.id = "div2";
672         document.body.appendChild(div2);
673     }
674
675     function step1()
676     {
677         test.evaluateInConsole_(populatePage.toString() + "; populatePage();" +
678                                 "inspect(document.getElementById('div1'))", function() {});
679         WebInspector.notifications.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, step2);
680     }
681
682     function step2()
683     {
684         WebInspector.notifications.removeEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, step2);
685         test.recordTimeline(onTimelineRecorded);
686         setTimeout(step3, 500);
687     }
688
689     function step3()
690     {
691         test.evaluateInConsole_("inspect(document.getElementById('div2'))", function() {});
692         WebInspector.notifications.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, step4);
693     }
694
695     function step4()
696     {
697         WebInspector.notifications.removeEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, step4);
698         test.stopTimeline();
699     }
700
701     function onTimelineRecorded(records)
702     {
703         var types = {};
704         for (var i = 0; i < records.length; ++i)
705             types[records[i].type] = (types[records[i].type] || 0) + 1;
706
707         var frameCount = types["BeginFrame"];
708         // There should be at least two updates caused by selection of nodes.
709         test.assertTrue(frameCount >= 2, "Not enough DevTools overlay updates");
710         // We normally expect up to 3 frames, but allow for a bit more in case
711         // of some unexpected invalidations.
712         test.assertTrue(frameCount < 6, "Too many updates caused by DevTools overlay");
713         test.releaseControl();
714     }
715
716     step1();
717     this.takeControl();
718 }
719
720
721 /**
722  * Records timeline till console.timeStamp("ready"), invokes callback with resulting records.
723  * @param {function(Array.<Object>)} callback
724  */
725 TestSuite.prototype.recordTimeline = function(callback)
726 {
727     var records = [];
728     var dispatchOnRecordType = {}
729
730     WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, addRecord);
731     WebInspector.timelineManager.start();
732
733     function addRecord(event)
734     {
735         innerAddRecord(event.data);
736     }
737
738     function innerAddRecord(record)
739     {
740         records.push(record);
741         if (record.type === "TimeStamp" && record.data.message === "ready")
742             done();
743
744         if (record.children)
745             record.children.forEach(innerAddRecord);
746     }
747
748     function done()
749     {
750         WebInspector.timelineManager.stop();
751         WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, addRecord);
752         callback(records);
753     }
754 }
755
756
757 TestSuite.prototype.stopTimeline = function()
758 {
759     this.evaluateInConsole_("console.timeStamp('ready')", function() {});
760 }
761
762 TestSuite.prototype.waitForTestResultsInConsole = function()
763 {
764     var messages = WebInspector.console.messages;
765     for (var i = 0; i < messages.length; ++i) {
766         var text = messages[i].text;
767         if (text === "PASS")
768             return;
769         else if (/^FAIL/.test(text))
770             this.fail(text); // This will throw.
771     }
772     // Neitwer PASS nor FAIL, so wait for more messages.
773     function onConsoleMessage(event)
774     {
775         var text = event.data.text;
776         if (text === "PASS")
777             this.releaseControl();
778         else if (/^FAIL/.test(text))
779             this.fail(text);
780     }
781
782     WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, onConsoleMessage, this);
783     this.takeControl();
784 };
785
786 TestSuite.prototype.checkLogAndErrorMessages = function()
787 {
788     var messages = WebInspector.console.messages;
789
790     var matchesCount = 0;
791     function validMessage(message)
792     {
793         if (message.text === "log" && message.level === WebInspector.ConsoleMessage.MessageLevel.Log) {
794             ++matchesCount;
795             return true;
796         }
797
798         if (message.text === "error" && message.level === WebInspector.ConsoleMessage.MessageLevel.Error) {
799             ++matchesCount;
800             return true;
801         }
802         return false;
803     }
804
805     for (var i = 0; i < messages.length; ++i) {
806         if (validMessage(messages[i]))
807             continue;
808         this.fail(messages[i].text + ":" + messages[i].level); // This will throw.
809     }
810
811     if (matchesCount === 2)
812         return;
813
814     // Wait for more messages.
815     function onConsoleMessage(event)
816     {
817         var message = event.data;
818         if (validMessage(message)) {
819             if (matchesCount === 2) {
820                 this.releaseControl();
821                 return;
822             }
823         } else
824             this.fail(message.text + ":" + messages[i].level);
825     }
826
827     WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, onConsoleMessage, this);
828     this.takeControl();
829 };
830
831 /**
832  * Serializes array of uiSourceCodes to string.
833  * @param {Array.<WebInspectorUISourceCode>} uiSourceCodes
834  * @return {string}
835  */
836 TestSuite.prototype.uiSourceCodesToString_ = function(uiSourceCodes)
837 {
838     var names = [];
839     for (var i = 0; i < uiSourceCodes.length; i++)
840         names.push('"' + uiSourceCodes[i].url + '"');
841     return names.join(",");
842 };
843
844
845 /**
846  * Returns all loaded non anonymous uiSourceCodes.
847  * @return {Array.<WebInspectorUISourceCode>}
848  */
849 TestSuite.prototype.nonAnonymousUISourceCodes_ = function()
850 {
851     function filterOutAnonymous(uiSourceCode)
852     {
853         return !!uiSourceCode.url;
854     }
855
856     function filterOutService(uiSourceCode)
857     {
858         return !uiSourceCode.project().isServiceProject();
859     }
860
861     var uiSourceCodes = WebInspector.workspace.uiSourceCodes();
862     uiSourceCodes = uiSourceCodes.filter(filterOutService);
863     return uiSourceCodes.filter(filterOutAnonymous);
864 };
865
866
867 /*
868  * Evaluates the code in the console as if user typed it manually and invokes
869  * the callback when the result message is received and added to the console.
870  * @param {string} code
871  * @param {function(string)} callback
872  */
873 TestSuite.prototype.evaluateInConsole_ = function(code, callback)
874 {
875     WebInspector.showConsole();
876     WebInspector.consoleView.prompt.text = code;
877     WebInspector.consoleView.promptElement.dispatchEvent(TestSuite.createKeyEvent("Enter"));
878
879     this.addSniffer(WebInspector.ConsoleView.prototype, "_appendConsoleMessage",
880         function(commandResult) {
881             callback(commandResult.toMessageElement().textContent);
882         });
883 };
884
885
886 /**
887  * Checks that all expected scripts are present in the scripts list
888  * in the Scripts panel.
889  * @param {Array.<string>} expected Regular expressions describing
890  *     expected script names.
891  * @return {boolean} Whether all the scripts are in "scripts-files" select
892  *     box
893  */
894 TestSuite.prototype._scriptsAreParsed = function(expected)
895 {
896     var uiSourceCodes = this.nonAnonymousUISourceCodes_();
897     // Check that at least all the expected scripts are present.
898     var missing = expected.slice(0);
899     for (var i = 0; i < uiSourceCodes.length; ++i) {
900         for (var j = 0; j < missing.length; ++j) {
901             if (uiSourceCodes[i].name().search(missing[j]) !== -1) {
902                 missing.splice(j, 1);
903                 break;
904             }
905         }
906     }
907     return missing.length === 0;
908 };
909
910
911 /**
912  * Waits for script pause, checks expectations, and invokes the callback.
913  * @param {function():void} callback
914  */
915 TestSuite.prototype._waitForScriptPause = function(callback)
916 {
917     function pauseListener(event) {
918         WebInspector.debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, pauseListener, this);
919         callback();
920     }
921     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, pauseListener, this);
922 };
923
924
925 /**
926  * Waits until all the scripts are parsed and asynchronously executes the code
927  * in the inspected page.
928  */
929 TestSuite.prototype._executeCodeWhenScriptsAreParsed = function(code, expectedScripts)
930 {
931     var test = this;
932
933     function executeFunctionInInspectedPage() {
934         // Since breakpoints are ignored in evals' calculate() function is
935         // execute after zero-timeout so that the breakpoint is hit.
936         test.evaluateInConsole_(
937             'setTimeout("' + code + '" , 0)',
938             function(resultText) {
939                 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText + ". Code: " + code);
940             });
941     }
942
943     test._waitUntilScriptsAreParsed(expectedScripts, executeFunctionInInspectedPage);
944 };
945
946
947 /**
948  * Waits until all the scripts are parsed and invokes the callback.
949  */
950 TestSuite.prototype._waitUntilScriptsAreParsed = function(expectedScripts, callback)
951 {
952     var test = this;
953
954     function waitForAllScripts() {
955         if (test._scriptsAreParsed(expectedScripts))
956             callback();
957         else
958             test.addSniffer(WebInspector.panels.scripts, "_addUISourceCode", waitForAllScripts);
959     }
960
961     waitForAllScripts();
962 };
963
964
965 /**
966  * Key event with given key identifier.
967  */
968 TestSuite.createKeyEvent = function(keyIdentifier)
969 {
970     var evt = document.createEvent("KeyboardEvent");
971     evt.initKeyboardEvent("keydown", true /* can bubble */, true /* can cancel */, null /* view */, keyIdentifier, "");
972     return evt;
973 };
974
975
976 /**
977  * Test runner for the test suite.
978  */
979 var uiTests = {};
980
981
982 /**
983  * Run each test from the test suit on a fresh instance of the suite.
984  */
985 uiTests.runAllTests = function()
986 {
987     // For debugging purposes.
988     for (var name in TestSuite.prototype) {
989         if (name.substring(0, 4) === "test" && typeof TestSuite.prototype[name] === "function")
990             uiTests.runTest(name);
991     }
992 };
993
994
995 /**
996  * Run specified test on a fresh instance of the test suite.
997  * @param {string} name Name of a test method from TestSuite class.
998  */
999 uiTests.runTest = function(name)
1000 {
1001     if (uiTests._populatedInterface)
1002         new TestSuite().runTest(name);
1003     else
1004         uiTests._pendingTestName = name;
1005 };
1006
1007 (function() {
1008
1009 function runTests()
1010 {
1011     uiTests._populatedInterface = true;
1012     var name = uiTests._pendingTestName;
1013     delete uiTests._pendingTestName;
1014     if (name)
1015         new TestSuite().runTest(name);
1016 }
1017
1018 var oldLoadCompleted = InspectorFrontendAPI.loadCompleted;
1019 InspectorFrontendAPI.loadCompleted = function()
1020 {
1021     oldLoadCompleted.call(InspectorFrontendAPI);
1022     runTests();
1023 }
1024
1025 })();
1026
1027 }