diff options
author | scr@chromium.org <scr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-12 00:17:36 +0000 |
---|---|---|
committer | scr@chromium.org <scr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-12 00:17:36 +0000 |
commit | bf8feaad4973013b788d5cd0a57ae976edc22468 (patch) | |
tree | 00ccf8b625486b3bf05c447f16bf4171c57ae673 /chrome/test/data/webui/test_api.js | |
parent | 839f4172be712f8ba62cdcb29cc3301ee2c7f3f9 (diff) | |
download | chromium_src-bf8feaad4973013b788d5cd0a57ae976edc22468.zip chromium_src-bf8feaad4973013b788d5cd0a57ae976edc22468.tar.gz chromium_src-bf8feaad4973013b788d5cd0a57ae976edc22468.tar.bz2 |
Added options browser_tests using the generator and js handler framework.
This patch turned out to be fairly large. Let me describe the ultimate goal:
- To write WebUI tests in javascript, with as little C++ as possible for the simple case, yet powerful enough to support more complicated cases. options.js illustrates the simple case, and print_preview.js illustrates the complicated case.
Original changes:
- Refactored test_tab_strip_observer into test_navigation_observer so that it could be used by itself without needing the TabInsertedAt logic, which PrintPreview needs when there's no TabContentsWrapper available.
- Added assertEqualsArray for comparing two arrays shallowly (javascript == fails).
- Provided logic in WebUIBrowserTest and in the javascript2webui.js generation script to allow browsing to a url with preload of injected javascript.
- Corrected test_navigation_observer to wait for the right notification before calling callback (which runs after the page's javascript is loaded but before its onload).
- Added guts to define OS_* ifdefs for javascript to test for disabling tests.
- Moved the handler from settings_browsertest.cc to settings.js
- use __proto__ when overriding chrome to allow other members to be seen (commandLineString, e.g.)
Additions made during review:
- Switched to generative mechanism: TEST_F, GEN, which output during generation, and register during runtime.
- JS fixtures provide configuration members
- Add configuration hooks to generate in C++ test function
- Output directly to .cc file rather than needing hand-made .cc file which includes the generated file.
- Changed preload to take testFixture and testName.
- include and use mock4js to ensure handler methods are called.
- auto-generate the typedef WebUIBrowserTest testFixture unless overridden.
R=jhawkins@chromium.org,dtseng@chromium.org
BUG=None
TEST=browser_tests --gtest_filter=SettingsWebUITest.*
Review URL: http://codereview.chromium.org/7237030
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@92084 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/test/data/webui/test_api.js')
-rw-r--r-- | chrome/test/data/webui/test_api.js | 443 |
1 files changed, 415 insertions, 28 deletions
diff --git a/chrome/test/data/webui/test_api.js b/chrome/test/data/webui/test_api.js index c9dddc2..65e3f2f 100644 --- a/chrome/test/data/webui/test_api.js +++ b/chrome/test/data/webui/test_api.js @@ -2,13 +2,233 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Library providing basic test framework functionality. +/** + * @fileoverview Library providing basic test framework functionality. + **/ + +/** + * Namespace for |Test|. + * @type {Object} + **/ +var testing = {}; + +/** + * Hold the currentTestCase across between PreLoad and Run. + * @type {TestCase} + **/ +var currentTestCase = null; (function() { + // Provide global objects for generation case. + if (this['window'] === undefined) + this['window'] = this; + if (this['chrome'] === undefined) { + this['chrome'] = { + send: function() {}, + }; + } + if (this['console'] === undefined) { + this['console'] = { + log: print, + }; + } + + /** + * This class will be exported as testing.Test, and is provided to hold the + * fixture's configuration and callback methods for the various phases of + * invoking a test. It is called "Test" rather than TestFixture to roughly + * mimic the gtest's class names. + * @constructor + **/ + function Test() {} + + Test.prototype = { + /** + * The name of the test. + **/ + name: null, + + /** + * When set to a string value representing a url, generate BrowsePreload + * call, which will browse to the url and call fixture.PreLoad of the + * currentTestCase. + * @type {String} + **/ + browsePreload: null, + + /** + * When set to a string value representing an html page in the test + * directory, generate BrowsePrintPreload call, which will browse to a url + * representing the file, cause print, and call fixture.PreLoad of the + * currentTestCase. + * @type {String} + **/ + browsePrintPreload: null, + + /** + * When set to a function, will be called in the context of the test + * generation inside the function, and before any generated C++. + * @type {function(string,string)} + **/ + testGenPreamble: null, + + /** + * When set to a function, will be called in the context of the test + * generation inside the function, and before any generated C++. + * @type {function(string,string)} + **/ + testGenPostamble: null, + + /** + * When set to a non-null String, auto-generate typedef before generating + * TEST*: {@code typedef typedefCppFixture testFixture}. + * @type {String} + **/ + typedefCppFixture: 'WebUIBrowserTest', + + /** + * This should be initialized by the test fixture and can be referenced + * during the test run. + * @type {Mock4JS.Mock} + **/ + mockHandler: null, + + /** + * Override this method to perform initialization during preload (such as + * creating mocks and registering handlers). + * @type {Function} + **/ + PreLoad: function() {}, + + /** + * Override this method to perform tasks before running your test. + * @type {Function} + **/ + SetUp: function() {}, + + /** + * Override this method to perform tasks after running your test. If you + * create a mock class, you must call Mock4JS.verifyAllMocks() in this + * phase. + * @type {Function} + **/ + TearDown: function() { + Mock4JS.verifyAllMocks(); + } + }; + + /** + * This class is not exported and is available to hold the state of the + * |currentTestCase| throughout preload and test run. + * @param {String} name The name of the test case. + * @param {Test} fixture The fixture object for this test case. + * @param {Function} body The code to run for the test. + * @constructor + **/ + function TestCase(name, fixture, body) { + this.name = name; + this.fixture = fixture; + this.body = body; + } + + TestCase.prototype = { + name: null, + fixture: null, + body: null, + + /** + * Called at preload time, proxies to the fixture. + * @type {Function} + **/ + PreLoad: function(name) { + if (this.fixture) + this.fixture.PreLoad(); + }, + + /** + * Runs this test case. + * @type {Function} + **/ + Run: function() { + if (this.fixture) + this.fixture.SetUp(); + if (this.body) + this.body.call(this.fixture); + if (this.fixture) + this.fixture.TearDown(); + }, + }; + + /** + * Registry of javascript-defined callbacks for {@code chrome.send}. + * @type {Object} + **/ + var sendCallbacks = {}; + + /** + * Registers the message, object and callback for {@code chrome.send} + * @param {String} name The name of the message to route to this |callback|. + * @param {Object} messageHAndler Pass as |this| when calling the |callback|. + * @param {function(...)} callback Called by {@code chrome.send}. + * @see sendCallbacks + **/ + function registerMessageCallback(name, messageHandler, callback) { + sendCallbacks[name] = [messageHandler, callback]; + } + + /** + * Register all methods of {@code mockClass.prototype} with messages of the + * same name as the method, using the proxy of the |mockObject| as the + * |messageHandler| when registering. + * @param {Mock4JS.Mock} mockObject The mock to register callbacks against. + * @param {function(new:Object)} mockClAss Constructor for the mocked class. + * @see registerMessageCallback + **/ + function registerMockMessageCallbacks(mockObject, mockClass) { + var mockProxy = mockObject.proxy(); + for (func in mockClass.prototype) { + if (typeof(mockClass.prototype[func]) == 'function') { + registerMessageCallback(func, + mockProxy, + mockProxy[func]); + } + } + } + + /** + * Holds the old chrome object when overriding for preload and registry of + * handlers. + * @type {Object} + **/ + var oldChrome = chrome; + + /** + * Overrides {@code chrome.send} for routing messages to javascript + * functions. Also fallsback to sending with the |oldChrome| object. + * @param {String} messageName The message to route. + * @see oldChrome + **/ + function send(messageName) { + var callback = sendCallbacks[messageName]; + var args = Array.prototype.slice.call(arguments, 1); + if (callback != undefined) + callback[1].apply(callback[0], args); + else + oldChrome.send.apply(oldChrome, args); + } + // Asserts. // Use the following assertions to verify a condition within a test. // If assertion fails, the C++ backend will be immediately notified. // If assertion passes, no notification will be sent to the C++ backend. + + /** + * When |test| !== |expected|, aborts the current test. + * @param {Boolean} test The predicate to check against |expected|. + * @param {Boolean} expected The expected value of |test|. + * @param {String=} message The message to include in the Error thrown. + * @throws {Error} upon failure. + **/ function assertBool(test, expected, message) { if (test !== expected) { if (message) @@ -19,30 +239,33 @@ } } - var old_chrome = chrome; - var send_callbacks = {}; - - function registerMessageCallback(name, object, callback) { - send_callbacks[name] = [object, callback]; - } - - function send(messageName) { - var callback = send_callbacks[messageName]; - var args = Array.prototype.slice.call(arguments, 1); - if (callback != undefined) - callback[1].apply(callback[0], args); - else - old_chrome.send.apply(old_chrome, args); - } - + /** + * When |test| !== true, aborts the current test. + * @param {Boolean} test The predicate to check against |expected|. + * @param {String=} message The message to include in the Error thrown. + * @throws {Error} upon failure. + **/ function assertTrue(test, message) { assertBool(test, true, message); } + /** + * When |test| !== false, aborts the current test. + * @param {Boolean} test The predicate to check against |expected|. + * @param {String=} message The message to include in the Error thrown. + * @throws {Error} upon failure. + **/ function assertFalse(test, message) { assertBool(test, false, message); } + /** + * When |expected| !== |actual|, aborts the current test. + * @param {*} expected The predicate to check against |expected|. + * @param {*} actual The expected value of |test|. + * @param {String=} message The message to include in the Error thrown. + * @throws {Error} upon failure. + **/ function assertEquals(expected, actual, message) { if (expected != actual) { throw new Error('Test Error. Actual: ' + actual + '\nExpected: ' + @@ -55,56 +278,220 @@ } } + /** + * Always aborts the current test. + * @param {String=} message The message to include in the Error thrown. + * @throws {Error} always. + **/ function assertNotReached(message) { throw new Error(message); } + /** + * Holds the errors, if any, caught by expects so that the test case can fail. + * @type {Array.<Error>} + **/ var errors = []; + /** + * Creates a function based upon a function that thows an exception on + * failure. The new function stuffs any errors into the |errors| array for + * checking by runTest. This allows tests to continue running other checks, + * while failing the overal test if any errors occurrred. + * @param {Function} assertFunc The function which may throw an Error. + * @return {Function} A function that applies its arguments to |assertFunc|. + * @see errors + * @see runTest + **/ function createExpect(assertFunc) { return function() { try { assertFunc.apply(null, arguments); } catch (e) { - console.log('Failed: ' + currentTest.name + '\n' + e.stack); errors.push(e); } }; } + /** + * This is the starting point for tests run by WebUIBrowserTest. It clears + * |errors|, runs the test surrounded by an expect to catch Errors. If + * |errors| is non-empty, it reports a failure and a message by joining + * |errors|. + * @param {String} testFunction The function name to call. + * @param {Array} testArguments The arguments to call |testFunction| with. + * @return {Array.<Boolean, String>} [test-succeeded, message-if-failed] + * @see errors + * @see createExpect + **/ function runTest(testFunction, testArguments) { errors = []; // Avoid eval() if at all possible, since it will not work on pages // that have enabled content-security-policy. - currentTest = this[testFunction]; // global object -- not a method. - if (typeof currentTest === "undefined") { - currentTest = eval(testFunction); - } - console.log('Running test ' + currentTest.name); - createExpect(currentTest).apply(null, testArguments); + var testBody = this[testFunction]; // global object -- not a method. + if (typeof testBody === "undefined") + testBody = eval(testFunction); + if (testBody != RUN_TEST_F) + console.log('Running test ' + testBody.name); + createExpect(testBody).apply(null, testArguments); if (errors.length) { + for (var i = 0; i < errors.length; ++i) { + console.log('Failed: ' + testFunction + '(' + + testArguments.toString() + ')\n' + errors[i].stack); + } return [false, errors.join('\n')]; + } else { + return [true]; } + } - return [true]; + /** + * Creates a new test case for the given |testFixture| and |testName|. Assumes + * |testFixture| describes a globally available subclass of type Test. + * @param {String} testFixture The fixture for this test case. + * @param {String} testName The name for this test case. + * @return {TestCase} A newly created TestCase. + **/ + function createTestCase(testFixture, testName) { + var fixtureConstructor = this[testFixture]; + var testBody = fixtureConstructor.testCaseBodies[testName]; + var fixture = new fixtureConstructor(); + fixture.name = testFixture; + return new TestCase(testName, fixture, testBody); } - function preloadJavascriptLibraries(overload_chrome_send) { - if (overload_chrome_send) - chrome = { 'send': send }; + /** + * Used by WebUIBrowserTest to preload the javascript libraries at the + * appropriate time for javascript injection into the current page. This + * creates a test case and calls its PreLoad for any early initialization such + * as registering handlers before the page's javascript runs it's OnLoad + * method. + * @param {String} testFixture The test fixture name. + * @param {String} testName The test name. + **/ + function preloadJavascriptLibraries(testFixture, testName) { + chrome = { + __proto__: oldChrome, + send: send, + }; + currentTestCase = createTestCase(testFixture, testName); + currentTestCase.PreLoad(); + } + + /** + * During generation phase, this outputs; do nothing at runtime. + **/ + function GEN() {} + + /** + * At runtime, register the testName with a test fixture. Since this method + * doesn't have a test fixture, we create a dummy fixture to hold its |name| + * and |testCaseBodies|. + * @param {String} testCaseName The name of the test case. + * @param {String} testName The name of the test function. + * @param {Function} testBody The body to execute when running this test. + **/ + function TEST(testCaseName, testName, testBody) { + var fixtureConstructor = this[testCaseName]; + if (fixtureConstructor === undefined) { + fixtureConstructor = function() {}; + this[testCaseName] = fixtureConstructor; + fixtureConstructor.prototype = { + __proto__: Test.prototype, + name: testCaseName, + }; + fixtureConstructor.testCaseBodies = {}; + } + fixtureConstructor.testCaseBodies[testName] = testBody; + } + + /** + * At runtime, register the testName with its fixture. Stuff the |name| into + * the |testFixture|'s prototype, if needed, and the |testCaseBodies| into its + * constructor. + * @param {String} testFixture The name of the test fixture class. + * @param {String} testName The name of the test function. + * @param {Function} testBody The body to execute when running this test. + **/ + function TEST_F(testFixture, testName, testBody) { + var fixtureConstructor = this[testFixture]; + if (!fixtureConstructor.prototype.name) + fixtureConstructor.prototype.name = testFixture; + if (fixtureConstructor['testCaseBodies'] === undefined) + fixtureConstructor.testCaseBodies = {}; + fixtureConstructor.testCaseBodies[testName] = testBody; + } + + /** + * RunJavascriptTestF uses this as the |testFunction| when invoking + * runTest. If |currentTestCase| is non-null at this point, verify that + * |testFixture| and |testName| agree with the preloaded values. Create + * |currentTestCase|, if needed, run it, and clear the |currentTestCase|. + * @param {String} testFixture The name of the test fixture class. + * @param {String} testName The name of the test function. + * @see preloadJavascriptLibraries + * @see runTest + **/ + function RUN_TEST_F(testFixture, testName) { + if (!currentTestCase) + currentTestCase = createTestCase(testFixture, testName); + assertEquals(currentTestCase.name, testName); + assertEquals(currentTestCase.fixture.name, testFixture); + console.log('Running TestCase ' + testFixture + '.' + testName); + currentTestCase.Run(); + currentTestCase = null; + } + + /** + * CallFunctionAction is provided to allow mocks to have side effects. + * @param {Function} func The function to call. + * @param {Array} args Any arguments to pass to func. + * @constructor + **/ + function CallFunctionAction(func, args) { + this._func = func; + this._args = args; + } + + CallFunctionAction.prototype = { + invoke: function() { + return this._func.apply(null, this._args); + }, + describe: function() { + return 'calls the given function with arguments ' + this._args; + } + }; + + /** + * Syntactic sugar for will() on a Mock4JS.Mock. + * @param {Function} func the function to call when the method is invoked. + * @param {...*} var_args arguments to pass when calling func. + **/ + function callFunction(func) { + return new CallFunctionAction(func, + Array.prototype.slice.call(arguments, 1)); } // Exports. + testing.Test = Test; window.assertTrue = assertTrue; window.assertFalse = assertFalse; window.assertEquals = assertEquals; window.assertNotReached = assertNotReached; + window.callFunction = callFunction; window.expectTrue = createExpect(assertTrue); window.expectFalse = createExpect(assertFalse); window.expectEquals = createExpect(assertEquals); window.expectNotReached = createExpect(assertNotReached); window.registerMessageCallback = registerMessageCallback; + window.registerMockMessageCallbacks = registerMockMessageCallbacks; window.runTest = runTest; window.preloadJavascriptLibraries = preloadJavascriptLibraries; + window.TEST = TEST; + window.TEST_F = TEST_F; + window.GEN = GEN; + + // Import the Mock4JS helpers. + Mock4JS.addMockSupport(window); })(); |