From 47f21f1fbaf0cfc59b84ee45c158098ab2f5ad1c Mon Sep 17 00:00:00 2001
From: "timothe@chromium.org"
 <timothe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>
Date: Thu, 31 Mar 2011 15:32:42 +0000
Subject: Allow webdriver users to choose between sending the key events when
 using send_keys to the window of the browser or to webkit directly as it was
 done before. BUG=74899 TEST=run chromedriver_tests.py using the new 2.0b3
 version python drivers.

Review URL: http://codereview.chromium.org/6630001

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@80000 0039d316-1c4b-4281-b951-d872f2087c98
---
 chrome/test/webdriver/automation.cc               | 14 ++++-
 chrome/test/webdriver/automation.h                | 13 ++++-
 chrome/test/webdriver/chromedriver_tests.py       | 63 ++++++++++++++++++++---
 chrome/test/webdriver/commands/create_session.cc  |  8 ++-
 chrome/test/webdriver/commands/session_with_id.cc |  1 +
 chrome/test/webdriver/session.cc                  | 32 +++++++++---
 chrome/test/webdriver/session.h                   |  8 +++
 chrome/test/webdriver/test_page.html              |  3 ++
 8 files changed, 123 insertions(+), 19 deletions(-)

(limited to 'chrome/test/webdriver')

diff --git a/chrome/test/webdriver/automation.cc b/chrome/test/webdriver/automation.cc
index aa14460..e2a91e5 100644
--- a/chrome/test/webdriver/automation.cc
+++ b/chrome/test/webdriver/automation.cc
@@ -227,11 +227,23 @@ void Automation::SendWebKeyEvent(int tab_id,
     *success = false;
     return;
   }
-
   *success = SendWebKeyEventJSONRequest(
       automation(), windex, tab_index, key_event);
 }
 
+void Automation::SendNativeKeyEvent(int tab_id,
+                                    ui::KeyboardCode key_code,
+                                    int modifiers,
+                                    bool* success) {
+  int windex = 0, tab_index = 0;
+  if (!GetIndicesForTab(tab_id, &windex, &tab_index)) {
+    *success = false;
+    return;
+  }
+  *success = SendNativeKeyEventJSONRequest(
+      automation(), windex, tab_index, key_code, modifiers);
+}
+
 void Automation::CaptureEntirePageAsPNG(int tab_id,
                                         const FilePath& path,
                                         bool* success) {
diff --git a/chrome/test/webdriver/automation.h b/chrome/test/webdriver/automation.h
index aee1695..4815257 100644
--- a/chrome/test/webdriver/automation.h
+++ b/chrome/test/webdriver/automation.h
@@ -56,9 +56,18 @@ class Automation {
                      std::string* result,
                      bool* success);
 
-  // Sends a key event to the current browser. Waits until the key has
+  // Sends a webkit key event to the current browser. Waits until the key has
   // been processed by the web page.
-  void SendWebKeyEvent(int tab_id, const WebKeyEvent& key_event, bool* success);
+  void SendWebKeyEvent(int tab_id,
+                       const WebKeyEvent& key_event,
+                       bool* success);
+
+  // Sends an OS level key event to the current browser. Waits until the key
+  // has been processed by the browser.
+  void SendNativeKeyEvent(int tab_id,
+                          ui::KeyboardCode key_code,
+                          int modifiers,
+                          bool* success);
 
   // Captures a snapshot of the tab to the specified path.  The  PNG will
   // contain the entire page, including what is not in the current view
diff --git a/chrome/test/webdriver/chromedriver_tests.py b/chrome/test/webdriver/chromedriver_tests.py
index 9bbd42d..723819e 100755
--- a/chrome/test/webdriver/chromedriver_tests.py
+++ b/chrome/test/webdriver/chromedriver_tests.py
@@ -26,10 +26,15 @@ from gtest_text_test_runner import GTestTextTestRunner
 sys.path += [chromedriver_paths.SRC_THIRD_PARTY]
 sys.path += [chromedriver_paths.PYTHON_BINDINGS]
 
-import simplejson as json
+try:
+  import simplejson as json
+except ImportError:
+  import json
 
 from selenium.webdriver.remote.command import Command
 from selenium.webdriver.remote.webdriver import WebDriver
+from selenium.webdriver.common.keys import Keys
+from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
 
 
 def DataDir():
@@ -146,17 +151,57 @@ class BasicTest(unittest.TestCase):
   def testCanStartChromeDriverOnSpecificPort(self):
     launcher = ChromeDriverLauncher(port=9520)
     self.assertEquals(9520, launcher.GetPort())
-    driver = WebDriver(launcher.GetURL(), {})
+    driver = WebDriver(launcher.GetURL(), DesiredCapabilities.CHROME)
     driver.quit()
     launcher.Kill()
 
 
+class NativeInputTest(unittest.TestCase):
+  """Native input ChromeDriver tests."""
+
+  def setUp(self):
+    self._launcher = ChromeDriverLauncher()
+    self._capabilities = DesiredCapabilities.CHROME
+    self._capabilities["chrome"] = { "nativeEvents" : True }
+
+  def tearDown(self):
+    self._launcher.Kill()
+
+  def testCanStartsWithNativeEvents(self):
+    driver = WebDriver(self._launcher.GetURL(), self._capabilities)
+    self.assertTrue(driver.capabilities["chrome"].has_key("nativeEvents"))
+    self.assertTrue(driver.capabilities["chrome"]["nativeEvents"])
+
+  def testSendKeysNative(self):
+    driver = WebDriver(self._launcher.GetURL(), self._capabilities)
+    driver.get(self._launcher.GetURL() + '/test_page.html')
+    # Find the text input.
+    q = driver.find_element_by_name("key_input_test")
+    # Send some keys.
+    q.send_keys("tokyo")
+    #TODO(timothe): change to .text when beta 4 wrappers are out.
+    self.assertEqual(q.value, "tokyo")
+
+  #@unittest.skip("Need to run this on a machine with an IME installed.")
+  #def testSendKeysNativeProcessedByIME(self):
+    #driver = WebDriver(self._launcher.GetURL(), self.capabilities)
+    #driver.get(self._launcher.GetURL() + '/test_page.html')
+    #q = driver.find_element_by_name("key_input_test")
+    ## Send key combination to turn IME on.
+    #q.send_keys(Keys.F7)
+    #q.send_keys("toukyou")
+    ## Now turning it off.
+    #q.send_keys(Keys.F7)
+    #self.assertEqual(q.value, "\xe6\x9d\xb1\xe4\xba\xac")
+
+
 class CookieTest(unittest.TestCase):
   """Cookie test for the json webdriver protocol"""
 
   def setUp(self):
     self._launcher = ChromeDriverLauncher()
-    self._driver = WebDriver(self._launcher.GetURL(), {})
+    self._driver = WebDriver(self._launcher.GetURL(),
+                             DesiredCapabilities.CHROME)
 
   def tearDown(self):
     self._driver.quit()
@@ -260,16 +305,16 @@ class SessionTest(unittest.TestCase):
     driver.quit()
 
   def testSessionCreationDeletion(self):
-    driver = WebDriver(self._launcher.GetURL(), {})
+    driver = WebDriver(self._launcher.GetURL(), DesiredCapabilities.CHROME)
     driver.quit()
 
   def testMultipleSessionCreationDeletion(self):
     for i in range(10):
-      driver = WebDriver(self._launcher.GetURL(), {})
+      driver = WebDriver(self._launcher.GetURL(), DesiredCapabilities.CHROME)
       driver.quit()
 
   def testSessionCommandsAfterSessionDeletionReturn404(self):
-    driver = WebDriver(self._launcher.GetURL(), {})
+    driver = WebDriver(self._launcher.GetURL(), DesiredCapabilities.CHROME)
     session_id = driver.session_id
     driver.quit()
     try:
@@ -282,7 +327,8 @@ class SessionTest(unittest.TestCase):
   def testMultipleConcurrentSessions(self):
     drivers = []
     for i in range(10):
-      drivers += [WebDriver(self._launcher.GetURL(), {})]
+      drivers += [WebDriver(self._launcher.GetURL(),
+                            DesiredCapabilities.CHROME)]
     for driver in drivers:
       driver.quit()
 
@@ -292,7 +338,8 @@ class MouseTest(unittest.TestCase):
 
   def setUp(self):
     self._launcher = ChromeDriverLauncher(root_path=os.path.dirname(__file__))
-    self._driver = WebDriver(self._launcher.GetURL(), {})
+    self._driver = WebDriver(self._launcher.GetURL(),
+                             DesiredCapabilities.CHROME)
 
   def tearDown(self):
     self._driver.quit()
diff --git a/chrome/test/webdriver/commands/create_session.cc b/chrome/test/webdriver/commands/create_session.cc
index b377f90..73bb97d 100644
--- a/chrome/test/webdriver/commands/create_session.cc
+++ b/chrome/test/webdriver/commands/create_session.cc
@@ -37,8 +37,14 @@ void CreateSession::ExecutePost(Response* const response) {
     return;
   }
 
-  bool screenshot_on_error = false;
   DictionaryValue* capabilities = NULL;
+  bool native_events_required = false;
+  if (GetDictionaryParameter("desiredCapabilities", &capabilities)) {
+   capabilities->GetBoolean("chrome.nativeEvents", &native_events_required);
+   session->set_use_native_events(native_events_required);
+  }
+
+  bool screenshot_on_error = false;
   if (GetDictionaryParameter("desiredCapabilities", &capabilities)) {
     capabilities->GetBoolean("takeScreenshotOnError", &screenshot_on_error);
     session->set_screenshot_on_error(screenshot_on_error);
diff --git a/chrome/test/webdriver/commands/session_with_id.cc b/chrome/test/webdriver/commands/session_with_id.cc
index a3c77ec..651a1cc 100644
--- a/chrome/test/webdriver/commands/session_with_id.cc
+++ b/chrome/test/webdriver/commands/session_with_id.cc
@@ -54,6 +54,7 @@ void SessionWithID::ExecuteGet(Response* const response) {
   // Custom non-standard session info.
   temp_value->SetString("chrome.chromedriverVersion", "1.0");
   temp_value->SetString("chrome.automationVersion", chrome::kChromeVersion);
+  temp_value->SetBoolean("chrome.nativeEvents", session_->use_native_events());
 
   response->SetStatus(kSuccess);
   response->SetValue(temp_value);
diff --git a/chrome/test/webdriver/session.cc b/chrome/test/webdriver/session.cc
index 223ce51..a91c284 100644
--- a/chrome/test/webdriver/session.cc
+++ b/chrome/test/webdriver/session.cc
@@ -61,7 +61,8 @@ Session::Session()
       thread_(id_.c_str()),
       implicit_wait_(0),
       screenshot_on_error_(false),
-      current_target_(FrameId(0, FramePath())) {
+      current_target_(FrameId(0, FramePath())),
+      use_native_events_(false) {
   SessionManager::GetInstance()->Add(this);
 }
 
@@ -182,18 +183,22 @@ ErrorCode Session::SendKeys(const WebElementId& element, const string16& keys) {
   if (!is_displayed)
     return kElementNotVisible;
 
+  // This method will first check if the element we want to send the keys to is
+  // already focused, if not it will try to focus on it first.
   ListValue args;
   args.Append(element.ToValue());
   // TODO(jleyba): Update this to use the correct atom.
-  std::string script = "document.activeElement.blur();arguments[0].focus();";
+  std::string script = "if(document.activeElement!=arguments[0]){"
+                       "  if(document.activeElement)"
+                       "    document.activeElement.blur();"
+                       "  arguments[0].focus();"
+                       "}";
   Value* unscoped_result = NULL;
   code = ExecuteScript(script, &args, &unscoped_result);
-  scoped_ptr<Value> result(unscoped_result);
   if (code != kSuccess) {
-    LOG(ERROR) << "Failed to focus element before sending keys";
+    LOG(ERROR) << "Failed to get or set focus element before sending keys";
     return code;
   }
-
   bool success = false;
   RunSessionTask(NewRunnableMethod(
       this,
@@ -829,8 +834,21 @@ void Session::SendKeysOnSessionThread(const string16& keys, bool* success) {
   }
   for (size_t i = 0; i < key_events.size(); ++i) {
     bool key_success = false;
-    automation_->SendWebKeyEvent(
-        current_target_.window_id, key_events[i], &key_success);
+    if (use_native_events_) {
+      // The automation provider will generate up/down events for us, we
+      // only need to call it once as compared to the WebKeyEvent method.
+      // Hence we filter events by their types, keeping only rawkeydown.
+      if (key_events[i].type != automation::kRawKeyDownType)
+        continue;
+      automation_->SendNativeKeyEvent(
+          current_target_.window_id,
+          key_events[i].key_code,
+          key_events[i].modifiers,
+          &key_success);
+    } else {
+      automation_->SendWebKeyEvent(
+          current_target_.window_id, key_events[i], &key_success);
+    }
     if (!key_success) {
       LOG(ERROR) << "Failed to send key event. Event details:\n"
                  << "Type: " << key_events[i].type << "\n"
diff --git a/chrome/test/webdriver/session.h b/chrome/test/webdriver/session.h
index ea217b9..340c62f 100644
--- a/chrome/test/webdriver/session.h
+++ b/chrome/test/webdriver/session.h
@@ -212,6 +212,12 @@ class Session {
 
   const FrameId& current_target() const;
 
+  inline bool use_native_events() const { return use_native_events_; }
+
+  inline void set_use_native_events(bool use_native_events) {
+    use_native_events_ = use_native_events;
+  }
+
  private:
   void RunSessionTask(Task* task);
   void RunSessionTaskOnSessionThread(
@@ -245,6 +251,8 @@ class Session {
 
   FrameId current_target_;
 
+  bool use_native_events_;
+
   DISALLOW_COPY_AND_ASSIGN(Session);
 };
 
diff --git a/chrome/test/webdriver/test_page.html b/chrome/test/webdriver/test_page.html
index 2f65957..b03c193 100644
--- a/chrome/test/webdriver/test_page.html
+++ b/chrome/test/webdriver/test_page.html
@@ -15,4 +15,7 @@ function succeed() {
 
 <div style="height:5000px"></div>
 <a name="far_away" style="margin-left:5000px" onclick="javascript:succeed()">Click</a>
+
+<input type="text" name="key_input_test"/>
+
 </html>
-- 
cgit v1.1