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