// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview This file contains small testing framework along with the * test suite for the frontend. These tests are a part of the continues build * and are executed by the devtools_sanity_unittest.cc as a part of the * Interactive UI Test suite. */ if (window.domAutomationController) { /** * Test suite for interactive UI tests. * @constructor */ TestSuite = function() { this.controlTaken_ = false; this.timerId_ = -1; }; /** * Reports test failure. * @param {string} message Failure description. */ TestSuite.prototype.fail = function(message) { if (this.controlTaken_) { this.reportFailure_(message); } else { throw message; } }; /** * Equals assertion tests that expected == actual. * @param {Object} expected Expected object. * @param {Object} actual Actual object. * @param {string} opt_message User message to print if the test fails. */ TestSuite.prototype.assertEquals = function(expected, actual, opt_message) { if (expected != actual) { var message = 'Expected: "' + expected + '", but was "' + actual + '"'; if (opt_message) { message = opt_message + '(' + message + ')'; } this.fail(message); } }; /** * True assertion tests that value == true. * @param {Object} value Actual object. * @param {string} opt_message User message to print if the test fails. */ TestSuite.prototype.assertTrue = function(value, opt_message) { this.assertEquals(true, value, opt_message); }; /** * Contains assertion tests that string contains substring. * @param {string} string Outer. * @param {string} substring Inner. */ TestSuite.prototype.assertContains = function(string, substring) { if (string.indexOf(substring) == -1) { this.fail('Expected to: "' + string + '" to contain "' + substring + '"'); } }; /** * Takes control over execution. */ TestSuite.prototype.takeControl = function() { this.controlTaken_ = true; // Set up guard timer. var self = this; this.timerId_ = setTimeout(function() { self.reportFailure_('Timeout exceeded: 20 sec'); }, 20000); }; /** * Releases control over execution. */ TestSuite.prototype.releaseControl = function() { if (this.timerId_ != -1) { clearTimeout(this.timerId_); this.timerId_ = -1; } this.reportOk_(); }; /** * Async tests use this one to report that they are completed. */ TestSuite.prototype.reportOk_ = function() { window.domAutomationController.send('[OK]'); }; /** * Async tests use this one to report failures. */ TestSuite.prototype.reportFailure_ = function(error) { if (this.timerId_ != -1) { clearTimeout(this.timerId_); this.timerId_ = -1; } window.domAutomationController.send('[FAILED] ' + error); }; /** * Runs all global functions starting with 'test' as unit tests. */ TestSuite.prototype.runTest = function(testName) { try { this[testName](); if (!this.controlTaken_) { this.reportOk_(); } } catch (e) { this.reportFailure_(e); } }; /** * @param {string} panelName Name of the panel to show. */ TestSuite.prototype.showPanel = function(panelName) { // Open Scripts panel. var toolbar = document.getElementById('toolbar'); var button = toolbar.getElementsByClassName(panelName)[0]; button.click(); this.assertEquals(WebInspector.panels[panelName], WebInspector.currentPanel); }; /** * Overrides the method with specified name until it's called first time. * @param {Object} receiver An object whose method to override. * @param {string} methodName Name of the method to override. * @param {Function} override A function that should be called right after the * overriden method returns. * @param {boolean} opt_sticky Whether restore original method after first run * or not. */ TestSuite.prototype.addSniffer = function(receiver, methodName, override, opt_sticky) { var orig = receiver[methodName]; if (typeof orig != 'function') { this.fail('Cannot find method to override: ' + methodName); } var test = this; receiver[methodName] = function(var_args) { try { var result = orig.apply(this, arguments); } finally { if (!opt_sticky) { receiver[methodName] = orig; } } // In case of exception the override won't be called. try { override.apply(this, arguments); } catch (e) { test.fail('Exception in overriden method "' + methodName + '": ' + e); } return result; }; }; // UI Tests /** * Tests that the real injected host is present in the context. */ TestSuite.prototype.testHostIsPresent = function() { var domAgent = devtools.tools.getDomAgent(); var doc = domAgent.getDocument(); this.assertTrue(typeof DevToolsHost == 'object' && !DevToolsHost.isStub); this.assertTrue(!!doc.documentElement); }; /** * Tests elements tree has an 'HTML' root. */ TestSuite.prototype.testElementsTreeRoot = function() { var domAgent = devtools.tools.getDomAgent(); var doc = domAgent.getDocument(); this.assertEquals('HTML', doc.documentElement.nodeName); this.assertTrue(doc.documentElement.hasChildNodes()); }; /** * Tests that main resource is present in the system and that it is * the only resource. */ TestSuite.prototype.testMainResource = function() { var tokens = []; var resources = WebInspector.resources; for (var id in resources) { tokens.push(resources[id].lastPathComponent); } this.assertEquals('simple_page.html', tokens.join(',')); }; /** * Tests that resources tab is enabled when corresponding item is selected. */ TestSuite.prototype.testEnableResourcesTab = function() { this.showPanel('resources'); var test = this; this.addSniffer(WebInspector, 'addResource', function(identifier, payload) { test.assertEquals('simple_page.html', payload.lastPathComponent); WebInspector.panels.resources.refresh(); WebInspector.resources[identifier]._resourcesTreeElement.select(); test.releaseControl(); }); // Following call should lead to reload that we capture in the // addResource override. WebInspector.panels.resources._enableResourceTracking(); // We now have some time to report results to controller. this.takeControl(); }; /** * Tests resource headers. */ TestSuite.prototype.testResourceHeaders = function() { this.showPanel('resources'); var test = this; var requestOk = false; var responseOk = false; var timingOk = false; this.addSniffer(WebInspector, 'addResource', function(identifier, payload) { var resource = this.resources[identifier]; if (resource.mainResource) { // We are only interested in secondary resources in this test. return; } var requestHeaders = JSON.stringify(resource.requestHeaders); test.assertContains(requestHeaders, 'Accept'); requestOk = true; }, true); this.addSniffer(WebInspector, 'updateResource', function(identifier, payload) { var resource = this.resources[identifier]; if (resource.mainResource) { // We are only interested in secondary resources in this test. return; } if (payload.didResponseChange) { var responseHeaders = JSON.stringify(resource.responseHeaders); test.assertContains(responseHeaders, 'Content-type'); test.assertContains(responseHeaders, 'Content-Length'); test.assertTrue(typeof resource.responseReceivedTime != 'undefnied'); responseOk = true; } if (payload.didTimingChange) { test.assertTrue(typeof resource.startTime != 'undefnied'); timingOk = true; } if (payload.didCompletionChange) { test.assertTrue(requestOk); test.assertTrue(responseOk); test.assertTrue(timingOk); test.assertTrue(typeof resource.endTime != 'undefnied'); test.releaseControl(); } }, true); WebInspector.panels.resources._enableResourceTracking(); this.takeControl(); }; /** * Test that profiler works. */ TestSuite.prototype.testProfilerTab = function() { this.showPanel('profiles'); var test = this; this.addSniffer(WebInspector, 'addProfile', function(profile) { var panel = WebInspector.panels.profiles; panel.showProfile(profile); var node = panel.visibleView.profileDataGridTree.children[0]; // Iterate over displayed functions and search for a function // that is called 'fib' or 'eternal_fib'. If found, it will mean // that we actually have profiled page's code. while (node) { if (node.functionName.indexOf("fib") != -1) { test.releaseControl(); } node = node.traverseNextNode(true, null, true); } test.fail(); }); InspectorController.startProfiling(); window.setTimeout('InspectorController.stopProfiling();', 1000); this.takeControl(); }; /** * Tests that scripts tab can be open and populated with inspected scripts. */ TestSuite.prototype.testShowScriptsTab = function() { var parsedDebuggerTestPageHtml = false; var parsedDebuggerTestJs = false; // Intercept parsedScriptSource calls to check that all expected scripts are // added to the debugger. var test = this; this.addSniffer(WebInspector, 'parsedScriptSource', function(sourceID, sourceURL, source, startingLine) { if (sourceURL.search(/debugger_test_page.html$/) != -1) { if (parsedDebuggerTestPageHtml) { test.fail('Unexpected parse event: ' + sourceURL); } parsedDebuggerTestPageHtml = true; } else if (sourceURL.search(/debugger_test.js$/) != -1) { if (parsedDebuggerTestJs) { test.fail('Unexpected parse event: ' + sourceURL); } parsedDebuggerTestJs = true; } else { test.fail('Unexpected script URL: ' + sourceURL); } if (!WebInspector.panels.scripts.visibleView) { test.fail('No visible script view: ' + sourceURL); } if (parsedDebuggerTestJs && parsedDebuggerTestPageHtml) { test.releaseControl(); } }, true /* sticky */); this.showPanel('scripts'); // Wait until all scripts are added to the debugger. this.takeControl(); }; /** * Tests that a breakpoint can be set. */ TestSuite.prototype.testSetBreakpoint = function() { var parsedDebuggerTestPageHtml = false; var parsedDebuggerTestJs = false; this.showPanel('scripts'); var scriptUrl = null; var breakpointLine = 12; var test = this; var orig = devtools.DebuggerAgent.prototype.handleScriptsResponse_; this.addSniffer(devtools.DebuggerAgent.prototype, 'handleScriptsResponse_', function(msg) { var scriptSelect = document.getElementById('scripts-files'); var scriptResource = scriptSelect.options[scriptSelect.selectedIndex].representedObject; test.assertTrue(scriptResource instanceof WebInspector.Resource); test.assertTrue(!!scriptResource.url); test.assertTrue( scriptResource.url.search(/debugger_test_page.html$/) != -1, 'Main HTML resource should be selected.'); // Store for access from setbreakpoint handler. scriptUrl = scriptResource.url; var scriptsPanel = WebInspector.panels.scripts; var view = scriptsPanel.visibleView; test.assertTrue(view instanceof WebInspector.SourceView); if (!view.sourceFrame._isContentLoaded()) { test.addSniffer(view, '_sourceFrameSetupFinished', function(event) { view._addBreakpoint(breakpointLine); // Force v8 execution. devtools.tools.evaluateJavaScript('javascript:void(0)'); }); } else { view._addBreakpoint(breakpointLine); // Force v8 execution. devtools.tools.evaluateJavaScript('javascript:void(0)'); } }); this.addSniffer( devtools.DebuggerAgent.prototype, 'handleSetBreakpointResponse_', function(msg) { var bps = this.urlToBreakpoints_[scriptUrl]; test.assertTrue(!!bps, 'No breakpoints for line ' + breakpointLine); var line = devtools.DebuggerAgent.webkitToV8LineNumber_(breakpointLine); test.assertTrue(!!bps[line].getV8Id(), 'Breakpoint id was not assigned.'); test.releaseControl(); }); this.takeControl(); }; /** * Key event with given key identifier. */ TestSuite.KeyEvent = function(key) { this.keyIdentifier = key; }; TestSuite.KeyEvent.prototype.preventDefault = function() {}; TestSuite.KeyEvent.prototype.stopPropagation = function() {}; /** * Tests console eval. */ TestSuite.prototype.testConsoleEval = function() { WebInspector.console.visible = true; WebInspector.console.prompt.text = '123'; WebInspector.console.promptElement.handleKeyEvent( new TestSuite.KeyEvent('Enter')); var test = this; this.addSniffer(WebInspector.Console.prototype, 'addMessage', function(commandResult) { test.assertEquals('123', commandResult.toMessageElement().textContent); test.releaseControl(); }); this.takeControl(); }; /** * Tests console log. */ TestSuite.prototype.testConsoleLog = function() { WebInspector.console.visible = true; var messages = WebInspector.console.messages; var index = 0; var test = this; var assertNext = function(line, message, opt_class, opt_count, opt_substr) { var elem = messages[index++].toMessageElement(); var clazz = elem.getAttribute('class'); var expectation = (opt_count || '') + 'console_test_page.html:' + line + message; if (opt_substr) { test.assertContains(elem.textContent, expectation); } else { test.assertEquals(expectation, elem.textContent); } if (opt_class) { test.assertContains(clazz, 'console-' + opt_class); } }; assertNext('5', 'log', 'log-level'); assertNext('7', 'debug', 'log-level'); assertNext('9', 'info', 'log-level'); assertNext('11', 'warn', 'warning-level'); assertNext('13', 'error', 'error-level'); assertNext('15', 'Message format number 1, 2 and 3.5'); assertNext('17', 'Message format for string'); assertNext('19', 'Object Object'); assertNext('22', 'repeated', 'log-level', 5); assertNext('26', 'count: 1'); assertNext('26', 'count: 2'); assertNext('29', 'group', 'group-title'); index++; assertNext('33', 'timer:', 'log-level', '', true); }; /** * Tests eval of global objects. */ TestSuite.prototype.testEvalGlobal = function() { WebInspector.console.visible = true; WebInspector.console.prompt.text = 'foo'; WebInspector.console.promptElement.handleKeyEvent( new TestSuite.KeyEvent('Enter')); var test = this; this.addSniffer(WebInspector.Console.prototype, 'addMessage', function(commandResult) { test.assertEquals('fooValue', commandResult.toMessageElement().textContent); test.releaseControl(); }); this.takeControl(); }; /** * Tests eval on call frame. */ TestSuite.prototype.testEvalCallFrame = function() { }; /** * Test runner for the test suite. */ var uiTests = {}; /** * Run each test from the test suit on a fresh instance of the suite. */ uiTests.runAllTests = function() { // For debugging purposes. for (var name in TestSuite.prototype) { if (name.substring(0, 4) == 'test' && typeof TestSuite.prototype[name] == 'function') { uiTests.runTest(name); } } }; /** * Run specified test on a fresh instance of the test suite. * @param {string} name Name of a test method from TestSuite class. */ uiTests.runTest = function(name) { new TestSuite().runTest(name); }; }