Implemnts the commands in webdriver to preform searching of elements on a page.
/session/:sessionId/timeouts/implicit_wait /session/:sessionId/element /session/:sessionId/elements /session/:sessionId/element/:id/element /session/:sessionId/element/:id/elements BUG=none TEST=webdriver_remote_tests.py Review URL: http://codereview.chromium.org/3643002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@70107 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
@ -685,6 +685,10 @@
|
||||
'test/webdriver/commands/command.cc',
|
||||
'test/webdriver/commands/create_session.h',
|
||||
'test/webdriver/commands/create_session.cc',
|
||||
'test/webdriver/commands/find_element_commands.h',
|
||||
'test/webdriver/commands/find_element_commands.cc',
|
||||
'test/webdriver/commands/implicit_wait_command.h',
|
||||
'test/webdriver/commands/implicit_wait_command.cc',
|
||||
'test/webdriver/commands/execute_command.h',
|
||||
'test/webdriver/commands/execute_command.cc',
|
||||
'test/webdriver/commands/navigate_commands.h',
|
||||
|
111
chrome/test/webdriver/commands/find_element_commands.cc
Normal file
111
chrome/test/webdriver/commands/find_element_commands.cc
Normal file
@ -0,0 +1,111 @@
|
||||
// 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.
|
||||
|
||||
#include "chrome/test/webdriver/commands/find_element_commands.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "base/string_number_conversions.h"
|
||||
#include "base/utf_string_conversions.h"
|
||||
#include "base/values.h"
|
||||
#include "third_party/webdriver/atoms.h"
|
||||
#include "chrome/test/webdriver/error_codes.h"
|
||||
#include "chrome/test/webdriver/utility_functions.h"
|
||||
|
||||
namespace webdriver {
|
||||
|
||||
bool FindElementCommand::Init(Response* const response) {
|
||||
if (!WebDriverCommand::Init(response)) {
|
||||
SET_WEBDRIVER_ERROR(response, "Failure on Init for find element",
|
||||
kInternalServerError);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GetStringASCIIParameter("using", &use_) ||
|
||||
!GetStringASCIIParameter("value", &value_)) {
|
||||
SET_WEBDRIVER_ERROR(response,
|
||||
"Request is missing required 'using' and/or 'value' data", kBadRequest);
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(jmikhail): The findElement(s) atom should handle this conversion.
|
||||
if ("class name" == use_) {
|
||||
use_ = "className";
|
||||
} else if ("link text" == use_) {
|
||||
use_ = "linkText";
|
||||
} else if ("partial link text" == use_) {
|
||||
use_ = "partialLinkText";
|
||||
} else if ("tag name" == use_) {
|
||||
use_ = "tagName";
|
||||
}
|
||||
|
||||
// Searching under a custom root if the URL pattern is
|
||||
// "/session/$session/element/$id/element(s)"
|
||||
root_element_id_ = GetPathVariable(4);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FindElementCommand::ExecutePost(Response* const response) {
|
||||
scoped_ptr<ListValue> args(new ListValue());
|
||||
DictionaryValue* locator = new DictionaryValue();
|
||||
ErrorCode error;
|
||||
std::wstring jscript;
|
||||
Value* result = NULL;
|
||||
bool done = false;
|
||||
|
||||
// Set the command we are using to locate the value beging searched for.
|
||||
locator->SetString(use_, value_);
|
||||
args->Append(locator);
|
||||
args->Append(root_element_id_.size() == 0 ? Value::CreateNullValue() :
|
||||
WebDriverCommand::GetElementIdAsDictionaryValue(root_element_id_));
|
||||
if (find_one_element_) {
|
||||
jscript = build_atom(FIND_ELEMENT, sizeof FIND_ELEMENT);
|
||||
jscript.append(L"var result = findElement(arguments[0], arguments[1]);")
|
||||
.append(L"if (!result) {")
|
||||
.append(L"var e = Error('Unable to locate element');")
|
||||
.append(L"e.code = ")
|
||||
.append(UTF8ToWide(base::IntToString(kNoSuchElement)))
|
||||
.append(L";throw e;")
|
||||
.append(L"} else { return result; }");
|
||||
} else {
|
||||
jscript = build_atom(FIND_ELEMENTS, sizeof FIND_ELEMENT);
|
||||
jscript.append(L"return findElements(arguments[0], arguments[1]);");
|
||||
}
|
||||
|
||||
// The element search needs to loop until at least one element is found or the
|
||||
// session's implicit wait timeout expires, whichever occurs first.
|
||||
base::Time start_time = base::Time::Now();
|
||||
|
||||
while (!done) {
|
||||
if (result) {
|
||||
delete result;
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
error = session_->ExecuteScript(jscript, args.get(), &result);
|
||||
if (error == kSuccess) {
|
||||
// If searching for many elements, make sure we found at least one before
|
||||
// stopping.
|
||||
done = find_one_element_ ||
|
||||
(result->GetType() == Value::TYPE_LIST &&
|
||||
static_cast<ListValue*>(result)->GetSize() > 0);
|
||||
} else if (error != kNoSuchElement) {
|
||||
SET_WEBDRIVER_ERROR(response, "Internal error in find_element atom",
|
||||
kInternalServerError);
|
||||
return;
|
||||
}
|
||||
|
||||
int64 elapsed_time = (base::Time::Now() - start_time).InMilliseconds();
|
||||
done = done || elapsed_time > session_->implicit_wait();
|
||||
PlatformThread::Sleep(50); // Prevent a busy loop that eats the cpu.
|
||||
}
|
||||
|
||||
response->set_value(result);
|
||||
response->set_status(error);
|
||||
}
|
||||
|
||||
} // namespace webdriver
|
||||
|
74
chrome/test/webdriver/commands/find_element_commands.h
Normal file
74
chrome/test/webdriver/commands/find_element_commands.h
Normal file
@ -0,0 +1,74 @@
|
||||
// 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.
|
||||
|
||||
#ifndef CHROME_TEST_WEBDRIVER_COMMANDS_FIND_ELEMENT_COMMANDS_H_
|
||||
#define CHROME_TEST_WEBDRIVER_COMMANDS_FIND_ELEMENT_COMMANDS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "chrome/test/webdriver/commands/command.h"
|
||||
#include "chrome/test/webdriver/commands/webdriver_command.h"
|
||||
|
||||
namespace webdriver {
|
||||
|
||||
// Base class for searching a page, this class can find either a single
|
||||
// webelement or return multiple matches.
|
||||
class FindElementCommand : public WebDriverCommand {
|
||||
public:
|
||||
FindElementCommand(const std::vector<std::string>& path_segments,
|
||||
const DictionaryValue* const parameters,
|
||||
const bool find_one_element)
|
||||
: WebDriverCommand(path_segments, parameters),
|
||||
find_one_element_(find_one_element) {}
|
||||
virtual ~FindElementCommand() {}
|
||||
|
||||
virtual bool Init(Response* const response);
|
||||
|
||||
virtual bool DoesPost() { return true; }
|
||||
virtual void ExecutePost(Response* const response);
|
||||
|
||||
private:
|
||||
virtual bool RequiresValidTab() { return false; }
|
||||
const bool find_one_element_;
|
||||
std::string root_element_id_;
|
||||
std::string use_;
|
||||
std::string value_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(FindElementCommand);
|
||||
};
|
||||
|
||||
// Search for an element on the page, starting from the document root.
|
||||
// The located element will be returned as a WebElement JSON object. See:
|
||||
// http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/element
|
||||
class FindOneElementCommand : public FindElementCommand {
|
||||
public:
|
||||
FindOneElementCommand(const std::vector<std::string>& path_segments,
|
||||
const DictionaryValue* const parameters)
|
||||
: FindElementCommand(path_segments, parameters, true) {}
|
||||
virtual ~FindOneElementCommand() {}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(FindOneElementCommand);
|
||||
};
|
||||
|
||||
// Search for multiple elements on the page, starting from the identified
|
||||
// element. The located elements will be returned as a WebElement JSON
|
||||
// objects. See:
|
||||
// http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/elements
|
||||
class FindManyElementsCommand : public FindElementCommand {
|
||||
public:
|
||||
FindManyElementsCommand(const std::vector<std::string>& path_segments,
|
||||
const DictionaryValue* const parameters)
|
||||
: FindElementCommand(path_segments, parameters, false) {}
|
||||
virtual ~FindManyElementsCommand() {}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(FindManyElementsCommand);
|
||||
};
|
||||
|
||||
} // namespace webdriver
|
||||
|
||||
#endif // CHROME_TEST_WEBDRIVER_COMMANDS_FIND_ELEMENT_COMMANDS_H_
|
||||
|
45
chrome/test/webdriver/commands/implicit_wait_command.cc
Normal file
45
chrome/test/webdriver/commands/implicit_wait_command.cc
Normal file
@ -0,0 +1,45 @@
|
||||
// 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.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/utf_string_conversions.h"
|
||||
#include "chrome/test/webdriver/commands/implicit_wait_command.h"
|
||||
|
||||
namespace webdriver {
|
||||
|
||||
bool ImplicitWaitCommand::Init(Response* const response) {
|
||||
if (!(WebDriverCommand::Init(response))) {
|
||||
SET_WEBDRIVER_ERROR(response, "Failure on Init for find element",
|
||||
kInternalServerError);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Record the requested wait time.
|
||||
if (!GetIntegerParameter("ms", &ms_to_wait_)) {
|
||||
SET_WEBDRIVER_ERROR(response, "Request missing ms parameter",
|
||||
kBadRequest);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImplicitWaitCommand::ExecutePost(Response* const response) {
|
||||
// Validate the wait time before setting it to the session.
|
||||
if (ms_to_wait_ < 0) {
|
||||
SET_WEBDRIVER_ERROR(response, "Wait must be non-negative",
|
||||
kBadRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
session_->set_implicit_wait(ms_to_wait_);
|
||||
LOG(INFO) << "Implicit wait set to: " << ms_to_wait_ << " ms";
|
||||
|
||||
response->set_value(new StringValue("success"));
|
||||
response->set_status(kSuccess);
|
||||
}
|
||||
|
||||
} // namespace webdriver
|
||||
|
42
chrome/test/webdriver/commands/implicit_wait_command.h
Normal file
42
chrome/test/webdriver/commands/implicit_wait_command.h
Normal file
@ -0,0 +1,42 @@
|
||||
// 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.
|
||||
|
||||
#ifndef CHROME_TEST_WEBDRIVER_COMMANDS_IMPLICIT_WAIT_COMMAND_H_
|
||||
#define CHROME_TEST_WEBDRIVER_COMMANDS_IMPLICIT_WAIT_COMMAND_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "chrome/test/webdriver/commands/webdriver_command.h"
|
||||
|
||||
namespace webdriver {
|
||||
|
||||
// Set the amount of time the driver should wait when searching for elements.
|
||||
// If this command is never sent, the driver will default to an implicit wait
|
||||
// of 0 ms. Until the webelement commands are checked in we do no use this
|
||||
// variable. For more information see:
|
||||
// http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/timeouts/implicit_wait
|
||||
class ImplicitWaitCommand : public WebDriverCommand {
|
||||
public:
|
||||
inline ImplicitWaitCommand(const std::vector<std::string>& path_segments,
|
||||
const DictionaryValue* const parameters)
|
||||
: WebDriverCommand(path_segments, parameters), ms_to_wait_(0) {}
|
||||
virtual ~ImplicitWaitCommand() {}
|
||||
|
||||
virtual bool Init(Response* const response);
|
||||
|
||||
virtual bool DoesPost() { return true; }
|
||||
virtual void ExecutePost(Response* const response);
|
||||
|
||||
private:
|
||||
int ms_to_wait_;
|
||||
virtual bool RequiresValidTab() { return true; }
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ImplicitWaitCommand);
|
||||
};
|
||||
|
||||
} // namespace webdriver
|
||||
|
||||
#endif // CHROME_TEST_WEBDRIVER_COMMANDS_IMPLICIT_WAIT_COMMAND_H_
|
||||
|
@ -26,6 +26,8 @@
|
||||
#include "chrome/test/webdriver/utility_functions.h"
|
||||
#include "chrome/test/webdriver/commands/create_session.h"
|
||||
#include "chrome/test/webdriver/commands/execute_command.h"
|
||||
#include "chrome/test/webdriver/commands/find_element_commands.h"
|
||||
#include "chrome/test/webdriver/commands/implicit_wait_command.h"
|
||||
#include "chrome/test/webdriver/commands/navigate_commands.h"
|
||||
#include "chrome/test/webdriver/commands/session_with_id.h"
|
||||
#include "chrome/test/webdriver/commands/source_command.h"
|
||||
@ -68,6 +70,13 @@ void InitCallbacks(struct mg_context* ctx) {
|
||||
SetCallback<URLCommand>(ctx, "/session/*/url");
|
||||
SetCallback<SpeedCommand>(ctx, "/session/*/speed");
|
||||
|
||||
// WebElement commands
|
||||
SetCallback<ImplicitWaitCommand>(ctx, "/session/*/timeouts/implicit_wait");
|
||||
SetCallback<FindOneElementCommand>(ctx, "/session/*/element");
|
||||
SetCallback<FindManyElementsCommand>(ctx, "/session/*/elements");
|
||||
SetCallback<FindOneElementCommand>(ctx, "/session/*/element/*/element");
|
||||
SetCallback<FindManyElementsCommand>(ctx, "/session/*/elements/*/elements");
|
||||
|
||||
// Since the /session/* is a wild card that would match the above URIs, this
|
||||
// line MUST be the last registered URI with the server.
|
||||
SetCallback<SessionWithID>(ctx, "/session/*");
|
||||
@ -80,7 +89,7 @@ void InitCallbacks(struct mg_context* ctx) {
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_context *ctx;
|
||||
base::AtExitManager exit;
|
||||
std::string port = "8080";
|
||||
std::string port = "9515";
|
||||
#ifdef OS_POSIX
|
||||
CommandLine cmd_line = CommandLine(argc, argv);
|
||||
#elif OS_WIN
|
||||
@ -110,14 +119,14 @@ int main(int argc, char *argv[]) {
|
||||
session->SetIPAddress(port);
|
||||
|
||||
// Initialize SHTTPD context.
|
||||
// Listen on port 8080 or port specified on command line.
|
||||
// TODO(jmikhail) Maybe add port 8081 as a secure connection.
|
||||
// Listen on port 9515 or port specified on command line.
|
||||
// TODO(jmikhail) Maybe add port 9516 as a secure connection.
|
||||
ctx = mg_start();
|
||||
mg_set_option(ctx, "ports", port.c_str());
|
||||
|
||||
webdriver::InitCallbacks(ctx);
|
||||
|
||||
std::cout << "Starting server" << std::endl;
|
||||
std::cout << "Starting server on port: " << port << std::endl;
|
||||
// The default behavior is to run this service forever.
|
||||
while (true)
|
||||
PlatformThread::Sleep(3600);
|
||||
|
@ -18,8 +18,10 @@ import time
|
||||
import types
|
||||
import unittest
|
||||
import urllib2
|
||||
from selenium.remote.webdriver import WebDriver
|
||||
from selenium.common.exceptions import ErrorInResponseException
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.remote.webdriver import WebDriver
|
||||
from selenium.remote.webdriver.webelement import WebElement
|
||||
from urlparse import urlparse
|
||||
|
||||
|
||||
@ -42,6 +44,19 @@ if not WEBDRIVER_SERVER_URL:
|
||||
WEBDRIVER_SERVER_URL = 'http://localhost:%d' % WEBDRIVER_PORT
|
||||
|
||||
class RemoteWebDriverTest(unittest.TestCase):
|
||||
SEARCH = "http://www.google.com/webhp?hl=en"
|
||||
NEWS = "http://www.google.com/news?hl=en"
|
||||
|
||||
"""Verifies that navigation to a specific page is correct by asserting on the
|
||||
the reported URL. The function will not work with pages that redirect."""
|
||||
def navigate(self, url):
|
||||
self.driver.get(url)
|
||||
self.assertURL(url)
|
||||
|
||||
def assertURL(self, url):
|
||||
u = self.driver.get_current_url()
|
||||
self.assertEqual(u, url)
|
||||
|
||||
"""A new instance of chrome driver is started for every test case"""
|
||||
def setUp(self):
|
||||
global WEBDRIVER_SERVER_URL
|
||||
@ -81,6 +96,50 @@ class RemoteWebDriverTest(unittest.TestCase):
|
||||
search = string.lower(search)
|
||||
self.assertNotEqual(-1, string.find(text, search))
|
||||
|
||||
class TestFindElement(RemoteWebDriverTest):
|
||||
def testFindByName(self):
|
||||
navigate(SEARCH)
|
||||
# Find the Google search button.
|
||||
q = self.driver.find_element_by_name("q")
|
||||
self.assertTrue(isinstance(q, WebElement))
|
||||
# Trying looking for an element not on the page.
|
||||
self.assertRaises(NoSuchElementException,
|
||||
self.driver.find_elment_by_name, "q2")
|
||||
# Try to find the Google search button using the multiple find method.
|
||||
q = self.driver.find_elements_by_name("q")
|
||||
self.assertTrue(isinstance(q, list))
|
||||
self.assertTrue(len(q), 1)
|
||||
self.assertTrue(isinstance(q[0], WebElement))
|
||||
# Try finding something not on page, with multiple find an empty array
|
||||
# should return and no exception thrown.
|
||||
q = self.driver.find_elements_by_name("q2")
|
||||
assertTrue(q == [])
|
||||
# Find a hidden element on the page
|
||||
q = self.driver.find_element_by_name("oq")
|
||||
self.assertTrue(isinstance(q, WebElement))
|
||||
|
||||
def testFindElementById(self):
|
||||
navigate(SEARCH)
|
||||
# Find the padding for the logo near the search bar.
|
||||
elem = self.driver.find_element_by_id("logocont")
|
||||
self.assertTrue(isinstance(elem, WebElement))
|
||||
# Look for an ID not there.
|
||||
self.assertRaises(NoSuchElementException,
|
||||
self.driver.find_element_by_id, "logocont")
|
||||
|
||||
def testFindElementById0WithTimeout(self):
|
||||
self.set_implicit_wait(0)
|
||||
navigate(SEARCH)
|
||||
# Find the padding for the logo near the search bar.
|
||||
elem = self.driver.find_element_by_id("logocont")
|
||||
self.assertTrue(isinstance(elem, WebElement))
|
||||
self.set_implicit_wait(5000)
|
||||
navigate(SEARCH)
|
||||
# Look for an ID not there.
|
||||
self.assertRaises(NoSuchElementException,
|
||||
self.driver.find_element_by_id, "logocont")
|
||||
|
||||
|
||||
class TestJavaScriptExecution(RemoteWebDriverTest):
|
||||
""" Test the execute javascript ability of the remote driver"""
|
||||
def testNoModification(self):
|
||||
@ -120,19 +179,6 @@ class TestJavaScriptExecution(RemoteWebDriverTest):
|
||||
|
||||
|
||||
class TestNavigation(RemoteWebDriverTest):
|
||||
SEARCH = "http://www.google.com/webhp?hl=en"
|
||||
NEWS = "http://www.google.com/news?hl=en"
|
||||
|
||||
"""Verifies that navigation to a specific page is correct by asserting on the
|
||||
the reported URL. The function will not work with pages that redirect."""
|
||||
def navigate(self, url):
|
||||
self.driver.get(url)
|
||||
self.assertURL(url)
|
||||
|
||||
def assertURL(self, url):
|
||||
u = self.driver.get_current_url()
|
||||
self.assertEqual(u, url)
|
||||
|
||||
def testNavigateToURL(self):
|
||||
# No redirects are allowed on the google home page.
|
||||
self.navigate(self.SEARCH)
|
||||
|
@ -173,6 +173,17 @@ class WebDriver(object):
|
||||
resp = self._execute(Command.GET_TITLE)
|
||||
return resp['value']
|
||||
|
||||
def set_implicit_wait(self, wait):
|
||||
"""Set the amount of time the driver should wait when searching for
|
||||
elements. When searching for a single element, the driver should poll
|
||||
the page until an element is found or the timeout expires, whichever
|
||||
occurs first. When searching for multiple elements, the driver should
|
||||
poll the page until at least one element is found or the timeout
|
||||
expires, at which point it should return an empty list. If this
|
||||
command is never sent, the driver should default to an implicit wait
|
||||
of 0ms."""
|
||||
self._execute(Command.IMPLICIT_WAIT, {'ms': wait})
|
||||
|
||||
def find_element_by_id(self, id_):
|
||||
"""Finds element by id."""
|
||||
return self._find_element_by("id", id_)
|
||||
|
@ -76,4 +76,5 @@ class Command(object):
|
||||
GET_ELEMENT_ATTRIBUTE = "getElementAttribute"
|
||||
GET_ELEMENT_VALUE_OF_CSS_PROPERTY = "getElementValueOfCssProperty"
|
||||
ELEMENT_EQUALS = "elementEquals"
|
||||
IMPLICIT_WAIT = "setImplicitWait"
|
||||
SCREENSHOT = "screenshot"
|
||||
|
@ -128,6 +128,7 @@ class RemoteConnection(object):
|
||||
Command.GET_CURRENT_URL: ('GET', '/session/$sessionId/url'),
|
||||
Command.GET_TITLE: ('GET', '/session/$sessionId/title'),
|
||||
Command.GET_PAGE_SOURCE: ('GET', '/session/$sessionId/source'),
|
||||
Command.IMPLICIT_WAIT: ('POST', '/session/$sessionId/timeouts/implicit_wait'),
|
||||
Command.SCREENSHOT: ('GET', '/session/$sessionId/screenshot'),
|
||||
Command.SET_BROWSER_VISIBLE:
|
||||
('POST', '/session/$sessionId/visible'),
|
||||
|
Reference in New Issue
Block a user