Home | History | Annotate | Download | only in chromedriver
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/test/chromedriver/element_commands.h"
      6 
      7 #include <cmath>
      8 #include <list>
      9 #include <vector>
     10 
     11 #include "base/callback.h"
     12 #include "base/files/file_path.h"
     13 #include "base/strings/string_split.h"
     14 #include "base/strings/stringprintf.h"
     15 #include "base/threading/platform_thread.h"
     16 #include "base/time/time.h"
     17 #include "base/values.h"
     18 #include "chrome/test/chromedriver/basic_types.h"
     19 #include "chrome/test/chromedriver/chrome/browser_info.h"
     20 #include "chrome/test/chromedriver/chrome/chrome.h"
     21 #include "chrome/test/chromedriver/chrome/js.h"
     22 #include "chrome/test/chromedriver/chrome/status.h"
     23 #include "chrome/test/chromedriver/chrome/ui_events.h"
     24 #include "chrome/test/chromedriver/chrome/web_view.h"
     25 #include "chrome/test/chromedriver/element_util.h"
     26 #include "chrome/test/chromedriver/session.h"
     27 #include "chrome/test/chromedriver/util.h"
     28 #include "third_party/webdriver/atoms.h"
     29 
     30 const int kFlickTouchEventsPerSecond = 30;
     31 
     32 namespace {
     33 
     34 Status SendKeysToElement(
     35     Session* session,
     36     WebView* web_view,
     37     const std::string& element_id,
     38     const base::ListValue* key_list) {
     39   bool is_displayed = false;
     40   bool is_focused = false;
     41   base::TimeTicks start_time = base::TimeTicks::Now();
     42   while (true) {
     43     Status status = IsElementDisplayed(
     44         session, web_view, element_id, true, &is_displayed);
     45     if (status.IsError())
     46       return status;
     47     if (is_displayed)
     48       break;
     49     status = IsElementFocused(session, web_view, element_id, &is_focused);
     50     if (status.IsError())
     51       return status;
     52     if (is_focused)
     53       break;
     54     if (base::TimeTicks::Now() - start_time >= session->implicit_wait) {
     55       return Status(kElementNotVisible);
     56     }
     57     base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
     58   }
     59 
     60   bool is_enabled = false;
     61   Status status = IsElementEnabled(session, web_view, element_id, &is_enabled);
     62   if (status.IsError())
     63     return status;
     64   if (!is_enabled)
     65     return Status(kInvalidElementState);
     66 
     67   if (!is_focused) {
     68     base::ListValue args;
     69     args.Append(CreateElement(element_id));
     70     scoped_ptr<base::Value> result;
     71     status = web_view->CallFunction(
     72         session->GetCurrentFrameId(), kFocusScript, args, &result);
     73     if (status.IsError())
     74       return status;
     75   }
     76 
     77   return SendKeysOnWindow(web_view, key_list, true, &session->sticky_modifiers);
     78 }
     79 
     80 Status ExecuteTouchSingleTapAtom(
     81     Session* session,
     82     WebView* web_view,
     83     const std::string& element_id,
     84     const base::DictionaryValue& params,
     85     scoped_ptr<base::Value>* value) {
     86   base::ListValue args;
     87   args.Append(CreateElement(element_id));
     88   return web_view->CallFunction(
     89       session->GetCurrentFrameId(),
     90       webdriver::atoms::asString(webdriver::atoms::TOUCH_SINGLE_TAP),
     91       args,
     92       value);
     93 }
     94 
     95 }  // namespace
     96 
     97 Status ExecuteElementCommand(
     98     const ElementCommand& command,
     99     Session* session,
    100     WebView* web_view,
    101     const base::DictionaryValue& params,
    102     scoped_ptr<base::Value>* value) {
    103   std::string id;
    104   if (params.GetString("id", &id) || params.GetString("element", &id))
    105     return command.Run(session, web_view, id, params, value);
    106   return Status(kUnknownError, "element identifier must be a string");
    107 }
    108 
    109 Status ExecuteFindChildElement(
    110     int interval_ms,
    111     Session* session,
    112     WebView* web_view,
    113     const std::string& element_id,
    114     const base::DictionaryValue& params,
    115     scoped_ptr<base::Value>* value) {
    116   return FindElement(
    117       interval_ms, true, &element_id, session, web_view, params, value);
    118 }
    119 
    120 Status ExecuteFindChildElements(
    121     int interval_ms,
    122     Session* session,
    123     WebView* web_view,
    124     const std::string& element_id,
    125     const base::DictionaryValue& params,
    126     scoped_ptr<base::Value>* value) {
    127   return FindElement(
    128       interval_ms, false, &element_id, session, web_view, params, value);
    129 }
    130 
    131 Status ExecuteHoverOverElement(
    132     Session* session,
    133     WebView* web_view,
    134     const std::string& element_id,
    135     const base::DictionaryValue& params,
    136     scoped_ptr<base::Value>* value) {
    137   WebPoint location;
    138   Status status = GetElementClickableLocation(
    139       session, web_view, element_id, &location);
    140   if (status.IsError())
    141     return status;
    142 
    143   MouseEvent move_event(
    144       kMovedMouseEventType, kNoneMouseButton, location.x, location.y,
    145       session->sticky_modifiers, 0);
    146   std::list<MouseEvent> events;
    147   events.push_back(move_event);
    148   status = web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
    149   if (status.IsOk())
    150     session->mouse_position = location;
    151   return status;
    152 }
    153 
    154 Status ExecuteClickElement(
    155     Session* session,
    156     WebView* web_view,
    157     const std::string& element_id,
    158     const base::DictionaryValue& params,
    159     scoped_ptr<base::Value>* value) {
    160   std::string tag_name;
    161   Status status = GetElementTagName(session, web_view, element_id, &tag_name);
    162   if (status.IsError())
    163     return status;
    164   if (tag_name == "option") {
    165     bool is_toggleable;
    166     status = IsOptionElementTogglable(
    167         session, web_view, element_id, &is_toggleable);
    168     if (status.IsError())
    169       return status;
    170     if (is_toggleable)
    171       return ToggleOptionElement(session, web_view, element_id);
    172     else
    173       return SetOptionElementSelected(session, web_view, element_id, true);
    174   } else {
    175     WebPoint location;
    176     status = GetElementClickableLocation(
    177         session, web_view, element_id, &location);
    178     if (status.IsError())
    179       return status;
    180 
    181     std::list<MouseEvent> events;
    182     events.push_back(
    183         MouseEvent(kMovedMouseEventType, kNoneMouseButton,
    184                    location.x, location.y, session->sticky_modifiers, 0));
    185     events.push_back(
    186         MouseEvent(kPressedMouseEventType, kLeftMouseButton,
    187                    location.x, location.y, session->sticky_modifiers, 1));
    188     events.push_back(
    189         MouseEvent(kReleasedMouseEventType, kLeftMouseButton,
    190                    location.x, location.y, session->sticky_modifiers, 1));
    191     status =
    192         web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
    193     if (status.IsOk())
    194       session->mouse_position = location;
    195     return status;
    196   }
    197 }
    198 
    199 Status ExecuteTouchSingleTap(
    200     Session* session,
    201     WebView* web_view,
    202     const std::string& element_id,
    203     const base::DictionaryValue& params,
    204     scoped_ptr<base::Value>* value) {
    205   // Fall back to javascript atom for pre-m30 Chrome.
    206   if (session->chrome->GetBrowserInfo()->build_no < 1576)
    207     return ExecuteTouchSingleTapAtom(
    208         session, web_view, element_id, params, value);
    209 
    210   WebPoint location;
    211   Status status = GetElementClickableLocation(
    212       session, web_view, element_id, &location);
    213   if (status.IsError())
    214     return status;
    215 
    216   std::list<TouchEvent> events;
    217   events.push_back(
    218       TouchEvent(kTouchStart, location.x, location.y));
    219   events.push_back(
    220       TouchEvent(kTouchEnd, location.x, location.y));
    221   return web_view->DispatchTouchEvents(events);
    222 }
    223 
    224 Status ExecuteFlick(
    225     Session* session,
    226     WebView* web_view,
    227     const std::string& element_id,
    228     const base::DictionaryValue& params,
    229     scoped_ptr<base::Value>* value) {
    230   WebPoint location;
    231   Status status = GetElementClickableLocation(
    232       session, web_view, element_id, &location);
    233   if (status.IsError())
    234     return status;
    235 
    236   int xoffset, yoffset, speed;
    237   if (!params.GetInteger("xoffset", &xoffset))
    238     return Status(kUnknownError, "'xoffset' must be an integer");
    239   if (!params.GetInteger("yoffset", &yoffset))
    240     return Status(kUnknownError, "'yoffset' must be an integer");
    241   if (!params.GetInteger("speed", &speed))
    242     return Status(kUnknownError, "'speed' must be an integer");
    243   if (speed < 1)
    244     return Status(kUnknownError, "'speed' must be a positive integer");
    245 
    246   status = web_view->DispatchTouchEvent(
    247       TouchEvent(kTouchStart, location.x, location.y));
    248   if (status.IsError())
    249     return status;
    250 
    251   const double offset =
    252       std::sqrt(static_cast<double>(xoffset * xoffset + yoffset * yoffset));
    253   const double xoffset_per_event =
    254       (speed * xoffset) / (kFlickTouchEventsPerSecond * offset);
    255   const double yoffset_per_event =
    256       (speed * yoffset) / (kFlickTouchEventsPerSecond * offset);
    257   const int total_events =
    258       (offset * kFlickTouchEventsPerSecond) / speed;
    259   for (int i = 0; i < total_events; i++) {
    260     status = web_view->DispatchTouchEvent(
    261         TouchEvent(kTouchMove,
    262                    location.x + xoffset_per_event * i,
    263                    location.y + yoffset_per_event * i));
    264     if (status.IsError())
    265       return status;
    266     base::PlatformThread::Sleep(
    267         base::TimeDelta::FromMilliseconds(1000 / kFlickTouchEventsPerSecond));
    268   }
    269   return web_view->DispatchTouchEvent(
    270       TouchEvent(kTouchEnd, location.x + xoffset, location.y + yoffset));
    271 }
    272 
    273 Status ExecuteClearElement(
    274     Session* session,
    275     WebView* web_view,
    276     const std::string& element_id,
    277     const base::DictionaryValue& params,
    278     scoped_ptr<base::Value>* value) {
    279   base::ListValue args;
    280   args.Append(CreateElement(element_id));
    281   scoped_ptr<base::Value> result;
    282   return web_view->CallFunction(
    283       session->GetCurrentFrameId(),
    284       webdriver::atoms::asString(webdriver::atoms::CLEAR),
    285       args, &result);
    286 }
    287 
    288 Status ExecuteSendKeysToElement(
    289     Session* session,
    290     WebView* web_view,
    291     const std::string& element_id,
    292     const base::DictionaryValue& params,
    293     scoped_ptr<base::Value>* value) {
    294   const base::ListValue* key_list;
    295   if (!params.GetList("value", &key_list))
    296     return Status(kUnknownError, "'value' must be a list");
    297 
    298   bool is_input = false;
    299   Status status = IsElementAttributeEqualToIgnoreCase(
    300       session, web_view, element_id, "tagName", "input", &is_input);
    301   if (status.IsError())
    302     return status;
    303   bool is_file = false;
    304   status = IsElementAttributeEqualToIgnoreCase(
    305       session, web_view, element_id, "type", "file", &is_file);
    306   if (status.IsError())
    307     return status;
    308   if (is_input && is_file) {
    309     // Compress array into a single string.
    310     base::FilePath::StringType paths_string;
    311     for (size_t i = 0; i < key_list->GetSize(); ++i) {
    312       base::FilePath::StringType path_part;
    313       if (!key_list->GetString(i, &path_part))
    314         return Status(kUnknownError, "'value' is invalid");
    315       paths_string.append(path_part);
    316     }
    317 
    318     // Separate the string into separate paths, delimited by '\n'.
    319     std::vector<base::FilePath::StringType> path_strings;
    320     base::SplitString(paths_string, '\n', &path_strings);
    321     std::vector<base::FilePath> paths;
    322     for (size_t i = 0; i < path_strings.size(); ++i)
    323       paths.push_back(base::FilePath(path_strings[i]));
    324 
    325     bool multiple = false;
    326     status = IsElementAttributeEqualToIgnoreCase(
    327         session, web_view, element_id, "multiple", "true", &multiple);
    328     if (status.IsError())
    329       return status;
    330     if (!multiple && paths.size() > 1)
    331       return Status(kUnknownError, "the element can not hold multiple files");
    332 
    333     scoped_ptr<base::DictionaryValue> element(CreateElement(element_id));
    334     return web_view->SetFileInputFiles(
    335         session->GetCurrentFrameId(), *element, paths);
    336   } else {
    337     return SendKeysToElement(session, web_view, element_id, key_list);
    338   }
    339 }
    340 
    341 Status ExecuteSubmitElement(
    342     Session* session,
    343     WebView* web_view,
    344     const std::string& element_id,
    345     const base::DictionaryValue& params,
    346     scoped_ptr<base::Value>* value) {
    347   base::ListValue args;
    348   args.Append(CreateElement(element_id));
    349   return web_view->CallFunction(
    350       session->GetCurrentFrameId(),
    351       webdriver::atoms::asString(webdriver::atoms::SUBMIT),
    352       args,
    353       value);
    354 }
    355 
    356 Status ExecuteGetElementText(
    357     Session* session,
    358     WebView* web_view,
    359     const std::string& element_id,
    360     const base::DictionaryValue& params,
    361     scoped_ptr<base::Value>* value) {
    362   base::ListValue args;
    363   args.Append(CreateElement(element_id));
    364   return web_view->CallFunction(
    365       session->GetCurrentFrameId(),
    366       webdriver::atoms::asString(webdriver::atoms::GET_TEXT),
    367       args,
    368       value);
    369 }
    370 
    371 Status ExecuteGetElementValue(
    372     Session* session,
    373     WebView* web_view,
    374     const std::string& element_id,
    375     const base::DictionaryValue& params,
    376     scoped_ptr<base::Value>* value) {
    377   base::ListValue args;
    378   args.Append(CreateElement(element_id));
    379   return web_view->CallFunction(
    380       session->GetCurrentFrameId(),
    381       "function(elem) { return elem['value'] }",
    382       args,
    383       value);
    384 }
    385 
    386 Status ExecuteGetElementTagName(
    387     Session* session,
    388     WebView* web_view,
    389     const std::string& element_id,
    390     const base::DictionaryValue& params,
    391     scoped_ptr<base::Value>* value) {
    392   base::ListValue args;
    393   args.Append(CreateElement(element_id));
    394   return web_view->CallFunction(
    395       session->GetCurrentFrameId(),
    396       "function(elem) { return elem.tagName.toLowerCase() }",
    397       args,
    398       value);
    399 }
    400 
    401 Status ExecuteIsElementSelected(
    402     Session* session,
    403     WebView* web_view,
    404     const std::string& element_id,
    405     const base::DictionaryValue& params,
    406     scoped_ptr<base::Value>* value) {
    407   base::ListValue args;
    408   args.Append(CreateElement(element_id));
    409   return web_view->CallFunction(
    410       session->GetCurrentFrameId(),
    411       webdriver::atoms::asString(webdriver::atoms::IS_SELECTED),
    412       args,
    413       value);
    414 }
    415 
    416 Status ExecuteIsElementEnabled(
    417     Session* session,
    418     WebView* web_view,
    419     const std::string& element_id,
    420     const base::DictionaryValue& params,
    421     scoped_ptr<base::Value>* value) {
    422   base::ListValue args;
    423   args.Append(CreateElement(element_id));
    424   return web_view->CallFunction(
    425       session->GetCurrentFrameId(),
    426       webdriver::atoms::asString(webdriver::atoms::IS_ENABLED),
    427       args,
    428       value);
    429 }
    430 
    431 Status ExecuteIsElementDisplayed(
    432     Session* session,
    433     WebView* web_view,
    434     const std::string& element_id,
    435     const base::DictionaryValue& params,
    436     scoped_ptr<base::Value>* value) {
    437   base::ListValue args;
    438   args.Append(CreateElement(element_id));
    439   return web_view->CallFunction(
    440       session->GetCurrentFrameId(),
    441       webdriver::atoms::asString(webdriver::atoms::IS_DISPLAYED),
    442       args,
    443       value);
    444 }
    445 
    446 Status ExecuteGetElementLocation(
    447     Session* session,
    448     WebView* web_view,
    449     const std::string& element_id,
    450     const base::DictionaryValue& params,
    451     scoped_ptr<base::Value>* value) {
    452   base::ListValue args;
    453   args.Append(CreateElement(element_id));
    454   return web_view->CallFunction(
    455       session->GetCurrentFrameId(),
    456       webdriver::atoms::asString(webdriver::atoms::GET_LOCATION),
    457       args,
    458       value);
    459 }
    460 
    461 Status ExecuteGetElementLocationOnceScrolledIntoView(
    462     Session* session,
    463     WebView* web_view,
    464     const std::string& element_id,
    465     const base::DictionaryValue& params,
    466     scoped_ptr<base::Value>* value) {
    467   WebPoint location;
    468   Status status = ScrollElementIntoView(
    469       session, web_view, element_id, &location);
    470   if (status.IsError())
    471     return status;
    472   value->reset(CreateValueFrom(location));
    473   return Status(kOk);
    474 }
    475 
    476 Status ExecuteGetElementSize(
    477     Session* session,
    478     WebView* web_view,
    479     const std::string& element_id,
    480     const base::DictionaryValue& params,
    481     scoped_ptr<base::Value>* value) {
    482   base::ListValue args;
    483   args.Append(CreateElement(element_id));
    484   return web_view->CallFunction(
    485       session->GetCurrentFrameId(),
    486       webdriver::atoms::asString(webdriver::atoms::GET_SIZE),
    487       args,
    488       value);
    489 }
    490 
    491 Status ExecuteGetElementAttribute(
    492     Session* session,
    493     WebView* web_view,
    494     const std::string& element_id,
    495     const base::DictionaryValue& params,
    496     scoped_ptr<base::Value>* value) {
    497   std::string name;
    498   if (!params.GetString("name", &name))
    499     return Status(kUnknownError, "missing 'name'");
    500   return GetElementAttribute(session, web_view, element_id, name, value);
    501 }
    502 
    503 Status ExecuteGetElementValueOfCSSProperty(
    504     Session* session,
    505     WebView* web_view,
    506     const std::string& element_id,
    507     const base::DictionaryValue& params,
    508     scoped_ptr<base::Value>* value) {
    509   std::string property_name;
    510   if (!params.GetString("propertyName", &property_name))
    511     return Status(kUnknownError, "missing 'propertyName'");
    512   std::string property_value;
    513   Status status = GetElementEffectiveStyle(
    514       session, web_view, element_id, property_name, &property_value);
    515   if (status.IsError())
    516     return status;
    517   value->reset(new base::StringValue(property_value));
    518   return Status(kOk);
    519 }
    520 
    521 Status ExecuteElementEquals(
    522     Session* session,
    523     WebView* web_view,
    524     const std::string& element_id,
    525     const base::DictionaryValue& params,
    526     scoped_ptr<base::Value>* value) {
    527   std::string other_element_id;
    528   if (!params.GetString("other", &other_element_id))
    529     return Status(kUnknownError, "'other' must be a string");
    530   value->reset(new base::FundamentalValue(element_id == other_element_id));
    531   return Status(kOk);
    532 }
    533