├── test ├── pages │ ├── alerts.html │ ├── js.html │ ├── webdriver.html │ ├── navigation2.html │ ├── navigation1.html │ ├── frame1.html │ ├── frame2.html │ ├── frame3.html │ ├── keyboard.html │ ├── session.html │ ├── redirect.html │ ├── frameset.html │ ├── frames.html │ ├── element.html │ ├── finder.html │ └── mouse.html ├── http_connection_test.cpp ├── client_test.cpp ├── alerts_test.cpp ├── keyboard_test.cpp ├── main.cpp ├── browsers_test.cpp ├── frames_test.cpp ├── examples_test.cpp ├── capabilities_test.cpp ├── wait_test.cpp ├── webdriver_test.cpp ├── element_test.cpp ├── to_string_test.cpp ├── wait_match_test.cpp ├── environment.h ├── mouse_test.cpp ├── shared_test.cpp ├── finder_test.cpp ├── CMakeLists.txt ├── conversions_test.cpp ├── js_test.cpp ├── session_test.cpp └── resource_test.cpp ├── .gitignore ├── include ├── webdriverxx.h └── webdriverxx │ ├── errors.h │ ├── detail │ ├── factories.h │ ├── http_client.h │ ├── finder.h │ ├── keyboard.h │ ├── factories_impl.h │ ├── meta_tools.h │ ├── time.h │ ├── http_connection.h │ ├── finder.inl │ ├── error_handling.h │ ├── types.h │ ├── shared.h │ ├── to_string.h │ ├── http_request.h │ └── resource.h │ ├── browsers │ ├── phantom.h │ ├── firefox.h │ ├── chrome.h │ └── ie.h │ ├── webdriver.h │ ├── js_args.h │ ├── client.h │ ├── window.h │ ├── by.h │ ├── types.h │ ├── element.h │ ├── client.inl │ ├── wait.h │ ├── keys.h │ ├── response_status_code.h │ ├── element.inl │ ├── wait_match.h │ ├── session.h │ ├── capabilities.h │ ├── conversions.h │ └── session.inl ├── .travis.yml ├── CMakeLists.txt ├── LICENSE └── README.md /test/pages/alerts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/pages/js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/pages/webdriver.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/pages/navigation2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Navigation 2 4 | 5 | -------------------------------------------------------------------------------- /test/pages/navigation1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Navigation 1 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/pages/frame1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/pages/frame2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/pages/frame3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/pages/keyboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/pages/session.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test title 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/pages/redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/pages/frameset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/pages/frames.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | -------------------------------------------------------------------------------- /test/pages/element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
Some text
7 |
8 | 9 |
visible text
10 | 11 | 12 | -------------------------------------------------------------------------------- /test/pages/finder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 | 7 | test link text 8 |
9 |
10 |
11 |
12 |
13 |
14 | 15 | -------------------------------------------------------------------------------- /include/webdriverxx.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_H 2 | #define WEBDRIVERXX_H 3 | 4 | #include "webdriverxx/webdriver.h" 5 | #include "webdriverxx/browsers/chrome.h" 6 | #include "webdriverxx/browsers/firefox.h" 7 | #include "webdriverxx/browsers/ie.h" 8 | #include "webdriverxx/browsers/phantom.h" 9 | #include "webdriverxx/wait.h" 10 | #include "webdriverxx/wait_match.h" 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /include/webdriverxx/errors.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_ERRORS_H 2 | #define WEBDRIVERXX_ERRORS_H 3 | 4 | #include 5 | #include 6 | 7 | namespace webdriverxx { 8 | 9 | struct WebDriverException : std::runtime_error { 10 | explicit WebDriverException(const std::string& message) 11 | : std::runtime_error(message) {} 12 | }; 13 | 14 | } // namespace webdriverxx 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | compiler: 4 | - gcc 5 | - clang 6 | 7 | branches: 8 | only: 9 | - master 10 | - dev 11 | 12 | before_script: 13 | - mkdir build 14 | - cd build 15 | - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo .. 16 | - phantomjs --webdriver=7777 --webdriver-loglevel=WARN & 17 | - npm install http-server -g 18 | - http-server ./test/pages --silent & 19 | 20 | script: 21 | - cmake --build . 22 | - ctest -V 23 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/factories.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_FACTORIES_H 2 | #define WEBDRIVERXX_DETAIL_FACTORIES_H 3 | 4 | #include "shared.h" 5 | #include 6 | 7 | namespace webdriverxx { 8 | 9 | class Element; 10 | 11 | namespace detail { 12 | 13 | class Finder; 14 | class Resource; 15 | 16 | struct IFinderFactory { 17 | virtual Finder MakeFinder(const Shared& context) = 0; 18 | virtual ~IFinderFactory() {} 19 | 20 | }; 21 | 22 | struct IElementFactory { 23 | virtual Element MakeElement(const std::string& id) = 0; 24 | virtual ~IElementFactory() {} 25 | }; 26 | 27 | } // namespace detail 28 | } // namespace webdriverxx 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /include/webdriverxx/browsers/phantom.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_BROWSERS_PHANTOM_H 2 | #define WEBDRIVERXX_BROWSERS_PHANTOM_H 3 | 4 | #include "../capabilities.h" 5 | 6 | namespace webdriverxx { 7 | 8 | struct Phantom : Capabilities { // copyable 9 | Phantom(const Capabilities& defaults = Capabilities()) 10 | : Capabilities(defaults) { 11 | SetBrowserName(browser::Phantom); 12 | SetVersion(""); 13 | SetPlatform(platform::Any); 14 | } 15 | 16 | WEBDRIVERXX_PROPERTIES_BEGIN(Phantom) 17 | // Profile is a profile folder, zipped and base64 encoded. 18 | WEBDRIVERXX_PROPERTY(LoggingPrefs, "loggingPrefs", LoggingPrefs) 19 | WEBDRIVERXX_PROPERTIES_END() 20 | }; 21 | 22 | } // namespace webdriverxx 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/http_client.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_HTTP_CLIENT_H 2 | #define WEBDRIVERXX_DETAIL_HTTP_CLIENT_H 3 | 4 | #include 5 | 6 | namespace webdriverxx { 7 | namespace detail { 8 | 9 | struct HttpResponse { 10 | long http_code; 11 | std::string body; 12 | 13 | HttpResponse() 14 | : http_code(0) 15 | {} 16 | }; 17 | 18 | struct IHttpClient { 19 | virtual HttpResponse Get(const std::string& url) const = 0; 20 | virtual HttpResponse Delete(const std::string& url) const = 0; 21 | virtual HttpResponse Post(const std::string& url, const std::string& data) const = 0; 22 | virtual ~IHttpClient() {} 23 | }; 24 | 25 | } // namespace detail 26 | } // namespace webdriverxx 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(webdriverxx) 3 | 4 | if (NOT MSVC) 5 | include(CheckCXXCompilerFlag) 6 | CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) 7 | CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) 8 | if(COMPILER_SUPPORTS_CXX11) 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 10 | elseif(COMPILER_SUPPORTS_CXX0X) 11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") 12 | endif() 13 | endif() 14 | 15 | enable_testing() 16 | 17 | include_directories(include) 18 | add_subdirectory(test) 19 | 20 | install(DIRECTORY 21 | ${CMAKE_SOURCE_DIR}/include/ 22 | DESTINATION /usr/local/include/${PROJECT_NAME} 23 | FILES_MATCHING PATTERN "*") 24 | 25 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/finder.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_FINDER_H 2 | #define WEBDRIVERXX_DETAIL_FINDER_H 3 | 4 | #include "shared.h" 5 | #include "resource.h" 6 | #include "factories.h" 7 | #include "../by.h" 8 | #include 9 | 10 | namespace webdriverxx { 11 | 12 | class Element; 13 | 14 | namespace detail { 15 | 16 | class Finder { // copyable 17 | public: 18 | Finder( 19 | const Shared& context, 20 | const Shared& factory 21 | ); 22 | 23 | Element FindElement(const By& by) const; 24 | std::vector FindElements(const By& by) const; 25 | 26 | private: 27 | Shared context_; 28 | Shared factory_; 29 | }; 30 | 31 | } // namespace detail 32 | } // namespace webdriverxx 33 | 34 | #include "finder.inl" 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /test/http_connection_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | #include 5 | 6 | namespace test { 7 | 8 | using namespace webdriverxx; 9 | using namespace webdriverxx::detail; 10 | 11 | TEST(HttpConnection, CanBeCreated) { 12 | HttpConnection connection; 13 | } 14 | 15 | TEST(HttpConnection, GetsPage) { 16 | HttpConnection connection; 17 | HttpResponse response = connection.Get(GetWebDriverUrl() + "status"); 18 | ASSERT_EQ(200, response.http_code); 19 | ASSERT_TRUE(!response.body.empty()); 20 | } 21 | 22 | TEST(HttpConnection, ThrowsExceptionIfPortIsClosed) { 23 | HttpConnection connection; 24 | const char *const kUrlWithClosedPort = "http://127.0.0.1:7778/"; 25 | ASSERT_THROW(connection.Get(kUrlWithClosedPort), WebDriverException); 26 | } 27 | 28 | } // namespace test 29 | -------------------------------------------------------------------------------- /include/webdriverxx/browsers/firefox.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_BROWSERS_FIREFOX_H 2 | #define WEBDRIVERXX_BROWSERS_FIREFOX_H 3 | 4 | #include "../capabilities.h" 5 | 6 | namespace webdriverxx { 7 | 8 | struct Firefox : Capabilities { // copyable 9 | Firefox(const Capabilities& defaults = Capabilities()) 10 | : Capabilities(defaults) { 11 | SetBrowserName(browser::Firefox); 12 | SetVersion(""); 13 | SetPlatform(platform::Any); 14 | } 15 | 16 | WEBDRIVERXX_PROPERTIES_BEGIN(Firefox) 17 | // Profile is a profile folder, zipped and base64 encoded. 18 | // TODO: add FirefoxProfile 19 | WEBDRIVERXX_PROPERTY(Profile, "firefox_profile", std::string) 20 | WEBDRIVERXX_PROPERTY(LoggingPrefs, "loggingPrefs", LoggingPrefs) 21 | WEBDRIVERXX_PROPERTY(FirefoxBinary, "firefox_binary", std::string) 22 | WEBDRIVERXX_PROPERTIES_END() 23 | }; 24 | 25 | } // namespace webdriverxx 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/keyboard.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_KEYBOARD_H 2 | #define WEBDRIVERXX_DETAIL_KEYBOARD_H 3 | 4 | #include "resource.h" 5 | #include "../conversions.h" 6 | #include "../keys.h" 7 | 8 | namespace webdriverxx { 9 | namespace detail { 10 | 11 | class Keyboard { // copyable 12 | public: 13 | Keyboard(const Shared& resource, const std::string& command) 14 | : resource_(resource) 15 | , command_(command) 16 | {} 17 | 18 | const Keyboard& SendKeys(const std::string& keys) const { 19 | return SendKeys(Shortcut() << keys); 20 | } 21 | 22 | const Keyboard& SendKeys(const Shortcut& shortcut) const { 23 | resource_->Post(command_, JsonObject() 24 | .Set("value", ToJson(shortcut.keys_))); 25 | return *this; 26 | } 27 | 28 | private: 29 | Shared resource_; 30 | std::string command_; 31 | }; 32 | 33 | } // namespace detail 34 | } // namespace webdriverxx 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /test/client_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | 5 | namespace test { 6 | 7 | using namespace webdriverxx; 8 | 9 | class TestClient : public ::testing::Test { 10 | protected: 11 | static void SetUpTestCase() { 12 | client = new Client(GetWebDriverUrl()); 13 | } 14 | 15 | static void TearDownTestCase() { 16 | delete client; 17 | client = 0; 18 | } 19 | 20 | static Client* client; 21 | }; 22 | 23 | Client* TestClient::client = 0; 24 | 25 | TEST_F(TestClient, GetsStatus) { 26 | picojson::object status = client->GetStatus(); 27 | ASSERT_TRUE(status["build"].is()); 28 | ASSERT_TRUE(status["os"].is()); 29 | } 30 | 31 | TEST_F(TestClient, GetsSessions) { 32 | client->GetSessions(); 33 | } 34 | 35 | TEST_F(TestClient, CreatesSession) { 36 | Parameters params = GetParameters(); 37 | client->CreateSession(params.desired, params.required); 38 | } 39 | 40 | } // namespace test 41 | -------------------------------------------------------------------------------- /test/pages/mouse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 20 | 21 | 22 |
28 |
29 |
30 |
31 |
32 | 33 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/factories_impl.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_FACTORIES_IMPL_H 2 | #define WEBDRIVERXX_DETAIL_FACTORIES_IMPL_H 3 | 4 | #include "factories.h" 5 | #include "resource.h" 6 | #include "finder.h" 7 | #include "shared.h" 8 | #include "../element.h" 9 | 10 | namespace webdriverxx { 11 | namespace detail { 12 | 13 | class SessionFactory // noncopyable 14 | : public IElementFactory 15 | , public IFinderFactory 16 | , public SharedObjectBase 17 | { 18 | public: 19 | explicit SessionFactory(const Shared& session_resource) 20 | : session_resource_(session_resource) 21 | {} 22 | 23 | virtual Element MakeElement(const std::string& id) { 24 | return Element( 25 | id, 26 | detail::MakeSubResource(session_resource_, "element", id), 27 | Shared(this) 28 | ); 29 | } 30 | 31 | virtual Finder MakeFinder(const Shared& context) { 32 | return Finder(context, Shared(this)); 33 | } 34 | 35 | private: 36 | const Shared session_resource_; 37 | }; 38 | 39 | } // namespace detail 40 | } // namespace webdriverxx 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/meta_tools.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_META_TOOLS_H 2 | #define WEBDRIVERXX_DETAIL_META_TOOLS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace webdriverxx { 10 | namespace detail { 11 | 12 | template 13 | struct type_is { typedef T type; }; 14 | 15 | template 16 | T& value_ref(); // MSVC2010 doesn't have std::declval 17 | 18 | template 19 | class is_iterable { 20 | template 21 | static std::false_type test(...); 22 | template 23 | static decltype(std::begin(value_ref()), std::end(value_ref()), 24 | std::true_type()) test(int); 25 | 26 | typedef decltype(test(0)) verdict; 27 | 28 | public: 29 | static const bool value = verdict::value; 30 | }; 31 | 32 | template 33 | struct is_string : std::is_convertible {}; 34 | 35 | template 36 | struct if_ : std::conditional {}; 37 | 38 | } // namespace detail 39 | } // namespace webdriverxx 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Sergey Kogan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/webdriverxx/webdriver.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_WEBDRIVER_H 2 | #define WEBDRIVERXX_WEBDRIVER_H 3 | 4 | #include "client.h" 5 | #include "session.h" 6 | 7 | namespace webdriverxx { 8 | 9 | // The main class for interactions with a server. Automatically connects to a server, 10 | // creates and deletes a session and gives access to session's API. 11 | class WebDriver // copyable 12 | : public Client 13 | , public Session { 14 | public: 15 | explicit WebDriver( 16 | const Capabilities& desired = Capabilities(), 17 | const Capabilities& required = Capabilities(), 18 | const std::string& url = kDefaultWebDriverUrl 19 | ) 20 | : Client(url) 21 | , Session(CreateSession(desired, required)) 22 | {} 23 | }; 24 | 25 | inline 26 | WebDriver Start( 27 | const Capabilities& desired, 28 | const Capabilities& required = Capabilities(), 29 | const std::string& url = kDefaultWebDriverUrl 30 | ) 31 | { 32 | return WebDriver(desired, required, url); 33 | } 34 | 35 | inline 36 | WebDriver Start(const Capabilities& desired, const std::string& url) 37 | { 38 | return Start(desired, Capabilities(), url); 39 | } 40 | 41 | } // namespace webdriverxx 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /include/webdriverxx/js_args.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_JS_ARGS_H 2 | #define WEBDRIVERXX_JS_ARGS_H 3 | 4 | #include "conversions.h" 5 | #include "picojson.h" 6 | 7 | namespace webdriverxx { 8 | 9 | class Session; 10 | 11 | class JsArgs // copyable 12 | { 13 | public: 14 | JsArgs() : args_(picojson::array()) {} 15 | 16 | // This member works out of the box for scalar values, Elements and std::vector 17 | // with scalar values or Elements. Additional code is needed to use it 18 | // with custom objects and custom containers. Take a look 19 | // to the TestJsExecutor/CanPassArray, TestJsExecutor/CanPassCustomArray 20 | // and TestJsExecutor/CanPassCustomObjects tests to get details. 21 | template 22 | JsArgs& operator << (const T& arg) { 23 | args_.get().push_back(ToJson(arg)); 24 | return *this; 25 | } 26 | 27 | // Alternative backdoor for passing custom data structures as arguments. 28 | JsArgs& operator << (const picojson::value& arg) { 29 | args_.get().push_back(arg); 30 | return *this; 31 | } 32 | 33 | private: 34 | friend class Session; 35 | picojson::value args_; 36 | }; 37 | 38 | } // namespace webdriverxx 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/time.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_TIME_H 2 | #define WEBDRIVERXX_DETAIL_TIME_H 3 | 4 | #include "error_handling.h" 5 | #include "../types.h" 6 | 7 | #ifdef _WIN32 8 | 9 | #include 10 | 11 | #else 12 | 13 | #include 14 | #include 15 | 16 | #endif 17 | 18 | namespace webdriverxx { 19 | namespace detail { 20 | 21 | inline 22 | TimePoint Now() { 23 | #ifdef _WIN32 24 | FILETIME time; 25 | ::GetSystemTimeAsFileTime(&time); 26 | return ((static_cast(time.dwHighDateTime) << 32) 27 | + time.dwLowDateTime)/10000; 28 | #else 29 | timeval time = {}; 30 | WEBDRIVERXX_CHECK(0 == gettimeofday(&time, nullptr), "gettimeofday failure"); 31 | return static_cast(time.tv_sec)*1000 + time.tv_usec/1000; 32 | #endif 33 | } 34 | 35 | inline 36 | void Sleep(Duration milliseconds) { 37 | #ifdef _WIN32 38 | ::Sleep(static_cast(milliseconds)); 39 | #else 40 | timespec time = { static_cast(milliseconds/1000), 41 | static_cast(milliseconds%1000)*1000000 }; 42 | while (nanosleep(&time, &time)) {} 43 | #endif 44 | } 45 | 46 | } // namespace detail 47 | } // namespace webdriverxx 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /include/webdriverxx/client.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_CLIENT_H 2 | #define WEBDRIVERXX_CLIENT_H 3 | 4 | #include "session.h" 5 | #include "capabilities.h" 6 | #include "detail/resource.h" 7 | #include "detail/http_connection.h" 8 | #include "picojson.h" 9 | #include 10 | #include 11 | 12 | namespace webdriverxx { 13 | 14 | const char *const kDefaultWebDriverUrl = "http://localhost:4444/wd/hub/"; 15 | 16 | // Gives low level access to server's resources. You normally should not use it. 17 | class Client { // copyable 18 | public: 19 | explicit Client(const std::string& url = kDefaultWebDriverUrl); 20 | virtual ~Client() {} 21 | 22 | picojson::object GetStatus() const; 23 | 24 | // Returns existing sessions. 25 | std::vector GetSessions() const; 26 | 27 | // Creates new session. 28 | Session CreateSession( 29 | const Capabilities& desired, 30 | const Capabilities& required 31 | ) const; 32 | 33 | private: 34 | Session MakeSession( 35 | const std::string& id, 36 | detail::Resource::Ownership mode 37 | ) const; 38 | 39 | private: 40 | detail::Shared resource_; 41 | }; 42 | 43 | } // namespace webdriverxx 44 | 45 | #include "client.inl" 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /include/webdriverxx/window.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_WINDOW_H 2 | #define WEBDRIVERXX_WINDOW_H 3 | 4 | #include "types.h" 5 | #include "conversions.h" 6 | #include "detail/resource.h" 7 | #include 8 | 9 | namespace webdriverxx { 10 | 11 | class Window { // copyable 12 | public: 13 | Window(const std::string& handle, const detail::Shared& resource) 14 | : handle_(handle) 15 | , resource_(resource) 16 | {} 17 | 18 | std::string GetHandle() const { 19 | return handle_; 20 | } 21 | 22 | Size GetSize() const { 23 | return resource_->GetValue("size"); 24 | } 25 | 26 | const Window& SetSize(const Size& size) const { 27 | resource_->PostValue("size", size); 28 | return *this; 29 | } 30 | 31 | Point GetPosition() const { 32 | return resource_->GetValue("position"); 33 | } 34 | 35 | const Window& SetPosition(const Point& position) const { 36 | resource_->PostValue("position", position); 37 | return *this; 38 | } 39 | 40 | const Window& Maximize() const { 41 | resource_->Post("maximize"); 42 | return *this; 43 | } 44 | 45 | private: 46 | std::string handle_; 47 | detail::Shared resource_; 48 | }; 49 | 50 | } // namespace webdriverxx 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/http_connection.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_HTTP_CONNECTION_H 2 | #define WEBDRIVERXX_DETAIL_HTTP_CONNECTION_H 3 | 4 | #include "http_client.h" 5 | #include "http_request.h" 6 | #include "error_handling.h" 7 | #include "shared.h" 8 | #include 9 | 10 | namespace webdriverxx { 11 | namespace detail { 12 | 13 | class HttpConnection // noncopyable 14 | : public IHttpClient 15 | , public SharedObjectBase 16 | { 17 | public: 18 | HttpConnection() 19 | : connection_(InitCurl()) 20 | {} 21 | 22 | ~HttpConnection() { 23 | curl_easy_cleanup(connection_); 24 | } 25 | 26 | HttpResponse Get(const std::string& url) const { 27 | return HttpGetRequest(connection_, url).Execute(); 28 | } 29 | 30 | HttpResponse Delete(const std::string& url) const { 31 | return HttpDeleteRequest(connection_, url).Execute(); 32 | } 33 | 34 | HttpResponse Post( 35 | const std::string& url, 36 | const std::string& upload_data 37 | ) const { 38 | return HttpPostRequest(connection_, url, upload_data).Execute(); 39 | } 40 | 41 | private: 42 | static 43 | CURL* InitCurl() { 44 | CURL *const result = curl_easy_init(); 45 | WEBDRIVERXX_CHECK(result, "Cannot initialize CURL"); 46 | return result; 47 | } 48 | 49 | private: 50 | CURL *const connection_; 51 | }; 52 | 53 | } // namespace detail 54 | } // namespace webdriverxx 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /include/webdriverxx/by.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_BY_H 2 | #define WEBDRIVERXX_BY_H 3 | 4 | #include 5 | 6 | namespace webdriverxx { 7 | 8 | class By { // copyable 9 | public: 10 | By(const std::string& strategy, const std::string& value) 11 | : strategy_(strategy) 12 | , value_(value) 13 | {} 14 | 15 | const std::string& GetStrategy() const { 16 | return strategy_; 17 | } 18 | 19 | const std::string& GetValue() const { 20 | return value_; 21 | } 22 | 23 | private: 24 | std::string strategy_; 25 | std::string value_; 26 | }; 27 | 28 | inline By ByClass(const std::string& value) { 29 | return By("class name", value); 30 | } 31 | 32 | inline By ByCss(const std::string& value) { 33 | return By("css selector", value); 34 | } 35 | 36 | inline By ById(const std::string& value) { 37 | return By("id", value); 38 | } 39 | 40 | inline By ByName(const std::string& value) { 41 | return By("name", value); 42 | } 43 | 44 | inline By ByLinkText(const std::string& value) { 45 | return By("link text", value); 46 | } 47 | 48 | inline By ByPartialLinkText(const std::string& value) { 49 | return By("partial link text", value); 50 | } 51 | 52 | inline By ByTag(const std::string& value) { 53 | return By("tag name", value); 54 | } 55 | 56 | inline By ByXPath(const std::string& value) { 57 | return By("xpath", value); 58 | } 59 | 60 | } // namespace webdriverxx 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /test/alerts_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | 5 | namespace test { 6 | 7 | using namespace webdriverxx; 8 | 9 | class TestAlerts : public ::testing::Test { 10 | protected: 11 | static void SetUpTestCase() { 12 | GetDriver().Navigate(GetTestPageUrl("alerts.html")); 13 | } 14 | 15 | TestAlerts() : driver(GetDriver()) {} 16 | 17 | WebDriver driver; 18 | }; 19 | 20 | TEST_F(TestAlerts, AcceptsAlert) { 21 | if (IsPhantom()) return; 22 | driver.Execute("alert('abc')"); 23 | driver.AcceptAlert(); 24 | } 25 | 26 | TEST_F(TestAlerts, DismissesAlert) { 27 | if (IsPhantom()) return; 28 | driver.Execute("alert('abc')"); 29 | driver.DismissAlert(); 30 | } 31 | 32 | TEST_F(TestAlerts, GetsAlertText) { 33 | if (IsPhantom()) return; 34 | driver.Execute("alert('abc')"); 35 | ASSERT_EQ("abc", driver.GetAlertText()); 36 | driver.DismissAlert(); 37 | } 38 | 39 | TEST_F(TestAlerts, SendsKeysToAlert) { 40 | if (IsPhantom()) return; 41 | driver.Execute("result = prompt('abc')"); 42 | driver.SendKeysToAlert("def"); 43 | driver.AcceptAlert(); 44 | ASSERT_EQ("def", driver.Eval("return result")); 45 | } 46 | 47 | TEST_F(TestAlerts, DismissesSendedKeys) { 48 | if (IsPhantom()) return; 49 | driver.Execute("result = prompt('abc')"); 50 | driver.SendKeysToAlert("def"); 51 | driver.DismissAlert(); 52 | ASSERT_FALSE(driver.Eval("return result")); 53 | } 54 | 55 | } // namespace test 56 | -------------------------------------------------------------------------------- /test/keyboard_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | #include 5 | 6 | namespace test { 7 | 8 | using namespace webdriverxx; 9 | 10 | class TestKeyboard : public ::testing::Test { 11 | protected: 12 | static void SetUpTestCase() { 13 | GetDriver().Navigate(GetTestPageUrl("keyboard.html")); 14 | } 15 | 16 | TestKeyboard() : driver(GetDriver()) {} 17 | 18 | WebDriver driver; 19 | }; 20 | 21 | TEST_F(TestKeyboard, SendsKeysToElement) { 22 | Element e = driver.FindElement(ByName("first")); 23 | e.Clear() 24 | .SendKeys("abc") 25 | .SendKeys(keys::Left).SendKeys(keys::Left).SendKeys(keys::Left) 26 | .SendKeys("def") 27 | ; 28 | ASSERT_EQ("abcdef", e.GetAttribute("value")); 29 | } 30 | 31 | TEST_F(TestKeyboard, SendsShortcuts) { 32 | Element e = driver.FindElement(ByName("first")); 33 | e.Clear() 34 | .SendKeys(Shortcut() << keys::Shift << "a" << "bc") 35 | .SendKeys("def") 36 | ; 37 | ASSERT_EQ("ABCdef", e.GetAttribute("value")); 38 | } 39 | 40 | TEST_F(TestKeyboard, SendsKeysToActiveElement) { 41 | Element first = driver.FindElement(ByName("first")); 42 | Element second = driver.FindElement(ByName("second")); 43 | first.Click().Clear(); 44 | driver.SendKeys("abc"); 45 | second.Click().Clear(); 46 | driver.SendKeys("def"); 47 | ASSERT_EQ("abc", first.GetAttribute("value")); 48 | ASSERT_EQ("def", second.GetAttribute("value")); 49 | } 50 | 51 | } // namespace test 52 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | 5 | namespace test { 6 | 7 | Environment* Environment::instance_ = 0; 8 | 9 | bool IsCommandLineArgument(const std::string& arg, const char* name) { 10 | return arg.find(std::string("--") + name) == 0; 11 | } 12 | 13 | std::string GetCommandLineArgumentValue(const std::string& arg) { 14 | const size_t pos = arg.find('='); 15 | return pos == std::string::npos ? std::string() : arg.substr(pos + 1); 16 | } 17 | 18 | Parameters ParseParameters(int argc, char **argv) { 19 | Parameters result; 20 | for(int i = 1; i < argc; ++i) { 21 | const std::string arg = argv[i]; 22 | if (IsCommandLineArgument(arg, "browser")) { 23 | result.web_driver_url = webdriverxx::kDefaultWebDriverUrl; 24 | const std::string browser_name = GetCommandLineArgumentValue(arg); 25 | result.desired.Set("browserName", browser_name); 26 | } else if (IsCommandLineArgument(arg, "pages")) { 27 | result.test_pages_url = GetCommandLineArgumentValue(arg); 28 | } else if (IsCommandLineArgument(arg, "webdriver")) { 29 | result.web_driver_url = GetCommandLineArgumentValue(arg); 30 | } else if (IsCommandLineArgument(arg, "test_real_browsers")) { 31 | result.test_real_browsers = true; 32 | } 33 | } 34 | return result; 35 | } 36 | 37 | } // namespace test 38 | 39 | int main(int argc, char **argv) { 40 | ::testing::InitGoogleTest(&argc, argv); 41 | ::testing::AddGlobalTestEnvironment( 42 | new test::Environment(test::ParseParameters(argc, argv)) 43 | ); 44 | return RUN_ALL_TESTS(); 45 | } 46 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/finder.inl: -------------------------------------------------------------------------------- 1 | #include "../conversions.h" 2 | #include "../element.h" 3 | #include "types.h" 4 | #include 5 | 6 | namespace webdriverxx { 7 | namespace detail { 8 | 9 | inline 10 | Finder::Finder( 11 | const Shared& context, 12 | const Shared& factory 13 | ) 14 | : context_(context) 15 | , factory_(factory) 16 | {} 17 | 18 | inline 19 | Element Finder::FindElement(const By& by) const { 20 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 21 | return factory_->MakeElement(FromJson( 22 | context_->Post("element", JsonObject() 23 | .Set("using", by.GetStrategy()) 24 | .Set("value", by.GetValue()) 25 | )).ref); 26 | WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(Fmt() 27 | << "context: " << context_->GetUrl() 28 | << ", strategy: " << by.GetStrategy() 29 | << ", value: " << by.GetValue() 30 | ) 31 | } 32 | 33 | inline 34 | std::vector Finder::FindElements(const By& by) const { 35 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 36 | const auto ids = 37 | FromJson>( 38 | context_->Post("elements", JsonObject() 39 | .Set("using", by.GetStrategy()) 40 | .Set("value", by.GetValue()) 41 | )); 42 | std::vector result; 43 | result.reserve(ids.size()); 44 | std::transform(ids.begin(), ids.end(), std::back_inserter(result), 45 | [this](const ElementRef& element_ref) { 46 | return factory_->MakeElement(element_ref.ref); 47 | }); 48 | return result; 49 | WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(Fmt() 50 | << "context: " << context_->GetUrl() 51 | << ", strategy: " << by.GetStrategy() 52 | << ", value: " << by.GetValue() 53 | ) 54 | } 55 | 56 | } // namespace detail 57 | } // namespace webdriverxx 58 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/error_handling.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_ERROR_HANDLING_H 2 | #define WEBDRIVERXX_DETAIL_ERROR_HANDLING_H 3 | 4 | #include "../errors.h" 5 | #include 6 | #include 7 | 8 | namespace webdriverxx { 9 | namespace detail { 10 | 11 | class Fmt { 12 | public: 13 | template 14 | Fmt& operator << (const T& value) { 15 | stream_ << value; 16 | return *this; 17 | } 18 | 19 | operator std::string() const { 20 | return stream_.str(); 21 | } 22 | 23 | private: 24 | std::ostringstream stream_; 25 | }; 26 | 27 | template 28 | bool BoolCast(T value) { 29 | return !!value; 30 | } 31 | 32 | } // namespace detail 33 | } // namespace webdriverxx 34 | 35 | #if __cplusplus >= 201103L 36 | #define WEBDRIVERXX_CURRENT_FUNCTION __func__ 37 | #else 38 | #define WEBDRIVERXX_CURRENT_FUNCTION __FUNCTION__ 39 | #endif 40 | 41 | #define WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() \ 42 | try { 43 | 44 | #define WEBDRIVERXX_FUNCTION_CONTEXT_END() \ 45 | } catch (const std::exception& e) { \ 46 | throw ::webdriverxx::WebDriverException(std::string(e.what()) \ 47 | + " called from " + WEBDRIVERXX_CURRENT_FUNCTION); \ 48 | } 49 | 50 | #define WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(details) \ 51 | } catch (const std::exception& e) { \ 52 | throw ::webdriverxx::WebDriverException(std::string(e.what()) \ 53 | + " called from " + WEBDRIVERXX_CURRENT_FUNCTION \ 54 | + " (" + std::string(details) + ")"); \ 55 | } 56 | 57 | #define WEBDRIVERXX_THROW(message) \ 58 | throw ::webdriverxx::WebDriverException(::webdriverxx::detail::Fmt() \ 59 | << std::string(message) \ 60 | << " at line " << __LINE__ \ 61 | << ", file " << __FILE__ \ 62 | ) 63 | 64 | #define WEBDRIVERXX_CHECK(pred, message) \ 65 | for (;!detail::BoolCast(pred);) \ 66 | WEBDRIVERXX_THROW(message) 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /test/browsers_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace test { 9 | 10 | using namespace webdriverxx; 11 | 12 | TEST(Firefox, WithTheSimplestSyntax) { 13 | if (!TestRealBrowsers()) return; 14 | auto ff = Start(Firefox()); 15 | } 16 | 17 | TEST(Firefox, WithCustomUrl) { 18 | if (!TestRealBrowsers()) return; 19 | auto ff = Start(Firefox(), kDefaultWebDriverUrl); 20 | } 21 | 22 | TEST(Firefox, WithDefaultCapabilities) { 23 | if (!TestRealBrowsers()) return; 24 | auto defaults = Capabilities().SetProxy(DirectConnection()); 25 | auto ff = Start(Firefox(defaults)); 26 | } 27 | 28 | TEST(Firefox, HasCapabilitiesProperties) { 29 | if (!TestRealBrowsers()) return; 30 | auto ff = Start(Firefox().SetProxy(DirectConnection())); 31 | } 32 | 33 | TEST(Firefox, ConvertsToJson) { 34 | auto ff = Firefox() 35 | .SetLoggingPrefs(LoggingPrefs().SetLevel(log_level::Warning)) 36 | .SetFirefoxBinary("abc"); 37 | const auto json = ToJson(ff); 38 | const auto c = FromJson(json); 39 | const auto logging = c.Get("loggingPrefs"); 40 | ASSERT_EQ(browser::Firefox, c.GetBrowserName()); 41 | ASSERT_EQ("WARNING", logging.Get("driver")); 42 | ASSERT_EQ("abc", c.Get("firefox_binary")); 43 | } 44 | 45 | TEST(InternetExplorer, ConvertsToJson) { 46 | auto ie = InternetExplorer(); 47 | const auto json = ToJson(ie); 48 | const auto c = FromJson(json); 49 | ASSERT_EQ(browser::InternetExplorer, c.GetBrowserName()); 50 | } 51 | 52 | TEST(Chrome, ConvertsToJson) { 53 | auto gc = Chrome(); 54 | const auto json = ToJson(gc); 55 | const auto c = FromJson(json); 56 | ASSERT_EQ(browser::Chrome, c.GetBrowserName()); 57 | } 58 | 59 | } // namespace test 60 | -------------------------------------------------------------------------------- /include/webdriverxx/types.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_TYPES_H 2 | #define WEBDRIVERXX_TYPES_H 3 | 4 | #include 5 | 6 | namespace webdriverxx { 7 | 8 | typedef unsigned long long TimePoint; 9 | typedef unsigned Duration; 10 | 11 | struct Size { 12 | int width; 13 | int height; 14 | Size() : width(0), height(0) {} 15 | }; 16 | 17 | struct Point { 18 | int x; 19 | int y; 20 | Point() : x(0), y(0) {} 21 | Point(int x, int y) : x(x), y(y) {} 22 | }; 23 | 24 | typedef Point Offset; 25 | 26 | struct Cookie { 27 | enum { 28 | NoExpiry = 0 29 | }; 30 | 31 | std::string name; 32 | std::string value; 33 | std::string path; 34 | std::string domain; 35 | bool secure; 36 | bool http_only; 37 | int expiry; // seconds since midnight, January 1, 1970 UTC 38 | 39 | Cookie() : secure(false), http_only(false), expiry(NoExpiry) {} 40 | Cookie( 41 | const std::string& name, 42 | const std::string& value, 43 | const std::string& path = std::string(), 44 | const std::string& domain = std::string(), 45 | bool secure = false, 46 | bool http_only = false, 47 | int expiry = NoExpiry 48 | ) 49 | : name(name) 50 | , value(value) 51 | , path(path) 52 | , domain(domain) 53 | , secure(secure) 54 | , http_only(http_only) 55 | , expiry(expiry) 56 | {} 57 | 58 | bool operator == (const Cookie& c) const { 59 | return name == c.name 60 | && value == c.value 61 | && path == c.path 62 | && domain == c.domain 63 | && secure == c.secure 64 | && http_only == c.http_only 65 | && expiry == c.expiry 66 | ; 67 | } 68 | }; 69 | 70 | namespace timeout { 71 | 72 | typedef const char* Type; 73 | 74 | Type const Implicit = "implicit"; 75 | Type const PageLoad = "page load"; 76 | Type const Script = "script"; 77 | 78 | } // namespace timeout 79 | 80 | namespace mouse { 81 | enum Button { 82 | LeftButton = 0, 83 | MiddleButton = 1, 84 | RightButton = 2 85 | }; 86 | } // namespace mouse 87 | 88 | } // namespace webdriverxx 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /test/frames_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | 5 | namespace test { 6 | 7 | using namespace webdriverxx; 8 | 9 | class TestFrames : public ::testing::Test { 10 | protected: 11 | TestFrames() 12 | : driver(GetDriver()) 13 | , url(GetTestPageUrl("frames.html")) 14 | {} 15 | 16 | void SetUp() 17 | { 18 | driver.Navigate(url); 19 | } 20 | 21 | WebDriver driver; 22 | std::string url; 23 | }; 24 | 25 | TEST_F(TestFrames, OnTopFrameByDefault) { 26 | ASSERT_EQ("top_frame", driver.FindElement(ById("tag")).GetAttribute("value")); 27 | } 28 | 29 | TEST_F(TestFrames, CanSwitchToFrameByNumber) { 30 | driver.SetFocusToFrame(1); 31 | ASSERT_EQ("frame3", driver.FindElement(ById("tag")).GetAttribute("value")); 32 | } 33 | 34 | TEST_F(TestFrames, CanSwitchToFrameByName) { 35 | driver.SetFocusToFrame("frame3_name"); 36 | ASSERT_EQ("frame3", driver.FindElement(ById("tag")).GetAttribute("value")); 37 | } 38 | 39 | TEST_F(TestFrames, CanSwitchToFrameByElement) { 40 | std::vector frames = driver.FindElements(ByTag("iframe")); 41 | ASSERT_EQ(2u, frames.size()); 42 | driver.SetFocusToFrame(frames[1]); 43 | ASSERT_EQ("frame3", driver.FindElement(ById("tag")).GetAttribute("value")); 44 | } 45 | 46 | TEST_F(TestFrames, CanSwitchToDefaultFrame) { 47 | driver.SetFocusToFrame(1); 48 | driver.SetFocusToDefaultFrame(); 49 | ASSERT_EQ("top_frame", driver.FindElement(ById("tag")).GetAttribute("value")); 50 | } 51 | 52 | TEST_F(TestFrames, CanSwitchToDeepFrames) { 53 | driver.SetFocusToFrame(0).SetFocusToFrame(1); 54 | ASSERT_EQ("frame2", driver.FindElement(ById("tag")).GetAttribute("value")); 55 | } 56 | 57 | TEST_F(TestFrames, CanSwitchToParentFrame) { 58 | if (IsPhantom()) return; // Not supported in PhantomJS 1.9.7 59 | driver.SetFocusToFrame(0).SetFocusToFrame(1) 60 | .SetFocusToParentFrame().SetFocusToParentFrame(); 61 | ASSERT_EQ("top_frame", driver.FindElement(ById("tag")).GetAttribute("value")); 62 | } 63 | 64 | } // namespace test 65 | -------------------------------------------------------------------------------- /include/webdriverxx/element.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_ELEMENT_H 2 | #define WEBDRIVERXX_ELEMENT_H 3 | 4 | #include "by.h" 5 | #include "types.h" 6 | #include "keys.h" 7 | #include "detail/shared.h" 8 | #include "detail/keyboard.h" 9 | #include "detail/resource.h" 10 | #include "detail/factories.h" 11 | #include 12 | #include 13 | 14 | namespace webdriverxx { 15 | 16 | // An element from DOM 17 | class Element { // copyable 18 | public: 19 | Element(); 20 | 21 | Element( 22 | const std::string& ref, 23 | const detail::Shared& resource, 24 | const detail::Shared& factory 25 | ); 26 | 27 | std::string GetRef() const; // Returns ID that is used by Webdriver to identify elements 28 | 29 | bool IsDisplayed() const; 30 | bool IsEnabled() const; 31 | bool IsSelected() const; 32 | Point GetLocation() const; 33 | Point GetLocationInView() const; 34 | Size GetSize() const; 35 | std::string GetAttribute(const std::string& name) const; 36 | std::string GetCssProperty(const std::string& name) const; 37 | std::string GetTagName() const; 38 | std::string GetText() const; 39 | 40 | Element FindElement(const By& by) const; 41 | std::vector FindElements(const By& by) const; 42 | 43 | const Element& Clear() const; 44 | const Element& Click() const; 45 | const Element& Submit() const; 46 | 47 | const Element& SendKeys(const std::string& keys) const; 48 | const Element& SendKeys(const Shortcut& shortcut) const; 49 | 50 | bool Equals(const Element& other) const; 51 | bool operator != (const Element& other) const; 52 | bool operator == (const Element& other) const; 53 | bool operator < (const Element& other) const; 54 | 55 | private: 56 | detail::Resource& GetResource() const; 57 | detail::Keyboard GetKeyboard() const; 58 | 59 | private: 60 | std::string ref_; 61 | detail::Shared resource_; 62 | detail::Shared factory_; 63 | }; 64 | 65 | } // namespace webdriverxx 66 | 67 | #include "element.inl" 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /test/examples_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #ifndef WEBDRIVERXX_ENABLE_GMOCK_MATCHERS 4 | #define WEBDRIVERXX_ENABLE_GMOCK_MATCHERS 5 | #endif 6 | #include 7 | #include 8 | 9 | namespace test { 10 | 11 | using namespace webdriverxx; 12 | 13 | class TestExamples : public ::testing::Test { 14 | protected: 15 | TestExamples() : driver(GetDriver()) {} 16 | 17 | void StopNavigation() { 18 | WaitForMatch([this] { 19 | return driver.Navigate(GetTestPageUrl("non_existing.html")).GetUrl(); 20 | }, ::testing::HasSubstr("non_existing")); 21 | } 22 | 23 | WebDriver driver; 24 | }; 25 | 26 | TEST_F(TestExamples, QuickExample) { 27 | driver 28 | .Navigate("http://google.com") 29 | .FindElement(ByCss("input[name=q]")) 30 | .SendKeys("Hello, world!") 31 | .Submit(); 32 | 33 | StopNavigation(); // Firefox doesn't perform navigation right after Submit. 34 | } 35 | 36 | TEST_F(TestExamples, ImplicitWait) { 37 | driver.SetImplicitTimeoutMs(0); 38 | try { 39 | Element element = driver.FindElement(ByName("akela")); 40 | FAIL(); 41 | } catch (const std::exception&) {} 42 | } 43 | 44 | TEST_F(TestExamples, ExplicitWait1) { 45 | auto find_element = [&]{ return driver.FindElement(ById("async_loaded")); }; 46 | try { 47 | int timeout = 0; 48 | Element element = WaitForValue(find_element, timeout); 49 | FAIL(); 50 | } catch (const std::exception&) {} 51 | } 52 | 53 | TEST_F(TestExamples, ExplicitWait2) { 54 | auto element_is_selected = [&]{ 55 | return driver.FindElement(ById("async_loaded")).IsSelected(); 56 | }; 57 | try { 58 | int timeout = 0; 59 | WaitUntil(element_is_selected, timeout); 60 | FAIL(); 61 | } catch (const std::exception&) {} 62 | } 63 | 64 | TEST_F(TestExamples, UseGmockMatchers) { 65 | driver.Navigate(GetTestPageUrl("redirect.html")); 66 | auto url = [&]{ return driver.GetUrl(); }; 67 | using namespace ::testing; 68 | WaitForMatch(url, HasSubstr("target")); 69 | StopNavigation(); 70 | } 71 | 72 | } // namespace test 73 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/types.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_TYPES_H 2 | #define WEBDRIVERXX_DETAIL_TYPES_H 3 | 4 | #include "../conversions.h" 5 | #include "../capabilities.h" 6 | #include "../picojson.h" 7 | #include 8 | 9 | namespace webdriverxx { 10 | namespace detail { 11 | 12 | struct SessionRef { 13 | std::string id; 14 | Capabilities capabilities; 15 | }; 16 | 17 | struct ElementRef { 18 | std::string ref; 19 | }; 20 | 21 | inline 22 | picojson::value CustomToJson(const ElementRef& ref) { 23 | return JsonObject() 24 | .Set("ELEMENT", ref.ref) 25 | ; 26 | } 27 | 28 | inline 29 | void CustomFromJson(const picojson::value& value, ElementRef& result) { 30 | WEBDRIVERXX_CHECK(value.is(), "ElementRef is not an object"); 31 | result.ref = FromJson(value.get("ELEMENT")); 32 | if (result.ref == "null") { 33 | std::stringstream element(value.serialize()); 34 | std::string segment; 35 | std::vector strArr; 36 | while (std::getline(element, segment, ':')) { 37 | segment.erase(std::remove(segment.begin(), segment.end(), '{'), segment.end()); 38 | segment.erase(std::remove(segment.begin(), segment.end(), '"'), segment.end()); 39 | segment.erase(std::remove(segment.begin(), segment.end(), '}'), segment.end()); 40 | strArr.push_back(segment); 41 | } 42 | if (strArr.size() >= 1) { 43 | std::stringstream ss; 44 | ss << "{\"ELEMENT\":" << "\"" << strArr[1] << "\"}"; 45 | picojson::value jSon; 46 | picojson::parse(jSon, ss); 47 | result.ref = FromJson(jSon.get("ELEMENT")); 48 | } 49 | } 50 | } 51 | 52 | inline 53 | void CustomFromJson(const picojson::value& value, SessionRef& result) { 54 | WEBDRIVERXX_CHECK(value.is(), "Session information is not an object"); 55 | result.id = value.get("sessionId").to_str(); 56 | if (value.get("capabilities").is()) 57 | result.capabilities = Capabilities(value.get("capabilities").get()); 58 | } 59 | 60 | } // namespace detail 61 | } // namespace webdriverxx 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /include/webdriverxx/browsers/chrome.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_BROWSERS_CHROME_H 2 | #define WEBDRIVERXX_BROWSERS_CHROME_H 3 | 4 | #include "../capabilities.h" 5 | 6 | namespace webdriverxx { 7 | namespace chrome { 8 | 9 | struct PerfLoggingPrefs : JsonObject { 10 | WEBDRIVERXX_PROPERTIES_BEGIN(PerfLoggingPrefs) 11 | WEBDRIVERXX_PROPERTY(EnableNetwork, "enableNetwork", bool) 12 | WEBDRIVERXX_PROPERTY(enablePage, "enablePage", bool) 13 | WEBDRIVERXX_PROPERTY(enableTimeline, "enableTimeline", bool) 14 | WEBDRIVERXX_PROPERTY(tracingCategories, "tracingCategories", std::string) 15 | WEBDRIVERXX_PROPERTY(bufferUsageReportingInterval, "bufferUsageReportingInterval", int) 16 | WEBDRIVERXX_PROPERTIES_END() 17 | }; 18 | 19 | } // namespace chrome 20 | 21 | struct Chrome : Capabilities { // copyable 22 | Chrome(const Capabilities& defaults = Capabilities()) 23 | : Capabilities(defaults) { 24 | SetBrowserName(browser::Chrome); 25 | SetVersion(""); 26 | SetPlatform(platform::Any); 27 | } 28 | 29 | // See https://sites.google.com/a/chromium.org/chromedriver/capabilities for details 30 | WEBDRIVERXX_PROPERTIES_BEGIN(Chrome) 31 | WEBDRIVERXX_PROPERTY(LoggingPrefs, "loggingPrefs", LoggingPrefs) 32 | WEBDRIVERXX_PROPERTY(Args, "args", std::vector) 33 | WEBDRIVERXX_PROPERTY(Binary, "binary", std::string) 34 | // Each extension is a base64-encoded .crx file 35 | WEBDRIVERXX_PROPERTY(Extensions, "extensions", std::vector) 36 | WEBDRIVERXX_PROPERTY(LocalState, "localState", JsonObject) 37 | WEBDRIVERXX_PROPERTY(Prefs, "prefs", JsonObject) 38 | WEBDRIVERXX_PROPERTY(Detach, "detach", bool) 39 | WEBDRIVERXX_PROPERTY(DebuggerAddress, "debuggerAddress", std::string) 40 | WEBDRIVERXX_PROPERTY(ExcludeSwitches, "excludeSwitches", std::vector) 41 | WEBDRIVERXX_PROPERTY(MinidumpPath, "minidumpPath", std::string) 42 | WEBDRIVERXX_PROPERTY(MobileEmulation, "mobileEmulation", JsonObject) 43 | WEBDRIVERXX_PROPERTY(PerfLoggingPrefs, "perfLoggingPrefs", chrome::PerfLoggingPrefs) 44 | WEBDRIVERXX_PROPERTIES_END() 45 | }; 46 | 47 | } // namespace webdriverxx 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/shared.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_SHARED_H 2 | #define WEBDRIVERXX_DETAIL_SHARED_H 3 | 4 | #include 5 | 6 | namespace webdriverxx { 7 | namespace detail { 8 | 9 | class SharedObjectBase { // noncopyable 10 | public: 11 | SharedObjectBase() : ref_(0) {} 12 | virtual ~SharedObjectBase() {} 13 | 14 | virtual void AddRef() { 15 | ++ref_; 16 | } 17 | 18 | virtual void Release() { 19 | if (--ref_ == 0) 20 | delete this; 21 | } 22 | 23 | private: 24 | SharedObjectBase(SharedObjectBase&); 25 | SharedObjectBase& operator = (SharedObjectBase&); 26 | 27 | private: 28 | unsigned ref_; 29 | }; 30 | 31 | // Copyable, not thread safe 32 | template 33 | class Shared { 34 | public: 35 | Shared() 36 | : ptr_(nullptr) 37 | , ref_(nullptr) 38 | {} 39 | 40 | template 41 | Shared(T2* ptr) 42 | : ptr_(ptr) 43 | , ref_(ptr) 44 | { 45 | if (ref_) ref_->AddRef(); 46 | } 47 | 48 | Shared(const Shared& other) 49 | : ptr_(other.ptr_) 50 | , ref_(other.ref_) 51 | { 52 | if (ref_) ref_->AddRef(); 53 | } 54 | 55 | template 56 | Shared(const Shared& other) 57 | : ptr_(other.ptr_) 58 | , ref_(other.ref_) 59 | { 60 | if (ref_) ref_->AddRef(); 61 | } 62 | 63 | ~Shared() { 64 | if (ref_) ref_->Release(); 65 | } 66 | 67 | Shared& operator = (const Shared& other) { 68 | if (&other != this) 69 | Shared(other).Swap(*this); 70 | return *this; 71 | } 72 | 73 | void Swap(Shared& other) { 74 | std::swap(ptr_, other.ptr_); 75 | std::swap(ref_, other.ref_); 76 | } 77 | 78 | T& operator * () const { 79 | return *ptr_; 80 | } 81 | 82 | T* operator -> () const { 83 | return ptr_; 84 | } 85 | 86 | T* Get() const { 87 | return ptr_; 88 | } 89 | 90 | operator T* () const { 91 | return ptr_; 92 | } 93 | 94 | private: 95 | template 96 | friend class Shared; 97 | 98 | T* ptr_; 99 | SharedObjectBase* ref_; 100 | }; 101 | 102 | } // namespace detail 103 | } // namespace webdriverxx 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /include/webdriverxx/browsers/ie.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_BROWSERS_IE_H 2 | #define WEBDRIVERXX_BROWSERS_IE_H 3 | 4 | #include "../capabilities.h" 5 | 6 | namespace webdriverxx { 7 | namespace ie { 8 | 9 | namespace log_level { 10 | typedef std::string Value; 11 | typedef const char* const ConstValue; 12 | ConstValue Trace = "TRACE"; 13 | ConstValue Debug = "DEBUG"; 14 | ConstValue Info = "INFO"; 15 | ConstValue Warning = "WARN"; 16 | ConstValue Error = "ERROR"; 17 | ConstValue Fatal = "FATAL"; 18 | } // namespace log_level 19 | 20 | } // namespace ie 21 | 22 | struct InternetExplorer : Capabilities { // copyable 23 | InternetExplorer(const Capabilities& defaults = Capabilities()) 24 | : Capabilities(defaults) { 25 | SetBrowserName(browser::InternetExplorer); 26 | SetVersion(""); 27 | SetPlatform(platform::Any); 28 | } 29 | 30 | WEBDRIVERXX_PROPERTIES_BEGIN(InternetExplorer) 31 | WEBDRIVERXX_PROPERTY(SkipProtectedModeCheck, "ignoreProtectedModeSettings", bool) 32 | WEBDRIVERXX_PROPERTY(IgnoreZoomSetting, "ignoreZoomSetting", bool) 33 | WEBDRIVERXX_PROPERTY(InitialUrl, "initialBrowserUrl", std::string) 34 | WEBDRIVERXX_PROPERTY(EnablePersistentHover, "enablePersistentHover", bool) 35 | WEBDRIVERXX_PROPERTY(EnableElementCacheCleanup, "enableElementCacheCleanup", bool) 36 | WEBDRIVERXX_PROPERTY(RequireWindowFocus, "requireWindowFocus", bool) 37 | WEBDRIVERXX_PROPERTY(BrowserAttachTimeoutMs, "browserAttachTimeout", int) 38 | WEBDRIVERXX_PROPERTY(ForceCreateProcessApi, "ie.forceCreateProcessApi", bool) 39 | WEBDRIVERXX_PROPERTY(CommandLineSwitches, "ie.browserCommandLineSwitches", std::string) 40 | WEBDRIVERXX_PROPERTY(UsePerProcessProxy, "ie.usePerProcessProxy", bool) 41 | WEBDRIVERXX_PROPERTY(EnsureCleanSession, "ie.ensureCleanSession", bool) 42 | WEBDRIVERXX_PROPERTY(LogFile, "logFile", std::string) 43 | WEBDRIVERXX_PROPERTY(LogLevel, "logLevel", ie::log_level::Value) 44 | WEBDRIVERXX_PROPERTY(Host, "host", std::string) 45 | WEBDRIVERXX_PROPERTY(ExtractPath, "extractPath", std::string) 46 | WEBDRIVERXX_PROPERTY(Silent, "silent", bool) 47 | WEBDRIVERXX_PROPERTY(ProxyByServer, "ie.setProxyByServer", bool) 48 | WEBDRIVERXX_PROPERTIES_END() 49 | }; 50 | 51 | } // namespace webdriverxx 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /test/capabilities_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace test { 5 | 6 | using namespace webdriverxx; 7 | 8 | TEST(Capabilities, AllowsToSetAndGetCustomValues) { 9 | Capabilities c; 10 | c.Set("int", 123); 11 | c.Set("double", 456.7); 12 | c.Set("string", "abc"); 13 | c.Set("bool", true); 14 | ASSERT_EQ(123, c.Get("int")); 15 | ASSERT_EQ(456.7, c.Get("double")); 16 | ASSERT_EQ("abc", c.Get("string")); 17 | ASSERT_EQ(true, c.Get("bool")); 18 | } 19 | 20 | TEST(Capabilities, ConvertibleToJson) { 21 | Capabilities c; 22 | c.Set("int", 123); 23 | c.Set("string", "abc"); 24 | const auto json = ToJson(c); 25 | const auto c_copy = FromJson(json); 26 | ASSERT_EQ(123, c_copy.Get("int")); 27 | ASSERT_EQ("abc", c_copy.Get("string")); 28 | } 29 | 30 | TEST(Capabilities, AllowsToSetProxy) { 31 | Capabilities().SetProxy(DirectConnection()); 32 | Capabilities().SetProxy(AutodetectProxy()); 33 | Capabilities().SetProxy(SystemProxy()); 34 | Capabilities().SetProxy(FtpProxy("127.0.0.1:3128").SetNoProxyFor("custom.host")); 35 | Capabilities().SetProxy(HttpProxy("127.0.0.1:3128").SetNoProxyFor("custom.host")); 36 | Capabilities().SetProxy(SslProxy("127.0.0.1:3128").SetNoProxyFor("custom.host")); 37 | Capabilities().SetProxy(SocksProxy("127.0.0.1:3128") 38 | .SetUsername("user").SetPassword("12345").SetNoProxyFor("custom.host") 39 | ); 40 | Capabilities().SetProxy(AutomaticProxyFromUrl("http://some.url")); 41 | } 42 | 43 | TEST(Capabilities, ConvertsProxyToJson) { 44 | Capabilities c; 45 | c.SetProxy(SocksProxy("127.0.0.1:3128").SetUsername("user") 46 | .SetPassword("12345").SetNoProxyFor("custom.host")); 47 | const auto json = ToJson(c); 48 | const auto c_copy = FromJson(json); 49 | const auto proxy = FromJson(c_copy.Get("proxy")); 50 | ASSERT_EQ("manual", proxy.Get("proxyType")); 51 | ASSERT_EQ("127.0.0.1:3128", proxy.Get("socksProxy")); 52 | ASSERT_EQ("user", proxy.Get("socksUsername")); 53 | ASSERT_EQ("12345", proxy.Get("socksPassword")); 54 | ASSERT_EQ("custom.host", proxy.Get("noProxy")); 55 | } 56 | 57 | } // namespace test 58 | -------------------------------------------------------------------------------- /include/webdriverxx/client.inl: -------------------------------------------------------------------------------- 1 | #include "conversions.h" 2 | #include "detail/shared.h" 3 | #include "detail/error_handling.h" 4 | #include "detail/types.h" 5 | #include 6 | 7 | namespace webdriverxx { 8 | 9 | inline 10 | Client::Client(const std::string& url) 11 | : resource_(new detail::RootResource( 12 | url, 13 | detail::Shared(new detail::HttpConnection) 14 | )) 15 | {} 16 | 17 | inline 18 | picojson::object Client::GetStatus() const { 19 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 20 | const auto value = resource_->Get("status").get("value"); 21 | WEBDRIVERXX_CHECK(value.is(), "Value is not an object"); 22 | return value.get(); 23 | WEBDRIVERXX_FUNCTION_CONTEXT_END() 24 | } 25 | 26 | inline 27 | std::vector Client::GetSessions() const { 28 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 29 | const auto sessions = 30 | FromJson>( 31 | resource_->Get("sessions").get("value") 32 | ); 33 | std::vector result; 34 | result.reserve(sessions.size()); 35 | std::transform(sessions.begin(), sessions.end(), std::back_inserter(result), 36 | [this](const detail::SessionRef& session_ref) { 37 | return MakeSession(session_ref.id, detail::Resource::IsObserver); 38 | }); 39 | return result; 40 | WEBDRIVERXX_FUNCTION_CONTEXT_END() 41 | } 42 | 43 | inline 44 | Session Client::CreateSession( 45 | const Capabilities& desired, 46 | const Capabilities& required 47 | ) const { 48 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 49 | const auto response = resource_->Post("session", 50 | JsonObject() 51 | .Set("desiredCapabilities", static_cast(desired)) 52 | .Set("requiredCapabilities", static_cast(required)) 53 | ); 54 | 55 | WEBDRIVERXX_CHECK(response.get("sessionId").is(), "Session ID is not a string"); 56 | WEBDRIVERXX_CHECK(response.get("value").is(), "Capabilities is not an object"); 57 | 58 | const auto sessionId = response.get("sessionId").to_str(); 59 | 60 | return MakeSession(sessionId, detail::Resource::IsOwner); 61 | WEBDRIVERXX_FUNCTION_CONTEXT_END() 62 | } 63 | 64 | inline 65 | Session Client::MakeSession( 66 | const std::string& id, 67 | detail::Resource::Ownership mode 68 | ) const { 69 | return Session(detail::MakeSubResource(resource_, "session", id, mode)); 70 | } 71 | 72 | } // namespace webdriverxx 73 | -------------------------------------------------------------------------------- /test/wait_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace test { 5 | 6 | using namespace webdriverxx; 7 | using namespace webdriverxx::detail; 8 | 9 | int FunctionGetter() { return 123; } 10 | 11 | struct FunctorGetter { 12 | int operator () () const { 13 | return 456; 14 | } 15 | }; 16 | 17 | TEST(WaitForValue, CanBeUsedWithFunctionFunctorAndLambda) { 18 | ASSERT_EQ(123, WaitForValue(FunctionGetter)); 19 | ASSERT_EQ(456, WaitForValue(FunctorGetter())); 20 | ASSERT_EQ(789, WaitForValue([]{ return 789; })); 21 | } 22 | 23 | TEST(WaitForValue, DoesNotWaitIfValueIsReturned) { 24 | Duration timeout = 1000; 25 | const TimePoint start = Now(); 26 | WaitForValue(FunctionGetter, timeout); 27 | ASSERT_TRUE(Now() - start < timeout/2); 28 | } 29 | 30 | TEST(WaitForValue, CallsGetterOnce) { 31 | int counter = 0; 32 | WaitForValue([&counter]{ return ++counter; }); 33 | ASSERT_EQ(1, counter); 34 | } 35 | 36 | TEST(WaitForValue, ThrowsExceptionOnTimeout) { 37 | Duration timeout = 0; 38 | ASSERT_THROW(WaitForValue([]() -> int { throw std::exception(); }, timeout), WebDriverException); 39 | } 40 | 41 | TEST(WaitForValue, CallsGetterUntilItSucceeds) { 42 | Duration timeout = 1000; 43 | Duration interval = 0; 44 | int counter = 0; 45 | WaitForValue([&counter]() -> int { 46 | if (++counter < 10) throw std::exception(); 47 | return counter; 48 | }, timeout, interval); 49 | ASSERT_EQ(10, counter); 50 | } 51 | 52 | TEST(WaitForValue, PassesErrorMessageFromGetter) { 53 | Duration timeout = 0; 54 | try { 55 | WaitForValue([]() -> int { throw std::runtime_error("abc"); }, timeout); 56 | FAIL(); 57 | } catch (const WebDriverException& e) { 58 | std::string message = e.what(); 59 | const auto npos = std::string::npos; 60 | ASSERT_NE(npos, message.find("abc")); 61 | ASSERT_NE(npos, message.find("imeout")); 62 | } 63 | } 64 | 65 | TEST(WaitUntil, DoesNotWaitIfValueNotFalsy) { 66 | Duration timeout = 1000; 67 | const TimePoint start = Now(); 68 | WaitUntil([]{ return true; }, timeout); 69 | ASSERT_TRUE(Now() - start < timeout/2); 70 | } 71 | 72 | TEST(WaitUntil, CallsGetterOnce) { 73 | int counter = 0; 74 | WaitUntil([&counter]{ return ++counter; }); 75 | ASSERT_EQ(1, counter); 76 | } 77 | 78 | TEST(WaitUntil, ThrowsExceptionOnTimeout) { 79 | Duration timeout = 0; 80 | ASSERT_THROW(WaitUntil([]() -> bool { throw std::exception(); }, timeout), WebDriverException); 81 | } 82 | 83 | TEST(WaitUntil, ThrowsExceptionOnTimeout2) { 84 | Duration timeout = 0; 85 | ASSERT_THROW(WaitUntil([]{ return false; }, timeout), WebDriverException); 86 | } 87 | 88 | } // namespace test 89 | -------------------------------------------------------------------------------- /test/webdriver_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | 5 | namespace test { 6 | 7 | using namespace webdriverxx; 8 | 9 | TEST(WebDriver, CreatesSession) { 10 | Client client(GetWebDriverUrl()); 11 | size_t number_of_sessions_before = client.GetSessions().size(); 12 | WebDriver testee = CreateDriver(); 13 | size_t number_of_sessions_after = client.GetSessions().size(); 14 | ASSERT_EQ(number_of_sessions_before + 1, number_of_sessions_after); 15 | } 16 | 17 | TEST(WebDriver, DeletesSessionOnDestruction) { 18 | Client client(GetWebDriverUrl()); 19 | size_t number_of_sessions_before = 0; 20 | { 21 | WebDriver testee = CreateDriver(); 22 | number_of_sessions_before = client.GetSessions().size(); 23 | } 24 | size_t number_of_sessions_after = client.GetSessions().size(); 25 | ASSERT_EQ(number_of_sessions_before - 1, number_of_sessions_after); 26 | } 27 | 28 | TEST(WebDriver, IsCopyable) { 29 | WebDriver driver1(GetDriver()); 30 | const WebDriver driver2 = driver1; 31 | WebDriver driver3 = driver1; 32 | driver3 = driver2; 33 | ASSERT_NO_THROW(GetDriver().GetSessions()); 34 | ASSERT_NO_THROW(driver1.GetSessions()); 35 | ASSERT_NO_THROW(driver2.GetSessions()); 36 | ASSERT_NO_THROW(driver3.GetSessions()); 37 | } 38 | 39 | TEST(WebDriver, CopyableToClient) { 40 | WebDriver driver = GetDriver(); 41 | Client client = driver; 42 | ASSERT_NO_THROW(client.GetSessions()); 43 | ASSERT_NO_THROW(driver.GetSessions()); 44 | } 45 | 46 | TEST(WebDriver, CopyableToSession) { 47 | WebDriver driver = GetDriver(); 48 | Session session = driver; 49 | ASSERT_NO_THROW(session.GetWindows()); 50 | ASSERT_NO_THROW(driver.GetWindows()); 51 | } 52 | 53 | TEST(WebDriver, AndSatelliteObjectsHasNoLifetimeIssues) { 54 | // It is too expensive to restart the driver -> 55 | // try to test all objects in one test. 56 | WebDriver& driver = GetDriver(); 57 | driver.Navigate(GetTestPageUrl("webdriver.html")); 58 | 59 | Element body = driver.FindElement(ByTag("body")); 60 | { 61 | Window window = driver.GetCurrentWindow(); 62 | { 63 | Session session = driver; 64 | { 65 | Client client = driver; 66 | { 67 | WebDriver local_driver = driver; 68 | GetFreshDriver(); // Destroy global instance 69 | ASSERT_NO_THROW(local_driver.GetSessions()); 70 | ASSERT_NO_THROW(local_driver.GetWindows()); 71 | ASSERT_NO_THROW(local_driver.FindElement(ByTag("input"))); 72 | } 73 | ASSERT_NO_THROW(client.GetSessions()); 74 | } 75 | ASSERT_NO_THROW(session.GetWindows()); 76 | ASSERT_NO_THROW(session.FindElement(ByTag("input"))); 77 | } 78 | ASSERT_NO_THROW(window.GetSize()); 79 | } 80 | ASSERT_NO_THROW(body.FindElement(ByTag("input"))); 81 | } 82 | 83 | } // namespace test 84 | -------------------------------------------------------------------------------- /include/webdriverxx/wait.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_WAIT_H 2 | #define WEBDRIVERXX_WAIT_H 3 | 4 | #include "detail/error_handling.h" 5 | #include "detail/time.h" 6 | #include "detail/to_string.h" 7 | #include 8 | #include 9 | 10 | namespace webdriverxx { 11 | namespace detail { 12 | 13 | template 14 | Value Wait( 15 | DescriptiveGetter getter, 16 | Duration timeoutMs = 5000, 17 | Duration intervalMs = 50 18 | ) { 19 | const TimePoint timeout = detail::Now() + timeoutMs; 20 | for (;;) { 21 | const auto value_ptr = getter(nullptr); 22 | if (value_ptr) 23 | return *value_ptr; 24 | if (detail::Now() >= timeout) { 25 | std::string description; 26 | const auto value_ptr = getter(&description); 27 | if (value_ptr) 28 | return *value_ptr; 29 | throw WebDriverException(detail::Fmt() 30 | << "Timeout after " << timeoutMs << "ms of waiting, last attempt returned: " 31 | << description 32 | ); 33 | } 34 | detail::Sleep(intervalMs); 35 | } 36 | } 37 | 38 | template 39 | std::unique_ptr TryToCallGetter(Getter getter, std::string* description) { 40 | std::unique_ptr value_ptr; 41 | try { 42 | value_ptr.reset(new Value(getter())); 43 | } catch (const std::exception& e) { 44 | if (description) 45 | *description = e.what(); 46 | } 47 | return value_ptr; 48 | } 49 | 50 | } // namespace detail 51 | 52 | // Waits for a value returned by a supplied getter. 53 | // Returns that value or throws exception on timeout. 54 | // Getter is a function or function-like object that returns some copyable value. 55 | template 56 | auto WaitForValue( 57 | Getter getter, 58 | Duration timeoutMs = 5000, 59 | Duration intervalMs = 50 60 | ) -> decltype(getter()) { 61 | typedef decltype(getter()) Value; 62 | return detail::Wait( 63 | [&getter](std::string* description) { 64 | return detail::TryToCallGetter(getter, description); 65 | }, 66 | timeoutMs, intervalMs); 67 | } 68 | 69 | // Waits until a truthy value is returned by a supplied getter. 70 | // Returns that value or throws exception on timeout. 71 | // Getter is a function or function-like object that returns some copyable value. 72 | template 73 | auto WaitUntil( 74 | Getter getter, 75 | Duration timeoutMs = 5000, 76 | Duration intervalMs = 50 77 | ) -> decltype(getter()) { 78 | typedef decltype(getter()) Value; 79 | return detail::Wait( 80 | [&getter](std::string* description) -> std::unique_ptr { 81 | auto value_ptr = detail::TryToCallGetter(getter, description); 82 | if (!value_ptr || !!*value_ptr) 83 | return value_ptr; 84 | if (description) 85 | *description = "Value is falsy"; 86 | value_ptr.reset(); 87 | return value_ptr; 88 | }, timeoutMs, intervalMs); 89 | } 90 | 91 | } // namespace webdriverxx 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /test/element_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | 5 | namespace test { 6 | 7 | using namespace webdriverxx; 8 | 9 | class TestElement : public ::testing::Test { 10 | protected: 11 | static void SetUpTestCase() { 12 | GetDriver().Navigate(GetTestPageUrl("element.html")); 13 | } 14 | 15 | TestElement() : driver(GetDriver()) {} 16 | 17 | WebDriver driver; 18 | }; 19 | 20 | TEST_F(TestElement, CanBeClicked) { 21 | driver.FindElement(ByTag("input")).Click(); 22 | } 23 | 24 | // TODO: Submit 25 | 26 | TEST_F(TestElement, GetsText) { 27 | ASSERT_EQ("Some text", driver.FindElement(ById("element_with_text")).GetText()); 28 | } 29 | 30 | TEST_F(TestElement, CanBeCleared) { 31 | Element e = driver.FindElement(ByTag("input")); 32 | e.SendKeys("abc"); 33 | ASSERT_NE("", e.GetAttribute("value")); 34 | e.Clear(); 35 | ASSERT_EQ("", e.GetAttribute("value")); 36 | } 37 | 38 | TEST_F(TestElement, GetsTagName) { 39 | ASSERT_EQ("div", driver.FindElement(ByTag("div")).GetTagName()); 40 | } 41 | 42 | // TODO: IsEnabled 43 | // TODO: IsSelected 44 | 45 | TEST_F(TestElement, GetsAttributes) { 46 | Element e = driver.FindElement(ById("div_with_attributes")); 47 | ASSERT_EQ("div_with_attributes", e.GetAttribute("id")); 48 | ASSERT_EQ("test value", e.GetAttribute("test")); 49 | } 50 | 51 | TEST_F(TestElement, IsEqualToOtherElement) { 52 | Element e = driver.FindElement(ById("first_div")); 53 | Element other = driver.FindElement(ById("first_div")); 54 | ASSERT_TRUE(e.Equals(other)); 55 | ASSERT_TRUE(e == other); 56 | } 57 | 58 | TEST_F(TestElement, IsNotEqualToOtherElement) { 59 | Element e = driver.FindElement(ById("first_div")); 60 | Element other = driver.FindElement(ById("second_div")); 61 | ASSERT_TRUE(!e.Equals(other)); 62 | ASSERT_TRUE(e != other); 63 | } 64 | 65 | TEST_F(TestElement, HasStrictWeakOrdering) { 66 | Element a = driver.FindElement(ById("first_div")); 67 | Element b = driver.FindElement(ById("second_div")); 68 | Element c = driver.FindElement(ById("third_div")); 69 | Element a2 = a; 70 | if (c < b) std::swap(b, c); 71 | if (b < a) std::swap(a, b); 72 | if (c < b) std::swap(b, c); 73 | ASSERT_FALSE(a < a2); 74 | ASSERT_FALSE(a2 < a); 75 | ASSERT_TRUE(a < b && !(b < a)); 76 | ASSERT_TRUE(a < b && b < c && a < c); 77 | } 78 | 79 | TEST_F(TestElement, GetsIsDisplayed) { 80 | ASSERT_TRUE(driver.FindElement(ById("visible")).IsDisplayed()); 81 | ASSERT_FALSE(driver.FindElement(ById("hidden")).IsDisplayed()); 82 | } 83 | 84 | TEST_F(TestElement, GetsLocation) { 85 | driver.FindElement(ById("visible")).GetLocation(); 86 | driver.FindElement(ById("visible")).GetLocationInView(); 87 | } 88 | 89 | TEST_F(TestElement, GetsSize) { 90 | Size size = driver.FindElement(ById("visible")).GetSize(); 91 | ASSERT_NE(0, size.width); 92 | ASSERT_NE(0, size.height); 93 | } 94 | 95 | TEST_F(TestElement, GetsCssProperty) { 96 | ASSERT_EQ("none", driver.FindElement(ById("hidden")).GetCssProperty("display")); 97 | } 98 | 99 | } // namespace test 100 | -------------------------------------------------------------------------------- /test/to_string_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace test { 5 | 6 | using namespace webdriverxx; 7 | using namespace webdriverxx::detail; 8 | 9 | TEST(ToString, ConvertsIntegralTypes) { 10 | ASSERT_EQ("123", ToString(123)); 11 | int i = 123; 12 | ASSERT_EQ("123", ToString(i)); 13 | ASSERT_EQ("123", ToString(static_cast(i))); 14 | ASSERT_EQ("'z'", ToString('z')); 15 | char c = 'z'; 16 | ASSERT_EQ("'z'", ToString(c)); 17 | ASSERT_EQ("'z'", ToString(static_cast(c))); 18 | } 19 | 20 | namespace custom { 21 | 22 | struct A {}; 23 | 24 | struct B {}; 25 | std::ostream& operator << (std::ostream& s, const B&) { 26 | return s << "B"; 27 | } 28 | 29 | struct C {}; 30 | 31 | void ToStream(const C&, std::ostream& s) { 32 | s << "C"; 33 | } 34 | 35 | struct G {}; 36 | void PrintTo(const G&, std::ostream* s) { 37 | *s << "G"; 38 | } 39 | 40 | } // namespace custom 41 | 42 | TEST(ToString, ConvertsCustomTypes) { 43 | ASSERT_EQ("", ToString(custom::A())); 44 | ASSERT_EQ("B", ToString(custom::B())); 45 | ASSERT_EQ("C", ToString(custom::C())); 46 | ASSERT_EQ("G", ToString(custom::G())); 47 | } 48 | 49 | TEST(ToString, ConvertsStrings) { 50 | ASSERT_EQ("\"abc\"", ToString("abc")); 51 | char a[] = "abc"; 52 | ASSERT_EQ("\"abc\"", ToString(a)); 53 | const char ca[] = "abc"; 54 | ASSERT_EQ("\"abc\"", ToString(ca)); 55 | char* pc = a; 56 | ASSERT_EQ("\"abc\"", ToString(pc)); 57 | const char* pcc = "abc"; 58 | ASSERT_EQ("\"abc\"", ToString(pcc)); 59 | 60 | ASSERT_EQ("\"abc\"", ToString(std::string("abc"))); 61 | std::string s("abc"); 62 | ASSERT_EQ("\"abc\"", ToString(s)); 63 | const std::string cs("abc"); 64 | ASSERT_EQ("\"abc\"", ToString(cs)); 65 | } 66 | 67 | TEST(ToString, ConvertsPointers) { 68 | int i = 123; 69 | ASSERT_EQ("*123", ToString(&i)); 70 | const int* cpi = &i; 71 | ASSERT_EQ("**123", ToString(&cpi)); 72 | const std::string s = "abc"; 73 | ASSERT_EQ("*\"abc\"", ToString(&s)); 74 | } 75 | 76 | TEST(ToString, ConvertsContainersOfIntegralTypes) { 77 | ASSERT_EQ("[]", ToString(std::vector())); 78 | std::vector v; 79 | v.push_back(123); 80 | v.push_back(456); 81 | ASSERT_EQ("*[123, 456]", ToString(&v)); 82 | int a[3] = { 123, 456, 789 }; 83 | ASSERT_EQ("[123, 456, 789]", ToString(a)); 84 | } 85 | 86 | TEST(ToString, ConvertsContainersOfCustomTypes) { 87 | std::vector as(1); 88 | ASSERT_EQ("[]", ToString(as)); 89 | std::vector bs(1); 90 | ASSERT_EQ("[B]", ToString(bs)); 91 | std::vector cs(1); 92 | ASSERT_EQ("[C]", ToString(cs)); 93 | std::vector gs(1); 94 | ASSERT_EQ("[G]", ToString(gs)); 95 | } 96 | 97 | TEST(ToString, ConvertsContainersOfStrings) { 98 | const char* ss[] = { "abc", "def" }; 99 | ASSERT_EQ("[\"abc\", \"def\"]", ToString(ss)); 100 | ASSERT_EQ("*[\"abc\", \"def\"]", ToString(&ss)); 101 | } 102 | 103 | } // namespace test 104 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/to_string.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_TO_STRING_H 2 | #define WEBDRIVERXX_TO_STRING_H 3 | 4 | #include "meta_tools.h" 5 | #include 6 | #include 7 | 8 | namespace webdriverxx { 9 | namespace detail { 10 | 11 | template 12 | void ToStream(const T& value, std::ostream& stream); 13 | 14 | namespace to_string_impl { 15 | 16 | template 17 | void WriteNonStreamableValue(const T&, std::ostream& stream) { 18 | stream << ""; 19 | } 20 | 21 | } // namespace to_string_impl 22 | } // detail 23 | } // webdriverxx 24 | 25 | namespace webdriverxx_to_string_impl { 26 | 27 | template 28 | std::ostream& operator << (std::ostream& stream, const T& value) { 29 | webdriverxx::detail::to_string_impl::WriteNonStreamableValue(value, stream); 30 | return stream; 31 | } 32 | 33 | } // namespace webdriverxx_to_string_impl 34 | 35 | namespace webdriverxx { 36 | namespace detail { 37 | namespace to_string_impl { 38 | 39 | struct DefaultTag {}; 40 | struct IterableTag {}; 41 | struct StringTag {}; 42 | 43 | template 44 | struct Tag : 45 | if_, type_is, 46 | if_, type_is, 47 | type_is 48 | >> {}; 49 | 50 | template 51 | void ToStreamImpl(const T& value, std::ostream& stream, DefaultTag) { 52 | using namespace webdriverxx_to_string_impl; 53 | stream << value; 54 | } 55 | 56 | template 57 | void ToStreamImpl(T* value, std::ostream& stream, DefaultTag) { 58 | stream << '*'; 59 | ToStream(*value, stream); 60 | } 61 | 62 | inline 63 | void ToStreamImpl(const char value, std::ostream& stream, DefaultTag) { 64 | stream << "'" << value << "'"; 65 | } 66 | 67 | inline 68 | void ToStreamImpl(const char* value, std::ostream& stream, StringTag) { 69 | stream << '"' << value << '"'; 70 | } 71 | 72 | inline 73 | void ToStreamImpl(const std::string& value, std::ostream& stream, StringTag) { 74 | ToStream(value.c_str(), stream); 75 | } 76 | 77 | template 78 | void ToStreamImpl(const T& value, std::ostream& stream, IterableTag) { 79 | auto it = std::begin(value); 80 | const auto end = std::end(value); 81 | int limit = 20; 82 | stream << "["; 83 | if (it != end) { 84 | ToStream(*it, stream); 85 | while (++it != end && --limit > 0) { 86 | stream << ", "; 87 | ToStream(*it, stream); 88 | } 89 | } 90 | stream << "]"; 91 | } 92 | 93 | } // namespace to_string_impl 94 | 95 | template 96 | void PrintTo(const T& value, std::ostream* stream) { 97 | using to_string_impl::ToStreamImpl; 98 | using to_string_impl::Tag; 99 | ToStreamImpl(value, *stream, typename Tag::type()); 100 | } 101 | 102 | template 103 | void ToStream(const T& value, std::ostream& stream) 104 | { 105 | PrintTo(value, &stream); // for compatibility with Google Test's values printers 106 | } 107 | 108 | template 109 | std::string ToString(const T& value) { 110 | std::ostringstream s; 111 | ToStream(value, s); 112 | return s.str(); 113 | } 114 | 115 | } // namespace detail 116 | } // namespace webdriverxx 117 | 118 | #endif 119 | -------------------------------------------------------------------------------- /test/wait_match_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace test { 5 | 6 | using namespace webdriverxx; 7 | using namespace webdriverxx::detail; 8 | 9 | bool FunctionMatcher(int) { return true; } 10 | 11 | struct FunctorMatcher { 12 | bool operator () (int) const { 13 | return true; 14 | } 15 | }; 16 | 17 | TEST(WaitForMatch, CanBeUsedWithFunctionFunctorAndLambda) { 18 | ASSERT_EQ(123, WaitForMatch([]{ return 123; }, FunctionMatcher)); 19 | ASSERT_EQ(123, WaitForMatch([]{ return 123; }, FunctorMatcher())); 20 | ASSERT_EQ(123, WaitForMatch([]{ return 123; }, [](int){ return true; })); 21 | } 22 | 23 | TEST(WaitForMatch, ReturnsMatchedValue) { 24 | ASSERT_EQ(123, WaitForMatch([]{ return 123; }, [](int){ return true; })); 25 | } 26 | 27 | TEST(WaitForMatch, DoesNotWaitIfValueIsMatched) { 28 | Duration timeout = 1000; 29 | const TimePoint start = Now(); 30 | WaitForMatch([]{ return 0; }, [](int){ return true; }, timeout); 31 | ASSERT_TRUE(Now() - start < timeout/2); 32 | } 33 | 34 | TEST(WaitForMatch, WaitsUntilValueIsMatched) { 35 | Duration timeout = 1000; 36 | Duration interval = 0; 37 | int counter = 0; 38 | WaitForMatch([]{ return 0; }, [&counter](int){ return ++counter == 10; }, timeout, interval); 39 | ASSERT_EQ(10, counter); 40 | } 41 | 42 | TEST(WaitForMatch, ThrowsExceptionOnTimeout) { 43 | Duration timeout = 0; 44 | ASSERT_THROW(WaitForMatch([]{ return 0; }, [](int){ return false; }, timeout), WebDriverException); 45 | } 46 | 47 | TEST(WaitForMatch, ExplainsTimeout) { 48 | try { 49 | Duration timeout = 0; 50 | WaitForMatch([]{ return 0; }, [](int){ return false; }, timeout); 51 | FAIL(); 52 | } catch (const std::exception& e) { 53 | std::string message = e.what(); 54 | const auto npos = std::string::npos; 55 | ASSERT_NE(npos, message.find("imeout")); 56 | } 57 | } 58 | 59 | TEST(WaitForMatch, CanUseGMockMatchers) { 60 | using namespace ::testing; 61 | ASSERT_EQ(123, WaitForMatch([]{ return 123; }, Eq(123))); 62 | ASSERT_EQ(123, WaitForMatch([]{ return 123; }, 123)); 63 | ASSERT_EQ("abc", WaitForMatch([]{ return std::string("abc"); }, "abc")); 64 | ASSERT_EQ("abc", WaitForMatch([]{ return std::string("abc"); }, Eq("abc"))); 65 | ASSERT_EQ(123, WaitForMatch([]{ return 123; }, _)); 66 | ASSERT_EQ(123, WaitForMatch([]{ return 123; }, An())); 67 | std::vector v(1, 123); 68 | ASSERT_EQ(v, WaitForMatch([&v]{ return v; }, Contains(123))); 69 | ASSERT_EQ(v, WaitForMatch([&v]{ return v; }, Not(Contains(456)))); 70 | Duration timeout = 0; 71 | ASSERT_THROW(WaitForMatch([&v]{ return v; }, Not(Contains(123)), timeout), WebDriverException); 72 | } 73 | 74 | TEST(WaitForMatch, ExplainsGMockMatcherMismatch) { 75 | try { 76 | Duration timeout = 0; 77 | using namespace ::testing; 78 | WaitForMatch([]{ return 123; }, Eq(456), timeout); 79 | FAIL(); 80 | } catch (const std::exception& e) { 81 | std::string message = e.what(); 82 | const auto npos = std::string::npos; 83 | ASSERT_NE(npos, message.find("123")); 84 | ASSERT_NE(npos, message.find("456")); 85 | ASSERT_NE(npos, message.find("imeout")); 86 | } 87 | } 88 | 89 | } // namespace test 90 | -------------------------------------------------------------------------------- /include/webdriverxx/keys.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_KEYS_H 2 | #define WEBDRIVERXX_KEYS_H 3 | 4 | #include 5 | #include 6 | 7 | namespace webdriverxx { 8 | namespace keys { 9 | 10 | const char *const Null = "\xee\x80\x80"; 11 | const char *const Cancel = "\xee\x80\x81"; 12 | const char *const Help = "\xee\x80\x82"; 13 | const char *const Backspace = "\xee\x80\x83"; 14 | const char *const Tab = "\xee\x80\x84"; 15 | const char *const Clear = "\xee\x80\x85"; 16 | const char *const Return = "\xee\x80\x86"; 17 | const char *const Enter = "\xee\x80\x87"; 18 | const char *const Shift = "\xee\x80\x88"; 19 | const char *const Control = "\xee\x80\x89"; 20 | const char *const Alt = "\xee\x80\x8a"; 21 | const char *const Pause = "\xee\x80\x8b"; 22 | const char *const Escape = "\xee\x80\x8c"; 23 | const char *const Space = "\xee\x80\x8d"; 24 | const char *const PageUp = "\xee\x80\x8e"; 25 | const char *const PageDown = "\xee\x80\x8f"; 26 | const char *const End = "\xee\x80\x90"; 27 | const char *const Home = "\xee\x80\x91"; 28 | const char *const Left = "\xee\x80\x92"; 29 | const char *const Up = "\xee\x80\x93"; 30 | const char *const Right = "\xee\x80\x94"; 31 | const char *const Down = "\xee\x80\x95"; 32 | const char *const Insert = "\xee\x80\x96"; 33 | const char *const Delete = "\xee\x80\x97"; 34 | const char *const Semicolon = "\xee\x80\x98"; 35 | const char *const Equals = "\xee\x80\x99"; 36 | const char *const Numpad0 = "\xee\x80\x9a"; 37 | const char *const Numpad1 = "\xee\x80\x9b"; 38 | const char *const Numpad2 = "\xee\x80\x9c"; 39 | const char *const Numpad3 = "\xee\x80\x9d"; 40 | const char *const Numpad4 = "\xee\x80\x9e"; 41 | const char *const Numpad5 = "\xee\x80\x9f"; 42 | const char *const Numpad6 = "\xee\x80\xa0"; 43 | const char *const Numpad7 = "\xee\x80\xa1"; 44 | const char *const Numpad8 = "\xee\x80\xa2"; 45 | const char *const Numpad9 = "\xee\x80\xa3"; 46 | const char *const Multiply = "\xee\x80\xa4"; 47 | const char *const Add = "\xee\x80\xa5"; 48 | const char *const Separator = "\xee\x80\xa6"; 49 | const char *const Subtract = "\xee\x80\xa7"; 50 | const char *const Decimal = "\xee\x80\xa8"; 51 | const char *const Divide = "\xee\x80\xa9"; 52 | const char *const F1 = "\xee\x80\xb1"; 53 | const char *const F2 = "\xee\x80\xb2"; 54 | const char *const F3 = "\xee\x80\xb3"; 55 | const char *const F4 = "\xee\x80\xb4"; 56 | const char *const F5 = "\xee\x80\xb5"; 57 | const char *const F6 = "\xee\x80\xb6"; 58 | const char *const F7 = "\xee\x80\xb7"; 59 | const char *const F8 = "\xee\x80\xb8"; 60 | const char *const F9 = "\xee\x80\xb9"; 61 | const char *const F10 = "\xee\x80\xba"; 62 | const char *const F11 = "\xee\x80\xbb"; 63 | const char *const F12 = "\xee\x80\xbc"; 64 | const char *const Command = "\xee\x80\xbd"; 65 | const char *const Meta = Command; 66 | 67 | } // namespace keys 68 | 69 | namespace detail { 70 | class Keyboard; 71 | } // namespace detail 72 | 73 | class Shortcut // copyable 74 | { 75 | public: 76 | Shortcut& operator << (const std::string& key) { 77 | keys_.push_back(key); 78 | return *this; 79 | } 80 | 81 | Shortcut& operator << (const char* key) { 82 | keys_.push_back(key); 83 | return *this; 84 | } 85 | 86 | private: 87 | friend class detail::Keyboard; 88 | std::vector keys_; 89 | }; 90 | 91 | } // namespace webdriverxx 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /test/environment.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_ENVIRONMENT_H 2 | #define WEBDRIVERXX_ENVIRONMENT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace test { 11 | 12 | const char* const kDefaultTestWebDriverUrl = "http://localhost:7777/"; 13 | const char* const kDefaultTestPagesUrl = "http://localhost:8080/"; 14 | 15 | struct Parameters { 16 | std::string web_driver_url; 17 | webdriverxx::Capabilities required; 18 | webdriverxx::Capabilities desired; 19 | std::string test_pages_url; 20 | bool test_real_browsers; 21 | 22 | Parameters() 23 | : web_driver_url(kDefaultTestWebDriverUrl) 24 | , test_pages_url(kDefaultTestPagesUrl) 25 | , test_real_browsers(false) 26 | {} 27 | }; 28 | 29 | class Environment : public ::testing::Environment { 30 | public: 31 | static Environment& Instance() { 32 | return *instance_; 33 | } 34 | 35 | explicit Environment(const Parameters& parameters) 36 | : driver_(0) 37 | , parameters_(parameters) 38 | {} 39 | 40 | webdriverxx::WebDriver& GetDriver() { 41 | return driver_ ? *driver_ : GetFreshDriver(); 42 | } 43 | 44 | webdriverxx::WebDriver& GetFreshDriver() { 45 | DeleteDriver(); 46 | driver_ = new webdriverxx::WebDriver(CreateDriver()); 47 | return *driver_; 48 | } 49 | 50 | webdriverxx::WebDriver CreateDriver() { 51 | return webdriverxx::WebDriver( 52 | parameters_.desired, 53 | parameters_.required, 54 | parameters_.web_driver_url 55 | ); 56 | } 57 | 58 | std::string GetWebDriverUrl() const { return parameters_.web_driver_url; } 59 | 60 | Parameters GetParameters() const { return parameters_; } 61 | 62 | std::string GetTestPageUrl(const std::string& page_name) const { 63 | std::string url = parameters_.test_pages_url; 64 | if (!url.empty() && url[url.length() - 1] != '/') 65 | url += "/"; 66 | url += page_name; 67 | return url; 68 | } 69 | 70 | private: 71 | void SetUp() { 72 | instance_ = this; 73 | } 74 | 75 | void TearDown() { 76 | instance_ = 0; 77 | DeleteDriver(); 78 | } 79 | 80 | void DeleteDriver() { 81 | delete driver_; 82 | driver_ = 0; 83 | } 84 | 85 | private: 86 | static Environment* instance_; 87 | webdriverxx::WebDriver* driver_; 88 | Parameters parameters_; 89 | }; 90 | 91 | inline Parameters GetParameters() { return Environment::Instance().GetParameters(); } 92 | inline std::string GetWebDriverUrl() { return Environment::Instance().GetWebDriverUrl(); } 93 | inline std::string GetTestPageUrl(const std::string& page_name) { return Environment::Instance().GetTestPageUrl(page_name); } 94 | inline webdriverxx::WebDriver& GetDriver() { return Environment::Instance().GetDriver(); } 95 | inline webdriverxx::WebDriver& GetFreshDriver() { return Environment::Instance().GetFreshDriver(); } 96 | inline webdriverxx::WebDriver CreateDriver() { return Environment::Instance().CreateDriver(); } 97 | inline bool TestRealBrowsers() { return GetParameters().test_real_browsers; } 98 | inline std::string GetBrowserName() { return GetDriver().GetCapabilities().GetBrowserName(); } 99 | inline bool IsFirefox() { return GetBrowserName() == webdriverxx::browser::Firefox; } 100 | inline bool IsPhantom() { return GetBrowserName() == webdriverxx::browser::Phantom; } 101 | 102 | } // namespace test 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /test/mouse_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | 5 | namespace test { 6 | 7 | using namespace webdriverxx; 8 | 9 | class TestMouse : public ::testing::Test { 10 | protected: 11 | static void SetUpTestCase() { 12 | GetDriver().Navigate(GetTestPageUrl("mouse.html")); 13 | } 14 | 15 | TestMouse() : driver(GetDriver()) {} 16 | 17 | void SetUp() { 18 | target = driver.FindElement(ById("target")); 19 | click_type = driver.FindElement(ById("click_type")).Clear(); 20 | updown_type = driver.FindElement(ById("updown_type")).Clear(); 21 | updown_button = driver.FindElement(ById("updown_button")).Clear(); 22 | } 23 | 24 | std::string GetValue(const Element& element) { 25 | return element.GetAttribute("value"); 26 | } 27 | 28 | WebDriver driver; 29 | Element target; 30 | Element click_type; 31 | Element updown_type; 32 | Element updown_button; 33 | }; 34 | 35 | TEST_F(TestMouse, SendsClick) { 36 | driver.MoveToCenterOf(target).Click(); 37 | ASSERT_EQ("click", GetValue(click_type)); 38 | } 39 | 40 | TEST_F(TestMouse, MovesPointerInsideTarget) { 41 | driver.MoveToCenterOf(target).MoveTo(Offset(-45,-45)).Click(); 42 | ASSERT_EQ("click", GetValue(click_type)); 43 | } 44 | 45 | TEST_F(TestMouse, MovesPointerOutsideTarget) { 46 | if (IsFirefox()) return; 47 | driver.MoveToCenterOf(target).MoveTo(Offset(55,55)).Click(); 48 | driver.MoveToCenterOf(target).MoveTo(Offset(-55,55)).Click(); 49 | driver.MoveToCenterOf(target).MoveTo(Offset(-55,-55)).Click(); 50 | driver.MoveToCenterOf(target).MoveTo(Offset(55,-55)).Click(); 51 | ASSERT_EQ("", GetValue(click_type)); 52 | } 53 | 54 | TEST_F(TestMouse, MovesPointerToTopLeftCorner) { 55 | driver.MoveToTopLeftOf(target).Click(); 56 | ASSERT_EQ("click", GetValue(click_type)); 57 | } 58 | 59 | TEST_F(TestMouse, MovesPointerToTopLeftCornerWithOffset) { 60 | driver.MoveToTopLeftOf(target, Offset(95,95)).Click(); 61 | ASSERT_EQ("click", GetValue(click_type)); 62 | } 63 | 64 | TEST_F(TestMouse, MovesPointerToTopLeftCornerWithOffset2) { 65 | if (IsFirefox()) return; 66 | driver.MoveToTopLeftOf(target, Offset(-5,-5)).Click(); 67 | driver.MoveToTopLeftOf(target, Offset(105,105)).Click(); 68 | ASSERT_EQ("", GetValue(click_type)); 69 | } 70 | 71 | TEST_F(TestMouse, SendsDoubleclicks) { 72 | driver.MoveToCenterOf(target).DoubleClick(); 73 | ASSERT_EQ("dblclick", GetValue(click_type)); 74 | } 75 | 76 | TEST_F(TestMouse, SendsButtonDown) { 77 | driver.MoveToCenterOf(target).ButtonDown(); 78 | ASSERT_EQ("mousedown", GetValue(updown_type)); 79 | driver.ButtonUp(); 80 | } 81 | 82 | TEST_F(TestMouse, SendsButtonUp) { 83 | driver.MoveToCenterOf(target).ButtonDown().ButtonUp(); 84 | ASSERT_EQ("mouseup", GetValue(updown_type)); 85 | } 86 | 87 | TEST_F(TestMouse, SendsDifferentButtons) { 88 | if (IsFirefox()) return; 89 | driver.MoveToCenterOf(target).ButtonDown(mouse::LeftButton).ButtonUp(mouse::LeftButton); 90 | const auto left_button = GetValue(updown_button); 91 | driver.ButtonDown(mouse::RightButton).ButtonUp(mouse::RightButton); 92 | const auto right_button = GetValue(updown_button); 93 | driver.ButtonDown(mouse::MiddleButton).ButtonUp(mouse::MiddleButton); 94 | const auto middle_button = GetValue(updown_button); 95 | ASSERT_NE(left_button, right_button); 96 | ASSERT_NE(left_button, middle_button); 97 | ASSERT_NE(right_button, middle_button); 98 | } 99 | 100 | } // namespace test 101 | -------------------------------------------------------------------------------- /test/shared_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace test { 6 | 7 | using namespace webdriverxx::detail; 8 | 9 | struct WidgetMonitor { 10 | WidgetMonitor() 11 | : created(0) 12 | , copied(0) 13 | , deleted(0) 14 | {} 15 | 16 | int created; 17 | int copied; 18 | int deleted; 19 | }; 20 | 21 | struct Simple : SharedObjectBase { 22 | int n; 23 | Simple() : n() {} 24 | }; 25 | 26 | class Widget : public SharedObjectBase { 27 | public: 28 | Widget(WidgetMonitor& monitor) 29 | : monitor(monitor) { 30 | ++monitor.created; 31 | } 32 | 33 | Widget(const Widget& other) 34 | : monitor(other.monitor) { 35 | ++monitor.created; 36 | } 37 | 38 | Widget& operator = (const Widget& other) { 39 | assert(&monitor == &other.monitor); 40 | ++monitor.copied; 41 | return *this; 42 | } 43 | 44 | virtual ~Widget() { 45 | ++monitor.deleted; 46 | } 47 | 48 | private: 49 | WidgetMonitor& monitor; 50 | }; 51 | 52 | class WidgetSubclass : public Widget { 53 | public: 54 | WidgetSubclass(WidgetMonitor& monitor) : Widget(monitor) {} 55 | }; 56 | 57 | TEST(WidgetMonitor, ZeroByDefault) { 58 | WidgetMonitor m; 59 | ASSERT_EQ(0, m.created); 60 | ASSERT_EQ(0, m.copied); 61 | ASSERT_EQ(0, m.deleted); 62 | } 63 | 64 | TEST(WidgetMonitor, ShowsCreateAndDelete) { 65 | WidgetMonitor m; 66 | ((void)Widget(m)); 67 | ASSERT_EQ(1, m.created); 68 | ASSERT_EQ(0, m.copied); 69 | ASSERT_EQ(1, m.deleted); 70 | } 71 | 72 | TEST(WidgetMonitor, ShowsCopy) { 73 | WidgetMonitor m; 74 | Widget w1(m); 75 | Widget w2(m); 76 | w2 = w1; 77 | ASSERT_EQ(1, m.copied); 78 | 79 | } 80 | 81 | TEST(WidgetMonitor, MonitorsWidgetSubclass) { 82 | WidgetMonitor m; 83 | ((void)WidgetSubclass(m)); 84 | ASSERT_EQ(1, m.created); 85 | ASSERT_EQ(0, m.copied); 86 | ASSERT_EQ(1, m.deleted); 87 | } 88 | 89 | TEST(Shared, DeletesWidget) { 90 | WidgetMonitor m; 91 | Shared(new Widget(m)); 92 | ASSERT_EQ(1, m.created); 93 | ASSERT_EQ(1, m.deleted); 94 | } 95 | 96 | TEST(Shared, ShareSingleWidget) { 97 | WidgetMonitor m; 98 | { 99 | Shared a(new Widget(m)); 100 | Shared b = a; 101 | Shared c; 102 | c = b; 103 | } 104 | ASSERT_EQ(1, m.created); 105 | ASSERT_EQ(0, m.copied); 106 | ASSERT_EQ(1, m.deleted); 107 | } 108 | 109 | TEST(Shared, SupportsImplicitTypecasts) { 110 | WidgetMonitor m; 111 | { 112 | Shared c; 113 | Shared a(new WidgetSubclass(m)); 114 | Shared b = a; 115 | c = b; 116 | } 117 | ASSERT_EQ(1, m.created); 118 | ASSERT_EQ(0, m.copied); 119 | ASSERT_EQ(1, m.deleted); 120 | } 121 | 122 | TEST(Shared, CanBeUsedInBoolContext) { 123 | Shared s1; 124 | Shared s2(new Simple); 125 | ASSERT_TRUE(!s1); 126 | ASSERT_TRUE(!!s2); 127 | } 128 | 129 | TEST(Shared, CanBeDereferenced) { 130 | Simple* p = new Simple; 131 | Shared s(p); 132 | p->n = 123; 133 | ASSERT_EQ(123, (*s).n); 134 | ASSERT_EQ(123, s->n); 135 | } 136 | 137 | TEST(Shared, CanBeCompared) { 138 | Shared s1; 139 | Shared s2(new Simple); 140 | Shared s3 = s2; 141 | Shared s4(new Simple); 142 | ASSERT_TRUE(s1 != s2); 143 | ASSERT_TRUE(s1 != s3); 144 | ASSERT_TRUE(s2 == s3); 145 | ASSERT_TRUE(s2 != s4); 146 | ASSERT_TRUE(s1 == s1); 147 | ASSERT_TRUE(s2 == s2); 148 | ASSERT_TRUE(s3 == s3); 149 | ASSERT_TRUE(s4 == s4); 150 | ASSERT_TRUE(s1 == 0); 151 | ASSERT_TRUE(s2 != 0); 152 | } 153 | 154 | } // namespace test 155 | -------------------------------------------------------------------------------- /test/finder_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | 5 | namespace test { 6 | 7 | using namespace webdriverxx; 8 | 9 | class TestFinder : public ::testing::Test { 10 | protected: 11 | static void SetUpTestCase() { 12 | WebDriver& driver = GetDriver(); 13 | driver.Navigate(GetTestPageUrl("finder.html")); 14 | driver.FindElement(ById("finder_loaded")); 15 | driver.SetImplicitTimeoutMs(0); 16 | } 17 | 18 | static void TearDownTestCase() { 19 | WebDriver& driver = GetDriver(); 20 | driver.SetImplicitTimeoutMs(1000); 21 | } 22 | 23 | TestFinder() : driver(GetDriver()) {} 24 | 25 | WebDriver driver; 26 | }; 27 | 28 | TEST_F(TestFinder, CanFindElement) { 29 | driver.FindElement(ById("test_id")); 30 | } 31 | 32 | TEST_F(TestFinder, ThrowsIfElementNotFound) { 33 | ASSERT_THROW(driver.FindElement(ById("non_existing")), WebDriverException); 34 | } 35 | 36 | TEST_F(TestFinder, CanFindMoreThanOneElement) { 37 | ASSERT_TRUE(0u < driver.FindElements(ByTag("div")).size()); 38 | } 39 | 40 | TEST_F(TestFinder, ReturnsZeroIfElementsNotFound) { 41 | ASSERT_EQ(0u, driver.FindElements(ById("non_existing")).size()); 42 | } 43 | 44 | TEST_F(TestFinder, FindsElementById) { 45 | driver.FindElement(ById("test_id")); 46 | ASSERT_EQ(0u, driver.FindElements(ById("non_existing")).size()); 47 | } 48 | 49 | TEST_F(TestFinder, FindsElementByClassName) { 50 | driver.FindElement(ByClass("test_class")); 51 | ASSERT_EQ(0u, driver.FindElements(ByClass("non_existing")).size()); 52 | } 53 | 54 | TEST_F(TestFinder, FindsElementByCssSelector) { 55 | driver.FindElement(ByCss("body div#css_selectable")); 56 | ASSERT_EQ(0u, driver.FindElements(ByCss("non_existing")).size()); 57 | } 58 | 59 | TEST_F(TestFinder, FindsElementByName) { 60 | driver.FindElement(ByName("test_name")); 61 | ASSERT_EQ(0u, driver.FindElements(ByName("non_existing")).size()); 62 | } 63 | 64 | TEST_F(TestFinder, FindsElementByLinkText) { 65 | driver.FindElement(ByLinkText("test link text")); 66 | ASSERT_EQ(0u, driver.FindElements(ByLinkText("non_existing")).size()); 67 | } 68 | 69 | TEST_F(TestFinder, FindsElementByPartialLinkText) { 70 | driver.FindElement(ByPartialLinkText("link text")); 71 | ASSERT_EQ(0u, driver.FindElements(ByPartialLinkText("non_existing")).size()); 72 | } 73 | 74 | TEST_F(TestFinder, FindsElementByTagName) { 75 | driver.FindElement(ByTag("body")); 76 | ASSERT_EQ(0u, driver.FindElements(ByTag("non_existing")).size()); 77 | } 78 | 79 | TEST_F(TestFinder, FindsElementByXPath) { 80 | driver.FindElement(ByXPath("//div")); 81 | ASSERT_EQ(0u, driver.FindElements(ByXPath("//non_existing")).size()); 82 | } 83 | 84 | TEST_F(TestFinder, OfElementFindsInnerElement) { 85 | Element outer = driver.FindElement(ById("outer")); 86 | outer.FindElement(ById("inner")); 87 | } 88 | 89 | TEST_F(TestFinder, OfElementDoesNotFindItself) { 90 | Element outer = driver.FindElement(ById("outer")); 91 | ASSERT_EQ(0u, outer.FindElements(ById("outer")).size()); 92 | } 93 | 94 | TEST_F(TestFinder, OfElementDoesNotFindNonExistingInnerElements) { 95 | Element outer = driver.FindElement(ById("outer")); 96 | driver.FindElement(ById("next_after_outer")); 97 | ASSERT_THROW(outer.FindElement(ById("next_after_outer")), WebDriverException); 98 | ASSERT_EQ(0u, outer.FindElements(ById("next_after_outer")).size()); 99 | } 100 | 101 | TEST_F(TestFinder, OfElementFindsMoreThanOneInnerElement) { 102 | ASSERT_EQ(2u, driver.FindElement(ById("outer")).FindElements(ByTag("div")).size()); 103 | } 104 | 105 | } // namespace test 106 | -------------------------------------------------------------------------------- /include/webdriverxx/response_status_code.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_RESPONSE_STATUS_CODE_H 2 | #define WEBDRIVERXX_RESPONSE_STATUS_CODE_H 3 | 4 | namespace webdriverxx { 5 | namespace response_status_code { 6 | 7 | enum Value { 8 | kSuccess = 0, 9 | kNoSuchDriver = 6, 10 | kNoSuchElement = 7, 11 | kNoSuchFrame = 8, 12 | kUnknownCommand = 9, 13 | kStaleElementReference = 10, 14 | kElementNotVisible = 11, 15 | kInvalidElementState = 12, 16 | kUnknownError = 13, 17 | kElementIsNotSelectable = 15, 18 | kJavaScriptError = 17, 19 | kXPathLookupError = 19, 20 | kTimeout = 21, 21 | kNoSuchWindow = 23, 22 | kInvalidCookieDomain = 24, 23 | kUnableToSetCookie = 25, 24 | kUnexpectedAlertOpen = 26, 25 | kNoAlertOpenError = 27, 26 | kScriptTimeout = 28, 27 | kInvalidElementCoordinates = 29, 28 | kIMENotAvailable = 30, 29 | kIMEEngineActivationFailed = 31, 30 | kInvalidSelector = 32, 31 | kSessionNotCreatedException = 33, 32 | kMoveTargetOutOfBounds = 34 33 | }; 34 | 35 | inline 36 | const char* ToString(Value code) { 37 | switch(code) { 38 | case kSuccess: return "The command executed successfully."; 39 | case kNoSuchDriver: return "A session is either terminated or not started"; 40 | case kNoSuchElement: return "An element could not be located on the page using the given search parameters."; 41 | case kNoSuchFrame: return "A request to switch to a frame could not be satisfied because the frame could not be found."; 42 | case kUnknownCommand: return "The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource."; 43 | case kStaleElementReference: return "An element command failed because the referenced element is no longer attached to the DOM."; 44 | case kElementNotVisible: return "An element command could not be completed because the element is not visible on the page."; 45 | case kInvalidElementState: return "An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element)."; 46 | case kUnknownError: return "An unknown server-side error occurred while processing the command."; 47 | case kElementIsNotSelectable: return "An attempt was made to select an element that cannot be selected."; 48 | case kJavaScriptError: return "An error occurred while executing user supplied JavaScript."; 49 | case kXPathLookupError: return "An error occurred while searching for an element by XPath."; 50 | case kTimeout: return "An operation did not complete before its timeout expired."; 51 | case kNoSuchWindow: return "A request to switch to a different window could not be satisfied because the window could not be found."; 52 | case kInvalidCookieDomain: return "An illegal attempt was made to set a cookie under a different domain than the current page."; 53 | case kUnableToSetCookie: return "A request to set a cookie's value could not be satisfied."; 54 | case kUnexpectedAlertOpen: return "A modal dialog was open, blocking this operation"; 55 | case kNoAlertOpenError: return "An attempt was made to operate on a modal dialog when one was not open."; 56 | case kScriptTimeout: return "A script did not complete before its timeout expired."; 57 | case kInvalidElementCoordinates: return "The coordinates provided to an interactions operation are invalid."; 58 | case kIMENotAvailable: return "IME was not available."; 59 | case kIMEEngineActivationFailed: return "An IME engine could not be started."; 60 | case kInvalidSelector: return "Argument was an invalid selector (e.g. XPath/CSS)."; 61 | case kSessionNotCreatedException: return "A new session could not be created."; 62 | case kMoveTargetOutOfBounds: return "Target provided for a move action is out of bounds."; 63 | } 64 | return "Unknown"; 65 | } 66 | 67 | } // namespace response_status_code 68 | } // namespace webdriverxx 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /include/webdriverxx/element.inl: -------------------------------------------------------------------------------- 1 | #include "conversions.h" 2 | #include "detail/finder.h" 3 | #include "detail/error_handling.h" 4 | 5 | namespace webdriverxx { 6 | 7 | inline 8 | picojson::value CustomToJson(const Element& element) { 9 | detail::ElementRef ref = { element.GetRef() }; 10 | return ToJson(ref); 11 | } 12 | 13 | inline 14 | Element::Element() {} 15 | 16 | inline 17 | Element::Element( 18 | const std::string& ref, 19 | const detail::Shared& resource, 20 | const detail::Shared& factory 21 | ) 22 | : ref_(ref) 23 | , resource_(resource) 24 | , factory_(factory) 25 | {} 26 | 27 | inline 28 | std::string Element::GetRef() const { 29 | return ref_; 30 | } 31 | 32 | inline 33 | bool Element::IsDisplayed() const { 34 | return GetResource().GetBool("displayed"); 35 | } 36 | 37 | inline 38 | bool Element::IsEnabled() const { 39 | return GetResource().GetBool("enabled"); 40 | } 41 | 42 | inline 43 | bool Element::IsSelected() const { 44 | return GetResource().GetBool("selected"); 45 | } 46 | 47 | inline 48 | Point Element::GetLocation() const { 49 | return GetResource().GetValue("location"); 50 | } 51 | 52 | inline 53 | Point Element::GetLocationInView() const { 54 | return GetResource().GetValue("location_in_view"); 55 | } 56 | 57 | inline 58 | Size Element::GetSize() const { 59 | return GetResource().GetValue("size"); 60 | } 61 | 62 | inline 63 | std::string Element::GetAttribute(const std::string& name) const { 64 | return GetResource().GetString(std::string("attribute/") + name); 65 | } 66 | 67 | inline 68 | std::string Element::GetCssProperty(const std::string& name) const { 69 | return GetResource().GetString(std::string("css/") + name); 70 | } 71 | 72 | inline 73 | std::string Element::GetTagName() const { 74 | return GetResource().GetString("name"); 75 | } 76 | inline 77 | std::string Element::GetText() const { 78 | return GetResource().GetString("text"); 79 | } 80 | 81 | inline 82 | Element Element::FindElement(const By& by) const { 83 | return factory_->MakeFinder(&GetResource()).FindElement(by); 84 | } 85 | 86 | inline 87 | std::vector Element::FindElements(const By& by) const { 88 | return factory_->MakeFinder(&GetResource()).FindElements(by); 89 | } 90 | 91 | inline 92 | const Element& Element::Clear() const { 93 | GetResource().Post("clear"); 94 | return *this; 95 | } 96 | 97 | inline 98 | const Element& Element::Click() const { 99 | GetResource().Post("click"); 100 | return *this; 101 | } 102 | 103 | inline 104 | const Element& Element::Submit() const { 105 | GetResource().Post("submit"); 106 | return *this; 107 | } 108 | 109 | inline 110 | const Element& Element::SendKeys(const std::string& keys) const { 111 | GetKeyboard().SendKeys(keys); 112 | return *this; 113 | } 114 | 115 | inline 116 | const Element& Element::SendKeys(const Shortcut& shortcut) const { 117 | GetKeyboard().SendKeys(shortcut); 118 | return *this; 119 | } 120 | 121 | inline 122 | bool Element::Equals(const Element& other) const { 123 | return GetResource().GetBool(std::string("equals/") + other.ref_); 124 | } 125 | 126 | inline 127 | bool Element::operator != (const Element& other) const { 128 | return ref_ != other.ref_; 129 | } 130 | 131 | inline 132 | bool Element::operator == (const Element& other) const { 133 | return ref_ == other.ref_; 134 | } 135 | 136 | inline 137 | bool Element::operator < (const Element& other) const { 138 | return ref_ < other.ref_; 139 | } 140 | 141 | inline 142 | detail::Resource& Element::GetResource() const { 143 | WEBDRIVERXX_CHECK(resource_, "Attempt to use empty element"); 144 | return *resource_; 145 | } 146 | 147 | inline 148 | detail::Keyboard Element::GetKeyboard() const 149 | { 150 | return detail::Keyboard(&GetResource(), "value"); 151 | } 152 | 153 | } // namespace webdriverxx 154 | -------------------------------------------------------------------------------- /include/webdriverxx/wait_match.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_WAIT_MATCH_H 2 | #define WEBDRIVERXX_WAIT_MATCH_H 3 | 4 | #include "wait.h" 5 | #include "detail/to_string.h" 6 | #include 7 | 8 | #ifdef WEBDRIVERXX_ENABLE_GMOCK_MATCHERS 9 | 10 | #include 11 | #include 12 | 13 | namespace webdriverxx { 14 | namespace detail { 15 | 16 | template 17 | class GMockMatcherAdapter { 18 | public: 19 | explicit GMockMatcherAdapter(::testing::Matcher matcher) : matcher_(matcher) {} 20 | 21 | bool Apply(const T& value) const { 22 | return matcher_.Matches(value); 23 | } 24 | 25 | std::string DescribeMismatch(const T& value) const { 26 | std::ostringstream s; 27 | s << "Expected: "; 28 | matcher_.DescribeTo(&s); 29 | s << ", actual: "; 30 | ToStream(value, s); 31 | const auto mismatch_details = GetMismatchDetails(value); 32 | if (!mismatch_details.empty()) 33 | s << ", " << mismatch_details; 34 | return s.str(); 35 | } 36 | 37 | private: 38 | std::string GetMismatchDetails(const T& value) const { 39 | std::ostringstream s; 40 | matcher_.ExplainMatchResultTo(value, &s); 41 | return s.str(); 42 | } 43 | 44 | private: 45 | ::testing::Matcher matcher_; 46 | }; 47 | 48 | } // detail 49 | 50 | template 51 | detail::GMockMatcherAdapter MakeMatcherAdapter(const M& matcher, typename std::enable_if>::value>::type* = nullptr) { 52 | return detail::GMockMatcherAdapter(matcher); 53 | }; 54 | 55 | } // namespace webdriverxx 56 | 57 | #endif // WEBDRIVERXX_ENABLE_GMOCK_MATCHERS 58 | 59 | namespace webdriverxx { 60 | namespace detail { 61 | 62 | template 63 | class PredicateMatcherAdapter { 64 | public: 65 | explicit PredicateMatcherAdapter(P& predicate) : predicate_(&predicate) {} 66 | 67 | bool Apply(const T& value) const { 68 | return (*predicate_)(value); 69 | } 70 | 71 | std::string DescribeMismatch(const T& value) const { 72 | return detail::Fmt() << "Value " << ToString(value) << " does not match predicate"; 73 | } 74 | 75 | private: 76 | P* predicate_; 77 | }; 78 | 79 | } // detail 80 | 81 | template 82 | void MakeMatcherAdapter(...); 83 | 84 | namespace detail { 85 | 86 | template 87 | PredicateMatcherAdapter SelectMakeMatcherAdapter(M& matcher, std::true_type /*no_custom_adapters*/) { 88 | return PredicateMatcherAdapter(matcher); 89 | } 90 | 91 | template 92 | auto SelectMakeMatcherAdapter(const M& matcher, std::false_type /*no_custom_adapters*/) -> decltype(MakeMatcherAdapter(matcher)) { 93 | return MakeMatcherAdapter(matcher); 94 | } 95 | 96 | } // detail 97 | 98 | // Waits until a value returned by a getter satisfies a supplied matcher. 99 | // Returns that value or throws exception on timeout. 100 | // Getter is a function or function-like object that returns some copyable value. 101 | // Matcher can be a predicate or a Google Mock matcher (if Google Mock matchers are enabled). 102 | template 103 | auto WaitForMatch( 104 | Getter getter, 105 | Matcher matcher, 106 | Duration timeoutMs = 5000, 107 | Duration intervalMs = 50 108 | ) -> decltype(getter()) { 109 | typedef decltype(getter()) Value; 110 | const auto& adapter = detail::SelectMakeMatcherAdapter(matcher, 111 | typename std::is_same(matcher))>::type()); 112 | return detail::Wait([&getter, &adapter](std::string* description) -> std::unique_ptr { 113 | auto value_ptr = detail::TryToCallGetter(getter, description); 114 | if (value_ptr && !adapter.Apply(*value_ptr)) { 115 | if (description) 116 | *description = adapter.DescribeMismatch(*value_ptr); 117 | value_ptr.reset(); 118 | } 119 | return value_ptr; 120 | }, timeoutMs, intervalMs); 121 | } 122 | 123 | } // namespace webdriverxx 124 | 125 | #endif 126 | -------------------------------------------------------------------------------- /include/webdriverxx/session.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_SESSION_H 2 | #define WEBDRIVERXX_SESSION_H 3 | 4 | #include "element.h" 5 | #include "window.h" 6 | #include "by.h" 7 | #include "capabilities.h" 8 | #include "keys.h" 9 | #include "js_args.h" 10 | #include "detail/resource.h" 11 | #include "detail/keyboard.h" 12 | #include "detail/shared.h" 13 | #include "detail/factories_impl.h" 14 | #include "picojson.h" 15 | #include 16 | 17 | namespace webdriverxx { 18 | 19 | class Client; 20 | 21 | class Session { // copyable 22 | public: 23 | Capabilities GetCapabilities() const; 24 | std::string GetSource() const; 25 | std::string GetTitle() const; 26 | std::string GetUrl() const; 27 | std::string GetScreenshot() const; // Base64 PNG 28 | 29 | const Session& Navigate(const std::string& url) const; 30 | const Session& Get(const std::string& url) const; // Same as Navigate 31 | const Session& Forward() const; 32 | const Session& Back() const; 33 | const Session& Refresh() const; 34 | 35 | const Session& Execute(const std::string& script, const JsArgs& args = JsArgs()) const; 36 | template 37 | T Eval(const std::string& script, const JsArgs& args = JsArgs()) const; 38 | const Session& ExecuteAsync(const std::string& script, const JsArgs& args = JsArgs()) const; 39 | template 40 | T EvalAsync(const std::string& script, const JsArgs& args = JsArgs()) const; 41 | 42 | const Session& SetFocusToFrame(const Element& frame) const; 43 | const Session& SetFocusToFrame(const std::string& id) const; 44 | const Session& SetFocusToFrame(int number) const; 45 | const Session& SetFocusToDefaultFrame() const; 46 | const Session& SetFocusToParentFrame() const; 47 | 48 | std::vector GetWindows() const; 49 | Window GetCurrentWindow() const; 50 | const Session& CloseCurrentWindow() const; 51 | const Session& SetFocusToWindow(const std::string& window_name) const; 52 | const Session& SetFocusToWindow(const Window& window) const; 53 | 54 | Element GetActiveElement() const; 55 | 56 | Element FindElement(const By& by) const; 57 | std::vector FindElements(const By& by) const; 58 | 59 | std::vector GetCookies() const; 60 | const Session& SetCookie(const Cookie& cookie) const; 61 | const Session& DeleteCookies() const; 62 | const Session& DeleteCookie(const std::string& name) const; 63 | 64 | std::string GetAlertText() const; 65 | const Session& SendKeysToAlert(const std::string& text) const; 66 | const Session& AcceptAlert() const; 67 | const Session& DismissAlert() const; 68 | 69 | const Session& SendKeys(const std::string& keys) const; 70 | const Session& SendKeys(const Shortcut& shortcut) const; 71 | 72 | const Session& MoveToTopLeftOf(const Element&, const Offset& = Offset()) const; 73 | const Session& MoveToCenterOf(const Element&) const; 74 | const Session& MoveTo(const Offset&) const; 75 | const Session& Click(mouse::Button = mouse::LeftButton) const; 76 | const Session& DoubleClick() const; 77 | const Session& ButtonDown(mouse::Button = mouse::LeftButton) const; 78 | const Session& ButtonUp(mouse::Button = mouse::LeftButton) const; 79 | 80 | const Session& SetTimeoutMs(timeout::Type type, int milliseconds); 81 | const Session& SetImplicitTimeoutMs(int milliseconds); 82 | const Session& SetAsyncScriptTimeoutMs(int milliseconds); 83 | 84 | void DeleteSession() const; // No need to delete sessions created by WebDriver or Client 85 | virtual ~Session() {} 86 | 87 | private: 88 | friend class Client; // Only Client can create Sessions 89 | 90 | explicit Session(const detail::Shared& resource); 91 | 92 | Window MakeWindow(const std::string& handle) const; 93 | detail::Keyboard GetKeyboard() const; 94 | template 95 | void InternalEval(const std::string& webdriver_command, 96 | const std::string& script, const JsArgs& args, 97 | T& result) const; 98 | void InternalEval(const std::string& webdriver_command, 99 | const std::string& script, const JsArgs& args, 100 | Element& result) const; 101 | picojson::value InternalEvalJsonValue(const std::string& command, 102 | const std::string& script, const JsArgs& args) const; 103 | const Session& InternalSetFocusToFrame(const picojson::value& id) const; 104 | const Session& InternalMoveTo(const Element*, const Offset*) const; 105 | const Session& InternalMouseButtonCommand(const char* command, mouse::Button button) const; 106 | 107 | private: 108 | detail::Shared resource_; 109 | detail::Shared factory_; 110 | }; 111 | 112 | } // namespace webdriverxx 113 | 114 | #include "session.inl" 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/http_request.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_HTTP_REQUEST_H 2 | #define WEBDRIVERXX_DETAIL_HTTP_REQUEST_H 3 | 4 | #include "error_handling.h" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace webdriverxx { 10 | namespace detail { 11 | 12 | const char *const kContentTypeJson = "application/json;charset=UTF-8"; 13 | 14 | class HttpHeaders { 15 | public: 16 | HttpHeaders() : head_(nullptr) {} 17 | 18 | ~HttpHeaders() { 19 | curl_slist_free_all(head_); 20 | } 21 | 22 | void Add(const std::string& name, const std::string& value) { 23 | head_ = curl_slist_append(head_, (name + ": " + value).c_str()); 24 | WEBDRIVERXX_CHECK(head_, "Cannot add HTTP header"); 25 | } 26 | 27 | curl_slist* Get() const { 28 | return head_; 29 | } 30 | 31 | private: 32 | curl_slist* head_; 33 | }; 34 | 35 | class HttpRequest { 36 | public: 37 | HttpRequest( 38 | CURL* http_connection, 39 | const std::string& url 40 | ) 41 | : http_connection_(http_connection) 42 | , url_(url) 43 | {} 44 | 45 | virtual ~HttpRequest() {} 46 | 47 | HttpResponse Execute() { 48 | curl_easy_reset(http_connection_); 49 | SetOption(CURLOPT_URL, url_.c_str()); 50 | HttpResponse response; 51 | SetOption(CURLOPT_WRITEFUNCTION, &WriteCallback); 52 | SetOption(CURLOPT_WRITEDATA, &response.body); 53 | char error_message[CURL_ERROR_SIZE]; 54 | SetOption(CURLOPT_ERRORBUFFER, &error_message); 55 | AddHeader("Accept", kContentTypeJson); 56 | 57 | SetCustomRequestOptions(); 58 | 59 | SetOption(CURLOPT_HTTPHEADER, headers_.Get()); 60 | 61 | const CURLcode result = curl_easy_perform(http_connection_); 62 | WEBDRIVERXX_CHECK(result == CURLE_OK, Fmt() 63 | << "Cannot perform HTTP request (" 64 | << "result: " << result 65 | << ", message: " << error_message 66 | << ")" 67 | ); 68 | 69 | response.http_code = GetHttpCode(); 70 | return response; 71 | } 72 | 73 | protected: 74 | virtual void SetCustomRequestOptions() {} 75 | 76 | template 77 | void SetOption(CURLoption option, const T& value) const { 78 | const auto result = curl_easy_setopt(http_connection_, option, value); 79 | WEBDRIVERXX_CHECK(result == CURLE_OK, Fmt() 80 | << "Cannot set HTTP session option (" 81 | << "option: " << option 82 | << ", message: \"" << curl_easy_strerror(result) << "\"" 83 | << ")" 84 | ); 85 | } 86 | 87 | void AddHeader(const std::string& name, const std::string& value) { 88 | headers_.Add(name, value); 89 | } 90 | 91 | private: 92 | long GetHttpCode() const { 93 | long http_code = 0; 94 | const auto result = curl_easy_getinfo(http_connection_, CURLINFO_RESPONSE_CODE, &http_code); 95 | WEBDRIVERXX_CHECK(result == CURLE_OK, Fmt() 96 | << "Cannot get HTTP code (" << curl_easy_strerror(result) << ")" 97 | ); 98 | return http_code; 99 | } 100 | 101 | static 102 | size_t WriteCallback(void* buffer, size_t size, size_t nmemb, void* userdata) { 103 | std::string* data_received = reinterpret_cast(userdata); 104 | const auto buffer_size = size * nmemb; 105 | data_received->append(reinterpret_cast(buffer), buffer_size); 106 | return buffer_size; 107 | } 108 | 109 | private: 110 | HttpRequest(HttpRequest&); 111 | HttpRequest& operator=(HttpRequest&); 112 | 113 | private: 114 | CURL *const http_connection_; 115 | const std::string url_; 116 | HttpHeaders headers_; 117 | }; 118 | 119 | typedef HttpRequest HttpGetRequest; 120 | 121 | class HttpDeleteRequest : public HttpRequest { 122 | public: 123 | HttpDeleteRequest(CURL* http_connection, const std::string& url) 124 | : HttpRequest(http_connection, url) 125 | {} 126 | 127 | private: 128 | void SetCustomRequestOptions() { 129 | SetOption(CURLOPT_CUSTOMREQUEST, "DELETE"); 130 | } 131 | }; 132 | 133 | class HttpPostRequest : public HttpRequest { 134 | public: 135 | HttpPostRequest( 136 | CURL* http_connection, 137 | const std::string& url, 138 | const std::string& upload_data 139 | ) 140 | : HttpRequest(http_connection, url) 141 | , upload_data_(upload_data) 142 | , unsent_ptr_(upload_data.c_str()) 143 | , unsent_length_(upload_data.size()) 144 | {} 145 | 146 | protected: 147 | void SetCustomRequestOptions() { 148 | SetOption(CURLOPT_POST, 1L); 149 | SetOption(CURLOPT_POSTFIELDSIZE, upload_data_.length()); 150 | AddHeader("Content-Type", kContentTypeJson); 151 | SetOption(CURLOPT_READFUNCTION, ReadCallback); 152 | SetOption(CURLOPT_READDATA, this); 153 | } 154 | 155 | private: 156 | static 157 | size_t ReadCallback(void* buffer, size_t size, size_t nmemb, void* userdata) { 158 | HttpPostRequest* that = reinterpret_cast(userdata); 159 | auto buffer_size = size * nmemb; 160 | auto copy_size = that->unsent_length_ < buffer_size ? that->unsent_length_ : buffer_size; 161 | std::copy(that->unsent_ptr_, that->unsent_ptr_ + copy_size, 162 | reinterpret_cast(buffer)); 163 | that->unsent_length_ -= copy_size; 164 | that->unsent_ptr_ += copy_size; 165 | return copy_size; 166 | } 167 | 168 | private: 169 | const std::string& upload_data_; 170 | const char* unsent_ptr_; 171 | size_t unsent_length_; 172 | }; 173 | 174 | } // namespace detail 175 | } // namespace webdriverxx 176 | 177 | #endif 178 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(HEADER_FILES 2 | ../include/webdriverxx.h 3 | ../include/webdriverxx/by.h 4 | ../include/webdriverxx/capabilities.h 5 | ../include/webdriverxx/client.h 6 | ../include/webdriverxx/client.inl 7 | ../include/webdriverxx/conversions.h 8 | ../include/webdriverxx/element.h 9 | ../include/webdriverxx/element.inl 10 | ../include/webdriverxx/errors.h 11 | ../include/webdriverxx/js_args.h 12 | ../include/webdriverxx/keys.h 13 | ../include/webdriverxx/response_status_code.h 14 | ../include/webdriverxx/session.h 15 | ../include/webdriverxx/session.inl 16 | ../include/webdriverxx/types.h 17 | ../include/webdriverxx/wait.h 18 | ../include/webdriverxx/wait_match.h 19 | ../include/webdriverxx/webdriver.h 20 | ../include/webdriverxx/window.h 21 | ../include/webdriverxx/picojson.h 22 | ../include/webdriverxx/browsers/chrome.h 23 | ../include/webdriverxx/browsers/firefox.h 24 | ../include/webdriverxx/browsers/ie.h 25 | ../include/webdriverxx/browsers/phantom.h 26 | ../include/webdriverxx/detail/error_handling.h 27 | ../include/webdriverxx/detail/factories.h 28 | ../include/webdriverxx/detail/factories_impl.h 29 | ../include/webdriverxx/detail/finder.h 30 | ../include/webdriverxx/detail/finder.inl 31 | ../include/webdriverxx/detail/http_client.h 32 | ../include/webdriverxx/detail/http_connection.h 33 | ../include/webdriverxx/detail/http_request.h 34 | ../include/webdriverxx/detail/keyboard.h 35 | ../include/webdriverxx/detail/meta_tools.h 36 | ../include/webdriverxx/detail/resource.h 37 | ../include/webdriverxx/detail/shared.h 38 | ../include/webdriverxx/detail/time.h 39 | ../include/webdriverxx/detail/to_string.h 40 | ../include/webdriverxx/detail/types.h 41 | ) 42 | 43 | set(SOURCE_FILES 44 | alerts_test.cpp 45 | browsers_test.cpp 46 | capabilities_test.cpp 47 | conversions_test.cpp 48 | client_test.cpp 49 | element_test.cpp 50 | environment.h 51 | examples_test.cpp 52 | finder_test.cpp 53 | frames_test.cpp 54 | http_connection_test.cpp 55 | js_test.cpp 56 | keyboard_test.cpp 57 | main.cpp 58 | mouse_test.cpp 59 | resource_test.cpp 60 | session_test.cpp 61 | shared_test.cpp 62 | to_string_test.cpp 63 | wait_match_test.cpp 64 | wait_test.cpp 65 | webdriver_test.cpp 66 | ) 67 | 68 | file(COPY pages DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 69 | 70 | add_definitions(-DWEBDRIVERXX_ENABLE_GMOCK_MATCHERS) 71 | 72 | if (MSVC) 73 | add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS) 74 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /WX") 75 | elseif ( 76 | "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR 77 | "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" 78 | ) 79 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror") 80 | if (NOT ${APPLE}) 81 | set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-fsanitize=address -g -O1 -fno-omit-frame-pointer") 82 | endif() 83 | endif() 84 | 85 | # Coverage 86 | if (UNIX) 87 | set(CMAKE_CONFIGURATION_TYPES ${CMAKE_CONFIGURATION_TYPES} Coverage) 88 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 89 | "Debug" "Release" "MinSizeRel" "RelWithDebInfo" "Coverage") 90 | set(CMAKE_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_DEBUG} --coverage") 91 | set(CMAKE_C_FLAGS_COVERAGE "${CMAKE_C_FLAGS_DEBUG} --coverage") 92 | 93 | find_program(LCOV_EXECUTABLE lcov) 94 | find_program(GENHTML_EXECUTABLE genhtml) 95 | 96 | add_custom_target(coverage 97 | ${LCOV_EXECUTABLE} --directory=. --zerocounters 98 | COMMAND ${CMAKE_CTEST_COMMAND} -V 99 | COMMAND ${LCOV_EXECUTABLE} --capture --directory=. --output-file coverage.info 100 | COMMAND ${GENHTML_EXECUTABLE} coverage.info --output-directory coverage 101 | DEPENDS ${PROJECT_NAME} 102 | ) 103 | endif() 104 | 105 | include(ExternalProject) 106 | 107 | # CURL 108 | if (WIN32) 109 | externalproject_add(curl_project 110 | URL http://curl.haxx.se/download/curl-7.55.0.tar.gz 111 | PREFIX curl 112 | CMAKE_ARGS -DCURL_STATICLIB=ON -DBUILD_SHARED_LIBS=OFF -DBUILD_CURL_EXE=OFF -DBUILD_CURL_TESTS=OFF -DCMAKE_USE_OPENSSL=OFF -DCURL_ZLIB=OFF -DHTTP_ONLY=ON 113 | INSTALL_COMMAND "" 114 | UPDATE_COMMAND "" 115 | ) 116 | externalproject_get_property(curl_project source_dir binary_dir) 117 | include_directories("${source_dir}/include") 118 | link_directories("${binary_dir}/lib") 119 | list(APPEND LIBS libcurl wsock32 ws2_32) 120 | list(APPEND DEPS curl_project) 121 | add_definitions(-DCURL_STATICLIB) 122 | else() 123 | find_package(CURL REQUIRED) 124 | include_directories(${CURL_INCLUDE_DIR}) 125 | list(APPEND LIBS ${CURL_LIBRARIES}) 126 | endif() 127 | 128 | # Google test 129 | externalproject_add(googletest 130 | PREFIX googletest 131 | GIT_REPOSITORY https://github.com/google/googletest 132 | UPDATE_COMMAND "" 133 | ) 134 | externalproject_get_property(googletest source_dir) 135 | include_directories("${source_dir}") 136 | list(APPEND DEPS googletest) 137 | list(APPEND LIBS gtest gmock) 138 | 139 | # pthread 140 | if (UNIX) 141 | set(CMAKE_THREAD_PREFER_PTHREAD TRUE) 142 | find_package(Threads REQUIRED) 143 | list(APPEND LIBS ${CMAKE_THREAD_LIBS_INIT}) 144 | endif() 145 | 146 | add_library(${PROJECT_NAME} ${SOURCE_FILES} ${HEADER_FILES}) 147 | add_dependencies(${PROJECT_NAME} ${DEPS}) 148 | target_link_libraries(${PROJECT_NAME} ${LIBS}) 149 | 150 | add_executable(${PROJECT_NAME}_test ${SOURCE_FILES} ${HEADER_FILES}) 151 | add_dependencies(${PROJECT_NAME}_test ${DEPS}) 152 | target_link_libraries(${PROJECT_NAME}_test ${LIBS}) 153 | add_test(${PROJECT_NAME}_test ${PROJECT_NAME}) 154 | 155 | if (APPLE) 156 | set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME} SUFFIX ".dylib") 157 | endif() 158 | 159 | if (UNIX AND NOT APPLE) 160 | set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME} SUFFIX ".so") 161 | endif() 162 | 163 | if (WIN32) 164 | set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME} SUFFIX ".dll") 165 | endif() 166 | 167 | install(TARGETS ${PROJECT_NAME} DESTINATION /usr/local/lib) 168 | -------------------------------------------------------------------------------- /test/conversions_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace test { 7 | 8 | using namespace webdriverxx; 9 | 10 | TEST(ToJson, ConvertsIntegralTypes) { 11 | ASSERT_EQ(123, ToJson(123).get()); 12 | int i = 123; 13 | ASSERT_EQ(123, ToJson(i).get()); 14 | ASSERT_EQ(123, ToJson(static_cast(123)).get()); 15 | ASSERT_EQ(123.5, ToJson(123.5).get()); 16 | ASSERT_TRUE(ToJson(true).get()); 17 | ASSERT_FALSE(ToJson(false).get()); 18 | } 19 | 20 | TEST(ToJson, ConvertsStrings) { 21 | ASSERT_EQ("abc", ToJson("abc").get()); 22 | std::string s("abc"); 23 | ASSERT_EQ("abc", ToJson(s).get()); 24 | ASSERT_EQ("abc", ToJson(s.c_str()).get()); 25 | ASSERT_EQ("abc", ToJson(const_cast(s.c_str())).get()); 26 | char a[] = "abc"; 27 | ASSERT_EQ("abc", ToJson(a).get()); 28 | } 29 | 30 | TEST(ToJson, ConvertsIterables) { 31 | int i[] = { 123, 456, 789 }; 32 | const auto ji = ToJson(i); 33 | ASSERT_TRUE(ji.is()); 34 | ASSERT_EQ(3u, ji.get().size()); 35 | ASSERT_EQ(789, ji.get()[2].get()); 36 | 37 | const char* s[] = { "abc", "def", "ghi" }; 38 | const auto js = ToJson(s); 39 | ASSERT_TRUE(js.is()); 40 | ASSERT_EQ(3u, js.get().size()); 41 | ASSERT_EQ("ghi", js.get()[2].get()); 42 | 43 | std::vector v(std::begin(i), std::end(i)); 44 | const auto jv = ToJson(v); 45 | ASSERT_TRUE(jv.is()); 46 | ASSERT_EQ(3u, jv.get().size()); 47 | ASSERT_EQ(789, jv.get()[2].get()); 48 | 49 | std::list l(std::begin(i), std::end(i)); 50 | const auto jl = ToJson(l); 51 | ASSERT_TRUE(jl.is()); 52 | ASSERT_EQ(3u, jl.get().size()); 53 | ASSERT_EQ(789, jl.get()[2].get()); 54 | 55 | int ii[2][3] = { { 1, 2, 3 }, { 4, 5, 123 } }; 56 | const auto jii = ToJson(ii); 57 | ASSERT_TRUE(jii.is()); 58 | ASSERT_EQ(2u, jii.get().size()); 59 | ASSERT_TRUE(jii.get()[0].is()); 60 | ASSERT_TRUE(jii.get()[1].is()); 61 | ASSERT_EQ(3u, jii.get()[0].get().size()); 62 | ASSERT_EQ(3u, jii.get()[1].get().size()); 63 | ASSERT_EQ(123, jii.get()[1].get()[2].get()); 64 | 65 | std::vector e; 66 | const auto je = ToJson(e); 67 | ASSERT_TRUE(je.is()); 68 | ASSERT_EQ(0u, je.get().size()); 69 | } 70 | 71 | namespace { 72 | namespace custom { 73 | 74 | struct Object { 75 | std::string string; 76 | int number; 77 | }; 78 | 79 | picojson::value CustomToJson(const Object& value) { 80 | return JsonObject() 81 | .Set("string", value.string) 82 | .Set("number", value.number); 83 | } 84 | 85 | void CustomFromJson(const picojson::value& value, Object& result) { 86 | WEBDRIVERXX_CHECK(value.is(), "custom::Object is not an object"); 87 | result.string = FromJson(value.get("string")); 88 | result.number = FromJson(value.get("number")); 89 | } 90 | 91 | } // namespace custom 92 | } // namespace 93 | 94 | TEST(ToJson, ConvertsCustomObjects) { 95 | custom::Object o = { "abc", 123 }; 96 | const auto jo = ToJson(o); 97 | ASSERT_TRUE(jo.is()); 98 | ASSERT_EQ(123, jo.get("number").get()); 99 | ASSERT_EQ("abc", jo.get("string").get()); 100 | 101 | const custom::Object os[] = { { "abc", 123 }, { "def", 456 } }; 102 | const auto jos = ToJson(os); 103 | ASSERT_TRUE(jos.is()); 104 | ASSERT_EQ(2u, jos.get().size()); 105 | ASSERT_TRUE(jos.get()[0].is()); 106 | ASSERT_EQ(456, jos.get()[1].get("number").get()); 107 | } 108 | 109 | picojson::value J(const std::string& json) { 110 | picojson::value result; 111 | picojson::parse(result, json.begin(), json.end(), nullptr); 112 | return result; 113 | } 114 | 115 | TEST(FromJson, ConvertsIntegralTypes) { 116 | ASSERT_EQ(123, FromJson(J("123"))); 117 | ASSERT_EQ(123.5, FromJson(J("123.5"))); 118 | ASSERT_EQ(123u, FromJson(J("123"))); 119 | ASSERT_FALSE(FromJson(J("false"))); 120 | ASSERT_TRUE(FromJson(J("true"))); 121 | } 122 | 123 | TEST(FromJson, ConvertsStrings) { 124 | ASSERT_EQ("abc", FromJson(J("\"abc\""))); 125 | } 126 | 127 | TEST(FromJson, ConvertsIterables) { 128 | const auto i = FromJson>(J("[ 123, 456, 789 ]")); 129 | ASSERT_EQ(3u, i.size()); 130 | ASSERT_EQ(123, i[0]); 131 | ASSERT_EQ(789, i[2]); 132 | 133 | const auto s = FromJson>(J("[ \"abc\", \"def\" ]")); 134 | ASSERT_EQ(2u, s.size()); 135 | ASSERT_EQ("abc", s.front()); 136 | ASSERT_EQ("def", s.back()); 137 | 138 | const auto e = FromJson>(J("[]")); 139 | ASSERT_EQ(0u, e.size()); 140 | 141 | const auto ii = FromJson>>(J("[ [ 123, 456 ], [ 789 ] ]")); 142 | ASSERT_EQ(2u, ii.size()); 143 | ASSERT_EQ(2u, ii[0].size()); 144 | ASSERT_EQ(1u, ii[1].size()); 145 | ASSERT_EQ(456, ii[0].back()); 146 | ASSERT_EQ(789, ii[1].front()); 147 | } 148 | 149 | TEST(FromJson, ConvertsCustomObjects) { 150 | const auto o = FromJson(J("{ \"number\": 123, \"string\": \"abc\" }")); 151 | ASSERT_EQ(123, o.number); 152 | ASSERT_EQ("abc", o.string); 153 | 154 | const auto os = FromJson>(J( 155 | "[ { \"number\": 123, \"string\": \"abc\" }," 156 | "{ \"number\": 456, \"string\": \"def\" } ]" 157 | )); 158 | ASSERT_EQ(2u, os.size()); 159 | ASSERT_EQ(123, os[0].number); 160 | ASSERT_EQ("abc", os[0].string); 161 | ASSERT_EQ(456, os[1].number); 162 | ASSERT_EQ("def", os[1].string); 163 | } 164 | 165 | } // namespace test 166 | -------------------------------------------------------------------------------- /test/js_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | #include 5 | 6 | namespace test { 7 | 8 | using namespace webdriverxx; 9 | 10 | class TestJsExecutor : public ::testing::Test { 11 | protected: 12 | static void SetUpTestCase() { 13 | GetDriver().Navigate(GetTestPageUrl("js.html")); 14 | } 15 | 16 | TestJsExecutor() : driver(GetDriver()) {} 17 | 18 | WebDriver driver; 19 | }; 20 | 21 | TEST_F(TestJsExecutor, ExecutesSimpleScript) { 22 | driver.Execute("document.title = 'abc'"); 23 | ASSERT_EQ("abc", driver.GetTitle()); 24 | } 25 | 26 | TEST_F(TestJsExecutor, CanPassStringArgument) { 27 | driver.Execute("document.title = arguments[0]", JsArgs() << std::string("abc")); 28 | ASSERT_EQ("abc", driver.GetTitle()); 29 | } 30 | 31 | TEST_F(TestJsExecutor, CanPassStringLiteralArgument) { 32 | driver.Execute("document.title = arguments[0]", JsArgs() << "abc"); 33 | ASSERT_EQ("abc", driver.GetTitle()); 34 | } 35 | 36 | TEST_F(TestJsExecutor, CanPassNumberArgument) { 37 | driver.Execute("document.title = String(arguments[0] + 21)", JsArgs() << 21); 38 | ASSERT_EQ("42", driver.GetTitle()); 39 | driver.Execute("document.title = String(arguments[0] + 21)", JsArgs() << 21.5); 40 | ASSERT_EQ("42.5", driver.GetTitle()); 41 | } 42 | 43 | TEST_F(TestJsExecutor, CanPassBooleanArgument) { 44 | driver.Execute("a = arguments; document.title = [ typeof(a[0]), a[0], typeof(a[1]), a[1] ].join(',')", 45 | JsArgs() << true << false); 46 | ASSERT_EQ("boolean,true,boolean,false", driver.GetTitle()); 47 | } 48 | 49 | TEST_F(TestJsExecutor, CanPassMoreThanOneArgument) { 50 | driver.Execute("document.title = arguments[0] + ',' + arguments[1]", 51 | JsArgs() << "abc" << "def"); 52 | ASSERT_EQ("abc,def", driver.GetTitle()); 53 | } 54 | 55 | TEST_F(TestJsExecutor, CanPassElement) { 56 | Element e = driver.FindElement(ByTag("input")).Clear(); 57 | ASSERT_EQ("", e.GetAttribute("value")); 58 | driver.Execute("arguments[0].value = arguments[1]", 59 | JsArgs() << e << "abc"); 60 | ASSERT_EQ("abc", e.GetAttribute("value")); 61 | } 62 | 63 | TEST_F(TestJsExecutor, CanPassArray) { 64 | std::vector numbers; 65 | numbers.push_back(123); 66 | numbers.push_back(321); 67 | driver.Execute("document.title = arguments[0][0] + arguments[0][1]", 68 | JsArgs() << numbers); 69 | ASSERT_EQ("444", driver.GetTitle()); 70 | } 71 | 72 | TEST_F(TestJsExecutor, CanPassOtherContainers) { 73 | std::list numbers; 74 | numbers.push_back(123); 75 | numbers.push_back(321); 76 | driver.Execute("document.title = arguments[0][0] + arguments[0][1]", 77 | JsArgs() << numbers); 78 | ASSERT_EQ("444", driver.GetTitle()); 79 | } 80 | 81 | TEST_F(TestJsExecutor, CanPassCArray) { 82 | const char* colors[] = { "red", "green", "blue" }; 83 | driver.Execute("document.title = arguments[0].reverse().join(', ')", 84 | JsArgs() << colors); 85 | ASSERT_EQ("blue, green, red", driver.GetTitle()); 86 | } 87 | 88 | namespace { 89 | namespace custom { 90 | 91 | struct Object { 92 | std::string string; 93 | int number; 94 | }; 95 | 96 | picojson::value CustomToJson(const Object& value) { 97 | return JsonObject() 98 | .Set("string", value.string) 99 | .Set("number", value.number); 100 | } 101 | 102 | void CustomFromJson(const picojson::value& value, Object& result) { 103 | WEBDRIVERXX_CHECK(value.is(), "custom::Object is not an object"); 104 | result.string = FromJson(value.get("string")); 105 | result.number = FromJson(value.get("number")); 106 | } 107 | 108 | } // namespace custom 109 | } // namespace 110 | 111 | TEST_F(TestJsExecutor, CanPassCustomObject) { 112 | custom::Object o = { "abc", 123 }; 113 | driver.Execute("o = arguments[0]; document.title = (o.string + 'def') + (o.number + 1)", 114 | JsArgs() << o); 115 | ASSERT_EQ("abcdef124", driver.GetTitle()); 116 | } 117 | 118 | /////////////////////////////////////////////////////////////////////////// 119 | 120 | TEST_F(TestJsExecutor, EvalsString) { 121 | ASSERT_EQ("abc", driver.Eval("return 'abc'")); 122 | } 123 | 124 | TEST_F(TestJsExecutor, EvalsNumber) { 125 | ASSERT_EQ(123, driver.Eval("return 123")); 126 | ASSERT_EQ(123.5, driver.Eval("return 123.5")); 127 | } 128 | 129 | TEST_F(TestJsExecutor, EvalsBoolean) { 130 | ASSERT_TRUE(true == driver.Eval("return true")); 131 | ASSERT_TRUE(false == driver.Eval("return false")); 132 | } 133 | 134 | TEST_F(TestJsExecutor, EvalsElement) { 135 | Element e = driver.FindElement(ByTag("input")); 136 | ASSERT_EQ(e, driver.Eval("return document.getElementsByTagName('input')[0]")); 137 | } 138 | 139 | TEST_F(TestJsExecutor, EvalsCustomObject) { 140 | custom::Object o = driver.Eval("return { string: 'abc', number: 123 }"); 141 | ASSERT_EQ("abc", o.string); 142 | ASSERT_EQ(123, o.number); 143 | } 144 | 145 | TEST_F(TestJsExecutor, EvalsArrayOfStrings) { 146 | std::vector v = driver.Eval>( 147 | "return [ 'abc', 'def' ]" 148 | ); 149 | ASSERT_EQ(2u, v.size()); 150 | ASSERT_EQ("abc", v[0]); 151 | ASSERT_EQ("def", v[1]); 152 | } 153 | 154 | TEST_F(TestJsExecutor, EvalsArrayOfNumbers) { 155 | std::vector v = driver.Eval>( 156 | "return [ 123, 456 ]" 157 | ); 158 | ASSERT_EQ(2u, v.size()); 159 | ASSERT_EQ(123, v[0]); 160 | ASSERT_EQ(456, v[1]); 161 | } 162 | 163 | /////////////////////////////////////////////////////////////////////////// 164 | 165 | // Makes asynchronous script from synchronous one 166 | std::string AsyncScript(const std::string& script) { 167 | return std::string() 168 | + "var args = Array.prototype.slice.call(arguments, 0);" 169 | + "var callback = args.pop();" 170 | + "setTimeout(function(){" 171 | + "var result = (function(){" + script + "}).apply(this, args);" 172 | + "callback(result)" 173 | + "}, 0);" 174 | ; 175 | } 176 | 177 | TEST_F(TestJsExecutor, ExecutesSimpleAsyncScript) { 178 | if (IsPhantom()) return; // Crashes PhantomJS 1.9.7 179 | driver.ExecuteAsync(AsyncScript("document.title = 'abc'")); 180 | ASSERT_EQ("abc", driver.GetTitle()); 181 | } 182 | 183 | TEST_F(TestJsExecutor, PassesArgumentsToAsyncScript) { 184 | if (IsPhantom()) return; // Crashes PhantomJS 1.9.7 185 | driver.ExecuteAsync(AsyncScript("document.title = JSON.stringify(Array.prototype.slice.call(arguments, 0))"), 186 | JsArgs() << std::string("abc") << 123 << true); 187 | ASSERT_EQ("[\"abc\",123,true]", driver.GetTitle()); 188 | } 189 | 190 | TEST_F(TestJsExecutor, ReturnsValueFromAsyncScript) { 191 | if (IsPhantom()) return; // Crashes PhantomJS 1.9.7 192 | ASSERT_EQ(123, driver.EvalAsync(AsyncScript("return 123"))); 193 | } 194 | 195 | TEST_F(TestJsExecutor, ReturnsElementFromAsyncScript) { 196 | if (IsPhantom()) return; // Crashes PhantomJS 1.9.7 197 | Element e = driver.FindElement(ByTag("input")); 198 | ASSERT_EQ(e, driver.EvalAsync(AsyncScript( 199 | "return document.getElementsByTagName('input')[0]"))); 200 | } 201 | 202 | } // namespace test 203 | -------------------------------------------------------------------------------- /test/session_test.cpp: -------------------------------------------------------------------------------- 1 | #include "environment.h" 2 | #include 3 | #include 4 | #include 5 | 6 | namespace test { 7 | 8 | using namespace webdriverxx; 9 | 10 | class TestSession : public ::testing::Test { 11 | protected: 12 | TestSession() : driver(GetDriver()) {} 13 | 14 | void ReplaceSpoiledSession() { 15 | driver = GetFreshDriver(); 16 | } 17 | 18 | WebDriver driver; 19 | }; 20 | 21 | TEST_F(TestSession, GetsCapabilities) 22 | { 23 | Capabilities c = driver.GetCapabilities(); 24 | ASSERT_TRUE(c.Has("browserName")); 25 | ASSERT_TRUE(c.Has("version")); 26 | ASSERT_TRUE(c.Has("platform")); 27 | ASSERT_NE("", c.GetBrowserName()); 28 | } 29 | 30 | TEST_F(TestSession, StartsSecondBrowser) { 31 | WebDriver second = CreateDriver(); 32 | } 33 | 34 | TEST_F(TestSession, GetsCurrentWindow) { 35 | driver.GetCurrentWindow(); 36 | } 37 | 38 | TEST_F(TestSession, GetsWindowHandle) { 39 | ASSERT_NE("", driver.GetCurrentWindow().GetHandle()); 40 | } 41 | 42 | TEST_F(TestSession, SetsFocusToWindow) { 43 | driver.SetFocusToWindow(driver.GetCurrentWindow().GetHandle()); 44 | } 45 | 46 | TEST_F(TestSession, ClosesCurrentWindow) { 47 | driver.CloseCurrentWindow(); 48 | ReplaceSpoiledSession(); 49 | } 50 | 51 | TEST_F(TestSession, GetsWindowSize) { 52 | Window window = driver.GetCurrentWindow(); 53 | window.GetSize(); 54 | } 55 | 56 | TEST_F(TestSession, SetsWindowSize) { 57 | Window window = driver.GetCurrentWindow(); 58 | Size size1; 59 | size1.width = 601; 60 | size1.height = 602; 61 | window.SetSize(size1); 62 | Size size2 = window.GetSize(); 63 | ASSERT_EQ(601, size2.width); 64 | ASSERT_EQ(602, size2.height); 65 | } 66 | 67 | TEST_F(TestSession, GetsWindowPosition) { 68 | Window window = driver.GetCurrentWindow(); 69 | window.GetPosition(); 70 | } 71 | 72 | TEST_F(TestSession, SetsWindowPosition) { 73 | if (IsPhantom()) return; 74 | Window window = driver.GetCurrentWindow(); 75 | Point position1; 76 | position1.x = 101; 77 | position1.y = 102; 78 | window.SetPosition(position1); 79 | Point position2 = window.GetPosition(); 80 | ASSERT_EQ(101, position2.x); 81 | ASSERT_EQ(102, position2.y); 82 | } 83 | 84 | TEST_F(TestSession, MaximizesWindow) { 85 | Window window = driver.GetCurrentWindow(); 86 | window.Maximize(); 87 | } 88 | 89 | TEST_F(TestSession, GetsWindows) { 90 | driver.GetWindows(); 91 | } 92 | 93 | TEST_F(TestSession, Navigates) { 94 | std::string url = GetWebDriverUrl() + "status"; 95 | driver.Navigate(url); 96 | ASSERT_EQ(url, driver.GetUrl()); 97 | } 98 | 99 | TEST_F(TestSession, NavigatesToTestPage) { 100 | const std::string url = GetTestPageUrl("session.html"); 101 | driver.Navigate(url); 102 | ASSERT_EQ(url, driver.GetUrl()); 103 | } 104 | 105 | TEST_F(TestSession, GoesBack) { 106 | const std::string page1 = GetTestPageUrl("navigation1.html"); 107 | const std::string page2 = GetTestPageUrl("navigation2.html"); 108 | driver.Navigate(page1).Navigate(page2).Back(); 109 | ASSERT_EQ(page1, driver.GetUrl()); 110 | } 111 | 112 | TEST_F(TestSession, GoesForward) { 113 | const std::string page1 = GetTestPageUrl("navigation1.html"); 114 | const std::string page2 = GetTestPageUrl("navigation2.html"); 115 | driver.Navigate(page1).Navigate(page2).Back().Forward(); 116 | ASSERT_EQ(page2, driver.GetUrl()); 117 | } 118 | 119 | TEST_F(TestSession, DoesRefresh) { 120 | const std::string page = GetTestPageUrl("navigation1.html"); 121 | driver.Navigate(page).FindElement(ByTag("input")).Click().SendKeys("abc"); 122 | ASSERT_EQ("abc", driver.FindElement(ByTag("input")).GetAttribute("value")); 123 | driver.Refresh(); 124 | ASSERT_EQ(page, driver.GetUrl()); 125 | ASSERT_EQ("", driver.FindElement(ByTag("input")).GetAttribute("value")); 126 | } 127 | 128 | TEST_F(TestSession, GetsPageSource) { 129 | driver.Navigate(GetTestPageUrl("session.html")); 130 | std::string source = driver.GetSource(); 131 | ASSERT_NE(std::string::npos, source.find("")); 133 | } 134 | 135 | TEST_F(TestSession, GetsPageTitle) { 136 | driver.Navigate(GetTestPageUrl("session.html")); 137 | ASSERT_EQ("Test title", driver.GetTitle()); 138 | } 139 | 140 | TEST_F(TestSession, GetsScreenshot) { 141 | driver.Navigate(GetTestPageUrl("session.html")); 142 | ASSERT_TRUE(!driver.GetScreenshot().empty()); 143 | } 144 | 145 | TEST_F(TestSession, SetsTimeouts) { 146 | driver.SetTimeoutMs(timeout::Implicit, 1000); 147 | driver.SetTimeoutMs(timeout::PageLoad, 1000); 148 | driver.SetTimeoutMs(timeout::Script, 1000); 149 | } 150 | 151 | TEST_F(TestSession, SetsAsyncScriptTimeout) { 152 | driver.SetAsyncScriptTimeoutMs(1000); 153 | } 154 | 155 | TEST_F(TestSession, SetsImplicitTimeout) { 156 | driver.SetImplicitTimeoutMs(1000); 157 | } 158 | 159 | TEST_F(TestSession, GetsActiveElement) { 160 | driver.Navigate(GetTestPageUrl("session.html")); 161 | Element e = driver.FindElement(ByTag("input")); 162 | e.Click(); 163 | ASSERT_EQ(e, driver.GetActiveElement()); 164 | } 165 | 166 | Cookie FindCookie( 167 | const std::vector& cookies, 168 | const std::string& name, 169 | const Cookie& default_value = Cookie() 170 | ) { 171 | const auto it = std::find_if(cookies.begin(), cookies.end(), 172 | [&name](const Cookie& cookie){ 173 | return cookie.name == name; 174 | }); 175 | return it != cookies.end() ? *it : default_value; 176 | } 177 | 178 | namespace webdriverxx { 179 | 180 | void PrintTo(const Cookie& c, ::std::ostream* os) { 181 | *os << ToJson(c).serialize(); 182 | } 183 | 184 | } // namespace webdriverxx 185 | 186 | TEST_F(TestSession, SetsAndGetsCookies) { 187 | driver.Navigate(GetTestPageUrl("session.html")); 188 | driver.SetCookie(Cookie("name1", "value1")).SetCookie(Cookie("name2", "value2")); 189 | std::vector cookies = driver.GetCookies(); 190 | ASSERT_TRUE(cookies.size() >= 2u); 191 | ASSERT_EQ("value1", FindCookie(cookies, "name1").value); 192 | ASSERT_EQ("value2", FindCookie(cookies, "name2").value); 193 | } 194 | 195 | TEST_F(TestSession, SetsAllFieldsOfACookie) { 196 | const Cookie c1("name1", "value1", "/path1", "domain1.com", true, true, 123); 197 | const Cookie c2("name2", "value2", "/path2", "domain2.com", false, false, 124); 198 | ASSERT_EQ(c1, FromJson(ToJson(c1))); 199 | ASSERT_EQ(c2, FromJson(ToJson(c2))); 200 | } 201 | 202 | TEST_F(TestSession, DeletesCookies) { 203 | driver.Navigate(GetTestPageUrl("session.html")); 204 | driver.SetCookie(Cookie("name1", "value1")).SetCookie(Cookie("name2", "value2")); 205 | driver.DeleteCookies(); 206 | ASSERT_EQ(0u, driver.GetCookies().size()); 207 | } 208 | 209 | TEST_F(TestSession, DeletesACookie) { 210 | driver.Navigate(GetTestPageUrl("session.html")); 211 | driver.SetCookie(Cookie("name1", "value1")).SetCookie(Cookie("name2", "value2")); 212 | driver.DeleteCookie("name1"); 213 | std::vector cookies = driver.GetCookies(); 214 | ASSERT_EQ("", FindCookie(cookies, "name1").value); 215 | ASSERT_EQ("value2", FindCookie(cookies, "name2").value); 216 | } 217 | 218 | } // namespace test 219 | -------------------------------------------------------------------------------- /include/webdriverxx/capabilities.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_CAPABILITIES_H 2 | #define WEBDRIVERXX_CAPABILITIES_H 3 | 4 | #include "conversions.h" 5 | #include "picojson.h" 6 | #include 7 | 8 | namespace webdriverxx { 9 | 10 | namespace browser { 11 | typedef std::string Value; 12 | typedef const char* const ConstValue; 13 | ConstValue Android = "android"; 14 | ConstValue Chrome = "chrome"; 15 | ConstValue Firefox = "firefox"; 16 | ConstValue HtmlUnit = "htmlunit"; 17 | ConstValue InternetExplorer = "internet explorer"; 18 | ConstValue IPhone = "iPhone"; 19 | ConstValue IPad = "iPad"; 20 | ConstValue Mock = "mock"; 21 | ConstValue Opera = "opera"; 22 | ConstValue Safari = "safari"; 23 | ConstValue Phantom = "phantomjs"; 24 | } // namespace browser 25 | 26 | namespace platform { 27 | typedef std::string Value; 28 | typedef const char* const ConstValue; 29 | ConstValue Any = "ANY"; 30 | ConstValue Windows = "WINDOWS"; 31 | ConstValue Xp = "XP"; 32 | ConstValue Vista = "VISTA"; 33 | ConstValue Mac = "MAC"; 34 | ConstValue Linux = "LINUX"; 35 | ConstValue Unix = "UNIX"; 36 | ConstValue Android = "ANDROID"; 37 | } // namespace platform 38 | 39 | namespace unexpected_alert_behaviour { 40 | typedef std::string Value; 41 | typedef const char* const ConstValue; 42 | ConstValue Accept = "accept"; 43 | ConstValue Dismiss = "dismiss"; 44 | ConstValue Ignore = "ignore"; 45 | } // namespace unexpected_alert_behaviour 46 | 47 | namespace proxy_type { 48 | typedef std::string Value; 49 | typedef const char* const ConstValue; 50 | ConstValue Direct = "direct"; 51 | ConstValue Manual = "manual"; // Manual proxy settings configured, e.g. setting a proxy for HTTP, a proxy for FTP 52 | ConstValue Pac = "pac"; // Proxy autoconfiguration from a URL 53 | ConstValue Autodetect = "autodetect"; // Proxy autodetection, probably with WPAD 54 | ConstValue System = "system"; // Use system settings 55 | } // namespace proxy_type 56 | 57 | #define WEBDRIVERXX_PROPERTIES_BEGIN(this_class) typedef this_class This; 58 | #define WEBDRIVERXX_PROPERTIES_END() 59 | 60 | #define WEBDRIVERXX_PROPERTY_RONLY(name, id, type) \ 61 | type Get##name() const { return GetOptional(id); } \ 62 | bool Has##name() { return Has(id); } 63 | 64 | #define WEBDRIVERXX_PROPERTY(name, id, type) \ 65 | WEBDRIVERXX_PROPERTY_RONLY(name, id, type) \ 66 | This& Set##name(const type& value) { Set(id, value); return *this; } 67 | 68 | struct Proxy : JsonObject { // copyable 69 | WEBDRIVERXX_PROPERTIES_BEGIN(Proxy) 70 | WEBDRIVERXX_PROPERTY(ProxyType, "proxyType", proxy_type::Value) 71 | WEBDRIVERXX_PROPERTIES_END() 72 | }; 73 | 74 | struct DirectConnection : Proxy { // copyable 75 | DirectConnection() { SetProxyType(proxy_type::Direct); } 76 | }; 77 | 78 | struct AutodetectProxy : Proxy { // copyable 79 | AutodetectProxy() { SetProxyType(proxy_type::Autodetect); } 80 | }; 81 | 82 | struct SystemProxy : Proxy { // copyable 83 | SystemProxy() { SetProxyType(proxy_type::System); } 84 | }; 85 | 86 | struct AutomaticProxyFromUrl : Proxy { // copyable 87 | explicit AutomaticProxyFromUrl(const std::string& url) { 88 | SetProxyType(proxy_type::Pac); 89 | SetAutoconfigUrl(url); 90 | } 91 | 92 | WEBDRIVERXX_PROPERTIES_BEGIN(AutomaticProxyFromUrl) 93 | WEBDRIVERXX_PROPERTY(AutoconfigUrl, "proxyAutoconfigUrl", std::string) 94 | WEBDRIVERXX_PROPERTIES_END() 95 | }; 96 | 97 | struct ManualProxy : Proxy { // copyable 98 | ManualProxy() { SetProxyType(proxy_type::Manual); } 99 | 100 | WEBDRIVERXX_PROPERTIES_BEGIN(ManualProxy) 101 | WEBDRIVERXX_PROPERTY(NoProxyFor, "noProxy", std::string) 102 | WEBDRIVERXX_PROPERTIES_END() 103 | }; 104 | 105 | struct FtpProxy : ManualProxy { // copyable 106 | explicit FtpProxy(const std::string& address) { SetProxyAddress(address); } 107 | 108 | WEBDRIVERXX_PROPERTIES_BEGIN(FtpProxy) 109 | WEBDRIVERXX_PROPERTY(ProxyAddress, "ftpProxy", std::string) 110 | WEBDRIVERXX_PROPERTIES_END() 111 | }; 112 | 113 | struct HttpProxy : ManualProxy { // copyable 114 | explicit HttpProxy(const std::string& address) { SetProxyAddress(address); } 115 | 116 | WEBDRIVERXX_PROPERTIES_BEGIN(HttpProxy) 117 | WEBDRIVERXX_PROPERTY(ProxyAddress, "httpProxy", std::string) 118 | WEBDRIVERXX_PROPERTIES_END() 119 | }; 120 | 121 | struct SslProxy : ManualProxy { // copyable 122 | explicit SslProxy(const std::string& address) { SetProxyAddress(address); } 123 | 124 | WEBDRIVERXX_PROPERTIES_BEGIN(SslProxy) 125 | WEBDRIVERXX_PROPERTY(ProxyAddress, "sslProxy", std::string) 126 | WEBDRIVERXX_PROPERTIES_END() 127 | }; 128 | 129 | struct SocksProxy : ManualProxy { // copyable 130 | explicit SocksProxy(const std::string& address) { SetProxyAddress(address); } 131 | 132 | WEBDRIVERXX_PROPERTIES_BEGIN(SocksProxy) 133 | WEBDRIVERXX_PROPERTY(ProxyAddress, "socksProxy", std::string) 134 | WEBDRIVERXX_PROPERTY(Username, "socksUsername", std::string) 135 | WEBDRIVERXX_PROPERTY(Password, "socksPassword", std::string) 136 | WEBDRIVERXX_PROPERTIES_END() 137 | }; 138 | 139 | namespace log_level { 140 | typedef std::string Value; 141 | typedef const char* const ConstValue; 142 | ConstValue Off = "OFF"; 143 | ConstValue Severe = "SEVERE"; 144 | ConstValue Warning = "WARNING"; 145 | ConstValue Info = "INFO"; 146 | ConstValue Config = "CONFIG"; 147 | ConstValue Fine = "FINE"; 148 | ConstValue Finer = "FINER"; 149 | ConstValue Finest = "FINEST"; 150 | ConstValue All = "ALL"; 151 | } // namespace log_level 152 | 153 | struct LoggingPrefs : JsonObject { 154 | WEBDRIVERXX_PROPERTIES_BEGIN(LoggingPrefs) 155 | WEBDRIVERXX_PROPERTY(Level, "driver", log_level::Value) 156 | WEBDRIVERXX_PROPERTIES_END() 157 | }; 158 | 159 | // List of keys and values indicating features that server can or should provide. 160 | struct Capabilities : JsonObject { // copyable 161 | Capabilities() {} 162 | explicit Capabilities(const picojson::object& object) : JsonObject(object) {} 163 | 164 | // Hardcoded capabilities are here just to add some sugar. 165 | // If a capability is not listed below use Get/Set/Has public members. 166 | 167 | WEBDRIVERXX_PROPERTIES_BEGIN(Capabilities) 168 | WEBDRIVERXX_PROPERTY(BrowserName, "browserName", browser::Value) 169 | WEBDRIVERXX_PROPERTY(Version, "version", std::string) 170 | WEBDRIVERXX_PROPERTY(Platform, "platform", platform::Value) 171 | 172 | WEBDRIVERXX_PROPERTY_RONLY(TakesScreenshot, "takesScreenshot", bool) 173 | WEBDRIVERXX_PROPERTY_RONLY(HandlesAlerts, "handlesAlerts", bool) 174 | WEBDRIVERXX_PROPERTY_RONLY(CssSelectorsEnabled, "cssSelectorsEnabled", bool) 175 | 176 | WEBDRIVERXX_PROPERTY(JavascriptEnabled, "javascriptEnabled", bool) 177 | WEBDRIVERXX_PROPERTY(DatabaseEnabled, "databaseEnabled", bool) 178 | WEBDRIVERXX_PROPERTY(LocationContextEnabled, "locationContextEnabled", bool) 179 | WEBDRIVERXX_PROPERTY(ApplicationCacheEnabled, "applicationCacheEnabled", bool) 180 | WEBDRIVERXX_PROPERTY(BrowserConnectionEnabled, "browserConnectionEnabled", bool) 181 | WEBDRIVERXX_PROPERTY(WebStorageEnabled, "webStorageEnabled", bool) 182 | WEBDRIVERXX_PROPERTY(AcceptSslCerts, "acceptSslCerts", bool) 183 | WEBDRIVERXX_PROPERTY(Rotatable, "rotatable", bool) 184 | WEBDRIVERXX_PROPERTY(NativeEvents, "nativeEvents", bool) 185 | WEBDRIVERXX_PROPERTY(Proxy, "proxy", Proxy) 186 | WEBDRIVERXX_PROPERTY(UnexpectedAlertBehaviour, "unexpectedAlertBehaviour", unexpected_alert_behaviour::Value) 187 | WEBDRIVERXX_PROPERTY(ElementScrollBehavior, "elementScrollBehavior", int) 188 | 189 | WEBDRIVERXX_PROPERTY_RONLY(SessionId, "webdriver.remote.sessionid", std::string) 190 | WEBDRIVERXX_PROPERTY(QuietExceptions, "webdriver.remote.quietExceptions", bool) 191 | WEBDRIVERXX_PROPERTIES_END() 192 | }; 193 | 194 | inline 195 | void CustomFromJson(const picojson::value& value, Capabilities& result) { 196 | WEBDRIVERXX_CHECK(value.is(), "Capabilities is not an object"); 197 | result = Capabilities(value.get()); 198 | } 199 | 200 | } // namespace webdriverxx 201 | 202 | #endif 203 | -------------------------------------------------------------------------------- /include/webdriverxx/detail/resource.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_DETAIL_RESOURCE_H 2 | #define WEBDRIVERXX_DETAIL_RESOURCE_H 3 | 4 | #include "error_handling.h" 5 | #include "http_client.h" 6 | #include "shared.h" 7 | #include "../conversions.h" 8 | #include "../response_status_code.h" 9 | #include "../picojson.h" 10 | 11 | namespace webdriverxx { 12 | namespace detail { 13 | 14 | class Resource : public SharedObjectBase { // noncopyable 15 | public: 16 | enum Ownership { IsOwner, IsObserver }; 17 | 18 | Resource( 19 | const std::string& url, 20 | const Shared& http_client, 21 | Ownership mode = IsObserver 22 | ) 23 | : http_client_(http_client) 24 | , url_(url) 25 | , ownership_(mode) 26 | {} 27 | 28 | Resource( 29 | const Shared& parent, 30 | const std::string& name, 31 | Ownership mode = IsObserver 32 | ) 33 | : http_client_(parent->http_client_) 34 | , parent_(parent) 35 | , url_(ConcatUrl(parent->url_, name)) 36 | , ownership_(mode) 37 | {} 38 | 39 | virtual ~Resource() { 40 | try { 41 | if (ownership_ == IsOwner) 42 | DeleteResource(); 43 | } catch (const std::exception&) {} 44 | } 45 | 46 | const std::string& GetUrl() const { 47 | return url_; 48 | } 49 | 50 | picojson::value Get(const std::string& command = std::string()) const { 51 | return Download(command, &IHttpClient::Get, "GET"); 52 | } 53 | 54 | template 55 | T GetValue(const std::string& command) const { 56 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 57 | return FromJson(Get(command)); 58 | WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(detail::Fmt() << 59 | "command: " << command 60 | ) 61 | } 62 | 63 | std::string GetString(const std::string& command) const { 64 | return GetValue(command); 65 | } 66 | 67 | bool GetBool(const std::string& command) const { 68 | return GetValue(command); 69 | } 70 | 71 | picojson::value Delete(const std::string& command = std::string()) const { 72 | return Download(command, &IHttpClient::Delete, "DELETE"); 73 | } 74 | 75 | picojson::value Post( 76 | const std::string& command = std::string(), 77 | const picojson::value& upload_data = picojson::value() 78 | ) const { 79 | return Upload(command, upload_data, &IHttpClient::Post, "POST"); 80 | } 81 | 82 | template 83 | void Post( 84 | const std::string& command, 85 | const std::string& arg_name, 86 | const T& arg_value 87 | ) const { 88 | Post(command, JsonObject().Set(arg_name, arg_value)); 89 | } 90 | 91 | template 92 | void PostValue(const std::string& command, const T& value) const { 93 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 94 | Post(command, ToJson(value)); 95 | WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(detail::Fmt() << 96 | "command: " << command 97 | ) 98 | } 99 | 100 | protected: 101 | virtual picojson::value TransformResponse(picojson::value& response) const { 102 | picojson::value result; 103 | response.get("value").swap(result); 104 | return result; 105 | } 106 | 107 | virtual void DeleteResource() { 108 | Delete(); 109 | } 110 | 111 | private: 112 | picojson::value Download( 113 | const std::string& command, 114 | HttpResponse (IHttpClient::* member)(const std::string& url) const, 115 | const char* request_type 116 | ) const { 117 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 118 | return ProcessResponse((http_client_->*member)( 119 | ConcatUrl(url_, command) 120 | )); 121 | WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(Fmt() 122 | << "request: " << request_type 123 | << ", command: " << command 124 | << ", resource: " << url_ 125 | ) 126 | } 127 | 128 | static std::string ToUploadData(const picojson::value& upload_data) 129 | { 130 | return upload_data.is() ? 131 | std::string() : upload_data.serialize(); 132 | } 133 | 134 | picojson::value Upload( 135 | const std::string& command, 136 | const picojson::value& upload_data, 137 | HttpResponse (IHttpClient::* member)(const std::string& url, const std::string& upload_data) const, 138 | const char* request_type 139 | ) const { 140 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 141 | return ProcessResponse((http_client_->*member)( 142 | ConcatUrl(url_, command), 143 | ToUploadData(upload_data) 144 | )); 145 | WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(Fmt() 146 | << "request: " << request_type 147 | << ", command: " << command 148 | << ", resource: " << url_ 149 | << ", data: " << ToUploadData(upload_data) 150 | ) 151 | } 152 | 153 | picojson::value ProcessResponse( 154 | const HttpResponse& http_response 155 | ) const { 156 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 157 | WEBDRIVERXX_CHECK( 158 | http_response.http_code / 100 != 4 && 159 | http_response.http_code != 501, 160 | "HTTP code indicates that request is invalid"); 161 | 162 | picojson::value response; 163 | std::string error_message; 164 | picojson::parse(response, http_response.body.begin(), http_response.body.end(), &error_message); 165 | 166 | WEBDRIVERXX_CHECK(error_message.empty(), 167 | Fmt() << "JSON parser error (" << error_message << ")" 168 | ); 169 | 170 | WEBDRIVERXX_CHECK(response.is(), "Server response is not an object"); 171 | WEBDRIVERXX_CHECK(response.contains("status"), "Server response has no member \"status\""); 172 | WEBDRIVERXX_CHECK(response.get("status").is(), "Response status code is not a number"); 173 | const auto status = 174 | static_cast(static_cast(response.get("status").get())); 175 | WEBDRIVERXX_CHECK(response.contains("value"), "Server response has no member \"value\""); 176 | const auto& value = response.get("value"); 177 | 178 | if (http_response.http_code == 500) { // Internal server error 179 | WEBDRIVERXX_CHECK(value.is(), "Server returned HTTP code 500 and \"response.value\" is not an object"); 180 | WEBDRIVERXX_CHECK(value.contains("message"), "Server response has no member \"value.message\""); 181 | WEBDRIVERXX_CHECK(value.get("message").is(), "\"value.message\" is not a string"); 182 | WEBDRIVERXX_THROW(Fmt() << "Server failed to execute command (" 183 | << "message: " << value.get("message").to_str() 184 | << ", status: " << response_status_code::ToString(status) 185 | << ", status_code: " << status 186 | << ")" 187 | ); 188 | } 189 | WEBDRIVERXX_CHECK(status == response_status_code::kSuccess, "Non-zero response status code"); 190 | WEBDRIVERXX_CHECK(http_response.http_code == 200, "Unsupported HTTP code"); 191 | 192 | return TransformResponse(response); 193 | WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(Fmt() 194 | << "HTTP code: " << http_response.http_code 195 | << ", body: " << http_response.body 196 | ) 197 | } 198 | 199 | static 200 | std::string ConcatUrl(const std::string& a, const std::string& b, const char delim = '/') { 201 | auto result = a.empty() ? b : a; 202 | if (!a.empty() && !b.empty()) { 203 | if (result[result.length()-1] != delim) 204 | result += delim; 205 | result.append(b[0] == delim ? b.begin() + 1 : b.begin(), b.end()); 206 | } 207 | return result; 208 | } 209 | 210 | private: 211 | const Shared http_client_; 212 | const Shared parent_; 213 | const std::string url_; 214 | const Ownership ownership_; 215 | }; 216 | 217 | class RootResource : public Resource { // noncopyable 218 | public: 219 | RootResource( 220 | const std::string& url, 221 | const Shared& http_client 222 | ) 223 | : Resource(url, http_client, IsObserver) 224 | {} 225 | 226 | private: 227 | virtual picojson::value TransformResponse(picojson::value& response) const { 228 | picojson::value result; 229 | response.swap(result); 230 | return result; 231 | } 232 | }; 233 | 234 | inline 235 | Shared MakeSubResource( 236 | const Shared& parent, 237 | const std::string& subpath, 238 | Resource::Ownership mode = Resource::IsObserver 239 | ) { 240 | return Shared(new Resource(parent, subpath, mode)); 241 | } 242 | 243 | inline 244 | Shared MakeSubResource( 245 | const Shared& parent, 246 | const std::string& subpath1, 247 | const std::string& subpath2, 248 | Resource::Ownership mode = Resource::IsObserver 249 | ) { 250 | return Shared(new Resource(parent, subpath1 + "/" + subpath2, mode)); 251 | } 252 | 253 | } // namespace detail 254 | } // namespace webdriverxx 255 | 256 | #endif 257 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Webdriver++ 3 | 4 | A C++ client library for [Selenium Webdriver](http://www.seleniumhq.org/). 5 | You can use this library in any C++ project. 6 | 7 | 8 | ## Install 9 | 10 | ```bash 11 | mkdir build 12 | cd build && cmake .. 13 | sudo make && sudo make install 14 | ``` 15 | 16 | ## A quick example 17 | 18 | ##### Dependencies 19 | You need to download and run selenium-server before run this example. 20 | http://www.seleniumhq.org/download/ 21 | 22 | ```cpp 23 | #include 24 | using namespace webdriverxx; 25 | 26 | int main() { 27 | WebDriver firefox = Start(Firefox()); 28 | firefox 29 | .Navigate("http://google.com") 30 | .FindElement(ByCss("input[name=q]")) 31 | .SendKeys("Hello, world!") 32 | .Submit(); 33 | return 0; 34 | } 35 | ``` 36 | 37 | ## Features 38 | 39 | - Chainable commands. 40 | - Value-like objects compatible with STL containers. 41 | - Header-only. 42 | - Lightweight dependencies: 43 | - [libcurl](http://curl.haxx.se/libcurl/), 44 | - [picojson](https://github.com/kazuho/picojson). 45 | - Can be used with any testing framework. 46 | - Linux, Mac and Windows. 47 | - clang (3.4), GCC (4.6) and Visual Studio (2010). 48 | 49 | ## More examples 50 | 51 | `#include ` and `using namespace webdriverxx` 52 | are assumed in all examples. 53 | 54 | ### Start browser 55 | 56 | ```cpp 57 | #include 58 | 59 | WebDriver ff = Start(Firefox()); 60 | ``` 61 | 62 | ```cpp 63 | #include 64 | 65 | WebDriver gc = Start(Chrome()); 66 | ``` 67 | 68 | ```cpp 69 | #include 70 | 71 | WebDriver ie = Start(InternetExplorer()); 72 | ``` 73 | 74 | ### Use proxy 75 | 76 | ```cpp 77 | WebDriver ie = Start(InternetExplorer().SetProxy( 78 | SocksProxy("127.0.0.1:3128") 79 | .SetUsername("user") 80 | .SetPassword("12345") 81 | .SetNoProxyFor("custom.host") 82 | )); 83 | ``` 84 | 85 | ```cpp 86 | WebDriver ff = Start(Firefox().SetProxy(DirectConnection())); 87 | ``` 88 | 89 | ### Navigate browser 90 | 91 | ```cpp 92 | driver 93 | .Navigate("http://facebook.com") 94 | .Navigate("http://twitter.com") 95 | .Back() 96 | .Forward() 97 | .Refresh(); 98 | ``` 99 | 100 | ### Find elements 101 | 102 | ```cpp 103 | // Throws exception if no match is found in the document 104 | Element menu = driver.FindElement(ById("menu")); 105 | 106 | // Returns empty vector if no such elements 107 | // The search is performed inside the menu element 108 | std::vector items = menu.FindElements(ByClass("item")); 109 | ``` 110 | 111 | ### Send keyboard input 112 | 113 | ```cpp 114 | // Sends text input or a shortcut to the element 115 | driver.FindElement(ByTag("input")).SendKeys("Hello, world!"); 116 | 117 | // Sends text input or a shortcut to the active window 118 | driver.SendKeys(Shortcut() << keys::Control << "t"); 119 | ``` 120 | 121 | ### Execute Javascript 122 | 123 | ```cpp 124 | // Simple script, no parameters 125 | driver.Execute("console.log('Hi there!')"); 126 | 127 | // A script with one parameter 128 | driver.Execute("document.title = arguments[0]", JsArgs() << "Cowabunga!"); 129 | 130 | // A script with more than one parameter 131 | driver.Execute("document.title = arguments[0] + '-' + arguments[1]", 132 | JsArgs() << "Beep" << "beep"); 133 | 134 | // Arrays or containers can also be used as parameters 135 | const char* ss[] = { "Yabba", "dabba", "doo" }; 136 | driver.Execute("document.title = arguments[0].join(', ')", JsArgs() << ss); 137 | 138 | // Even an Element can be passed to a script 139 | auto element = driver.FindElement(ByTag("input")); 140 | driver.Execute("arguments[0].value = 'That was nuts!'", JsArgs() << element); 141 | ``` 142 | 143 | ### Get something from Javascript 144 | 145 | ```cpp 146 | // Scalar types 147 | auto title = driver.Eval("return document.title") 148 | auto number = driver.Eval("return 123"); 149 | auto another_number = driver.Eval("return 123.5"); 150 | auto flag = driver.Eval("return true"); 151 | 152 | // Containers (all std::back_inserter compatible) 153 | std::vector v = driver.Eval>( 154 | "return [ 'abc', 'def' ]" 155 | ); 156 | 157 | // Elements! 158 | Element document_element = driver.Eval("return document.documentElement"); 159 | ``` 160 | 161 | ### [Wait implicitly](http://selenium-python.readthedocs.org/en/latest/waits.html) for asynchronous operations 162 | 163 | ```cpp 164 | driver.SetImplicitTimeoutMs(5000); 165 | 166 | // Should poll the DOM for 5 seconds before throwing an exception. 167 | auto element = driver.FindElement(ByName("async_element")); 168 | ``` 169 | 170 | ### [Wait explicitly](http://selenium-python.readthedocs.org/en/latest/waits.html) for asynchronous operations 171 | 172 | ```cpp 173 | #include 174 | 175 | auto find_element = [&]{ return driver.FindElement(ById("async_element")); }; 176 | Element element = WaitForValue(find_element); 177 | ``` 178 | 179 | ```cpp 180 | #include 181 | 182 | auto element_is_selected = [&]{ 183 | return driver.FindElement(ById("asynchronously_loaded_element")).IsSelected(); 184 | }; 185 | WaitUntil(element_is_selected); 186 | ``` 187 | 188 | ### Use matchers from [Google Mock](https://code.google.com/p/googlemock/) for waiting 189 | 190 | ```cpp 191 | #define WEBDRIVERXX_ENABLE_GMOCK_MATCHERS 192 | #include 193 | 194 | driver.Navigate("http://initial_url.host.net"); 195 | auto url = [&]{ return driver.GetUrl(); }; 196 | using namespace ::testing; 197 | auto final_url = WaitForMatch(url, HasSubstr("some_magic")); 198 | ``` 199 | 200 | ### Testing with real browsers 201 | 202 | Prerequisites: 203 | - [Selenium Server](http://www.seleniumhq.org/download/) 204 | 205 | ```bash 206 | selenium-server -p 4444 & 207 | ./webdriverxx --browser= 208 | ``` 209 | 210 | ## Advanced topics 211 | 212 | ### Unicode 213 | 214 | The library is designed to be encoding-agnostic. It doesn't make 215 | any assumptions about encodings. All strings are transferred 216 | as is, without modifications. 217 | 218 | The WebDriver protocol is based on UTF-8, so all strings passed 219 | to the library/received from the library should be/are encoded 220 | using UTF-8. 221 | 222 | ### Thread safety 223 | 224 | - Webdriver++ objects are not thread safe. It is not safe to use 225 | neither any single object nor different objects obtained from a single WebDriver 226 | concurrently without synchronization. On the other side, Webdriver++ objects 227 | don't use global variables so it is OK to use different instances of WebDriver 228 | in different threads. 229 | 230 | - The CURL library should be explicitly initialized if several WebDrivers are used from 231 | multiple threads. Call `curl_global_init(CURL_GLOBAL_ALL);` from `` 232 | once per process before using this library. 233 | 234 | ### Use common capabilities for all browsers 235 | 236 | ```cpp 237 | Capabilities common; 238 | common.SetProxy(DirectConnection()); 239 | auto ff = Start(Firefox(common)); 240 | auto ie = Start(InternetExplorer(common)); 241 | auto gc = Start(Chrome(common)); 242 | ``` 243 | 244 | ### Use required capabilities 245 | 246 | ```cpp 247 | Capabilities required = /* ... */; 248 | auto ff = Start(Firefox(), required); 249 | ``` 250 | 251 | ### Use custom URL for connecting to WebDriver 252 | 253 | ```cpp 254 | const char* url = "http://localhost:4444/wd/hub/"; 255 | 256 | auto ff = Start(Firefox(), url); 257 | 258 | // or 259 | auto ff = Start(Firefox(), Capabilities() /* required */, url); 260 | ``` 261 | 262 | ### Transfer objects between C++ and Javascript 263 | 264 | ```cpp 265 | namespace custom { 266 | 267 | struct Object { 268 | std::string string; 269 | int number; 270 | }; 271 | 272 | // Conversion functions should be in the same namespace as the object 273 | picojson::value CustomToJson(const Object& value) { 274 | return JsonObject() 275 | .Set("string", value.string) 276 | .Set("number", value.number); 277 | } 278 | 279 | void CustomFromJson(const picojson::value& value, Object& result) { 280 | assert(value.is()); 281 | result.string = FromJson(value.get("string")); 282 | result.number = FromJson(value.get("number")); 283 | } 284 | 285 | } // namespace custom 286 | 287 | custom::Object o1 = { "abc", 123 }; 288 | driver.Execute("var o1 = arguments[0];", JsArgs() << o1); 289 | custom::Object o1_copy = driver.Eval("return o1"); 290 | custom::Object o2 = driver.Eval("return { string: 'abc', number: 123 }"); 291 | ``` 292 | 293 | -------------------- 294 | 295 | Copyright © 2014 Sergey Kogan. 296 | Licensed under [The MIT license](https://github.com/sekogan/webdriverxx/blob/master/LICENSE). 297 | -------------------------------------------------------------------------------- /include/webdriverxx/conversions.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBDRIVERXX_CONVERSIONS_H 2 | #define WEBDRIVERXX_CONVERSIONS_H 3 | 4 | #include "types.h" 5 | #include "detail/error_handling.h" 6 | #include "detail/meta_tools.h" 7 | #include "picojson.h" 8 | #include 9 | 10 | namespace webdriverxx { 11 | 12 | template 13 | picojson::value ToJson(const T& value); 14 | 15 | template 16 | T FromJson(const picojson::value& value); 17 | 18 | class JsonObject { // copyable 19 | public: 20 | JsonObject() : value_(picojson::object()) {} 21 | explicit JsonObject(const picojson::object& object) : value_(object) {} 22 | 23 | template 24 | T Get(const std::string& name) const { 25 | const auto& map = value_.get(); 26 | const auto it = map.find(name); 27 | WEBDRIVERXX_CHECK(it != map.end(), detail::Fmt() << "No \"" << name << "\" in JsonObject"); 28 | return FromJson(it->second); 29 | } 30 | 31 | template 32 | T GetOptional(const std::string& name, const T& default_value = T()) const { 33 | const auto& map = value_.get(); 34 | const auto it = map.find(name); 35 | return it != map.end() ? FromJson(it->second) : default_value; 36 | } 37 | 38 | template 39 | JsonObject& Set(const std::string& name, const T& value) { 40 | value_.get()[name] = ToJson(value); 41 | return *this; 42 | } 43 | 44 | bool Has(const std::string& name) const { 45 | const auto& map = value_.get(); 46 | return map.find(name) != map.end(); 47 | } 48 | 49 | operator const picojson::value& () const { 50 | return value_; 51 | } 52 | 53 | private: 54 | picojson::value value_; 55 | }; 56 | 57 | namespace conversions_detail { 58 | 59 | struct DefaultTag {}; 60 | struct IterableTag {}; 61 | 62 | using namespace detail; 63 | 64 | template 65 | struct Tag : 66 | if_, type_is, 67 | type_is 68 | > {}; 69 | 70 | template 71 | picojson::value ToJsonImpl(const T& value, DefaultTag) { 72 | // Compile error here usually indicates 73 | // that compiler doesn't know how to convert the type T 74 | // to the picojson::value. Define CustomToJson 75 | // function (see examples below) in the T's namespace 76 | // to resolve the issue. 77 | return picojson::value(value); 78 | } 79 | 80 | template 81 | picojson::value ToJsonImpl(const T& value, IterableTag) { 82 | typedef typename std::iterator_traits::value_type Item; 83 | picojson::value result = picojson::value(picojson::array()); 84 | picojson::array& dst = result.get(); 85 | std::transform(std::begin(value), std::end(value), std::back_inserter(dst), [](const Item& item) { 86 | return ToJson(item); 87 | }); 88 | return result; 89 | } 90 | 91 | } // conversions_detail 92 | 93 | inline 94 | picojson::value CustomToJson(const char* value) { 95 | return picojson::value(value); 96 | } 97 | 98 | inline 99 | picojson::value CustomToJson(const std::string& value) { 100 | return ToJson(value.c_str()); 101 | } 102 | 103 | inline 104 | picojson::value CustomToJson(const picojson::value& value) { 105 | return value; 106 | } 107 | 108 | inline 109 | picojson::value CustomToJson(const picojson::object& value) { 110 | return picojson::value(value); 111 | } 112 | 113 | inline 114 | picojson::value CustomToJson(const JsonObject& value) { 115 | return static_cast(value); 116 | } 117 | 118 | inline 119 | picojson::value CustomToJson(int value) { 120 | return picojson::value(static_cast(value)); 121 | } 122 | 123 | template 124 | picojson::value CustomToJson(const T& value) { 125 | using conversions_detail::ToJsonImpl; 126 | using conversions_detail::Tag; 127 | return ToJsonImpl(value, typename Tag::type()); 128 | } 129 | 130 | template 131 | picojson::value ToJson(const T& value) { 132 | return CustomToJson(value); 133 | } 134 | 135 | /////////////////////////////////////////////////////////////////// 136 | 137 | namespace conversions_detail { 138 | 139 | template 140 | void FromJsonImpl(const picojson::value& value, T& result, DefaultTag) { 141 | // Compile error here usually indicates 142 | // that compiler doesn't know how to convert the picojson::value 143 | // to the type T. Define CustomFromJson function (see examples below) 144 | // in the T's namespace to resolve the issue. 145 | result = value.get(); 146 | } 147 | 148 | template 149 | void FromJsonImpl(const picojson::value& value, T& result, IterableTag) { 150 | WEBDRIVERXX_CHECK(value.is(), "Value is not an array"); 151 | const picojson::array& array = value.get(); 152 | typedef typename std::iterator_traits::value_type Item; 153 | std::transform(array.begin(), array.end(), std::back_inserter(result), FromJson); 154 | } 155 | 156 | } // conversions_detail 157 | 158 | inline 159 | void CustomFromJson(const picojson::value& value, std::string& result) { 160 | result = value.to_str(); 161 | } 162 | 163 | inline 164 | void CustomFromJson(const picojson::value& value, bool& result) { 165 | result = value.evaluate_as_boolean(); 166 | } 167 | 168 | inline 169 | void CustomFromJson(const picojson::value& value, int& result) { 170 | WEBDRIVERXX_CHECK(value.is(), "Value is not a number"); 171 | result = static_cast(value.get()); 172 | } 173 | 174 | inline 175 | void CustomFromJson(const picojson::value& value, unsigned& result) { 176 | WEBDRIVERXX_CHECK(value.is(), "Value is not a number"); 177 | result = static_cast(value.get()); 178 | } 179 | 180 | inline 181 | void CustomFromJson(const picojson::value& value, picojson::value& result) { 182 | result = value; 183 | } 184 | 185 | inline 186 | void CustomFromJson(const picojson::value& value, picojson::object& result) { 187 | WEBDRIVERXX_CHECK(value.is(), "Value is not an object"); 188 | result = value.get(); 189 | } 190 | 191 | inline 192 | void CustomFromJson(const picojson::value& value, JsonObject& result) { 193 | WEBDRIVERXX_CHECK(value.is(), "Value is not an object"); 194 | result = JsonObject(value.get()); 195 | } 196 | 197 | template 198 | void CustomFromJson(const picojson::value& value, T& result) { 199 | using conversions_detail::FromJsonImpl; 200 | using conversions_detail::Tag; 201 | return FromJsonImpl(value, result, typename Tag::type()); 202 | } 203 | 204 | template 205 | T FromJson(const picojson::value& value) { 206 | T result; 207 | CustomFromJson(value, result); 208 | return result; 209 | } 210 | 211 | template 212 | T OptionalFromJson(const picojson::value& value, const T& default_value = T()) { 213 | return value.is() ? default_value : FromJson(value); 214 | } 215 | 216 | /////////////////////////////////////////////////////////////////// 217 | 218 | inline 219 | picojson::value CustomToJson(const Size& size) { 220 | return JsonObject() 221 | .Set("width", size.width) 222 | .Set("height", size.height) 223 | ; 224 | } 225 | 226 | inline 227 | void CustomFromJson(const picojson::value& value, Size& result) { 228 | WEBDRIVERXX_CHECK(value.is(), "Size is not an object"); 229 | result.width = FromJson(value.get("width")); 230 | result.height = FromJson(value.get("height")); 231 | } 232 | 233 | inline 234 | picojson::value CustomToJson(const Point& position) { 235 | return JsonObject() 236 | .Set("x", position.x) 237 | .Set("y", position.y) 238 | ; 239 | } 240 | 241 | inline 242 | void CustomFromJson(const picojson::value& value, Point& result) { 243 | WEBDRIVERXX_CHECK(value.is(), "Point is not an object"); 244 | result.x = FromJson(value.get("x")); 245 | result.y = FromJson(value.get("y")); 246 | } 247 | 248 | inline 249 | picojson::value CustomToJson(const Cookie& cookie) { 250 | JsonObject result; 251 | result.Set("name", cookie.name); 252 | result.Set("value", cookie.value); 253 | if (!cookie.path.empty()) result.Set("path", cookie.path); 254 | if (!cookie.domain.empty()) result.Set("domain", cookie.domain); 255 | if (cookie.secure) result.Set("secure", true); 256 | if (cookie.http_only) result.Set("httpOnly", true); 257 | if (cookie.expiry != Cookie::NoExpiry) result.Set("expiry", cookie.expiry); 258 | return result; 259 | } 260 | 261 | inline 262 | void CustomFromJson(const picojson::value& value, Cookie& result) { 263 | WEBDRIVERXX_CHECK(value.is(), "Cookie is not an object"); 264 | result.name = FromJson(value.get("name")); 265 | result.value = FromJson(value.get("value")); 266 | result.path = OptionalFromJson(value.get("path")); 267 | result.domain = OptionalFromJson(value.get("domain")); 268 | result.secure = OptionalFromJson(value.get("secure"), false); 269 | result.http_only = OptionalFromJson(value.get("httpOnly"), false); 270 | result.expiry = OptionalFromJson(value.get("expiry"), Cookie::NoExpiry); 271 | } 272 | 273 | } // namespace webdriverxx 274 | 275 | #endif 276 | -------------------------------------------------------------------------------- /include/webdriverxx/session.inl: -------------------------------------------------------------------------------- 1 | #include "conversions.h" 2 | #include "detail/error_handling.h" 3 | #include "detail/types.h" 4 | #include 5 | 6 | namespace webdriverxx { 7 | 8 | inline 9 | Session::Session(const detail::Shared& resource) 10 | : resource_(resource) 11 | , factory_(new detail::SessionFactory(resource)) 12 | {} 13 | 14 | inline 15 | void Session::DeleteSession() const { 16 | resource_->Delete(); 17 | } 18 | 19 | inline 20 | Capabilities Session::GetCapabilities() const { 21 | return Capabilities(resource_->Get().get()); 22 | } 23 | 24 | inline 25 | std::string Session::GetSource() const { 26 | return resource_->GetString("source"); 27 | } 28 | 29 | inline 30 | std::string Session::GetTitle() const { 31 | return resource_->GetString("title"); 32 | } 33 | 34 | inline 35 | std::string Session::GetUrl() const { 36 | return resource_->GetString("url"); 37 | } 38 | 39 | inline 40 | std::string Session::GetScreenshot() const { 41 | return resource_->GetString("screenshot"); 42 | } 43 | 44 | inline 45 | const Session& Session::SetTimeoutMs(timeout::Type type, int milliseconds) { 46 | resource_->Post("timeouts", 47 | JsonObject() 48 | .Set("type", type) 49 | .Set("ms", milliseconds) 50 | ); 51 | return *this; 52 | } 53 | 54 | inline 55 | const Session& Session::SetImplicitTimeoutMs(int milliseconds) { 56 | resource_->Post("timeouts/implicit_wait", 57 | JsonObject().Set("ms", milliseconds)); 58 | return *this; 59 | } 60 | 61 | inline 62 | const Session& Session::SetAsyncScriptTimeoutMs(int milliseconds) { 63 | resource_->Post("timeouts/async_script", 64 | JsonObject().Set("ms", milliseconds)); 65 | return *this; 66 | } 67 | 68 | inline 69 | Window Session::GetCurrentWindow() const { 70 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 71 | return MakeWindow(resource_->GetString("window_handle")); 72 | WEBDRIVERXX_FUNCTION_CONTEXT_END() 73 | } 74 | 75 | inline 76 | const Session& Session::CloseCurrentWindow() const { 77 | resource_->Delete("window"); 78 | return *this; 79 | } 80 | 81 | inline 82 | const Session& Session::Navigate(const std::string& url) const { 83 | resource_->Post("url", "url", url); 84 | return *this; 85 | } 86 | 87 | inline 88 | const Session& Session::Get(const std::string& url) const { 89 | return Navigate(url); 90 | } 91 | 92 | inline 93 | const Session& Session::Forward() const { 94 | resource_->Post("forward"); 95 | return *this; 96 | } 97 | 98 | inline 99 | const Session& Session::Back() const { 100 | resource_->Post("back"); 101 | return *this; 102 | } 103 | 104 | inline 105 | const Session& Session::Refresh() const { 106 | resource_->Post("refresh"); 107 | return *this; 108 | } 109 | 110 | inline 111 | const Session& Session::Execute(const std::string& script, const JsArgs& args) const { 112 | InternalEvalJsonValue("execute", script, args); 113 | return *this; 114 | } 115 | 116 | template 117 | T Session::Eval(const std::string& script, const JsArgs& args) const { 118 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 119 | T result = T(); 120 | InternalEval("execute", script, args, result); 121 | return result; 122 | WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(detail::Fmt() 123 | << "script: " << script 124 | ) 125 | } 126 | 127 | inline 128 | const Session& Session::ExecuteAsync(const std::string& script, const JsArgs& args) const { 129 | InternalEvalJsonValue("execute_async", script, args); 130 | return *this; 131 | } 132 | 133 | template 134 | T Session::EvalAsync(const std::string& script, const JsArgs& args) const { 135 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 136 | T result; 137 | InternalEval("execute_async", script, args, result); 138 | return result; 139 | WEBDRIVERXX_FUNCTION_CONTEXT_END_EX(detail::Fmt() 140 | << "script: " << script 141 | ) 142 | } 143 | 144 | inline 145 | const Session& Session::SetFocusToWindow(const std::string& window_name_or_handle) const { 146 | resource_->Post("window", "name", window_name_or_handle); 147 | return *this; 148 | } 149 | 150 | inline 151 | const Session& Session::SetFocusToWindow(const Window& window) const { 152 | SetFocusToWindow(window.GetHandle()); 153 | return *this; 154 | } 155 | 156 | inline 157 | const Session& Session::SetFocusToFrame(const Element& frame) const { 158 | return InternalSetFocusToFrame(ToJson(frame)); 159 | } 160 | 161 | inline 162 | const Session& Session::SetFocusToFrame(const std::string& id) const { 163 | return InternalSetFocusToFrame(ToJson(id)); 164 | } 165 | 166 | inline 167 | const Session& Session::SetFocusToFrame(int number) const { 168 | return InternalSetFocusToFrame(ToJson(number)); 169 | } 170 | 171 | inline 172 | const Session& Session::SetFocusToDefaultFrame() const { 173 | return InternalSetFocusToFrame(picojson::value()); 174 | } 175 | 176 | inline 177 | const Session& Session::SetFocusToParentFrame() const { 178 | resource_->Post("frame/parent"); 179 | return *this; 180 | } 181 | 182 | inline 183 | const Session& Session::InternalSetFocusToFrame(const picojson::value& id) const { 184 | resource_->Post("frame", JsonObject().Set("id", id)); 185 | return *this; 186 | } 187 | 188 | inline 189 | std::vector Session::GetWindows() const { 190 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 191 | const auto handles = 192 | FromJson>( 193 | resource_->Get("window_handles") 194 | ); 195 | std::vector result; 196 | result.reserve(handles.size()); 197 | std::transform(handles.begin(), handles.end(), std::back_inserter(result), 198 | [this](const std::string& window_handle){ 199 | return MakeWindow(window_handle); 200 | }); 201 | return result; 202 | WEBDRIVERXX_FUNCTION_CONTEXT_END() 203 | } 204 | 205 | inline 206 | Element Session::GetActiveElement() const { 207 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 208 | return factory_->MakeElement(FromJson(resource_->Post("element/active")).ref); 209 | WEBDRIVERXX_FUNCTION_CONTEXT_END() 210 | } 211 | 212 | inline 213 | Element Session::FindElement(const By& by) const { 214 | return factory_->MakeFinder(resource_).FindElement(by); 215 | } 216 | 217 | inline 218 | std::vector Session::FindElements(const By& by) const { 219 | return factory_->MakeFinder(resource_).FindElements(by); 220 | } 221 | 222 | inline 223 | std::vector Session::GetCookies() const { 224 | WEBDRIVERXX_FUNCTION_CONTEXT_BEGIN() 225 | return FromJson>(resource_->Get("cookie")); 226 | WEBDRIVERXX_FUNCTION_CONTEXT_END() 227 | } 228 | 229 | inline 230 | const Session& Session::SetCookie(const Cookie& cookie) const { 231 | resource_->Post("cookie", JsonObject() 232 | .Set("cookie", ToJson(cookie))); 233 | return *this; 234 | } 235 | 236 | inline 237 | const Session& Session::DeleteCookies() const { 238 | resource_->Delete("cookie"); 239 | return *this; 240 | } 241 | 242 | inline 243 | const Session& Session::DeleteCookie(const std::string& name) const { 244 | resource_->Delete(std::string("cookie/") + name); 245 | return *this; 246 | } 247 | 248 | inline 249 | std::string Session::GetAlertText() const { 250 | return resource_->GetString("alert_text"); 251 | } 252 | 253 | inline 254 | const Session& Session::SendKeysToAlert(const std::string& text) const { 255 | resource_->Post("alert_text", "text", text); 256 | return *this; 257 | } 258 | 259 | inline 260 | const Session& Session::AcceptAlert() const { 261 | resource_->Post("accept_alert"); 262 | return *this; 263 | } 264 | 265 | inline 266 | const Session& Session::DismissAlert() const { 267 | resource_->Post("dismiss_alert"); 268 | return *this; 269 | } 270 | 271 | inline 272 | const Session& Session::SendKeys(const std::string& keys) const { 273 | GetKeyboard().SendKeys(keys); 274 | return *this; 275 | } 276 | 277 | inline 278 | const Session& Session::SendKeys(const Shortcut& shortcut) const { 279 | GetKeyboard().SendKeys(shortcut); 280 | return *this; 281 | } 282 | 283 | inline 284 | const Session& Session::MoveToTopLeftOf(const Element& element, const Offset& offset) const { 285 | return InternalMoveTo(&element, &offset); 286 | } 287 | 288 | inline 289 | const Session& Session::MoveToCenterOf(const Element& element) const { 290 | return InternalMoveTo(&element, nullptr); 291 | } 292 | 293 | inline 294 | const Session& Session::MoveTo(const Offset& offset) const { 295 | return InternalMoveTo(nullptr, &offset); 296 | } 297 | 298 | inline 299 | const Session& Session::InternalMoveTo( 300 | const Element* element, 301 | const Offset* offset 302 | ) const { 303 | JsonObject args; 304 | if (element) 305 | args.Set("element", element->GetRef()); 306 | if (offset) { 307 | args.Set("xoffset", offset->x); 308 | args.Set("yoffset", offset->y); 309 | } 310 | resource_->Post("moveto", args); 311 | return *this; 312 | } 313 | 314 | inline 315 | const Session& Session::Click(mouse::Button button) const { 316 | return InternalMouseButtonCommand("click", button); 317 | } 318 | 319 | inline 320 | const Session& Session::DoubleClick() const { 321 | resource_->Post("doubleclick"); 322 | return *this; 323 | } 324 | 325 | inline 326 | const Session& Session::ButtonDown(mouse::Button button) const { 327 | return InternalMouseButtonCommand("buttondown", button); 328 | } 329 | 330 | inline 331 | const Session& Session::ButtonUp(mouse::Button button) const { 332 | return InternalMouseButtonCommand("buttonup", button); 333 | } 334 | 335 | inline 336 | const Session& Session::InternalMouseButtonCommand(const char* command, mouse::Button button) const { 337 | resource_->Post(command, "button", static_cast(button)); 338 | return *this; 339 | } 340 | 341 | inline 342 | Window Session::MakeWindow(const std::string& handle) const { 343 | return Window(handle, 344 | detail::MakeSubResource(resource_, "window", handle) 345 | ); 346 | } 347 | 348 | inline 349 | detail::Keyboard Session::GetKeyboard() const 350 | { 351 | return detail::Keyboard(resource_, "keys"); 352 | } 353 | 354 | template 355 | void Session::InternalEval(const std::string& webdriver_command, 356 | const std::string& script, const JsArgs& args, 357 | T& result) const { 358 | result = FromJson(InternalEvalJsonValue(webdriver_command, script, args)); 359 | } 360 | 361 | inline 362 | void Session::InternalEval(const std::string& webdriver_command, 363 | const std::string& script, const JsArgs& args, 364 | Element& result) const { 365 | detail::ElementRef element_ref; 366 | InternalEval(webdriver_command, script, args, element_ref); 367 | result = factory_->MakeElement(element_ref.ref); 368 | } 369 | 370 | inline 371 | picojson::value Session::InternalEvalJsonValue( 372 | const std::string& webdriver_command, 373 | const std::string& script, 374 | const JsArgs& args 375 | ) const { 376 | return resource_->Post(webdriver_command, 377 | JsonObject() 378 | .Set("script", script) 379 | .Set("args", args.args_) 380 | ); 381 | } 382 | 383 | } // namespace webdriverxx 384 | -------------------------------------------------------------------------------- /test/resource_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace test { 8 | 9 | using namespace webdriverxx; 10 | using namespace webdriverxx::detail; 11 | 12 | const char *const kTestUrl = "http://test/"; 13 | 14 | struct MockHttpClient : IHttpClient, SharedObjectBase { 15 | MOCK_CONST_METHOD1(Get, HttpResponse(const std::string& url)); 16 | MOCK_CONST_METHOD2(Post, HttpResponse(const std::string& url, const std::string& data)); 17 | MOCK_CONST_METHOD1(Delete, HttpResponse(const std::string& url)); 18 | }; 19 | 20 | using namespace ::testing; 21 | 22 | struct TestResource : Test { 23 | void SetUp() { 24 | http_response.http_code = 200; 25 | http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":12345}"; 26 | 27 | http_client = Shared(new MockHttpClient); 28 | DefaultValue::Set(HttpResponse()); 29 | ON_CALL(*http_client, Get(_)).WillByDefault(ReturnPointee(&http_response)); 30 | ON_CALL(*http_client, Post(_,_)).WillByDefault(ReturnPointee(&http_response)); 31 | ON_CALL(*http_client, Delete(_)).WillByDefault(ReturnPointee(&http_response)); 32 | EXPECT_CALL(*http_client, Get(_)).Times(AnyNumber()); 33 | EXPECT_CALL(*http_client, Post(_,_)).Times(AnyNumber()); 34 | EXPECT_CALL(*http_client, Delete(_)).Times(AnyNumber()); 35 | } 36 | 37 | Shared http_client; 38 | HttpResponse http_response; 39 | }; 40 | 41 | // Positive tests 42 | 43 | TEST_F(TestResource, CanBeCreated) { 44 | Resource resource(kTestUrl, http_client); 45 | } 46 | 47 | TEST_F(TestResource, ReturnsUrl) { 48 | Resource resource(kTestUrl, http_client); 49 | ASSERT_EQ(kTestUrl, resource.GetUrl()); 50 | } 51 | 52 | TEST_F(TestResource, CanBeUsedToMakeSubResource) { 53 | Shared a(new Resource("a", http_client)); 54 | ASSERT_EQ("a/c", MakeSubResource(a, "c")->GetUrl()); 55 | ASSERT_EQ("a/c",MakeSubResource(a, "/c")->GetUrl()); 56 | Shared b(new Resource("b/", http_client)); 57 | ASSERT_EQ("b/c", MakeSubResource(b, "c")->GetUrl()); 58 | ASSERT_EQ("b/c", MakeSubResource(b, "/c")->GetUrl()); 59 | } 60 | 61 | TEST_F(TestResource, DoesNotDeleteResourceByDefault) 62 | { 63 | EXPECT_CALL(*http_client, Delete(_)).Times(0); 64 | Resource resource(kTestUrl, http_client); 65 | } 66 | 67 | TEST_F(TestResource, DeletesResourceIfOwnershipIsEnabled) 68 | { 69 | EXPECT_CALL(*http_client, Delete(kTestUrl)); 70 | Resource resource(kTestUrl, http_client, Resource::IsOwner); 71 | } 72 | 73 | TEST_F(TestResource, SharesOwnershipOfParentResource) 74 | { 75 | EXPECT_CALL(*http_client, Delete(_)).Times(0); 76 | Shared parent(new Resource("parent", http_client, Resource::IsOwner)); 77 | Shared child = MakeSubResource(parent, "child", Resource::IsOwner); 78 | parent = Shared(); 79 | Mock::VerifyAndClear(http_client); // Parent shouldn't be deleted at this point because child is alive 80 | InSequence check_delete_order; 81 | EXPECT_CALL(*http_client, Delete("parent/child")); 82 | EXPECT_CALL(*http_client, Delete("parent")); 83 | child = Shared(); 84 | } 85 | 86 | TEST_F(TestResource, RootResourceReturnsJsonObject) 87 | { 88 | RootResource resource(kTestUrl, http_client); 89 | http_response.http_code = 200; 90 | http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":12345}"; 91 | ASSERT_TRUE(resource.Get("command").is()); 92 | } 93 | 94 | TEST_F(TestResource, RootResourceReturnsSessionId) 95 | { 96 | RootResource resource(kTestUrl, http_client); 97 | http_response.http_code = 200; 98 | http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":12345}"; 99 | ASSERT_TRUE(resource.Get("command").contains("sessionId")); 100 | ASSERT_EQ("123", resource.Get("command").get("sessionId").to_str()); 101 | } 102 | 103 | TEST_F(TestResource, RootResourceReturnsNullSessionId) 104 | { 105 | RootResource resource(kTestUrl, http_client); 106 | http_response.body = "{\"sessionId\":null,\"status\":0,\"value\":12345}"; 107 | ASSERT_TRUE(resource.Get("command").contains("sessionId")); 108 | ASSERT_TRUE(resource.Get("command").get("sessionId").is()); 109 | } 110 | 111 | TEST_F(TestResource, RootResourceReturnsScalarValueFromPositiveResponse) 112 | { 113 | RootResource resource(kTestUrl, http_client); 114 | http_response.http_code = 200; 115 | http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":12345}"; 116 | picojson::value value = resource.Get("command").get("value"); 117 | ASSERT_TRUE(value.is()); 118 | ASSERT_EQ(12345, value.get()); 119 | } 120 | 121 | TEST_F(TestResource, RootResourceReturnsObjectValueFromPositiveResponse) 122 | { 123 | RootResource resource(kTestUrl, http_client); 124 | http_response.http_code = 200; 125 | http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":{\"member\":12345}}"; 126 | picojson::value value = resource.Get("command").get("value"); 127 | ASSERT_TRUE(value.is()); 128 | ASSERT_TRUE(value.contains("member")); 129 | ASSERT_TRUE(value.get("member").is()); 130 | ASSERT_EQ(12345, value.get("member").get()); 131 | } 132 | 133 | TEST_F(TestResource, ReturnsScalarValueFromPositiveResponse) 134 | { 135 | Resource resource(kTestUrl, http_client); 136 | http_response.http_code = 200; 137 | http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":12345}"; 138 | picojson::value value = resource.Get("command"); 139 | ASSERT_TRUE(value.is()); 140 | ASSERT_EQ(12345, value.get()); 141 | } 142 | 143 | TEST_F(TestResource, ReturnsObjectValueFromPositiveResponse) 144 | { 145 | Resource resource(kTestUrl, http_client); 146 | http_response.http_code = 200; 147 | http_response.body = "{\"sessionId\":\"123\",\"status\":0,\"value\":{\"member\":12345}}"; 148 | picojson::value value = resource.Get("command"); 149 | ASSERT_TRUE(value.is()); 150 | ASSERT_TRUE(value.contains("member")); 151 | ASSERT_TRUE(value.get("member").is()); 152 | ASSERT_EQ(12345, value.get("member").get()); 153 | } 154 | 155 | // Negative tests 156 | 157 | TEST_F(TestResource, ThrowsOnHttp404) 158 | { 159 | http_response.http_code = 404; 160 | Resource resource(kTestUrl, http_client); 161 | ASSERT_THROW(resource.Get("command"), WebDriverException); 162 | } 163 | 164 | TEST_F(TestResource, ThrowsOnHttp400) 165 | { 166 | http_response.http_code = 400; 167 | Resource resource(kTestUrl, http_client); 168 | ASSERT_THROW(resource.Get("command"), WebDriverException); 169 | } 170 | 171 | TEST_F(TestResource, ThrowsOnHttp499) 172 | { 173 | http_response.http_code = 499; 174 | Resource resource(kTestUrl, http_client); 175 | ASSERT_THROW(resource.Get("command"), WebDriverException); 176 | } 177 | 178 | TEST_F(TestResource, ThrowsOnHttp501) 179 | { 180 | http_response.http_code = 501; 181 | Resource resource(kTestUrl, http_client); 182 | ASSERT_THROW(resource.Get("command"), WebDriverException); 183 | } 184 | 185 | TEST_F(TestResource, DoesNotHideHttpExceptions) 186 | { 187 | EXPECT_CALL(*http_client, Get(_)).WillOnce(Throw(WebDriverException("HTTP failed"))); 188 | Resource resource(kTestUrl, http_client); 189 | try { 190 | resource.Get("command"); 191 | FAIL(); // Shouldn't get here 192 | } catch (const std::exception& e) { 193 | const std::string message = e.what(); 194 | ASSERT_NE(std::string::npos, message.find("HTTP failed")); 195 | } 196 | } 197 | 198 | TEST_F(TestResource, AddsContextToExceptions) 199 | { 200 | EXPECT_CALL(*http_client, Get(_)).WillOnce(Throw(WebDriverException("HTTP failed"))); 201 | Resource resource(kTestUrl, http_client); 202 | try { 203 | resource.Get("pinky"); 204 | FAIL(); // Shouldn't get here 205 | } catch (const std::exception& e) { 206 | const std::string message = e.what(); 207 | ASSERT_NE(std::string::npos, message.find("pinky")); 208 | } 209 | } 210 | 211 | TEST_F(TestResource, WebDriverExceptionContainsCommandAndHttpCodeAndBody) 212 | { 213 | http_response.http_code = 501; 214 | http_response.body = "--oops--"; 215 | Resource resource(kTestUrl, http_client); 216 | try { 217 | resource.Get("pinky"); 218 | FAIL(); // Shouldn't get here 219 | } catch (const std::exception& e) { 220 | const std::string message = e.what(); 221 | ASSERT_NE(std::string::npos, message.find("pinky")); 222 | ASSERT_NE(std::string::npos, message.find("--oops--")); 223 | ASSERT_NE(std::string::npos, message.find("501")); 224 | } 225 | } 226 | 227 | TEST_F(TestResource, WebDriverExceptionContainsStatusAndStatusDescription) 228 | { 229 | http_response.http_code = 500; 230 | http_response.body = Fmt() << "{\"status\":" 231 | << response_status_code::kNoSuchWindow 232 | << ",\"value\":{\"message\":\"12345\"}}"; 233 | Resource resource(kTestUrl, http_client); 234 | try { 235 | resource.Get("pinky"); 236 | FAIL(); // Shouldn't get here 237 | } catch (const std::exception& e) { 238 | const std::string message = e.what(); 239 | ASSERT_NE(std::string::npos, message.find(Fmt() << response_status_code::kNoSuchWindow)); 240 | ASSERT_NE(std::string::npos, message.find(response_status_code::ToString(response_status_code::kNoSuchWindow))); 241 | } 242 | } 243 | 244 | TEST_F(TestResource, ThrowsOnHttp500) 245 | { 246 | http_response.http_code = 500; 247 | http_response.body = "{\"status\":12,\"value\":{\"message\":\"12345\"}}"; 248 | Resource resource(kTestUrl, http_client); 249 | ASSERT_THROW(resource.Get("command"), WebDriverException); 250 | } 251 | 252 | TEST_F(TestResource, ThrowsOnHttp500AndMissingStatus) 253 | { 254 | http_response.http_code = 500; 255 | http_response.body = "{\"value\":{\"message\":\"12345\"}}"; 256 | Resource resource(kTestUrl, http_client); 257 | ASSERT_THROW(resource.Get("command"), WebDriverException); 258 | } 259 | 260 | TEST_F(TestResource, ThrowsOnHttp500AndInvalidStatus) 261 | { 262 | http_response.http_code = 500; 263 | http_response.body = "{\"status\":\"xxx\",\"value\":{\"message\":\"12345\"}}"; 264 | Resource resource(kTestUrl, http_client); 265 | ASSERT_THROW(resource.Get("command"), WebDriverException); 266 | } 267 | 268 | TEST_F(TestResource, ThrowsOnHttp500AndMissingValue) 269 | { 270 | http_response.http_code = 500; 271 | http_response.body = "{\"status\":12}"; 272 | Resource resource(kTestUrl, http_client); 273 | ASSERT_THROW(resource.Get("command"), WebDriverException); 274 | } 275 | 276 | TEST_F(TestResource, ThrowsOnHttp500AndInvalidValue) 277 | { 278 | http_response.http_code = 500; 279 | http_response.body = "{\"status\":\"xxx\",\"value\":\"12345\"}"; 280 | Resource resource(kTestUrl, http_client); 281 | ASSERT_THROW(resource.Get("command"), WebDriverException); 282 | } 283 | 284 | TEST_F(TestResource, ThrowsOnHttp500AndMissingMessage) 285 | { 286 | http_response.http_code = 500; 287 | http_response.body = "{\"status\":12,\"value\":{\"xxx\":\"12345\"}}"; 288 | Resource resource(kTestUrl, http_client); 289 | ASSERT_THROW(resource.Get("command"), WebDriverException); 290 | } 291 | 292 | TEST_F(TestResource, ThrowsOnHttp500AndInvalidMessage) 293 | { 294 | http_response.http_code = 500; 295 | http_response.body = "{\"status\":12,\"value\":{\"message\":12345}}"; 296 | Resource resource(kTestUrl, http_client); 297 | ASSERT_THROW(resource.Get("command"), WebDriverException); 298 | } 299 | 300 | TEST_F(TestResource, ThrowsOnHttp399) 301 | { 302 | http_response.http_code = 399; 303 | Resource resource(kTestUrl, http_client); 304 | ASSERT_THROW(resource.Get("command"), WebDriverException); 305 | } 306 | 307 | TEST_F(TestResource, ThrowsOnHttp502) 308 | { 309 | http_response.http_code = 502; 310 | Resource resource(kTestUrl, http_client); 311 | ASSERT_THROW(resource.Get("command"), WebDriverException); 312 | } 313 | 314 | TEST_F(TestResource, ThrowsOnEmptyResponse) 315 | { 316 | Resource resource(kTestUrl, http_client); 317 | http_response.body = ""; 318 | ASSERT_THROW(resource.Get("command"), WebDriverException); 319 | } 320 | 321 | TEST_F(TestResource, ThrowsOnMalformedResponse) 322 | { 323 | Resource resource(kTestUrl, http_client); 324 | http_response.body = "Blah blah blah"; 325 | ASSERT_THROW(resource.Get("command"), WebDriverException); 326 | } 327 | 328 | TEST_F(TestResource, ThrowsIfResponseIsNotAnObject) 329 | { 330 | Resource resource(kTestUrl, http_client); 331 | http_response.body = "\"value\":123"; 332 | ASSERT_THROW(resource.Get("command"), WebDriverException); 333 | } 334 | 335 | TEST_F(TestResource, ThrowsOnMissingStatus) 336 | { 337 | http_response.body = "{\"sessionId\":\"123\",\"value\":12345}"; 338 | Resource resource(kTestUrl, http_client); 339 | ASSERT_THROW(resource.Get("command"), WebDriverException); 340 | } 341 | 342 | TEST_F(TestResource, ThrowsOnInvalidStatus) 343 | { 344 | http_response.body = "{\"sessionId\":\"123\",\"status\":\"5\",\"value\":12345}"; 345 | Resource resource(kTestUrl, http_client); 346 | ASSERT_THROW(resource.Get("command"), WebDriverException); 347 | } 348 | 349 | TEST_F(TestResource, ThrowsOnNonZeroStatus) 350 | { 351 | Resource resource(kTestUrl, http_client); 352 | http_response.body = "{\"sessionId\":\"123\",\"status\":5,\"value\":12345}"; 353 | ASSERT_THROW(resource.Get("command"), WebDriverException); 354 | } 355 | 356 | TEST_F(TestResource, ThrowsOnMissingValue) 357 | { 358 | http_response.body = "{\"sessionId\":\"123\",\"status\":0}"; 359 | Resource resource(kTestUrl, http_client); 360 | ASSERT_THROW(resource.Get("command"), WebDriverException); 361 | } 362 | 363 | } // namespace test 364 | --------------------------------------------------------------------------------