diff options
-rw-r--r-- | chrome/test/data/database/database_tester.html | 161 | ||||
-rw-r--r-- | chrome/test/functional/databases.py | 253 | ||||
-rw-r--r-- | chrome/test/functional/notifications.py | 42 | ||||
-rw-r--r-- | chrome/test/pyautolib/pyauto.py | 34 | ||||
-rw-r--r-- | chrome/test/pyautolib/pyautolib.cc | 6 | ||||
-rw-r--r-- | chrome/test/pyautolib/pyautolib.h | 4 | ||||
-rw-r--r-- | chrome/test/pyautolib/pyautolib.i | 5 |
7 files changed, 471 insertions, 34 deletions
diff --git a/chrome/test/data/database/database_tester.html b/chrome/test/data/database/database_tester.html new file mode 100644 index 0000000..e5bbe56 --- /dev/null +++ b/chrome/test/data/database/database_tester.html @@ -0,0 +1,161 @@ +<html> +<!--database_tester.html +Script with javascript functions for simple database operations. This is used in +pyauto tests. +--> +<script> + +// Open a Web SQL database. +var g_db = null; +if (typeof window.openDatabase == "undefined") { + document.write("Error: Web SQL databases are not supported."); +} +try { + g_db = openDatabase("test", "1.0", "test database", 1024 * 1024); +} catch(err) { + document.write("Error: cannot open database."); +} + +// Creates a table named "table1" with one text column named "data". +function createTable() { + if (!g_db) { + sendErrorToTest("database is not open"); + } + g_db.transaction(function(tx) { + tx.executeSql( + "CREATE TABLE table1 (data TEXT)", + [], + function(tx, result) { + sendValueToTest("created"); + }, + function(tx, error) { + sendErrorToTest("cannot create table: " + error); + }); + }); +} + +// Inserts a record into the database. +function insertRecord(text) { + g_db.transaction(function(tx) { + tx.executeSql( + "INSERT INTO table1 VALUES (?)", + [text], + function(tx, result) { + sendValueToTest("inserted"); + }, + function(tx, error) { + sendErrorToTest("insert error: " + error); + }); + }); +} + +// Updates a record at the given index with the given text. The indices are +// 0-based and are ordered from oldest record, to newest record. +function updateRecord(index, text) { + findId(index, function(rowId) { + g_db.transaction(function(tx) { + tx.executeSql( + "UPDATE table1 SET data=? WHERE ROWID=?", + [text, rowId], + function(tx, result) { + if (result.rowsAffected == 1) + sendValueToTest("updated"); + else if (result.rowsAffected == 0) + sendErrorToTest("could not update index: " + index); + else + sendErrorToTest("multiple rows with index: " + index); + }, + function(tx, error) { + sendErrorToTest("update error: " + error); + }); + }); + }); +} + +// Deletes a record at the given index. +function deleteRecord(index) { + findId(index, function (rowId) { + g_db.transaction(function(tx) { + tx.executeSql( + "DELETE FROM table1 WHERE ROWID=?", + [rowId], + function(tx, result) { + sendValueToTest("deleted"); + }, + function(tx, error) { + sendErrorToTest("delete error: " + error); + }); + }); + }); +} + +// Gets all the records in the database, ordered by their age. +function getRecords() { + g_db.readTransaction(function(tx) { + tx.executeSql( + "SELECT data FROM table1 ORDER BY ROWID", + [], + function(tx, result) { + items = []; + for (var i = 0; i < result.rows.length; i++) { + items.push(result.rows.item(i).data); + } + sendValueToTest(items); + }, + function(tx, error) { + sendErrorToTest("getRecords error: " + error); + }); + }); +} + +// Helper function that finds the ID for a record based on a given index. +function findId(index, callback) { + g_db.readTransaction(function(tx) { + // |ROWID| is a special sqlite column. It is unique and is incremented + // automatically when a new record is created. + // |LIMIT| is a nonstandard clause supported by sqlite that lets us pick + // rows from the database by index. E.g., LIMIT 2,10 will give us 10 records + // starting at offset 2. + tx.executeSql( + "SELECT ROWID AS id FROM table1 ORDER BY ROWID LIMIT ?,1", + [index], + function(tx, result) { + if (result.rows.length >= 1) + callback(result.rows.item(0).id); + else + sendErrorToTest("could not find row with index: " + index); + }, + function(tx, error) { + sendErrorToTest("findId error: " + error); + }); + }); +} + +// Helper function that sends a message back to the test, which contains a value +// corresponding to the logical return value of the function, and a boolean +// indicating success. +function sendValueToTest(value) { + sendHelper(true, "", value); +} + +// Helper function that sends a message back to the test, which contains an +// error message and a boolean indicating failure. +function sendErrorToTest(errorMsg) { + sendHelper(false, errorMsg, 0); +} + +function sendHelper(success, errorMsg, returnValue) { + var result = { + "succeeded": success, + "errorMsg": errorMsg, + "returnValue": returnValue + }; + window.domAutomationController.send(JSON.stringify(result)); +} + +</script> + +<body> +This page is used for testing Web SQL databases. +</body> +</html> diff --git a/chrome/test/functional/databases.py b/chrome/test/functional/databases.py new file mode 100644 index 0000000..7e15d14 --- /dev/null +++ b/chrome/test/functional/databases.py @@ -0,0 +1,253 @@ +#!/usr/bin/python +# Copyright (c) 2010 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. + +import simplejson +import os + +import pyauto_functional +import pyauto + + +class SQLExecutionError(RuntimeError): + """Represents an error that occurs while executing an SQL statement.""" + pass + + +class DatabasesTest(pyauto.PyUITest): + """Test of Web SQL Databases.""" + + def __init__(self, methodName='runTest'): + super(DatabasesTest, self).__init__(methodName) + # HTML page used for database testing. + self.TEST_PAGE_URL = self.GetFileURLForDataPath( + os.path.join('database', 'database_tester.html')) + + def _ParseAndCheckResultFromTestPage(self, json): + """Helper function that parses the message sent from |TEST_PAGE_URL| and + checks that it succeeded. + + Args: + json: the message, encoded in JSON, that the test page sent to us + + Returns: + dictionary representing the result from the test page, with format: + { + 'succeeded': boolean + 'errorMsg': optional string + 'returnValue': optional any type + } + + Raises: + SQLExecutionError if the message contains an error message + """ + result_dict = simplejson.loads(json) + if result_dict['succeeded'] == False: + raise SQLExecutionError(result_dict['errorMsg']) + return result_dict + + def _CreateTable(self, tab_index=0, windex=0): + """Creates a table in the database. + + This should only be called once per database. This should be called before + attempting to insert, update, delete, or get the records in the database. + + Defaults to first tab in first window. + + Args: + tab_index: index of the tab that will create the database + windex: index of the window containing the tab that will create the + database + """ + json = self.CallJavascriptFunc('createTable', [], tab_index, windex) + self._ParseAndCheckResultFromTestPage(json) + + def _InsertRecord(self, record, tab_index=0, windex=0): + """Inserts a record, i.e., a row, into the database. + + Defaults to first tab in first window. + + Args: + record: string that will be added as a new row in the database + tab_index: index of the tab that will insert the record + windex: index of the window containing the tab that will insert the record + """ + json = self.CallJavascriptFunc('insertRecord', [record], tab_index, windex) + self._ParseAndCheckResultFromTestPage(json) + + def _UpdateRecord(self, index, updated_record, tab_index=0, windex=0): + """Updates a record, i.e., a row, in the database. + + Defaults to first tab in first window. + + Args: + index: index of the record to update. Index 0 refers to the oldest item in + the database + updated_record: string that will be used to update the row in the database + tab_index: index of the tab that will update the record + windex: index of the window containing the tab that will update the record + """ + json = self.CallJavascriptFunc( + 'updateRecord', [index, updated_record], tab_index, windex) + self._ParseAndCheckResultFromTestPage(json) + + def _DeleteRecord(self, index, tab_index=0, windex=0): + """Deletes a record, i.e., a row, from the database. + + Defaults to first tab in first window. + + Args: + index: index of the record to be deleted. Index 0 refers to the oldest + item in the database + tab_index: index of the tab that will delete the record + windex: index of the window containing the tab that will delete the record + """ + json = self.CallJavascriptFunc('deleteRecord', [index], tab_index, windex) + self._ParseAndCheckResultFromTestPage(json) + + def _GetRecords(self, tab_index=0, windex=0): + """Returns all the records, i.e., rows, in the database. + + The records are ordererd from oldest to newest. + + Defaults to first tab in first window. + + Returns: + array of all the records in the database + + Args: + tab_index: index of the tab that will query the database + windex: index of the window containing the tab that will query the + database + """ + json = self.CallJavascriptFunc('getRecords', [], tab_index, windex) + return self._ParseAndCheckResultFromTestPage(json)['returnValue'] + + def testInsertRecord(self): + """Insert records to the database.""" + self.NavigateToURL(self.TEST_PAGE_URL) + self._CreateTable() + self._InsertRecord('text') + self.assertEquals(['text'], self._GetRecords()) + self._InsertRecord('text2') + self.assertEquals(['text', 'text2'], self._GetRecords()) + + def testUpdateRecord(self): + """Update records in the database.""" + self.NavigateToURL(self.TEST_PAGE_URL) + self._CreateTable() + + # Update the first record. + self._InsertRecord('text') + self._UpdateRecord(0, '0') + records = self._GetRecords() + self.assertEquals(1, len(records)) + self.assertEquals('0', records[0]) + + # Update the middle record. + self._InsertRecord('1') + self._InsertRecord('2') + self._UpdateRecord(1, '1000') + self.assertEquals(['0', '1000', '2'], self._GetRecords()) + + def testDeleteRecord(self): + """Delete records in the database.""" + self.NavigateToURL(self.TEST_PAGE_URL) + self._CreateTable() + + # Delete the first and only record. + self._InsertRecord('text') + self._DeleteRecord(0) + self.assertFalse(self._GetRecords()) + + # Delete the middle record. + self._InsertRecord('0') + self._InsertRecord('1') + self._InsertRecord('2') + self._DeleteRecord(1) + self.assertEquals(['0', '2'], self._GetRecords()) + + def testDeleteNonexistentRow(self): + """Attempts to delete a nonexistent row in the table.""" + self.NavigateToURL(self.TEST_PAGE_URL) + self._CreateTable() + self._InsertRecord('text') + did_throw_exception = False + try: + self._DeleteRecord(1) + except: + did_throw_exception = True + self.assertTrue(did_throw_exception) + self.assertEquals(['text'], self._GetRecords()) + + def testDatabaseOperations(self): + """Insert, update, and delete records in the database.""" + self.NavigateToURL(self.TEST_PAGE_URL) + self._CreateTable() + + for i in range(10): + self._InsertRecord(str(i)) + records = self._GetRecords() + self.assertEqual([str(i) for i in range(10)], records) + + for i in range(10): + self._UpdateRecord(i, str(i * i)) + records = self._GetRecords() + self.assertEqual([str(i * i) for i in range(10)], records) + + for i in range(10): + self._DeleteRecord(0) + self.assertEqual(0, len(self._GetRecords())) + + def testReloadActiveTab(self): + """Create records in the database and verify they persist after reload.""" + self.NavigateToURL(self.TEST_PAGE_URL) + self._CreateTable() + self._InsertRecord('text') + self.ReloadActiveTab() + self.assertEquals(['text'], self._GetRecords()) + + def testIncognitoCannotReadRegularDatabase(self): + """Attempt to read a database created in a regular browser from an incognito + browser. + """ + self.NavigateToURL(self.TEST_PAGE_URL) + self._CreateTable() + self._InsertRecord('text') + self.RunCommand(pyauto.IDC_NEW_INCOGNITO_WINDOW) + self.NavigateToURL(self.TEST_PAGE_URL, 1, 0) + can_read_regular_database = False + try: + # |_GetRecords| should throw an error because the table does not exist. + if len(self._GetRecords(windex=1)) == 1: + can_read_regular_database = True + except SQLExecutionError: + pass + self.assertFalse(can_read_regular_database) + self._CreateTable(windex=1) + self.assertEqual(0, len(self._GetRecords(windex=1))) + + def testRegularCannotReadIncognitoDatabase(self): + """Attempt to read a database created in an incognito browser from a regular + browser. + """ + self.RunCommand(pyauto.IDC_NEW_INCOGNITO_WINDOW) + self.NavigateToURL(self.TEST_PAGE_URL, 1, 0) + self._CreateTable(windex=1) + self._InsertRecord('text', windex=1) + + # Verify a regular browser cannot read the incognito database. + self.NavigateToURL(self.TEST_PAGE_URL) + can_read_incognito_database = False + try: + # |_GetRecords| should throw an error because the table does not exist. + if len(self._GetRecords()) == 1: + can_read_incognito_database = True + except SQLExecutionError: + pass + self.assertFalse(can_read_incognito_database) + + +if __name__ == '__main__': + pyauto_functional.Main() diff --git a/chrome/test/functional/notifications.py b/chrome/test/functional/notifications.py index 9d19ba0..d6a1b9d 100644 --- a/chrome/test/functional/notifications.py +++ b/chrome/test/functional/notifications.py @@ -3,6 +3,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import os import urllib import pyauto_functional @@ -20,8 +21,8 @@ class NotificationsTest(pyauto.PyUITest): self.ASK_SETTING = 3 # HTML page used for notification testing. - self.TEST_PAGE_URL = ( - self.GetFileURLForDataPath('notifications/notification_tester.html')) + self.TEST_PAGE_URL = self.GetFileURLForDataPath( + os.path.join('notifications', 'notification_tester.html')) def Debug(self): """Test method for experimentation. @@ -132,33 +133,6 @@ class NotificationsTest(pyauto.PyUITest): self.assertEqual('Allow', infobar['buttons'][0]) self.assertEqual('Deny', infobar['buttons'][1]) - def _CallJavascriptFunc(self, function, args=[], tab_index=0, windex=0): - """Helper function to execute a script that calls a given function. - - Defaults to first tab in first window. - - Args: - function: name of the function - args: list of all the arguments to pass into the called function. These - should be able to be converted to a string using the |str| function. - tab_index: index of the tab within the given window - windex: index of the window - """ - # Convert the given arguments for evaluation in a javascript statement. - converted_args = [] - for arg in args: - # If it is a string argument, we need to quote and escape it properly. - if type(arg) == type('string') or type(arg) == type(u'unicode'): - # We must convert all " in the string to \", so that we don't try - # to evaluate invalid javascript like ""arg"". - converted_arg = '"' + arg.replace('"', '\\"') + '"' - else: - # Convert it to a string so that we can use |join| later. - converted_arg = str(arg) - converted_args += [converted_arg] - js = '%s(%s)' % (function, ', '.join(converted_args)) - return self.ExecuteJavascript(js, windex, tab_index) - def _CreateSimpleNotification(self, img_url, title, text, replace_id='', tab_index=0, windex=0): """Creates a simple notification. @@ -179,7 +153,7 @@ class NotificationsTest(pyauto.PyUITest): tab_index: index of the tab within the given window windex: index of the window """ - return self._CallJavascriptFunc('createNotification', + return self.CallJavascriptFunc('createNotification', [img_url, title, text, replace_id], tab_index, windex); @@ -202,7 +176,7 @@ class NotificationsTest(pyauto.PyUITest): tab_index: index of the tab within the given window windex: index of the window """ - return self._CallJavascriptFunc('createHTMLNotification', + return self.CallJavascriptFunc('createHTMLNotification', [content_url, replace_id], tab_index, windex) @@ -216,7 +190,7 @@ class NotificationsTest(pyauto.PyUITest): tab_index: index of the tab within the given window windex: index of the window """ - self._CallJavascriptFunc('requestPermission', [], windex, tab_index) + self.CallJavascriptFunc('requestPermission', [], windex, tab_index) def _CancelNotification(self, notification_id, tab_index=0, windex=0): """Cancels a notification with the given id. @@ -236,7 +210,7 @@ class NotificationsTest(pyauto.PyUITest): notification windex: index of the window """ - msg = self._CallJavascriptFunc( + msg = self.CallJavascriptFunc( 'cancelNotification', [notification_id], tab_index, windex) # '1' signifies success. self.assertEquals('1', msg) @@ -293,7 +267,7 @@ class NotificationsTest(pyauto.PyUITest): def testAllowOnPermissionInfobar(self): """Tries to create a notification and clicks allow on the infobar.""" self.NavigateToURL(self.TEST_PAGE_URL) - # This notification should not be shown because we don't have permission. + # This notification should not be shown because we do not have permission. self._CreateHTMLNotification(self.NO_SUCH_URL) self.assertFalse(self.GetActiveNotifications()) diff --git a/chrome/test/pyautolib/pyauto.py b/chrome/test/pyautolib/pyauto.py index 60b4728..a152c7e 100644 --- a/chrome/test/pyautolib/pyauto.py +++ b/chrome/test/pyautolib/pyauto.py @@ -1596,6 +1596,40 @@ class PyUITest(pyautolib.PyUITestBase, unittest.TestCase): } return self._GetResultFromJSONRequest(cmd_dict, windex=windex) + def CallJavascriptFunc(self, function, args=[], tab_index=0, windex=0): + """Executes a script which calls a given javascript function. + + The invoked javascript function must send a result back via the + domAutomationController.send function, or this function will never return. + + Defaults to first tab in first window. + + Args: + function: name of the function + args: list of all the arguments to pass into the called function. These + should be able to be converted to a string using the |str| function. + tab_index: index of the tab within the given window + windex: index of the window + + Returns: + a string that was sent back via the domAutomationController.send method + """ + # Convert the given arguments for evaluation in a javascript statement. + converted_args = [] + for arg in args: + # If it is a string argument, we need to quote and escape it properly. + if type(arg) == type('string') or type(arg) == type(u'unicode'): + # We must convert all " in the string to \", so that we don't try + # to evaluate invalid javascript like ""arg"". + converted_arg = '"' + arg.replace('"', '\\"') + '"' + else: + # Convert it to a string so that we can use |join| later. + converted_arg = str(arg) + converted_args += [converted_arg] + js = '%s(%s)' % (function, ', '.join(converted_args)) + logging.debug('Executing javascript: ', js) + return self.ExecuteJavascript(js, windex, tab_index) + class PyUITestSuite(pyautolib.PyUITestSuiteBase, unittest.TestSuite): """Base TestSuite for PyAuto UI tests.""" diff --git a/chrome/test/pyautolib/pyautolib.cc b/chrome/test/pyautolib/pyautolib.cc index e1007df..6d35548 100644 --- a/chrome/test/pyautolib/pyautolib.cc +++ b/chrome/test/pyautolib/pyautolib.cc @@ -80,6 +80,12 @@ void PyUITestBase::NavigateToURL( UITestBase::NavigateToURL(url, window_index, tab_index); } +void PyUITestBase::ReloadActiveTab(int window_index) { + scoped_refptr<TabProxy> tab_proxy(GetActiveTab()); + ASSERT_TRUE(tab_proxy.get()); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, tab_proxy->Reload()); +} + bool PyUITestBase::AppendTab(const GURL& tab_url, int window_index) { scoped_refptr<BrowserProxy> browser_proxy = automation()->GetBrowserWindow(window_index); diff --git a/chrome/test/pyautolib/pyautolib.h b/chrome/test/pyautolib/pyautolib.h index c40b97e..00f1c28 100644 --- a/chrome/test/pyautolib/pyautolib.h +++ b/chrome/test/pyautolib/pyautolib.h @@ -58,6 +58,10 @@ class PyUITestBase : public UITestBase { // Blocks until page loaded. void NavigateToURL(const char* url_string, int window_index, int tab_index); + // Reloads the active tab in the given window. + // Blocks until page reloaded. + void ReloadActiveTab(int window_index = 0); + // Get the URL of the active tab. GURL GetActiveTabURL(int window_index = 0); diff --git a/chrome/test/pyautolib/pyautolib.i b/chrome/test/pyautolib/pyautolib.i index 875d530..1a46ee0 100644 --- a/chrome/test/pyautolib/pyautolib.i +++ b/chrome/test/pyautolib/pyautolib.i @@ -221,6 +221,11 @@ class PyUITestBase { void NavigateToURL(const char* url_string); void NavigateToURL(const char* url_string, int window_index, int tab_index); + %feature("docstring", "Reload the active tab in the given window (or first " + "window if index not given). Blocks until page has reloaded.") + ReloadActiveTab; + void ReloadActiveTab(int window_index = 0); + // BrowserProxy methods %feature("docstring", "Apply the accelerator with given id " "(IDC_BACK, IDC_NEWTAB ...) to the given or first window. " |